库维奥热电站
@coveo/thermidor 是一个实验性,框架不可知的客户端 一个单一,状态,意图的路径 统一反向终点它暴露了一个工厂 createSession(config) ——这个返回 a Session: 一个简单的可观测列表 Turns 从服务器发送事件(SSE)流中折叠出,外加一个单一的动作发送出入口直接接通了渲染器.
包裹是私人的,实验性的,只消耗在这个单体内(由 samples/thermidor/demo-schema-react) (中文(简体) ).
有一个 没有引擎,没有接口层,没有Redux/RTK,没有每个端点外观旧订正中提及这些层面已过时.
这个想法
服务器串流图定义 A2UI (英语) 组件; 客户端生成服务器发送的内容。 瑟米多的工作是将消费者的输入传递到端点,将流出的结果折叠成可观察到的转折列表——它不会在折叠后产生任何用例行为. 合同计划,端点URL,以及环境背景都是由消费者注入的,所以一个单一的软件包构建服务于公共,内部(私人-注册计划)和代理部署.
组件状态旅行 内入 通过 A2-UI 数据模型: 服务器发送 updateDataModel 业务 /state/<id>,而被冻结的渲染器通过A2-UI Data Binding({ path: <JSON Pointer> })与该数据模型相对. 制作者是“哑巴”:他们得到解析状态 props 从不给控制器水分 也不加入快照 A2-UI是唯一支持的状态源. 此内置模型取代了早先的远程控制器 / AG- UI StateSnapshot 设计.
公众表面
import {createSession} from '@coveo/thermidor';
import {ComponentContractsSchema} from '@coveo/thermidor-schema';
const session = createSession({
organizationId: 'my-org',
accessToken: 'xx-token',
agentConfigurationId: '00000000-0000-0000-0000-000000000000', // the configured agent that answers the session
trackingId: 'my-tracking-id',
contracts: ComponentContractsSchema, // injected Zod discriminated union (Zod 3 or 4)
navigatorContextProvider: () => getNavigatorContext(), // required; includes language, country, currency
commerceContextProvider: () => ({cart: getCart()}),
});
session.subscribe(() => render(session.getSnapshot()));
await session.dispatchAction({name: 'submitPrompt', payload: {prompt: 'show me running shoes'}});
createSession(config) 返回 a Session 完全暴露:
| 成员 | 目的 |
|---|---|
contracts | 本次会话中注入的合同计划(只读)可以验证. |
turns | 仅读可观察列表 Turns从溪来折叠(最初为空). |
initialization | 运行 initialize a. 会议开幕的行动,或 undefined。见 会议开幕。 。 。 . |
surfaces | 会话的每个活表面,按创建顺序排列,由谁运行创建. |
a2uiMessages | 会话的整个渲染器流,为A2-UI v0.9形状. 它只会增长:将每个新消息反馈给一个渲染器一次. |
state | 每个活地表的验证数据模型,由地表id键入. |
actionError | 最后一个失败的组件动作( X)ActionError数字 : message, (中文(简体) ), component, (中文(简体) ), name, (中文(简体) ), surfaceId, (中文(简体) ), sourceComponentId),或 undefined。 。 。 . |
request | 发出的请求,或 undefined 当闲置。 见 一次一个请求。 。 。 . |
subscribe(listener) | 记录每次更改转折列表、初始化、, actionError 或者说 request。 。 。 . |
getSnapshot() | turns, (中文(简体) ), initialization, (中文(简体) ), surfaces, (中文(简体) ), a2uiMessages, (中文(简体) ), state, (中文(简体) ), request 财务报告和审定财务报表 actionError 作为永恒的 SessionSnapshot,更改时替换. |
dispatchAction(message) | 提示的单个切入点( Q)submitPrompt和组件动作,直接作为渲染器的 onAction。 。 。 . |
cancel() | 停止活动流 . |
retry(turnId) | 在错误时重新提交上个回合的输入:它的快取,或打开它的动作. |
serialize() | 将笔录序列化为已版本 SerializedSession。 。 。 . |
subscribe 财务报告和审定财务报表 getSnapshot 不依赖 this,所以它们可以通过引用来传递给任何通过引用来比较快照的商店,比如React's useSyncExternalStore(session.subscribe, session.getSnapshot)。软件包本身取决于任何框架.
混凝土 contracts 类型 createSession 呼叫站点线索未断 Session,因此组件类型、动作名称和有效载荷在呼叫地点保持完全打入(否) string,没有 never,没有 unknown) (中文(简体) ).
会议开幕
1. 会议开幕: createSession 发送一个 initialize 返回后动作一个微任务,所以在同一任务中订阅的听众会看到它开始。 它没有表面或源部件,因为还没有部件。 当它飞行时, session.request 实值 {kind: 'initialize'}它穿过 单一请求规则 和冲突再试,并采用会话编号 从它的运行,像一个提示。 制作人以会话外壳作为普通表面回答,如它的搜索框.
运行记录为 session.initialization (单位:千美元)activities, (中文(简体) ), status, (中文(简体) ), error从不作为转弯。 失败 initialize 未重审 : initialization.status 变成 'error',第一个提示仍然打开会话. cancel() 停止它的任何请求。 恢复的会话从不发送,也没有创建会话 initialize: false,用于测试或用于在渲染时创建会话的服务器.
调度行动
session.dispatchAction 是消费者的单一行动入口。 它接受两种信息.
提交提示
在 A2-UI 构成外输入的即时键, 如集成器的搜索框中, 是 a SubmitPromptAction数字 :
session.dispatchAction({name: 'submitPrompt', payload: {prompt: 'show me running shoes'}});
它打开了一个新的流转方向 记录了转弯时的提示 input.prompt,然后将其输入到反向端点。 它不需要变换组件或主动转弯,因此它也开启了会话的第一个转弯. 当另一个请求在逃时,它会等待它(参见: 一次一个请求),以及 retry(turnId) 重新开出一圈.
构成部分行动
组件动作是标准 A2-UI 客户端到服务器消息( NameA2uiClientMessage被冻结的制造器将手伸到其 onAction 处理器,所以它有线 没有适配器:
<A2UIProvider catalog={catalog} onAction={session.dispatchAction}>{/* … */}</A2UIProvider>
鉴于一个消息, dispatchAction 打开它 userAction,从会话的活出地表取回调度部件的区别性物质,无论由哪个运行产生,对照组件内部注入的合同验证动作有效载荷,并在成功时将动作定位到反向终点.
制作方宣布为打开转盘的动作,如后续芯片或搜索选项,作为对话的新转盘被回答,如提示. 制片人用表面宣布,在AG-UI中 metadata 简况metadata.turnOpeningActions,通过表面的ID),所以消费者宣布无所事事,而转弯会一经hopper取出动作就打开. 这样的行动开启了一个新的接踵而至的转折 input.action 记录它(组件、动作名称、地表代号、源节点和有效载荷),然后转弯成为活转。 转弯时的表面 一直没有接受动作 它不需要主动转弯,所以在外壳上打开的转弯动作打开会话的第一个转弯. 任何其他动作只更新表面:其表面快照用活转来记录,或用 session.initialization 在第一个转弯之前.
没错 火灾和遗忘: 回归的承诺总是解决,而永不拒绝,所以消费者不需要 .catch。一个没有 userAction,没有 sourceComponentId,一个解析为无组件的节点,或者一个内部验证失败被丢弃,只发出一个 dev-exected 警告,而什么也没有发出.
一次一个请求
瑟米多每次发送一个请求, initialize,激励和构成行动相同,但 并行行动 制片人会宣布。 请求中发出的命令,是被扣留的;任何警告,都不被发出来;命令中发出的警告,是无效的。 每个 dispatchAction 承诺一旦结束就会解决.
session.request正在发送的请求( e){kind: 'initialize'}, (中文(简体) ),{kind: 'prompt', turnId}或者说{kind: 'action', component, name, surfaceId, sourceComponentId, turnId?},没有turnId在第一个回合之前),或undefined当闲置。 一个动作的运行计数, 包括后续。 同步读取,或者通过subscribe,以锁定或淡化接口,并延迟发送,直到会话自由.component是发送节点的区别者,所以消费者可以按其自己的行动清单,如按部件和行动名称,按与下列单位签订的合同键入的使其表面能够使用的动作清单ComponentActionNames。 。 。 .cancel()在飞行中停止请求,但session.request等它没有受伤 以后再做一些微小任务 之后就发出来cancel()被扣留。 等着request改为undefined,通过subscribe在发送下一个之前.
同步和缓存动作
生产者可以在回合之外回答一些行动,例如针对突出建议即时产品. 在AG -UI中公布它们与地表 metadata 在宣布开启动作时
"metadata": {
"concurrentActions": {"<surfaceId>": {"fetchInstantProducts": {"cache": {"ttlMs": 60000}}}}
}
每项同时进行的行动可以包括: cache 政策、答案的寿命;没有答案,答案就得不到重复。 最新宣布的表面胜出,空入则撤销. 渲染器只发送:它没有缓存,没有重试和等待自由会话.
- 无论飞行中的请求如何,同时发出一个行动,从不设定
session.request即时或cancel()不制止它,它将被扣留,直到会议有它的迹象. - 只应用从节点发送的最新消息 。 飞行中一个更古老的请求被中止,除非动作被缓存:然后它运行到最后,所以它的答案被缓存,一个带有同一种有效载荷的调度会加入它而不是发送另一个.
- 在宣布的寿命内再次以相同有效载荷发送的缓存动作,从会话缓存中解答而无所寄出. 缓存保存了最后100个答案,活在记忆中,从未被序列化.
- 它的答案可能只更新会话已经持有的节点的数据模型;其他任何东西,如新的表面或组件,都会被丢去并附带一个 dev-leven的警告,答案是:
metadata未读。 它的书写范围session.a2uiMessages财务报告和审定财务报表session.state,但是没有运行记录它们:后来录制的写到同一路径的文字代替它们,session.serialize()离开它们,一个恢复的会话会显示该州最后记录的左跑,所以渲染器再次问起. - 它的失败,包括一个被拒绝的请求,是一个虔诚的警告. 没有
session.actionError更改不运行 .
乐观价值
为了显示一个手势的预期值,直到会话解答为止,显示从手势被发出到其发出承诺决定为止的价值,然后让位于制作者的价值. 一个返回它的渲染器 onAction 处理者的承诺 从自己的派遣手 回来 session.dispatchAction 答应我,等待造物师的指令就够了 热子不依赖于UI框架,因此任何状态机制都有效. 与反应 useOptimistic例如,等待发送的过渡会持续那么长:
const [values, addOptimisticValues] = useOptimistic(producerValues);
startTransition(async () => {
addOptimisticValues(values.map((v) => (v.value === 'Nike' ? {...v, state: 'selected'} : v)));
await renderer.dispatchAction({event: {name: 'toggleSelect', context: {value: 'Nike'}}});
});
乐观值让位于制作方的手势一结束,无论结果如何,所以一个失败或被扣留的手势不需要回滚. 热力学从不计算预期值:整数是.
货币冲突
当“网关”仍然忙于处理关于届会的另一个请求时,它拒绝新的请求(409, (中文(简体) ), AGENT:ACTION_IN_FLIGHT) (中文(简体) ). 热电站自动重试最多可达30秒,而一个恢复的冲突却无所表现. 如果它放弃了:
- a 一个 动作 不打开一转而不改变任何转折,从而释放出乐观的持有
session.actionError(一) 报告情况,直至发出下一个行动或提示; - a 一个 打开转弯的动作 套装
session.actionError轮到他error,可用retry(turnId)这是最后的转折; - (单位:千美元) 提示轮到
error,可用retry(turnId)而这是最后的转折.
转动/回转模式
每个转一对 input 及其流出 response数字 :
interface Turn {
id: string;
input: {prompt?: string; action?: TurnAction}; // the prompt, or the action that opened the turn
response: TurnResponse;
status: 'streaming' | 'complete' | 'error';
error?: string; // present iff status === 'error'
}
interface TurnResponse {
activities: Activity[]; // raw snapshot log, latest snapshot per messageId, defaults to []
surfaces: DiscoveredSurface[]; // the live surfaces this turn created
agent?: {messages: AgentMessage[]; reasoningSteps: ReasoningStep[]}; // present only when the router invoked an agent
}
activities 路由中立,而且始终存在. agent 仅当路由器为转弯而引用代理人时才会出现。 消费者读取 turn.input.prompt, (中文(简体) ), turn.response.surfaces (在哪里画转弯的块)和 turn.response.agent?.messages 编号 : turn.response.agent?.reasoningSteps。 。 。 .
会话范围表面
地表活在会话中,而不是创造它们的转弯. 会话来源于一个命令的活动日志:初始化的活动,然后每个转弯. 水面从它的 createSurface 直至 deleteSurface以运行者为准 createSurface 对于活的id取代了表面, 所以一个生产者薄荷一个新的id 对于每一个新的表面.
session.surfaces按创建顺序列出每个活表面;一个表面,代理删除它,一个再创建移动到结尾。 每个转弯response.surfaces列出生成的活表面; 找到一个没有生成的, 如外壳, 由其rootComponentType。只有在创建它的运行流出时才列出加载占位符:当其转动或初始化流出时,或者当飞行流中的请求进入该运行而未发现其已留下时;留下的代理人(取消运行、出错、恢复会话)之一离开列表,所以消费者为列出的每个表面画出一个骨架isLoading别动.session.a2uiMessages按照到达顺序是整个会话的渲染流。 它只生长,所以一个单一的渲染器每个新消息都会被喂入一次. a 之前,一个createSurface代替活表面,它携带一个deleteSurface因为它没有在表面做活的手术, 因为造物者拒绝两者。 它之所以存在,是因为Agent Gateways exits A2-UI v1.0,而每个可用的渲染器消耗v0.9:热电压降低消费者代表的流(已记录的临时债务,见下文).session.state是每个活地表的验证数据模型,由地表id键入,以所写的运行为准,上面有同时动作的答案.- 一个动作解决了整个会话的活表面的源组件,所以更早运行的表面会不断接受动作. 它的运行只更新了表面:它的表面快照用活转来记录,或者在一转前进行初始化,它永远不会改变一转或初始化的状态或反应. 当它失败,不管终点拒绝它,它会流出
RUN_ERROR,其流结束没有终端事件或请求无法构建,session.actionError识别此动作( e)message, (中文(简体) ),component, (中文(简体) ),name, (中文(简体) ),surfaceId, (中文(简体) ),sourceComponentId),因此消费者可以显示它来自的街区旁边的故障.
每个 DiscoveredSurface 携带 surfaceId, (中文(简体) ), rootComponentType,以及 isLoading,这是 true 当表层的数据模型宣布时 isLoading: true (用于 rootComponentType) (中文(简体) ).
验证和显示值
- 客户端验证保留,透明. 注射时
contractsschema仍然是唯一的验证来源,但消费者从未运行Zod并写出任何适配器胶. 入境updateDataModel行动在应用到session.state,与每个活地表面的组件相对抗;出站动作有效载荷在POST之前的私人发送路径被验证. 两种边界都生活在核心内.session.a2uiMessages来源于生源流,所以渲染器仍然收到一个操作,验证从session.state,且组件状态由createSurface.dataModel,它永远不会被验证. - 显示值格式是代理生成的. 渲染器显示它通过其
{ path }绑定; Thermidor 不重塑显示的值.
注射计划、终点和上下文
- 合同计划a 由消费者注入的Zod受歧视工会
contracts)是组件状态和动作负载验证的唯一来源。 通过结构打字接缝(ContractsSchema编号 :ComponentContractSchema编号 :ParsableSchema)只读出公共 Zod API,因此它宣布不依赖任何特定的计划包. 它宣布zod作为跨两大类的同伴依赖关系3.25.76 || ^4),所以验证通过单一共享zod实例通过注入的图解。 由Zod 3制造出制造器的消费者注入了Zod 3图案,例如:@coveo/thermidor-schema/zod3。 。 。 . - 终点 何时
endpoint提供客户邮递服务。 如果缺席,就解决了https://{orgId}.org.coveo.com并附加固定的逆向路径/api/preview/organizations/{orgId}/agents/commerce/agui/converse。 。 。 . - 上下文提供者 —— 说
navigatorContextProvider(所需)和commerceContextProvider是每个请求读取的同步函数。 导航器上下文包含请求的language, (中文(简体) ),country财务报告和审定财务报表currency。上下文从不存储在会话上,也从不序列化.
序列化
session.serialize() 生成一个版本 SerializedSession (整数) version >= 1),不同于运行时间 Turn 类型。 传回去吧 sessionToRestore 内 createSession 继续谈话 开始,转弯和会话记录 state 表面和成形器流从未存在过 activities 在恢复时); 转弯或初始化在中游持续; 降级为 error 与 'Stream was interrupted'保留它持有的东西.
记录的临时债务和范围外的项目
以下是故意的 尚未执行 并记录为债务或延期工作:
- 临时地表引出 —— 说
surfaces是来自activities,注释为临时;核心解释为否rootComponentType;消费者阅读. 服务器发送打入的路由后, 它将被退役 . - A2-UI v0.9 渲染器投影 —— 说
session.a2uiMessages降级代理Gateway的v1.0流为今天的v0.9渲染器. 一旦 V1.0 有能力的渲染器存在,则在断裂变化中删除 . - 渲染器流绕过验证 - 输入操作,即中途验证从
session.state仍然通过session.a2uiMessages,组件的种子由createSurface.dataModel,它永远不会被验证。 固定必须覆盖两条路. - 在渲染器边界上强制操作负载打字 - 变形手
dispatchAction输入的动作信息很松散,因此,每个组件的动作打字由公约适用(消费者选择输入生成的XxxAction而不是在边界上执行。 如果软件包拥有约束性分辨率,则以后可以恢复. - 软件包重命名 ——被推迟为低价;一揽子措施仍然有效
@coveo/thermidor。 。 。 .
发展
pnpm --filter @coveo/thermidor build # build project
pnpm --filter @coveo/thermidor test # run unit + property tests
pnpm --filter @coveo/thermidor test:dts # validate public API surface (incl. DX type test)
建筑
见 架构指南 内部结构,以及 docs/internal/adr。 。 。 .