@coda-console/protocol 3.5.0 → 4.0.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/CHANGELOG.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  按 semver。协议自己的大版本写在协议名里(如 `coda/run@1`),与 npm 包版本、插件清单的 v2 独立;更早的版本见 git 历史。
4
4
 
5
+ ## 4.0.0 — 2026-11-09
6
+
7
+ 不兼容变更:
8
+
9
+ - 插件清单 `plugin.json` 的 `contributes` 只列 11 个通用键(`configuration`、`extensionPoints`、`extensions`、`services`、`setup`、`environment`、`path`、`endpoints`、`views`、`protocols`、`protocolImplementations`)。`capabilities`、`instructions`、`agents`、`groups`、`events`、`commands`、`gitCredentials`、`repositories`、`skills`、`mcpServers`、`modelProviders`、`executors`、`composerModes` 由 Coda 的贡献点登记表声明(Coda #1917):`manifestProblem` 不再校验它们的形状,只按 `propertyNames` 查键的写法;它们的 schema 文件留在 `schemas/plugin/`,照旧能按文件名从 `SCHEMAS` 取到。
10
+ - `validate.mjs` 的 `TYPES` 只含这 11 个通用键。
11
+
12
+ 新增:
13
+
14
+ - `validate.mjs` 导出 `contributionProblem(schema, manifest, key, dir?)`:把一个键按声明方给的 schema 放在整份清单里校验,指针从清单的根算(如 `groups` 的 `capacity`、`modelProviders` 的 `${config:…}` 指向本插件的设置),`x-file` 在给出插件目录时查;说法同 `manifestProblem`。
15
+ - 协议定义的可选 `plural`:给人寻址的复数名(Kubernetes CRD 的 `names.plural`),`^[a-z][a-z0-9-]{0,62}$`,不写就是短名;同一大版本内不可改。
16
+ - `coda/agent@1`:`"plural": "agents"`;spec 加可选 `plugin`(提供 Agent 程序的插件 ID,记录的 provider 是写记录的编排);操作 `spawn`(`run: true`),输入同 Coda 的 `agents`/`spawn`。
17
+
5
18
  ## 3.5.0 — 2026-11-09
6
19
 
7
20
  - 流程参数的 `agent` 类型:值是一组 Agent 选择,与插件设置的 Agent 组合(`format: coda-agent`)相同:`agent`,以及可选的 `model`、`effort`、`mode`、`options`。不写 `default`;`defaultFrom="session.agent"` 是从会话发起、没给值时取那个会话的 Agent 选择,只用于这种参数(Coda #2303 第 4 步)。
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Coda 与插件之间的约定,Coda 和插件都引用它,独立演进:
4
4
 
5
- - **插件清单的类型**(`schemas/plugin/`):插件目录 `package.json` 里 `contributes` 的每个键是一种类型,各有一份 JSON Schema(draft 2020-12,`$id` 为 `https://coda.pub/schemas/plugin/v2/<文件>`)。`plugin.json` 是整份清单,`setting.json` 是一项设置的声明。插件写的合乎它用到的类型就对;写给别的插件扩展点的键由那个插件声明,扩展点可以带自己的 schema(`extensionPoints.<名>.schema`,声明它的插件目录里的一个文件),写给它的东西合乎那份 schema 才对。
5
+ - **插件清单的类型**(`schemas/plugin/`):插件目录 `package.json` 里 `contributes` 的每个键是一种类型,各有一份 JSON Schema(draft 2020-12,`$id` 为 `https://coda.pub/schemas/plugin/v2/<文件>`)。`plugin.json` 是整份清单,只列通用键(`configuration`、`extensionPoints`、`extensions`、`services`、`setup`、`environment`、`path`、`endpoints`、`views`、`protocols`、`protocolImplementations`);`setting.json` 是一项设置的声明。其余键由 Coda 的贡献点登记表或某个插件的扩展点声明:Coda 自己的(`agents`、`composerModes`、`modelProviders` 等)的 schema 仍放在这里作公开数据(如 `agents.json`、`composer-modes.json`),由登记它的 provider 引用;扩展点可以带自己的 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
  - **协议定义与调用**(`schemas/protocol/`、`protocol.mjs`、`docs/protocol.md`):协议是 `命名空间/名字@大版本` + spec/status 的 JSON Schema + 操作;插件在 `contributes.protocols` 里定义自己命名空间下的协议,在 `contributes.protocolImplementations` 里声明实现哪些协议。调用是 `{ record, operation, input, requestId, parent, awaited }`,回答是 `{ result }` 或 `{ run }`;`coda/run@1`、`coda/executor@1`、`coda/agent@1`、`coda/session@1`、`coda/environment@1` 是五个核心协议。
8
8
  - **流程方言**(`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` 负责跨节点检查。
@@ -27,10 +27,13 @@ npm install @coda-console/protocol
27
27
  ```
28
28
 
29
29
  ```js
30
- import { manifestProblem, pointProblem, valueProblem, TYPES, SCHEMAS } from '@coda-console/protocol/validate';
30
+ import { contributionProblem, manifestProblem, pointProblem, valueProblem, TYPES, SCHEMAS } from '@coda-console/protocol/validate';
31
31
 
32
- // 整份清单;给出插件目录时,连同它点名的文件一起查(x-file)。合乎类型返回 null,否则是一句说明:哪里、schema 怎么说。
33
- const problem = manifestProblem(JSON.parse(readFileSync('plugin/package.json', 'utf8')), 'plugin');
32
+ // 整份清单的外壳与通用键;给出插件目录时,连同它点名的文件一起查(x-file)。合乎类型返回 null,否则是一句说明:哪里、schema 怎么说。
33
+ const manifest = JSON.parse(readFileSync('plugin/package.json', 'utf8'));
34
+ const problem = manifestProblem(manifest, 'plugin');
35
+ // 不是通用键的一个键,按声明它的一方给的 schema,放在整份清单里查:指针从清单的根算(groups 的 capacity 指向本插件的设置)。
36
+ contributionProblem(SCHEMAS['agents.json'], manifest, 'agents', 'plugin');
34
37
  // 某个类型里的一部分,如输入框模式声明的参数。
35
38
  valueProblem('composer-modes.json#/$defs/parameters', parameters, '插件给出的参数');
36
39
  // 写给别的插件扩展点的东西,按那个扩展点的 schema(已读出的对象);给出写它的插件目录时连同它点名的文件一起查。
@@ -70,7 +73,7 @@ fieldsProblem(JSON.parse(fieldsJSON), 'ask');
70
73
 
71
74
  扩展点的 schema 和这里的并列:可以 `$ref` 协议的 schema(`common.json#/$defs/line40`),可以用五个关键字;它自己写的 `$id` 不算数,指针只指它校验的那份值里的位置。
72
75
 
73
- `TYPES` 是 Coda 自己的类型(`contributes` 的键),`SCHEMAS` 是 `schemas/plugin/` 里的全部 schema(按文件名)。协议定义的 schema 在 `schemas/protocol/v1/`,由 `@coda-console/protocol/protocol` 导出。
76
+ `TYPES` 是清单 schema 自己校验的通用键(4.0.0 起不含 `agents` 等 Coda 业务键),`SCHEMAS` 是 `schemas/plugin/` 里的全部 schema(按文件名),包括业务键的。协议定义的 schema 在 `schemas/protocol/v1/`,由 `@coda-console/protocol/protocol` 导出。
74
77
 
75
78
  ## 插件的后端通道
76
79
 
@@ -123,7 +126,7 @@ agentValueProblem('review.json', review, '工具的审核');
123
126
 
124
127
  - **`coda/run@1`**:被调用方自己的运行记录。spec 记录调用的记录、操作、输入、请求 ID、父引用和是否被等待;status 是 `working`、`input-required`、`completed`、`failed`、`canceled`,后三个是终态。`completed`/`failed` 必带 `result`,`failed` 的 `result` 约定为 `{ code, message }`(`code: "unknown"` 表示结局不明),`canceled` 可带取消前的话和留下的东西;另有可选的 `progress` 和等输入时的 `awaiting`。操作是 `cancel`。
125
128
  - **`coda/executor@1`**:执行器。操作 `execute` 接一份执行工作并返回运行引用,运行记录在执行器那边;没有 `target`、`executor` 或 `assignmentId` 绑定,也不回写调用方的记录。
126
- - **`coda/agent@1`**:Agent 资源,spec 照 Coda 的 Agent 资源(`agent`、`title`、`defaultModel`、`models`、`modes` 等),删除 `plugin` 字段;status 为空对象,本版本不定义操作。
129
+ - **`coda/agent@1`**:Agent 资源,复数名 `agents`;spec 照 Coda 的 Agent 资源(`agent`、`title`、`plugin?`、`defaultModel`、`models`、`modes` 等),`plugin` 是提供 Agent 程序的插件、不是记录的 provider;status 为空对象;操作 `spawn`(`run: true`)开一个子会话。
127
130
  - **`coda/session@1`**:插件提供的外部会话。spec 是标题、容器和可选的环境引用;status 是 `head` 游标与参与者;操作 `read`、`post` 都是立即结果,撤回、编辑、表情是用 `about` 指向旧消息的新消息。
128
131
  - **`coda/environment@1`**:插件提供的只读外部环境。spec 带 `actions`;操作 `list` 列目录、`read` 读文件(一次至多 1 MiB)。
129
132
 
package/docs/protocol.md CHANGED
@@ -55,6 +55,12 @@ import { protocolShortName } from '@coda-console/protocol/protocol';
55
55
  protocolShortName('coda/run@1'); // 'coda-1run-21'
56
56
  ```
57
57
 
58
+ ### 复数名
59
+
60
+ 定义可以写 `plural`,给人寻址用的复数名,和 Kubernetes CRD 的 `names.plural` 同一个位置:引用 `coda://<复数名>/<ID>`、资源列表的种类、节点内核 CRD 的复数名都用它。不写就是协议的短名。写法是小写字母开头,只用小写字母、数字和「-」,至多 63 个字符(`^[a-z][a-z0-9-]{0,62}$`)。
61
+
62
+ 复数名是协议的一部分:同一大版本内不可改,改名等于新协议。核心协议之间的复数名(不写的按短名算)互不相同,本包的自测会拦下重复的。`coda/agent@1` 写 `"plural": "agents"`,所以 Agent 的引用仍是 `coda://agents/<ID>`。
63
+
58
64
  ## 定义文档
59
65
 
60
66
  一份定义是一个 JSON 对象:
@@ -64,6 +70,7 @@ protocolShortName('coda/run@1'); // 'coda-1run-21'
64
70
  | `name` | 协议名,必填 |
65
71
  | `title` | 给人看的名字,可选 |
66
72
  | `description` | 一句话说明,可选 |
73
+ | `plural` | 复数名(见「复数名」),可选;不写就是短名 |
67
74
  | `spec` | 记录的 spec 的 JSON Schema,必填 |
68
75
  | `status` | 记录的 status 的 JSON Schema,必填 |
69
76
  | `operations` | 操作名 → `{ description?, input, result, safe? }` 或 `{ description?, input, run: true }`,必填;不定义操作写 `{}` |
@@ -176,7 +183,7 @@ invocationResponseProblem(response); // 恰好有 result 或 run 之一
176
183
 
177
184
  `contributes.protocols` 是插件拥有的定义,每份都按定义文档 schema 校验;定义里的命名空间必须等于插件 ID。`contributes.protocolImplementations` 是插件实现的协议名列表,可以包含别的命名空间下的协议(包括 `coda/...`),不重复。
178
185
 
179
- `manifestProblem` 校验清单的形状。命名空间约束需要插件 ID,单独用:
186
+ `manifestProblem` 校验清单的外壳与通用键(这两个键在其中);别的键由 Coda 的贡献点登记表或某个插件的扩展点声明,按声明方的 schema 校验,本包不管它们的形状。命名空间约束需要插件 ID,单独用:
180
187
 
181
188
  ```js
182
189
  import { ownedProtocolsProblem } from '@coda-console/protocol/protocol';
@@ -245,23 +252,27 @@ status:
245
252
 
246
253
  ## coda/agent@1
247
254
 
248
- 一台机器上装着的 Agent 程序,记录格式照 Coda 的 Agent 资源 spec:
255
+ 一台机器上装着的 Agent 程序,记录格式照 Coda 的 Agent 资源 spec。复数名是 `agents`。记录的 provider 是写记录的一方,即编排(现在是 `coda`),不是提供 Agent 程序的插件;那个插件写在 `spec.plugin`。
249
256
 
250
257
  | 字段 | 含义 |
251
258
  | --- | --- |
252
259
  | `agent` | Agent 的 ID,必填 |
253
260
  | `title` | 给人看的名字 |
261
+ | `plugin` | 提供这个 Agent 程序的插件 ID,可选 |
254
262
  | `defaultModel` | 不指定模型时用的模型 ID;读到目录前为空 |
255
263
  | `models` | `[{ id, name?, shortName?, group?, efforts, options }]` |
256
264
  | `modes` | `[{ id, name?, shortName? }]` |
257
265
 
258
- `models[].efforts` 是 `[{ id, name?, shortName? }]`;`models[].options` 是 `{ id, name?, shortName?, type: "boolean"|"select", choices? }`,`select` 带 `choices`,`boolean` 不带。旧 spec 里的 `plugin` 字段删除,由记录的 provider 表达。status 是空对象;本版本不定义操作。
266
+ `models[].efforts` 是 `[{ id, name?, shortName? }]`;`models[].options` 是 `{ id, name?, shortName?, type: "boolean"|"select", choices? }`,`select` 带 `choices`,`boolean` 不带。status 是空对象。
267
+
268
+ 操作 `spawn`(`run: true`):在这台机器上用这个 Agent 开一个子会话,返回运行引用。输入是 `{ projectId, threadId, title, text, settings?, checkout?, parentTitle, parentUnattended }`:子会话所在的项目与工作树(`checkout`)、Agent 的设置(`settings`,如模型与推理强度)、标题与第一条任务(`text`);`threadId` 是调用方给的子会话 ID,重试找到的是同一个;`parentTitle` 是发起方在这里显示的名字,`parentUnattended` 说发起方是否无人值守。子会话为谁工作由调用信封的 `parent` 说,不写在输入里。
259
269
 
260
270
  ```json
261
271
  {
262
272
  "spec": {
263
273
  "agent": "codex",
264
274
  "title": "Codex",
275
+ "plugin": "codex",
265
276
  "defaultModel": "gpt-5",
266
277
  "models": [{"id": "gpt-5", "efforts": [], "options": []}],
267
278
  "modes": [{"id": "auto", "name": "自动"}]
@@ -365,4 +376,5 @@ status 是 `{ state: "available"|"unreachable", reason? }`。
365
376
 
366
377
  - 协议名里的大版本是协议自己的版本,和 npm 包版本、插件清单的 v2 独立。
367
378
  - 同一大版本只加可选字段和新操作;改含义、删字段或删操作就升大版本。新旧版本可并存,插件在 `contributes.protocolImplementations` 里声明实现哪个。
379
+ - 复数名同一大版本内不可改。
368
380
  - 3.0.0 删除了 `executor`、`execution` 两个旧协议与 `@coda-console/protocol/executor`,是不兼容变更;不提供旧格式兼容代码。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coda-console/protocol",
3
- "version": "3.5.0",
3
+ "version": "4.0.0",
4
4
  "description": "Coda 的协议:插件清单的类型(JSON Schema)、通用校验器、协议定义与调用信封、流程方言与 Agent 接入协议",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://coda.pub/schemas/plugin/v2/plugin.json",
4
4
  "title": "插件清单(插件目录的 package.json)",
5
- "description": "照 VS Code 扩展的 package.json 写;Coda 读的是下面这些键,其余是 npm 的,不管。contributes 的每个键是一种类型,各有自己的 schema;不是 Coda 的键,是写给别的插件的扩展点的(它们声明的类型),由那个扩展点的 schema 校验。",
5
+ "description": "照 VS Code 扩展的 package.json 写;Coda 读的是下面这些键,其余是 npm 的,不管。contributes 下这里只写通用键,各有自己的 schema;其余键由 Coda 的贡献点登记表或某个插件的扩展点声明,由声明它的一方的 schema 校验。",
6
6
  "type": "object",
7
7
  "properties": {
8
8
  "id": {"$ref": "common.json#/$defs/id", "description": "插件 ID:小写字母、数字和「-」,以字母开头,至多 40 个字符;不写就用 name"},
@@ -28,33 +28,20 @@
28
28
  "activationEvents": {"description": "宿主什么时候加载 main:onStartupFinished(后端一启动),不重复", "type": "array", "items": {"enum": ["onStartupFinished"]}, "uniqueItems": true},
29
29
  "extensionDependencies": {"description": "需要先装的插件的 ID,不重复", "type": "array", "items": {"$ref": "common.json#/$defs/id"}, "uniqueItems": true},
30
30
  "contributes": {
31
- "description": "插件带来的东西:每个键一种类型",
31
+ "description": "插件带来的东西:每个键一种类型。这里只列通用键;其余键由 Coda 的贡献点登记表或某个插件的扩展点声明",
32
32
  "type": "object",
33
33
  "properties": {
34
34
  "configuration": {"$ref": "configuration.json"},
35
- "capabilities": {"$ref": "capabilities.json"},
36
- "instructions": {"$ref": "instructions.json"},
37
- "agents": {"$ref": "agents.json"},
38
35
  "extensionPoints": {"$ref": "extension-points.json"},
39
36
  "extensions": {"$ref": "extensions.json"},
40
- "groups": {"$ref": "groups.json"},
41
- "events": {"$ref": "events.json"},
42
- "commands": {"$ref": "commands.json"},
43
- "gitCredentials": {"$ref": "git-credentials.json"},
44
- "repositories": {"$ref": "repositories.json"},
45
37
  "services": {"$ref": "services.json"},
46
38
  "setup": {"$ref": "setup.json"},
47
- "skills": {"$ref": "skills.json"},
48
39
  "environment": {"$ref": "environment.json"},
49
40
  "path": {"$ref": "path.json"},
50
41
  "endpoints": {"$ref": "endpoints.json"},
51
- "mcpServers": {"$ref": "mcp-servers.json"},
52
42
  "views": {"$ref": "views.json"},
53
- "modelProviders": {"$ref": "model-providers.json"},
54
- "executors": {"$ref": "executors.json"},
55
43
  "protocols": {"$ref": "protocols.json"},
56
- "protocolImplementations": {"$ref": "protocol-implementations.json"},
57
- "composerModes": {"$ref": "composer-modes.json"}
44
+ "protocolImplementations": {"$ref": "protocol-implementations.json"}
58
45
  },
59
46
  "propertyNames": {"description": "contributes 的键:字母和数字,以小写字母开头", "pattern": "^[a-z][A-Za-z0-9]{0,39}$"}
60
47
  }
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "coda/agent@1",
3
3
  "title": "Agent",
4
- "description": "一台机器上装着的 Agent 程序及其模型与审批模式,照 Coda 的 Agent 资源 spec。插件不再写在 spec 里,由记录的 provider 表达;本版本不定义操作。",
4
+ "description": "一台机器上装着的 Agent 程序及其模型与审批模式,照 Coda 的 Agent 资源 spec。记录的 provider 是写记录的一方(编排),提供 Agent 程序的插件写在 spec.plugin;spawn 在那台机器上用它开一个子会话。",
5
+ "plural": "agents",
5
6
  "spec": {
6
7
  "$schema": "https://json-schema.org/draft/2020-12/schema",
7
8
  "type": "object",
@@ -35,6 +36,7 @@
35
36
  "properties": {
36
37
  "agent": {"type": "string", "minLength": 1, "maxLength": 200, "description": "Agent 的 ID:spawn 的 agent 写它"},
37
38
  "title": {"type": "string", "maxLength": 200, "description": "给人看的名字"},
39
+ "plugin": {"type": "string", "pattern": "^[a-z][a-z0-9-]{0,39}$", "description": "提供这个 Agent 程序的插件 ID;不是记录的 provider"},
38
40
  "defaultModel": {"type": "string", "maxLength": 200, "description": "不指定模型时用的模型 ID;读到目录前为空"},
39
41
  "models": {
40
42
  "type": "array",
@@ -65,5 +67,25 @@
65
67
  "additionalProperties": false,
66
68
  "description": "Agent 记录没有 status 字段"
67
69
  },
68
- "operations": {}
70
+ "operations": {
71
+ "spawn": {
72
+ "description": "在这台机器上用这个 Agent 开一个子会话:项目与工作树、Agent 的设置、标题与第一条任务;子会话的 ID 由调用方给,重试找到的是同一个,为谁工作由调用信封的 parent 说",
73
+ "run": true,
74
+ "input": {
75
+ "type": "object",
76
+ "properties": {
77
+ "projectId": {"type": "string"},
78
+ "threadId": {"type": "string"},
79
+ "title": {"type": "string"},
80
+ "text": {"type": "string"},
81
+ "settings": {"type": "object", "additionalProperties": true},
82
+ "checkout": {"type": "object", "additionalProperties": true},
83
+ "parentTitle": {"type": "string"},
84
+ "parentUnattended": {"type": "boolean"}
85
+ },
86
+ "required": ["projectId", "threadId", "title", "text", "parentTitle", "parentUnattended"],
87
+ "additionalProperties": false
88
+ }
89
+ }
90
+ }
69
91
  }
@@ -8,6 +8,7 @@
8
8
  "name": {"$ref": "#/$defs/name"},
9
9
  "title": {"$ref": "#/$defs/title"},
10
10
  "description": {"$ref": "#/$defs/description"},
11
+ "plural": {"$ref": "#/$defs/plural"},
11
12
  "spec": {"$ref": "#/$defs/schema"},
12
13
  "status": {"$ref": "#/$defs/schema"},
13
14
  "operations": {
@@ -27,6 +28,11 @@
27
28
  "maxLength": 61,
28
29
  "pattern": "^[a-z][a-z0-9-]{0,39}/[a-z0-9][a-z0-9-]{0,39}@[1-9][0-9]*$"
29
30
  },
31
+ "plural": {
32
+ "description": "给人寻址的复数名(Kubernetes CRD 的 names.plural):小写字母开头,只用小写字母、数字和「-」,至多 63 个字符;不写就是协议的短名。同一大版本内不可改,改名等于新协议",
33
+ "type": "string",
34
+ "pattern": "^[a-z][a-z0-9-]{0,62}$"
35
+ },
30
36
  "operationName": {
31
37
  "description": "操作名:小写字母开头,只用小写字母、数字和「-」,不超过 64 个字符",
32
38
  "type": "string",
package/validate.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  // A plugin is right when what it writes is of the types it names: each key of a manifest's `contributes` is a type with a
2
- // JSON Schema of its own (schemas/plugin/, `https://coda.pub/schemas/plugin/v2/…`), and a key that is no type of Coda's is
3
- // one a plugin's extension point declares, for that plugin to read: of the schema that point names, a file of the declaring
4
- // plugin's (`extensionPoints.<point>.schema`), when it names one. Checking one is checking it against those schemas,
5
- // nothing else: a field joins a type by joining its schema, never by a rule here. Coda checks a plugin with this module
6
- // when it reads one, and coda-plugins' contract check does where a plugin is written.
2
+ // JSON Schema of its own. The manifest's schema names only the general ones (schemas/plugin/,
3
+ // `https://coda.pub/schemas/plugin/v2/…`); every other key is one Coda's registry of contribution points declares, with a
4
+ // schema of its provider's (those of Coda's own still kept here, by file), or one a plugin's extension point declares, for
5
+ // that plugin to read: of the schema that point names, a file of the declaring plugin's (`extensionPoints.<point>.schema`),
6
+ // when it names one. Checking one is checking it against those schemas, nothing else: a field joins a type by joining its
7
+ // schema, never by a rule here. Coda checks a plugin with this module when it reads one, and coda-plugins' contract check
8
+ // does where a plugin is written.
7
9
  // What JSON Schema cannot say of one value alone it says with five keywords, each meaning one thing wherever it is written,
8
10
  // as Kubernetes extends its OpenAPI with `x-kubernetes-*`:
9
11
  // - `x-unique-by`: [name, { part } or { key, nested }] — in an array, each named property of its objects is unique among the
@@ -34,7 +36,7 @@ export const SCHEMAS = Object.fromEntries(readdirSync(SCHEMA_DIR).filter(file =>
34
36
  .map(file => [file, JSON.parse(readFileSync(join(SCHEMA_DIR, file), 'utf8'))]));
35
37
  // The protocol definition schema the manifest schemas reference; it is not a contributes type of its own.
36
38
  const PROTOCOL_DEFINITION = JSON.parse(readFileSync(new URL('./schemas/protocol/v1/definition.json', import.meta.url), 'utf8'));
37
- // The types a plugin may contribute to Coda itself, as the manifest's schema names them.
39
+ // The general types a manifest's schema checks itself; a key of none of them is for whoever declares it.
38
40
  export const TYPES = Object.keys(SCHEMAS['plugin.json'].properties.contributes.properties);
39
41
 
40
42
  // What a pointer names, from where a keyword is: the manifest's root, or levels up from the value itself; and where that is.
@@ -323,3 +325,12 @@ export function pointProblem(schema, value, where, dir = null) {
323
325
  const point = declared(schema);
324
326
  return point.validate.call({ dir }, value) ? null : explain(point.validate.errors, where, [point.schema]);
325
327
  }
328
+ // A key of a manifest's `contributes` checked against the schema whoever declares the key gives for it (a file of these, as
329
+ // `SCHEMAS['agents.json']`, or a provider's own), in the whole manifest: its pointers name places from the manifest's root
330
+ // (`/contributes/configuration/properties`), as they do in the manifest's own schema. `dir`: the plugin's directory, for the
331
+ // files it names. Null, or what is wrong, said as `manifestProblem` says it.
332
+ export function contributionProblem(schema, manifest, key, dir = null) {
333
+ const point = declared(schema);
334
+ return point.validate.call({ dir }, manifest.contributes[key], { instancePath: `/contributes/${escape(key)}`, parentData: manifest.contributes,
335
+ parentDataProperty: key, rootData: manifest, dynamicAnchors: {} }) ? null : explain(point.validate.errors, '', [point.schema]);
336
+ }