@coda-console/protocol 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,7 +5,7 @@ Coda 与插件之间的约定,Coda 和插件都引用它,独立演进:
5
5
  - **插件清单的类型**(`schemas/plugin/`):插件目录 `package.json` 里 `contributes` 的每个键是一种类型,各有一份 JSON Schema(draft 2020-12,`$id` 为 `https://coda.pub/schemas/plugin/v1/<文件>`)。`plugin.json` 是整份清单,`setting.json` 是一项设置的声明。插件写的合乎它用到的类型就对;写给别的插件扩展点的键由那个插件声明,扩展点可以带自己的 schema(`extensionPoints.<名>.schema`,声明它的插件目录里的一个文件),写给它的东西合乎那份 schema 才对。
6
6
  - **通用校验器**(`validate.mjs`):用 Ajv 对清单做 schema 校验。JSON Schema 说不了的只用五个通用关键字(`x-unique-by`、`x-max-contains`、`x-ref`、`x-placeholders`、`x-file`,照 Kubernetes 的 `x-kubernetes-*`),每个在哪里写都是同一个意思,写法见文件开头。校验器不随功能变长:类型加字段就是改 schema。
7
7
  - **流程方言**(`workflow/`):项目和插件的流程(`.coda/workflows/*.bpmn`,BPMN 2.0)在 Coda 的命名空间 `https://coda.pub/schema/bpmn/1.0` 里能写什么。`moddle.json` 是 `coda:` 属性与元素(bpmn-moddle 的描述格式),`values.json` 是每个值能写什么(每个 `coda:` 类型的属性组成的对象的 JSON Schema,一个元素里同种 `coda:` 元素成组的 schema),`actions.json` 是 `coda:action` 的各个动作和它们接受的参数,`dialect.json` 是流程种类、Coda 自己发的事件、步骤失败的错误码、表达式能读的运行变量和运行的输入能是哪些类型。`workflow.mjs` 用它们读一份流程:方言里没有的、值不合 schema 的都拒绝,说法是「位置:schema 对那里的说明」。值的 schema 用清单的那几个关键字,另加三个:`x-feel`(是 FEEL:「=」开头是表达式,否则文字里的 `{{ … }}` 是,按这份流程里的名字读得通)、`x-duration`(ISO 8601 时长,不为负)、`x-number`(文字换算成数字是个有限的数)。`structure.json` 声明流程结构规则,`structure.mjs` 负责跨节点检查。
8
- - **Agent 接入协议**(`docs/agent-protocol.md`):Coda 怎样用 ACP 接 Agent,以及 Coda 加的扩展(会话指令、插话、`_coda/wait`、执行失败、工具调用的审核)。
8
+ - **Agent 接入扩展**(`schemas/agent/`、`agent.mjs`、`docs/agent-protocol.md`):Coda 怎样用 ACP 接 Agent,以及 Coda 加的扩展(会话指令、插话、`_coda/wait`、执行失败、工具调用的审核)。
9
9
 
10
10
  流程方言的 **结构规则**从 1.8.0 起也由 `readWorkflow` / `workflowProblem` 检查。`workflow/structure.json` 声明支持的顶层元素与不支持的流程元素、各位置的事件定义、`coda:` 属性的位置限制、任务与多实例的局部写法、脚本数量、定时间隔和生命周期等待类型。`rules` 使用 JSON Schema 检查节点的无环投影(原始标量属性与父级、事件种类等固定事实),复用 `newAjv()` 和已有关键字;`moddle.json` 的 `extends` 仍声明属性可附着的基础类型。`CODA_STRUCTURE` 导出同一份表。新增局部规则改数据,不增加校验器分支。
11
11
 
@@ -63,6 +63,31 @@ fieldsProblem(JSON.parse(fieldsJSON), 'ask');
63
63
 
64
64
  设置的 `items.anyOf` 选项可附带 `workflowKind`(`check`、`premerge`、`deploy`、`preview`、`release`)。全局设置显示全部选项;项目设置只显示该项目有的流程类别,未写此字段的选项始终显示。宿主负责按项目提供可选项,流程执行时仍需按运行的提交判断可用操作。
65
65
 
66
+ ## Agent 接入扩展
67
+
68
+ 从 1.9.0 起,[Agent 协议正文](docs/agent-protocol.md#可执行的形状定义)引用 `schemas/agent/` 的可执行定义:
69
+ 活动报告与停止、上下文压缩、远端入口、双方能力声明、插话的 Coda 约定、执行失败、工具审核、用量数据和默认选项标记。
70
+ `messages.json` 是方向与参数/答复 schema 的索引;ACP 本身仍由 ACP SDK 校验。
71
+
72
+ ```js
73
+ import {
74
+ agentMessageProblem, agentResultProblem,
75
+ agentCapabilitiesProblem, agentValueProblem,
76
+ AGENT_METHODS, AGENT_SCHEMAS,
77
+ } from '@coda-console/protocol/agent';
78
+
79
+ agentMessageProblem('_coda/wait', {
80
+ sessionId: 's1', waitId: 'build', state: 'waiting',
81
+ }); // null, or "location: schema description"
82
+ agentResultProblem('_coda/session/compact', { compacted: true });
83
+ agentCapabilitiesProblem({ coda: { waits: {}, compact: {} } }, 'agent');
84
+ agentValueProblem('review.json', review, '工具的审核');
85
+ ```
86
+
87
+ 校验不修改输入。未知扩展键保留;活动文本长度沿用 UTF-16,日期沿用 `Date.parse`,用量数据按 JSON 的 UTF-8 字节数限制。
88
+ 深层/大型 JSON 用显式栈检查,非有限数字与循环引用不会通过。会话归属、活动状态迁移、调用起因、协商、超时、重试和授权仍由宿主判断;
89
+ 丢弃未知风险值、压缩答复的裁剪与兜底等整理也留在 Coda。详见正文的兼容性与接入说明。
90
+
66
91
  ## 谁在用
67
92
 
68
93
  - **Coda**:读插件时用它校验清单,再整理成自己的结构;只另看插件身份(ID、设置名在 ID 之下),以及装到一台机器上才知道的(来源、已装插件的冲突)。读流程时用 `readWorkflow` 同时校验方言、值与结构,再编译成引擎需要的数据。
package/agent.mjs ADDED
@@ -0,0 +1,84 @@
1
+ // Coda's extensions beside ACP: methods select schemas from a table; metadata selects a schema by reference.
2
+ // ACP itself stays with its SDK. These checks neither normalize values nor decide anything about a live session.
3
+ import { readFileSync, readdirSync } from 'node:fs';
4
+ import { newAjv, described } from './validate.mjs';
5
+ import { jsonFits, valueKeywords } from './workflow/keywords.mjs';
6
+
7
+ const dir = new URL('./schemas/agent/', import.meta.url), base = 'https://coda.pub/schemas/agent/v1/';
8
+ const read = file => JSON.parse(readFileSync(new URL(file, dir), 'utf8'));
9
+ export const AGENT_METHODS = read('messages.json').methods;
10
+ export const AGENT_SCHEMAS = Object.fromEntries(readdirSync(dir).filter(file => file.endsWith('.json') && file !== 'messages.json')
11
+ .sort().map(file => [file, read(file)]));
12
+ const schemas = Object.values(AGENT_SCHEMAS), ajv = newAjv();
13
+ // Wire objects have own keys, including names also found on Object.prototype.
14
+ ajv.opts.ownProperties = true;
15
+ valueKeywords(ajv, {});
16
+ ajv.addFormat('date-parse', value => Number.isFinite(Date.parse(value)));
17
+ for (const schema of schemas) ajv.addSchema(schema);
18
+
19
+ const unescape = step => step.replaceAll('~1', '/').replaceAll('~0', '~');
20
+ const escape = step => step.replaceAll('~', '~0').replaceAll('/', '~1');
21
+ const place = (path, where) => path.split('/').slice(1).map(unescape).reduce((at, step) =>
22
+ /^\d+$/.test(step) ? `${at}[${step}]` : /^[A-Za-z_$][\w$]*$/.test(step) ? `${at}.${step}` : `${at}「${step}」`, where);
23
+ function schemaAt(ref) {
24
+ if (typeof ref !== 'string') return undefined;
25
+ const [file, fragment = '', extra] = ref.split('#');
26
+ if (extra !== undefined || !Object.hasOwn(AGENT_SCHEMAS, file)) return undefined;
27
+ let pointer;
28
+ try { pointer = decodeURIComponent(fragment); } catch { return undefined; }
29
+ if (pointer && !pointer.startsWith('/')) return undefined;
30
+ let node = AGENT_SCHEMAS[file];
31
+ for (const token of pointer.split('/').slice(1)) {
32
+ if (/~(?:[^01]|$)/.test(token)) return undefined;
33
+ const key = unescape(token);
34
+ node = node && typeof node === 'object' && Object.hasOwn(node, key) ? node[key] : undefined;
35
+ }
36
+ return typeof node === 'boolean' || node && typeof node === 'object' && !Array.isArray(node) ? node : undefined;
37
+ }
38
+ // Resolve only schema references, never pointers through incoming data or prototype properties.
39
+ function resolve(schema) {
40
+ while (schema?.$ref) {
41
+ const url = new URL(schema.$ref, base), file = url.pathname.split('/').at(-1);
42
+ schema = schemaAt(`${file}${url.hash}`);
43
+ }
44
+ return schema;
45
+ }
46
+ function description(schema) {
47
+ const stack = [schema];
48
+ while (stack.length) {
49
+ const node = resolve(stack.pop());
50
+ if (node?.description) return node.description;
51
+ if (node?.allOf) for (let i = node.allOf.length - 1; i >= 0; i--) stack.push(node.allOf[i]);
52
+ }
53
+ }
54
+ function problem(error, where) {
55
+ let path = error.instancePath, said;
56
+ if (error.keyword === 'required') {
57
+ const key = error.params.missingProperty, properties = error.parentSchema.properties;
58
+ path += `/${escape(key)}`;
59
+ said = properties && Object.hasOwn(properties, key) ? description(properties[key]) : undefined;
60
+ }
61
+ return `${place(path, where)}:${said ?? described(error, { schemas }) ?? error.message}`;
62
+ }
63
+
64
+ // A reference is relative to schemas/agent/, optionally with a JSON Pointer fragment.
65
+ export function agentValueProblem(ref, value, where = ref) {
66
+ const validate = schemaAt(ref) !== undefined ? ajv.getSchema(`${base}${ref}`) : null;
67
+ if (!validate) return `${where}:没有这个 Agent schema`;
68
+ if (!validate(value)) return problem(validate.errors[0], where);
69
+ // Opaque remote payloads and future metadata are JSON too. An explicit stack handles deep/wide values and cycles.
70
+ return jsonFits(value, Infinity) ? null : `${where}:${AGENT_SCHEMAS['json.json'].description}`;
71
+ }
72
+ function messageProblem(method, part, value, where) {
73
+ const entry = typeof method === 'string' && Object.hasOwn(AGENT_METHODS, method) ? AGENT_METHODS[method] : null;
74
+ if (!entry) return `${where}:没有这个 Coda 扩展方法`;
75
+ if (!Object.hasOwn(entry, part)) return `${where}:通知没有答复`;
76
+ return agentValueProblem(entry[part], value, where);
77
+ }
78
+ export const agentMessageProblem = (method, params, where = `${method} params`) => messageProblem(method, 'params', params, where);
79
+ export const agentResultProblem = (method, result, where = `${method} result`) => messageProblem(method, 'result', result, where);
80
+ // Pass the capabilities' _meta object, not initialize itself. An absent _meta means no Coda capabilities declared.
81
+ export function agentCapabilitiesProblem(meta, party = 'agent', where = `${party}Capabilities._meta`) {
82
+ if (party !== 'agent' && party !== 'client') return `${where}:能力声明方只能是 agent 或 client`;
83
+ return meta === undefined ? null : agentValueProblem(`${party}-capabilities.json`, meta, where);
84
+ }
@@ -7,6 +7,47 @@ Coda 就知道这个 Agent 在一轮之外还在做什么,如实显示、不
7
7
 
8
8
  **改协议**:本文在 `coda-console/coda-protocol`,与插件清单的类型(`schemas/`)同一个仓库、同一个版本(npm 包 `@coda-console/protocol`)。Coda 与公开仓库 `coda-console/coda-plugins` 的契约检查包都依赖这个包:改协议就在这里开 PR,发布新版本后两边各自升依赖。检查要换判定方式时(新的请求怎么发、怎样算合格),另给 coda-plugins 开 PR 改检查本身。文中提到的 Coda 文档与源码在 Coda 的仓库里。
9
9
 
10
+ ## 可执行的形状定义
11
+
12
+ 扩展的字段、必填项和取值以 [`schemas/agent/`](../schemas/agent/) 为准(JSON Schema draft 2020-12,
13
+ `$id` 为 `https://coda.pub/schemas/agent/v1/<文件>`)。本文解释方向、生命周期与处理方式;
14
+ [`messages.json`](../schemas/agent/messages.json) 是方法到 schema 的数据索引,标明请求/通知与方向。
15
+ ACP 的消息、内容块、会话指令和能力由 ACP SDK 校验,这里只补 Coda 的部分,不代替 ACP 校验。
16
+
17
+ | 扩展或位置 | schema |
18
+ | --- | --- |
19
+ | `_coda/wait` 通知参数 | [wait.json](../schemas/agent/wait.json) |
20
+ | `_coda/wait/stop` 参数、答复 | [wait-stop.json](../schemas/agent/wait-stop.json) 的 `$defs/params`、`$defs/result` |
21
+ | `_coda/session/compact` 参数、答复 | [compact.json](../schemas/agent/compact.json) 的 `$defs/params`、`$defs/result` |
22
+ | `_coda/remote/code` 参数、答复 | [remote-code.json](../schemas/agent/remote-code.json) 的 `$defs/params`、`$defs/result` |
23
+ | `_coda/remote` 参数、答复 | [remote.json](../schemas/agent/remote.json) 的 `$defs/params`、`$defs/result` |
24
+ | `clientCapabilities._meta`、`agentCapabilities._meta` 的 Coda 声明 | [client-capabilities.json](../schemas/agent/client-capabilities.json)、[agent-capabilities.json](../schemas/agent/agent-capabilities.json) |
25
+ | `_session/steering` 的 Coda 约定 | [steering.json](../schemas/agent/steering.json) 的 `$defs/params`、`$defs/result`、`$defs/capabilities`(initialize 顶层 `_meta`);`prompt` 内容仍由 ACP SDK 校验 |
26
+ | `error.data._meta.coda.failure` | [failure.json](../schemas/agent/failure.json) |
27
+ | 工具更新的 `_meta.coda.review` | [review.json](../schemas/agent/review.json);已知风险值见 `$defs/risk` |
28
+ | 用量更新的 `_meta.coda` | [usage.json](../schemas/agent/usage.json) |
29
+ | 配置选项的 `_meta.coda.default` | [default.json](../schemas/agent/default.json)(传入 `_meta.coda` 对象) |
30
+
31
+ `@coda-console/protocol/agent` 导出 `agentMessageProblem(method, params)`、`agentResultProblem(method, result)`、
32
+ `agentCapabilitiesProblem(meta, party = 'agent')` 和 `agentValueProblem(ref, value, where)`,合法返回 `null`,
33
+ 不合则返回「位置:schema 的 description」。前两个函数也接受第三个 `where`;能力校验接受第三个 `where`,
34
+ `meta` 是能力声明的整个 `_meta`(缺省合法),`party` 是 `agent` 或 `client`;`ref` 是上述文件名,可带 `#/$defs/…`。
35
+ 不认识的方法返回问题,调用方须先按 `AGENT_METHODS` 区分 ACP、Coda 扩展和其他扩展;通知没有答复 schema。
36
+ `AGENT_SCHEMAS` 导出按文件名索引的同一份 schema。
37
+
38
+ 保留现有接收规则:未知键允许并且不改写;活动的 `kind` 不限于本文列出的词,文本上限用 `x-length` 按 UTF-16 长度计算;
39
+ `date-parse` format 沿用 `Date.parse`,发送端仍应使用 ISO 8601。`x-json` 复用流程的 JSON 字节预算检查。
40
+ API 接受 [json.json](../schemas/agent/json.json) 所述的 JSON 值,额外以显式栈检查不在 JSON 中的值(非有限数字、循环引用等),不设不必要的嵌套层数或数组长度上限。
41
+ Coda 过去静默丢弃的值,契约检查现在可得到具体位置;过去没有独立报错的字段补了 description,已有报错沿用原句,
42
+ 不把原始输入拼入错误(避免巨大值或深层对象导致报错本身失败)。
43
+
44
+ **校验不是整理或运行状态判断**:审核未知 `risk` 不影响整条审核有效,宿主仍只保存 `$defs/risk` 接受的值;
45
+ 额度不足只按 `kind` 判断,不校验也不采用它附带的 `input`/`retryAfterMs`;压缩答复的未知字段照样允许,
46
+ 宿主仍只保存已知字段、裁剪原因,畸形答复仍按原有兜底处理,不应把整个答复的校验失败改成执行失败。
47
+ 压缩的 `before`/`after` 沿用有限数要求,不新增整数或非负限制。能力声明 schema 描述对象声明,
48
+ 不代替能力探测:未声明、不合法的声明仍按宿主原有方式处理(旧 `supportsRemote` 还把数组视为声明,不能直接用 schema 替换该谓词)。
49
+ 会话归属、能力是否协商、活动是否存在/终结、调用起因、轮次、顺序、重试、超时、绑定码有效期和授权仍由 Coda 处理。
50
+
10
51
  ## 生命周期归属
11
52
 
12
53
  插件在 `contributes.agents[]` 声明 `lifecycle`,默认 `managed`;外部自主运行的 Bot 声明 `external`。
@@ -58,7 +99,7 @@ ACP 只允许在一轮(`session/prompt`)进行时交互。Agent 在两轮之
58
99
  之后 `config_option_update` 与 `session/set_config_option`、`session/set_model` 的结果改了它,以改后的为准。Coda 每轮开始前把会话设置设给 Agent,
59
100
  再读当前模型记作这一轮的实际模型;读不到就拒绝这一轮(「适配器未报告实际模型」),不发 `session/prompt`。
60
101
  - **设置**:会话设置里选了的模型、推理强度与模式,每轮开始前设给 Agent;没选的保持会话现状。当前模式是空字符串表示还没有选模式(Agent 按它自己的配置),按没有设置显示。模型或推理强度的选择型配置项里,
61
- 可以有一项带 `_meta.coda.default: true`,表示交给 Agent 自己的默认:会话设置里没选时,Coda 把配置项切回这一项。这一项不进模型或强度的列表,
102
+ 可以有一项带 `_meta.coda.default: true`([default.json](../schemas/agent/default.json)),表示交给 Agent 自己的默认:会话设置里没选时,Coda 把配置项切回这一项。这一项不进模型或强度的列表,
62
103
  当前值是它时按没有设置显示。
63
104
  - **其他配置项**:模型、推理强度与模式之外的配置项,Coda 原样列给用户:选择型是一组选项,开关型(`type: boolean`)是一个开关;Coda 在 `initialize` 的
64
105
  `clientCapabilities.session.configOptions.boolean` 里声明接受开关型。会话设置里按配置项 ID 给的值,每轮开始前设给 Agent;当前模型没有这一项就不设。
@@ -73,6 +114,8 @@ ACP 只允许在一轮(`session/prompt`)进行时交互。Agent 在两轮之
73
114
 
74
115
  ## 会话指令
75
116
 
117
+ 本节形状由 ACP SDK 的 `systemPrompt` schema 定义,不在 Coda schema 中重复。
118
+
76
119
  Coda 给每个会话一份指令:回复怎样在 Mac 与 iPhone 上显示、默认回复语言,再接目录启用的插件说明(插件(Coda 的 docs/plugins.md)的 `contributes.instructions`),
77
120
  正文在 `backend/src/components/session-config/client-instructions.mjs`。它走 ACP 的 [`systemPrompt`](https://agentclientprotocol.com/protocol/draft/session-setup#system-prompt-unstable)(unstable),
78
121
  **接入 Coda 的 Agent 必须支持**:
@@ -94,6 +137,8 @@ Coda 的 MCP 服务器只提供工具,不带 `instructions`。
94
137
 
95
138
  ## 插话
96
139
 
140
+ Coda 使用的形状见 [steering.json](../schemas/agent/steering.json);`prompt` 内容块仍使用 ACP schema。
141
+
97
142
  用户在一轮进行中「立即插话」时,支持插话的 Agent 把这条消息加进正在进行的一轮,不打断它;不支持的,Coda 停止这一轮,
98
143
  在它结束后把消息作为下一轮送出(会话行为契约(Coda 的 docs/session-contracts.md「发送行为」))。父会话给正为它跑一轮的子会话发消息(`send`)时同样插话;
99
144
  不支持的 Agent,这条消息只排到这一轮结束,Coda 不为它停止(工具面(Coda 的 docs/subagent-design.md「工具面」))。插话用生态里已有的 `_session/steering` 扩展:
@@ -114,6 +159,8 @@ Coda 的 MCP 服务器只提供工具,不带 `instructions`。
114
159
 
115
160
  ## 执行失败
116
161
 
162
+ 扩展值的形状见 [failure.json](../schemas/agent/failure.json)。
163
+
117
164
  插件负责将上游错误归为通用类别,Coda 负责显示与重试策略;Coda 不依赖 Agent 名字、错误文字或私有上游错误码。
118
165
  `session/prompt` 的 JSON-RPC 错误通过 `error.data._meta.coda.failure` 报告类别。标准 `error.message` 保留可读的失败原因;
119
166
  其他 ACP 客户端可以忽略扩展。未标记、未知或不合格式的类别按普通错误处理,不自动重试。
@@ -166,6 +213,8 @@ Coda 统一按 15、30、60 秒退避,每次执行最多重试三次,沿用
166
213
 
167
214
  ## 用量里的数据
168
215
 
216
+ 扩展对象及大小限制见 [usage.json](../schemas/agent/usage.json)。
217
+
169
218
  Agent 在 `session/update` 的 `usage_update` 里可以带 `_meta.coda`:一个 JSON 对象,放生命周期流程用得着的数据。Coda 不解释里面的字段,
170
219
  原样作为会话事实 `usage` 交给生命周期流程(Coda 的 docs/workflows.md「生命周期流程」),由流程(通常是同一个插件带的)读、判断。
171
220
 
@@ -182,6 +231,8 @@ Agent 在 `session/update` 的 `usage_update` 里可以带 `_meta.coda`:一个
182
231
 
183
232
  ## 工具调用的审核
184
233
 
234
+ 审核的形状与已知风险值见 [review.json](../schemas/agent/review.json)。
235
+
185
236
  Agent 运行一个工具调用之前自己做的检查(如 Codex 的自动审核:按风险决定放不放行),是这个调用的一部分:
186
237
  在这个调用的 `tool_call` 或 `tool_call_update` 里带 `_meta.coda.review`,不另报一个工具调用。它不是[审批](#acp-部分):审批由用户回答,审核由 Agent 自己判断。
187
238
 
@@ -201,6 +252,8 @@ Agent 运行一个工具调用之前自己做的检查(如 Codex 的自动审
201
252
 
202
253
  ## 压缩上下文
203
254
 
255
+ 参数和答复见 [compact.json](../schemas/agent/compact.json),声明见 [agent-capabilities.json](../schemas/agent/agent-capabilities.json)。
256
+
204
257
  Coda 可以请 Agent 压缩会话的上下文(同 Claude Code 的 `/compact`),由生命周期流程的 `compact-session`(Coda 的 docs/workflows.md「coda-的动作」) 发起;
205
258
  什么时候压、值不值得压由流程按[用量里的数据](#用量里的数据)判断,Agent 只执行。
206
259
 
@@ -237,6 +290,8 @@ Coda 存的就是这段文字,两端客户端显示时按看的那台设备的
237
290
 
238
291
  ### 能力协商
239
292
 
293
+ 声明形状见 [client-capabilities.json](../schemas/agent/client-capabilities.json) 与 [agent-capabilities.json](../schemas/agent/agent-capabilities.json)。
294
+
240
295
  - Coda 在 `initialize` 的 `clientCapabilities` 里总是带 `_meta.coda.waits: {}`。没看到它的客户端不认识这条扩展,Agent 不应对它发 `_coda/wait`。
241
296
  - Agent 在 `initialize` 结果的 `agentCapabilities` 里带 `_meta.coda.waits: {}`,表示它会登记。这只是声明:Coda 把它记在托管握手里,
242
297
  健康检查(`GET /v1/health` 的会话项 `waits`)报出来;不声明的 Agent 发来的 `_coda/wait` 也照收。
@@ -247,6 +302,8 @@ Coda 存的就是这段文字,两端客户端显示时按看的那台设备的
247
302
 
248
303
  ### `_coda/wait`
249
304
 
305
+ 通知参数及所有字段上限见 [wait.json](../schemas/agent/wait.json)。
306
+
250
307
  Agent 发给 Coda 的通知(没有 `id`,不回复),和 `session/update` 走同一个顺序:说在前面的先处理。
251
308
 
252
309
  | 字段 | 必填 | 说明 |
@@ -291,6 +348,8 @@ Agent 发给 Coda 的通知(没有 `id`,不回复),和 `session/update`
291
348
 
292
349
  ### `_coda/wait/stop`
293
350
 
351
+ 请求参数和答复见 [wait-stop.json](../schemas/agent/wait-stop.json)。
352
+
294
353
  Coda 发给 Agent 的请求,参数 `{ "sessionId": …, "waitId": … }`,Agent 回 `{}`。只对声明了 `stoppable` 的活动发:
295
354
  用户点了任务或子 Agent 的停止、父会话取消这次调用(含已经交回、还在为补交跟进的)时它树下的项。
296
355
  回复只表示收到;Coda 不当它已经停了,直到 Agent 随后用 `_coda/wait` 报它结束(通常是 `cancelled`)。
@@ -324,6 +383,8 @@ Coda 发给 Agent 的请求,参数 `{ "sessionId": …, "waitId": … }`,Age
324
383
 
325
384
  ## 远端入口
326
385
 
386
+ 绑定码与转交请求的形状分别见 [remote-code.json](../schemas/agent/remote-code.json)、[remote.json](../schemas/agent/remote.json)。
387
+
327
388
  有的 Agent 不在这台机器上干活:它是别处的一个 Bot(比如 Grok Bot,在它自己的云电脑上),会话里的适配器只是它的代言。
328
389
  Bot 需要随时进到自己的会话里来:说话、调 Coda 的工具、开一轮。远端入口给它一条只通往这个会话的路。
329
390
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coda-console/protocol",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Coda 的协议:插件清单的类型(JSON Schema)、通用校验器、流程方言与 Agent 接入协议",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -14,6 +14,7 @@
14
14
  "files": [
15
15
  "schemas",
16
16
  "validate.mjs",
17
+ "agent.mjs",
17
18
  "workflow",
18
19
  "workflow.mjs",
19
20
  "docs"
@@ -24,7 +25,8 @@
24
25
  "./schemas/*": "./schemas/*",
25
26
  "./workflow/*": "./workflow/*",
26
27
  "./docs/*": "./docs/*",
27
- "./package.json": "./package.json"
28
+ "./package.json": "./package.json",
29
+ "./agent": "./agent.mjs"
28
30
  },
29
31
  "engines": {
30
32
  "node": ">=24"
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/agent-capabilities.json",
4
+ "type": "object",
5
+ "description": "_meta 须为对象",
6
+ "properties": {
7
+ "coda": {
8
+ "type": "object",
9
+ "description": "_meta.coda 须为对象",
10
+ "properties": {
11
+ "waits": {
12
+ "type": "object",
13
+ "description": "waits 能力用对象声明(通常为 {})",
14
+ "properties": {}
15
+ },
16
+ "compact": {
17
+ "type": "object",
18
+ "description": "compact 能力用对象声明(通常为 {})",
19
+ "properties": {}
20
+ },
21
+ "remote": {
22
+ "type": "object",
23
+ "description": "remote 能力用对象声明(通常为 {})",
24
+ "properties": {}
25
+ }
26
+ }
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/client-capabilities.json",
4
+ "type": "object",
5
+ "description": "_meta 须为对象",
6
+ "properties": {
7
+ "coda": {
8
+ "type": "object",
9
+ "description": "_meta.coda 须为对象",
10
+ "properties": {
11
+ "waits": {
12
+ "type": "object",
13
+ "description": "waits 能力用对象声明(通常为 {})",
14
+ "properties": {}
15
+ },
16
+ "remote": {
17
+ "type": "object",
18
+ "description": "remote 能力用对象声明(通常为 {})",
19
+ "properties": {}
20
+ }
21
+ }
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,46 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/compact.json",
4
+ "description": "压缩上下文的一来一回,不是一轮;额外答复字段由宿主忽略。",
5
+ "$defs": {
6
+ "params": {
7
+ "type": "object",
8
+ "description": "params 须为对象",
9
+ "properties": {
10
+ "sessionId": {
11
+ "type": "string",
12
+ "description": "缺 sessionId",
13
+ "minLength": 1
14
+ }
15
+ },
16
+ "required": [
17
+ "sessionId"
18
+ ]
19
+ },
20
+ "result": {
21
+ "type": "object",
22
+ "description": "压缩答复须为对象",
23
+ "properties": {
24
+ "compacted": {
25
+ "type": "boolean",
26
+ "description": "compacted 须为 true 或 false"
27
+ },
28
+ "reason": {
29
+ "type": "string",
30
+ "description": "没压的原因,给人看"
31
+ },
32
+ "before": {
33
+ "type": "number",
34
+ "description": "before 须为有限数"
35
+ },
36
+ "after": {
37
+ "type": "number",
38
+ "description": "after 须为有限数"
39
+ }
40
+ },
41
+ "required": [
42
+ "compacted"
43
+ ]
44
+ }
45
+ }
46
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/default.json",
4
+ "type": "object",
5
+ "description": "_meta.coda 须为对象",
6
+ "properties": {
7
+ "default": {
8
+ "type": "boolean",
9
+ "description": "default 须为 true 或 false;true 表示交给 Agent 自己的默认"
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/failure.json",
4
+ "type": "object",
5
+ "description": "执行失败须为对象",
6
+ "properties": {
7
+ "kind": {
8
+ "enum": [
9
+ "quota_exhausted",
10
+ "service_unavailable"
11
+ ],
12
+ "description": "kind 只能是 quota_exhausted 或 service_unavailable"
13
+ }
14
+ },
15
+ "required": [
16
+ "kind"
17
+ ],
18
+ "if": {
19
+ "properties": {
20
+ "kind": {
21
+ "const": "service_unavailable"
22
+ }
23
+ },
24
+ "required": [
25
+ "kind"
26
+ ],
27
+ "type": "object"
28
+ },
29
+ "then": {
30
+ "type": "object",
31
+ "description": "服务暂不可用须说明输入是否已接收",
32
+ "properties": {
33
+ "input": {
34
+ "enum": [
35
+ "accepted",
36
+ "not_accepted"
37
+ ],
38
+ "description": "input 只能是 accepted 或 not_accepted"
39
+ },
40
+ "retryAfterMs": {
41
+ "type": "number",
42
+ "minimum": 0,
43
+ "description": "retryAfterMs 须为非负有限数"
44
+ }
45
+ },
46
+ "required": [
47
+ "input"
48
+ ]
49
+ },
50
+ "$comment": "quota_exhausted 不检查 input 或 retryAfterMs;原 agentFailure 对额度不足只检查 kind。"
51
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/json.json",
4
+ "description": "须为 JSON 值(数字须有限,不能循环引用)",
5
+ "$comment": "所有接口的输入域。JSON 本身不容纳非有限数或循环;直接调用 JavaScript API 时,校验器用显式栈检查整个值,不用递归 schema 遍历不透明载荷。",
6
+ "type": ["null", "boolean", "number", "string", "array", "object"]
7
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "description": "Coda 使用的扩展方法与参数、答复 schema 的索引;本文件是数据表,不是 JSON Schema。",
3
+ "methods": {
4
+ "_coda/wait": {
5
+ "kind": "notification",
6
+ "direction": "agent-to-client",
7
+ "params": "wait.json"
8
+ },
9
+ "_coda/wait/stop": {
10
+ "kind": "request",
11
+ "direction": "client-to-agent",
12
+ "params": "wait-stop.json#/$defs/params",
13
+ "result": "wait-stop.json#/$defs/result"
14
+ },
15
+ "_coda/session/compact": {
16
+ "kind": "request",
17
+ "direction": "client-to-agent",
18
+ "params": "compact.json#/$defs/params",
19
+ "result": "compact.json#/$defs/result"
20
+ },
21
+ "_coda/remote/code": {
22
+ "kind": "request",
23
+ "direction": "agent-to-client",
24
+ "params": "remote-code.json#/$defs/params",
25
+ "result": "remote-code.json#/$defs/result"
26
+ },
27
+ "_coda/remote": {
28
+ "kind": "request",
29
+ "direction": "client-to-agent",
30
+ "params": "remote.json#/$defs/params",
31
+ "result": "remote.json#/$defs/result"
32
+ },
33
+ "_session/steering": {
34
+ "kind": "request",
35
+ "direction": "client-to-agent",
36
+ "params": "steering.json#/$defs/params",
37
+ "result": "steering.json#/$defs/result"
38
+ }
39
+ }
40
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/remote-code.json",
4
+ "description": "Agent 请求本会话的绑定码;归属、有效期和一次性使用由 Coda 检查。",
5
+ "$defs": {
6
+ "params": {
7
+ "type": "object",
8
+ "description": "params 须为对象",
9
+ "properties": {
10
+ "sessionId": {
11
+ "type": "string",
12
+ "description": "缺 sessionId",
13
+ "minLength": 1
14
+ }
15
+ },
16
+ "required": [
17
+ "sessionId"
18
+ ]
19
+ },
20
+ "result": {
21
+ "type": "object",
22
+ "description": "绑定码答复须为对象",
23
+ "properties": {
24
+ "code": {
25
+ "type": "string",
26
+ "description": "一次性的绑定码",
27
+ "minLength": 1
28
+ },
29
+ "expiresAt": {
30
+ "type": "string",
31
+ "description": "expiresAt 读不出时间",
32
+ "format": "date-parse"
33
+ },
34
+ "addresses": {
35
+ "type": "array",
36
+ "description": "addresses 须为入口地址的数组;不在 tailnet 时为空",
37
+ "items": {
38
+ "type": "string",
39
+ "description": "入口地址须为文本"
40
+ }
41
+ }
42
+ },
43
+ "required": [
44
+ "code",
45
+ "expiresAt",
46
+ "addresses"
47
+ ]
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/remote.json",
4
+ "description": "转交远端请求,method、params 与答复的业务含义由 Agent 定义;HTTP 入口的 256 KiB 限制不施加到 ACP 答复。",
5
+ "$defs": {
6
+ "params": {
7
+ "type": "object",
8
+ "description": "请求须为 {method, params}",
9
+ "properties": {
10
+ "sessionId": {
11
+ "type": "string",
12
+ "description": "缺 sessionId",
13
+ "minLength": 1
14
+ },
15
+ "method": {
16
+ "type": "string",
17
+ "description": "请求须为 {method, params}",
18
+ "minLength": 1,
19
+ "x-length": 200
20
+ },
21
+ "params": {
22
+ "type": "object",
23
+ "description": "请求须为 {method, params}",
24
+ "properties": {}
25
+ }
26
+ },
27
+ "required": [
28
+ "sessionId",
29
+ "method",
30
+ "params"
31
+ ]
32
+ },
33
+ "result": {
34
+ "description": "Agent 的回答原样作为 { result } 回给 Bot;可以是任意 JSON 值"
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,38 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/review.json",
4
+ "type": "object",
5
+ "description": "审核须为对象",
6
+ "properties": {
7
+ "state": {
8
+ "enum": [
9
+ "reviewing",
10
+ "approved",
11
+ "denied"
12
+ ],
13
+ "description": "state 只能是 reviewing、approved、denied"
14
+ },
15
+ "reason": {
16
+ "type": "string",
17
+ "description": "reason 须为不超过 4000 字的文本",
18
+ "x-length": 4000
19
+ },
20
+ "risk": {
21
+ "description": "不认识的 risk 不记;不因此丢弃审核。已知值见 $defs/risk。"
22
+ }
23
+ },
24
+ "required": [
25
+ "state"
26
+ ],
27
+ "$defs": {
28
+ "risk": {
29
+ "enum": [
30
+ "low",
31
+ "medium",
32
+ "high",
33
+ "critical"
34
+ ],
35
+ "description": "risk 只能是 low、medium、high 或 critical"
36
+ }
37
+ }
38
+ }
@@ -0,0 +1,83 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/steering.json",
4
+ "description": "生态的 _session/steering 扩展中 Coda 使用的部分;prompt 的内容块仍交 ACP SDK 校验。",
5
+ "$defs": {
6
+ "capabilities": {
7
+ "type": "object",
8
+ "description": "initialize 的 _meta 须为对象",
9
+ "properties": {
10
+ "steering": {
11
+ "type": "object",
12
+ "description": "steering 须为对象",
13
+ "properties": {
14
+ "supported": {
15
+ "type": "boolean",
16
+ "description": "supported 须为 true 或 false"
17
+ }
18
+ }
19
+ }
20
+ }
21
+ },
22
+ "params": {
23
+ "type": "object",
24
+ "description": "插话参数须为对象",
25
+ "properties": {
26
+ "sessionId": {
27
+ "type": "string",
28
+ "description": "缺 sessionId",
29
+ "minLength": 1
30
+ },
31
+ "prompt": {
32
+ "description": "与 session/prompt 相同,由 ACP SDK 校验;Coda 发一个文本块"
33
+ },
34
+ "_meta": {
35
+ "type": "object",
36
+ "description": "_meta 须为对象",
37
+ "properties": {
38
+ "steering": {
39
+ "type": "object",
40
+ "description": "steering 须为对象",
41
+ "properties": {
42
+ "idleBehavior": {
43
+ "const": "promptRequired",
44
+ "description": "idleBehavior 须为 promptRequired"
45
+ }
46
+ },
47
+ "required": [
48
+ "idleBehavior"
49
+ ]
50
+ }
51
+ },
52
+ "required": [
53
+ "steering"
54
+ ]
55
+ }
56
+ },
57
+ "required": [
58
+ "sessionId",
59
+ "prompt",
60
+ "_meta"
61
+ ]
62
+ },
63
+ "result": {
64
+ "type": "object",
65
+ "description": "插话答复须为对象",
66
+ "properties": {
67
+ "outcome": {
68
+ "enum": [
69
+ "injected",
70
+ "promptRequired"
71
+ ],
72
+ "description": "插话须回答 injected 或 promptRequired;其他结果的投递状态未知"
73
+ },
74
+ "reason": {
75
+ "description": "原因由 Agent 给出,Coda 只按 outcome 判断投递结果"
76
+ }
77
+ },
78
+ "required": [
79
+ "outcome"
80
+ ]
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/usage.json",
4
+ "type": "object",
5
+ "description": "须为不超过 4096 字节的 JSON 对象",
6
+ "x-json": {
7
+ "schema": {
8
+ "type": "object",
9
+ "description": "须为不超过 4096 字节的 JSON 对象"
10
+ },
11
+ "maxBytes": 4096
12
+ }
13
+ }
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/wait-stop.json",
4
+ "description": "停止一项活动;答复只表示收到,是否已停止由之后的活动报告确认。",
5
+ "$defs": {
6
+ "params": {
7
+ "type": "object",
8
+ "description": "params 须为对象",
9
+ "properties": {
10
+ "sessionId": {
11
+ "$ref": "wait.json#/properties/sessionId"
12
+ },
13
+ "waitId": {
14
+ "$ref": "wait.json#/properties/waitId"
15
+ }
16
+ },
17
+ "required": [
18
+ "sessionId",
19
+ "waitId"
20
+ ]
21
+ },
22
+ "result": {
23
+ "type": "object",
24
+ "description": "Agent 回 {};回复只表示收到",
25
+ "properties": {}
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,99 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://coda.pub/schemas/agent/v1/wait.json",
4
+ "type": "object",
5
+ "description": "params 须为对象",
6
+ "properties": {
7
+ "sessionId": {
8
+ "allOf": [
9
+ {
10
+ "type": "string",
11
+ "description": "缺 sessionId",
12
+ "minLength": 1
13
+ },
14
+ {
15
+ "type": "string",
16
+ "description": "sessionId 须为不超过 200 字的文本",
17
+ "x-length": 200
18
+ }
19
+ ]
20
+ },
21
+ "waitId": {
22
+ "allOf": [
23
+ {
24
+ "type": "string",
25
+ "description": "缺 waitId",
26
+ "minLength": 1
27
+ },
28
+ {
29
+ "type": "string",
30
+ "description": "waitId 须为不超过 200 字的文本",
31
+ "x-length": 200
32
+ }
33
+ ]
34
+ },
35
+ "state": {
36
+ "enum": [
37
+ "waiting",
38
+ "handling",
39
+ "done",
40
+ "failed",
41
+ "cancelled"
42
+ ],
43
+ "description": "state 只能是 waiting、handling、done、failed、cancelled"
44
+ },
45
+ "kind": {
46
+ "type": "string",
47
+ "description": "kind 须为不超过 50 字的文本",
48
+ "x-length": 50
49
+ },
50
+ "title": {
51
+ "type": "string",
52
+ "description": "title 须为不超过 500 字的文本",
53
+ "x-length": 500
54
+ },
55
+ "detail": {
56
+ "type": "string",
57
+ "description": "detail 须为不超过 4000 字的文本",
58
+ "x-length": 4000
59
+ },
60
+ "childSessionId": {
61
+ "type": "string",
62
+ "description": "childSessionId 须为不超过 200 字的文本",
63
+ "x-length": 200
64
+ },
65
+ "model": {
66
+ "type": "string",
67
+ "description": "model 须为不超过 200 字的文本",
68
+ "x-length": 200
69
+ },
70
+ "outputFile": {
71
+ "type": "string",
72
+ "description": "outputFile 须为不超过 4096 字的文本",
73
+ "x-length": 4096
74
+ },
75
+ "toolCallId": {
76
+ "type": "string",
77
+ "description": "toolCallId 须为不超过 200 字的文本",
78
+ "x-length": 200
79
+ },
80
+ "stoppable": {
81
+ "type": "boolean",
82
+ "description": "stoppable 须为 true 或 false"
83
+ },
84
+ "recurring": {
85
+ "type": "boolean",
86
+ "description": "recurring 须为 true 或 false"
87
+ },
88
+ "at": {
89
+ "type": "string",
90
+ "description": "at 读不出时间",
91
+ "format": "date-parse"
92
+ }
93
+ },
94
+ "required": [
95
+ "sessionId",
96
+ "waitId",
97
+ "state"
98
+ ]
99
+ }
@@ -5,7 +5,7 @@ import { isAbsolute } from 'node:path';
5
5
 
6
6
  // The byte length of a JSON value, without recursion or serializing the whole object. Stop as soon as the budget is
7
7
  // exceeded. Only JSON values count: cycles, non-finite numbers and non-JSON objects cannot turn into a different value.
8
- function jsonFits(value, max) {
8
+ export function jsonFits(value, max) {
9
9
  let bytes = 0;
10
10
  const active = new Set(), stack = [{ value }];
11
11
  while (stack.length && bytes <= max) {