@coda-console/plugin-check 0.4.6 → 0.4.8
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 +42 -22
- package/check/coda.mjs +12 -8
- package/check/manifest.mjs +27 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ plugins/codex/ Codex:入口在同一进程里运行锁定版本的 c
|
|
|
11
11
|
plugins/claude/ Claude SDK:基于 Claude Agent SDK 的 ACP 入口(npm 依赖),面板「Claude」
|
|
12
12
|
plugins/kimi/ Kimi Code:在运行机的 kimi acp 与 Coda 之间转发,面板「Kimi」
|
|
13
13
|
plugins/grok/ Grok:转发运行机上 Grok CLI 的 ACP,并适配审批模式,面板「Grok」
|
|
14
|
-
plugins/opencode/ OpenCode:在运行机的 opencode acp 与 Coda
|
|
14
|
+
plugins/opencode/ OpenCode:在运行机的 opencode acp 与 Coda 之间转发,送入会话指令与所选模型服务,模型按来源分组
|
|
15
15
|
plugins/codex-accounts/ Codex 多账号:管理登录、自动续期与账号选择,接管 Codex 额度面板
|
|
16
16
|
plugins/cursor/ Cursor:在运行机的 cursor-agent acp 与 Coda 之间转发,送入会话指令
|
|
17
17
|
plugins/magpie/ Magpie:把本机的 Magpie 网关作为模型服务提供给 Pi 等插件,安装时装好 Magpie 并让网关常驻
|
|
@@ -22,21 +22,17 @@ bin/、check/ 契约检查(仓库根的 package.json 就是它,@
|
|
|
22
22
|
test/ 检查包的测试;test/plugins/ 是各插件的测试(插件目录里只放运行要用的文件)
|
|
23
23
|
docs/agent-protocol.md Coda 接入协议正文的公开副本
|
|
24
24
|
.coda/workflows/ 本仓库自己的流程:sync-coda 等新版本发到 npm,再给 Coda 开升级依赖的 PR
|
|
25
|
-
.claude-plugin/ 官方市场的索引 marketplace.json,见「官方市场」
|
|
26
25
|
```
|
|
27
26
|
|
|
28
|
-
##
|
|
27
|
+
## 商店
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
[
|
|
32
|
-
它列不随 Coda 发布、维护者推荐的插件:本仓库里的(`./plugins/<id>`),以及别的公开仓库或 npm 包。随 Coda 发布的那几个在 Coda 自带的市场里,这里不重复列。
|
|
29
|
+
Coda 插件页的「商店」列出 Coda 自带的插件,以及 npm 上 `@coda-console` 下的包(Coda 的 docs/plugins.md「商店」):
|
|
30
|
+
本仓库的插件合进 main 后发布为 `@coda-console/<id>`([npm 发布](#npm-发布)),发出来就在商店里,不用另写索引。
|
|
33
31
|
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
- **本仓库里的插件**条目要写 `version`,与它清单里的一样:Coda 按条目的版本标「可更新」。升插件版本时一起改。
|
|
39
|
-
- `npm test` 里的 `test/check-marketplace.test.mjs` 用 `scripts/check-marketplace.mjs` 核对上面几条(CI 跑 `npm test`),本地也可以直接运行这个脚本。
|
|
32
|
+
- **分类**写在插件清单的 `categories` 里(照 VS Code,取第一个),只用这几个,Coda 的插件页按它分组:`Agent`、`模型与账号`、`工作流程`、`机器与资源`、`Skills 与工具`。
|
|
33
|
+
`npm test` 里的 `test/categories.test.mjs` 核对本仓库的每个插件都写了;契约检查对没写的、写了别的给出提醒。
|
|
34
|
+
- **下架**:给它的最新版标 `npm deprecate`,商店就不再列它。
|
|
35
|
+
- 别处的插件(别的仓库、别的 npm 包)不在商店里,用户在 Coda 里填仓库地址或包名添加。
|
|
40
36
|
|
|
41
37
|
## Coda 自带的插件
|
|
42
38
|
|
|
@@ -62,7 +58,7 @@ docs/agent-protocol.md Coda 接入协议正文的公开副本
|
|
|
62
58
|
| `codex-accounts` | `proper-lockfile` 4.1.2;用依赖插件 `codex` 的 Codex CLI | 无;依赖 `codex` 插件 |
|
|
63
59
|
| `kimi` | 无 | Kimi CLI 2.1.1 及以上(设置动作安装或升级;面板可从 Coda 安装、登录) |
|
|
64
60
|
| `grok` | 无 | Grok CLI 1.0.52 及以上(设置动作安装或升级;面板可从 Coda 安装、登录) |
|
|
65
|
-
| `opencode` |
|
|
61
|
+
| `opencode` | `jsonc-parser` 3.3.1 | 已登录的 OpenCode CLI 2.1.4 及以上(设置动作安装或升级,OpenCode 1 升到 2) |
|
|
66
62
|
|
|
67
63
|
带依赖的插件各有自己的 `package-lock.json`(`node_modules` 不提交);Coda 在插件的版本目录里 `npm ci` 装它们,
|
|
68
64
|
这几个插件不是本仓库根的 npm workspaces(根包是契约检查,装它不该带上 Codex、Claude 的平台二进制)。
|
|
@@ -77,23 +73,32 @@ Kimi、Grok、OpenCode、Cursor 的 CLI 不接受它:入口不转给 CLI,在
|
|
|
77
73
|
把指令包在 `<coda-session-instructions>` 里作为单独的一段文本放在用户输入前面,之后与 `session/load` 恢复的会话不再送;
|
|
78
74
|
第一条是斜杠命令时留到下一条。Codex 另声明插话:一轮已经结束时收到的 `_session/steering` 回 `promptRequired`,不另开一轮。
|
|
79
75
|
|
|
76
|
+
OpenCode 遇到模型服务的 429,会照 `Retry-After` 等(OpenCode 2 每次最多 15 分钟、最多 10 次),并在 ACP 里用 `session_info_update` 的
|
|
77
|
+
`_meta["opencode/retry"]` 说下次什么时候重试、服务报了什么;子 Agent(OpenCode 的 `subagent`)的等待也报在等它的那一轮的会话里。
|
|
78
|
+
Coda 自己对服务故障只等一分钟以内,所以一轮里 OpenCode 要等超过一分钟才重试时,入口先 `session/cancel` 停下这一轮(子 Agent 随之停下),
|
|
79
|
+
再把这一轮答成失败,原因是服务给的原文加上 OpenCode 要等多久;停下后 OpenCode 回的是什么都不转出。一分钟以内的等待照旧由 OpenCode 自己处理。
|
|
80
|
+
不在入口里按厂商的错误文字判断额度耗尽。
|
|
81
|
+
|
|
80
82
|
Kimi、Grok、OpenCode、Cursor 带设置动作(`contributes.setup`,`scripts/setup.mjs`),用户安装或更新插件时 Coda 在每台机器上运行:
|
|
81
83
|
CLI 不在或运行不了就用它自己的安装程序安装,低于插件要求的版本就升级,较新的版本保留。要求的版本:Kimi 2.1.1(入口读得懂的会话文件布局)、
|
|
82
|
-
Grok 1.0.52(审批模式通知核对过的版本)、OpenCode 1.
|
|
84
|
+
Grok 1.0.52(审批模式通知核对过的版本)、OpenCode 2.1.4(入口核对过的版本;OpenCode 1 遇到模型服务的 `Retry-After` 会一声不响地等,多久都等,见下文。用 OpenCode 2 自己的安装程序 `https://opencode.ai/v2/install`,装在同一个 `~/.opencode/bin`,OpenCode 1 的会话由它按原 ID 接过去);Cursor 没有已知的下限,能运行就行(它自己会更新)。
|
|
83
85
|
Codex 与 Claude 的 CLI、SDK 是插件的 npm 依赖,不需要设置动作。
|
|
84
86
|
|
|
85
87
|
### 模型服务
|
|
86
88
|
|
|
87
|
-
Claude 与
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
Claude、Codex 与 OpenCode 各有一项设置「模型服务」(`claude.modelProviders`、`codex.modelProviders`、`opencode.modelProviders`),
|
|
90
|
+
勾选任意几个别的插件提供的[模型服务](https://github.com/coda-console/coda/blob/main/docs/plugins.md#模型服务),如 Magpie、CLIProxy;
|
|
91
|
+
设置写的是 Coda 的「多个模型服务」(`type: array`,`items.format: coda-model-provider`,`items.modelApis` 写各自接得住的接口,客户端只列这些)。
|
|
92
|
+
会话的模型按来源分组上报(ACP 选择型配置项的分组,Coda 的 docs/agent-protocol.md「模型的分组」):Agent 自己的一组,每个勾选的服务一组、组名是服务标题,组内只写模型名。
|
|
93
|
+
只经会话进程的启动参数与按会话的覆盖生效,不改运行机上的 `~/.claude/settings.json`、`~/.codex/config.toml`、`opencode.json`;不选、或提供它的插件卸载了,与原来一样。
|
|
90
94
|
|
|
91
95
|
- **接口**:Claude 只接 `anthropic-messages` 的服务,Codex 只接 `openai-responses` 的(Codex 0.162 已不接受 `wire_api = "chat"`)。
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
-
|
|
96
|
+
OpenCode 接 `openai-completions`、`openai-responses` 与 `anthropic-messages`。
|
|
97
|
+
勾了别的接口的服务(旧设置留下的),入口只在标准错误记一行,不列它的模型。
|
|
98
|
+
- **Claude / Codex 模型**:会话打开时从每个服务的「地址/models」列出模型(各服务同时问),问法同 Coda 的「检查可用模型」(Claude 带 `x-api-key`,Codex 带 `Authorization: Bearer`),
|
|
99
|
+
按勾选的先后排在 Agent 自己的模型后面:ID 是 `<服务>/<模型>`(如 `magpie/anthropic/deepseek/deepseek-v4-pro`),名字只是模型(如 `deepseek/deepseek-v4-pro`),
|
|
100
|
+
在服务那一组里,没有推理强度可选。哪个服务列不出来就没有它那一组,其他的照旧。Codex 的分组在 codex-acp 生成模型配置项的地方打补丁做。
|
|
101
|
+
- **Claude / Codex 何时换**:选模型只是记下(Coda 读模型目录时会把每个模型都选一遍,不该因此启动什么);下一轮开始前,所选模型的服务与会话正在用的不同(换到另一个服务、或回到自己的),才把会话换过去。
|
|
97
102
|
- **Claude**:以 `resume` 新起一个 SDK 会话(还没说过话的会话用同一个会话 ID 新起),flag settings 的 `env` 设 `ANTHROPIC_BASE_URL`、
|
|
98
103
|
`ANTHROPIC_AUTH_TOKEN`(服务的 Key,没有 Key 写 `none`)与 `ANTHROPIC_MODEL`、`ANTHROPIC_DEFAULT_{OPUS,SONNET,HAIKU}_MODEL`、
|
|
99
104
|
`CLAUDE_CODE_SUBAGENT_MODEL`(子 Agent、标题等后台调用也用这个模型)。运行机自己通往 API 的办法一样都不带过去:`ANTHROPIC_API_KEY`、
|
|
@@ -108,6 +113,21 @@ Claude 与 Codex 各有一项设置「模型服务」(`claude.modelProvider`
|
|
|
108
113
|
codex-acp 在 resume 之前就记下了 gateway,新 app-server 起不来时还留着已关闭的旧客户端,都不能拿来当换成了)。
|
|
109
114
|
一句话都还没说的线程 Codex 没有写下来,resume 不了:先以第一条消息(前 80 字)为名 `thread/name/set` 把它写下,这也就成了会话标题(不再由 Codex 生成)。
|
|
110
115
|
Codex 没有服务的模型的元数据,它为此发的「Model metadata … not found」与恢复时的「recorded with model …」提示不进会话。
|
|
116
|
+
- **OpenCode**:启动 `opencode acp` 前按接口从每个服务的「地址/models」列出模型,通过子进程的 `OPENCODE_CONFIG_CONTENT` 给每个服务加一个独立 Provider,
|
|
117
|
+
ID 为 `coda-<插件>.<服务>`(如 `coda-magpie.openai`),模型 ID 为 `<Provider>/<模型>`,保留模型名里的斜杠。模型列表、选择与恢复由 OpenCode 原生处理,
|
|
118
|
+
切模型不用重启;服务的标题作为 Provider 名。入口把 OpenCode 报给 Coda 的模型配置项按 Provider 分组(OpenCode 写作「Provider ID/模型名」,
|
|
119
|
+
组内去掉前缀):服务的组名是它完整的标题,OpenCode 自己的 Provider 各成一组,组名是 Provider ID(OpenCode 只报这个);模型 ID 不变。Chat 用 `@ai-sdk/openai-compatible`,Responses 用 `@ai-sdk/openai`,Anthropic 用 `@ai-sdk/anthropic`,
|
|
120
|
+
都是 OpenCode 自带的 SDK。Anthropic 请求模型列表用服务给的地址,生成请求的地址若未以 `/v1` 结尾则补上。
|
|
121
|
+
Key 只用服务的;无 Key 时显式传 `none`,避免 SDK 回退到机器自己的环境变量凭据。哪个服务列不出模型、接口不支持或 Key 文件读不到,就不添加那一个,其他的与原来的模型照旧。
|
|
122
|
+
已有的 inline 配置支持 JSONC(注释、尾逗号),合并时保留用户自己的变量与字符串转义;服务生成片段中的 `{` 在 JSON 字符串内编码成 `\u007b`,
|
|
123
|
+
防止模型 ID、标题等服务数据触发 OpenCode 的 `{env:…}` / `{file:…}` 展开。真正无效的配置仍明确报错。机器与项目的 Provider 启停限制也仍然有效。
|
|
124
|
+
用户未加引号的布尔值、数字等变量节点在解析时暂用等长占位节点,合并后原样还原,由 OpenCode 自己展开;合并器不读取它们引用的环境变量或文件。
|
|
125
|
+
`"provider":{env:VAR}` 也可由环境变量提供整个 Provider 对象:合并器只读取该对象的结构,结果放进子进程独有的变量,
|
|
126
|
+
仍交给 OpenCode 做一次环境变量展开,再处理文件模板。原变量及其他配置入口保持不变;对象里的相对文件引用仍以原生会话目录为基准。
|
|
127
|
+
`/models` 没有上下文与输出上限时暂用 32768 / 4096 tokens,
|
|
128
|
+
服务给出 `context_length` / `max_output_tokens` 时用它们;不根据模型名猜测图片与推理能力。
|
|
129
|
+
真 CLI 的本地验证(不使用实际模型服务与登录):`OPENCODE_TEST_BINARY=/absolute/path/to/opencode node --test test/plugins/opencode/native.test.mjs`,
|
|
130
|
+
覆盖三种接口的请求、服务与原生模型切换、进程重启后恢复,服务模型的变量模板、用户 JSONC 和 Provider 对象模板配置,以及主会话与子 Agent 里长短两种 `Retry-After`。
|
|
111
131
|
|
|
112
132
|
### Grok 审批模式
|
|
113
133
|
|
package/check/coda.mjs
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { PROTOCOL_VERSION } from '@agentclientprotocol/sdk';
|
|
6
6
|
|
|
7
7
|
// The Coda commit these rules were copied from (an empty current mode is none chosen).
|
|
8
|
-
export const CODA_COMMIT = '
|
|
8
|
+
export const CODA_COMMIT = 'a52459b1f950957923a6e187075ba981263279f4';
|
|
9
9
|
// The ACP version Coda speaks: the SDK's (backend/package.json pins @agentclientprotocol/sdk 1.4.0, as package.json here).
|
|
10
10
|
export const ACP_PROTOCOL_VERSION = PROTOCOL_VERSION;
|
|
11
11
|
|
|
@@ -76,13 +76,14 @@ export const TERM_GRACE = 800;
|
|
|
76
76
|
// adapters/acp.mjs `open` and application/option-cache.mjs: opening a session (initialize, session/new or session/load) may take this.
|
|
77
77
|
export const OPEN_LIMIT = 60_000;
|
|
78
78
|
|
|
79
|
-
// ---- What every process Coda runs for a plugin is given (backend/src/
|
|
79
|
+
// ---- What every process Coda runs for a plugin is given (backend/src/components/plugins/plugin-rules.mjs pluginEnv, cleared) ----
|
|
80
80
|
// Its agents', its panels' and the commands it brings alike: Coda's own Node, the plugin's directory, its state, its settings,
|
|
81
81
|
// what is plugged into its points (none here: the checker installs nothing beside it), where the plugins it depends on are
|
|
82
82
|
// (`dependencies`, by ID), and CODA_ARBITER only for an instance's own backend, which the checker is not. What a process
|
|
83
|
-
// inherits is cleared of them first
|
|
84
|
-
|
|
85
|
-
|
|
83
|
+
// inherits is cleared of them first, those it is given only sometimes too (its secrets' files, the model services, the PATH
|
|
84
|
+
// without Coda's directories): it never takes another plugin's for its own.
|
|
85
|
+
export const PLUGIN_VARIABLES = ['CODA_NODE', 'CODA_ARBITER', 'CODA_PLUGIN_PATH', 'CODA_PLUGIN_STATE', 'CODA_PLUGIN_SETTINGS', 'CODA_PLUGIN_SECRETS',
|
|
86
|
+
'CODA_PLUGIN_EXTENSIONS', 'CODA_PLUGIN_DEPENDENCIES', 'CODA_MODEL_PROVIDERS', 'CODA_USER_PATH'];
|
|
86
87
|
export const pluginEnv = ({ pluginDir, stateDir, settings = {}, dependencies = {} }) => ({ CODA_NODE: process.execPath, CODA_PLUGIN_PATH: pluginDir,
|
|
87
88
|
CODA_PLUGIN_STATE: stateDir, CODA_PLUGIN_SETTINGS: JSON.stringify(settings),
|
|
88
89
|
...(Object.keys(dependencies).length ? { CODA_PLUGIN_DEPENDENCIES: JSON.stringify(dependencies) } : {}) });
|
|
@@ -106,9 +107,12 @@ export const SETTING_KEY = /^[a-z][a-z0-9-]*(\.[a-z][A-Za-z0-9]*)+$/;
|
|
|
106
107
|
export const STRING_FORMATS = ['duration', 'coda-repo', 'coda-directory', 'coda-model-provider', 'coda-model-url'];
|
|
107
108
|
export const MODEL_PROVIDER = /^[a-z][a-z0-9-]{0,39}\/[a-z][a-z0-9-]{0,39}$/;
|
|
108
109
|
export const CAPABILITY = /^[a-z][a-z0-9-]{0,63}$/;
|
|
109
|
-
// What a plugin may contribute (backend/src/components/plugins/plugin-rules.mjs CONTRIBUTES)
|
|
110
|
-
export const CONTRIBUTES = ['configuration', 'capabilities', 'instructions', '
|
|
111
|
-
'repositories', 'services', 'setup', 'skills', 'environment', 'path', 'endpoints', 'mcpServers', 'views', 'modelProviders'];
|
|
110
|
+
// What a plugin may contribute to Coda itself (backend/src/components/plugins/plugin-rules.mjs CONTRIBUTES).
|
|
111
|
+
export const CONTRIBUTES = ['configuration', 'capabilities', 'instructions', 'agents', 'extensionPoints', 'extensions', 'groups', 'events', 'commands', 'gitCredentials',
|
|
112
|
+
'repositories', 'services', 'setup', 'skills', 'environment', 'path', 'endpoints', 'mcpServers', 'views', 'modelProviders', 'executors'];
|
|
113
|
+
// The keys of `contributes` points of the plugins Coda ships take (Coda's docs/plugins.md「写在 contributes 里的扩展点」): the
|
|
114
|
+
// resources plugin's `panels`. Anything else is refused.
|
|
115
|
+
export const POINT_KEYS = ['panels'];
|
|
112
116
|
// What only a plugin Coda ships may contribute, one installed from a repository or npm being refused it
|
|
113
117
|
// (backend/src/components/plugins/plugin-source-rules.mjs catalogProblem): the clients do not show these in the confirmation yet.
|
|
114
118
|
export const BUILTIN_ONLY = { gitCredentials: '只有 Coda 自带的插件能提供 Git 凭据', repositories: '只有 Coda 自带的插件能提供仓库列表' };
|
package/check/manifest.mjs
CHANGED
|
@@ -8,10 +8,12 @@ import { existsSync, lstatSync, readFileSync, readdirSync, realpathSync } from '
|
|
|
8
8
|
import { basename, isAbsolute, join, normalize, relative, sep } from 'node:path';
|
|
9
9
|
import { inflateSync } from 'node:zlib';
|
|
10
10
|
import { AGENT_ID, AGENT_LIMITS, BUILTIN_AGENTS, BUILTIN_COMMANDS, BUILTIN_GROUPS, BUILTIN_HERE, BUILTIN_SCOPE, CAPABILITY,
|
|
11
|
-
CODA_COMMANDS, CODA_EVENT_DOMAINS, COMMAND, COMMAND_LIMIT, BUILTIN_ONLY, CONTRIBUTES, EVENT, EVENT_LIMIT, DEPTH, ICON_LIMIT, INSTRUCTIONS_LIMIT, NPM, NPM_LOCK, PANEL_ID, PANEL_LIMITS, PLUGIN_ID, POINT,
|
|
11
|
+
CODA_COMMANDS, CODA_EVENT_DOMAINS, COMMAND, COMMAND_LIMIT, BUILTIN_ONLY, CONTRIBUTES, POINT_KEYS, EVENT, EVENT_LIMIT, DEPTH, ICON_LIMIT, INSTRUCTIONS_LIMIT, NPM, NPM_LOCK, PANEL_ID, PANEL_LIMITS, PLUGIN_ID, POINT,
|
|
12
12
|
MODEL_PROVIDER, SEGMENT, SERVICE, SETTING_KEY, SETUP_KEYS, SIZE_LIMIT, STRING_FORMATS } from './coda.mjs';
|
|
13
13
|
|
|
14
14
|
class Invalid extends Error {}
|
|
15
|
+
// The categories Coda's own plugins use, which its plugin pages list first, in this order.
|
|
16
|
+
export const CATEGORIES = ['Agent', '模型与账号', '工作流程', '机器与资源', 'Skills 与工具'];
|
|
15
17
|
const check = (condition, message) => { if (!condition) throw new Invalid(message); };
|
|
16
18
|
const object = (value, where) => check(value && typeof value === 'object' && !Array.isArray(value), `${where} 须为对象`);
|
|
17
19
|
const keys = (value, allowed, where) => {
|
|
@@ -57,7 +59,17 @@ function schema(value, where, entry = false) {
|
|
|
57
59
|
check(!STRING_FORMATS.includes(value.format) || value.type === 'string', `${where}:${value.format} 是文本`);
|
|
58
60
|
check(value.enum === undefined || (Array.isArray(value.enum) && value.enum.length && ['string', 'integer', 'number'].includes(value.type)), `${where} 的 enum 无效`);
|
|
59
61
|
const out = { type: value.type, format: value.format, enum: value.enum, minimum: value.minimum, maximum: value.maximum };
|
|
60
|
-
if (value.type === 'array') {
|
|
62
|
+
if (value.type === 'array' && value.items?.format === 'coda-model-provider') {
|
|
63
|
+
// Several model services (plugin-rules.mjs `schema`): `<plugin>/<id>` each; `modelApis` the interfaces the plugin takes.
|
|
64
|
+
const items = value.items;
|
|
65
|
+
check(value.format === undefined && value.minimum === undefined && value.maximum === undefined, `${where}:多个模型服务不写 format、minimum 与 maximum`);
|
|
66
|
+
check(value.uniqueItems === undefined || value.uniqueItems === true, `${where}:多个模型服务的 uniqueItems 只能是 true`);
|
|
67
|
+
check(Object.keys(items).every(k => ['type', 'format', 'modelApis'].includes(k)) && items.type === 'string',
|
|
68
|
+
`${where}:多个模型服务写 items: { type: string, format: coda-model-provider, modelApis }`);
|
|
69
|
+
check(items.modelApis === undefined || (Array.isArray(items.modelApis) && items.modelApis.length >= 1 && items.modelApis.length <= 10
|
|
70
|
+
&& items.modelApis.every(api => typeof api === 'string' && COMMAND.test(api))), `${where}.items.modelApis 须为 1–10 个接口名,如 anthropic-messages`);
|
|
71
|
+
out.format = 'coda-model-provider';
|
|
72
|
+
} else if (value.type === 'array') {
|
|
61
73
|
check(value.format === undefined && value.minimum === undefined && value.maximum === undefined, `${where}:多选不写 format、minimum 与 maximum`);
|
|
62
74
|
check(value.uniqueItems === undefined || value.uniqueItems === true, `${where}:多选的 uniqueItems 只能是 true(每项至多选一次)`);
|
|
63
75
|
const items = value.items, options = items?.anyOf;
|
|
@@ -93,6 +105,10 @@ function checkValue(spec, value, where) {
|
|
|
93
105
|
check(value && typeof value === 'object' && !Array.isArray(value) && typeof value.agent === 'string' && AGENT_ID.test(value.agent), `${where} 须为 Agent 组合,并指明 Agent`);
|
|
94
106
|
return;
|
|
95
107
|
}
|
|
108
|
+
if (spec.type === 'array' && spec.format === 'coda-model-provider') {
|
|
109
|
+
check(Array.isArray(value) && value.length <= 20 && value.every(v => typeof v === 'string' && MODEL_PROVIDER.test(v)), `${where}须为至多 20 个「插件/服务」,如 cliproxy/cliproxy`);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
96
112
|
if (spec.type === 'array') {
|
|
97
113
|
check(Array.isArray(value) && value.every(v => typeof v === 'string'), `${where}须为选中的几项`);
|
|
98
114
|
const unknown = value.filter(v => !spec.options.includes(v));
|
|
@@ -274,13 +290,21 @@ export function checkManifest(dir, say) {
|
|
|
274
290
|
const contributes = part('manifest.contributes', () => {
|
|
275
291
|
check(manifest.contributes && typeof manifest.contributes === 'object' && !Array.isArray(manifest.contributes),
|
|
276
292
|
'contributes 须为对象(Coda 只把带 contributes 的目录当插件)');
|
|
277
|
-
const unknown = Object.keys(manifest.contributes).filter(k => !CONTRIBUTES.includes(k));
|
|
293
|
+
const unknown = Object.keys(manifest.contributes).filter(k => !CONTRIBUTES.includes(k) && !POINT_KEYS.includes(k));
|
|
278
294
|
check(!unknown.length, `contributes 里有 Coda 不支持的贡献:${unknown.join('、')}`);
|
|
279
295
|
// What is checked here is installed from a repository or npm, never as a copy Coda ships.
|
|
280
296
|
for (const [key, refusal] of Object.entries(BUILTIN_ONLY)) check(manifest.contributes[key] === undefined, `contributes.${key}:${refusal}`);
|
|
281
297
|
return manifest.contributes;
|
|
282
298
|
}) ?? {};
|
|
283
299
|
if (typeof manifest.version !== 'string') say.warn('manifest.version', 'package.json 没有 version:Coda 只认提交,version 只作显示,但安装确认里会空着');
|
|
300
|
+
// Coda's plugin pages group plugins by the first of their `categories` (Coda's docs/plugins.md「商店」), as VS Code writes them.
|
|
301
|
+
part('manifest.categories', () => {
|
|
302
|
+
check(manifest.categories === undefined || (Array.isArray(manifest.categories) && manifest.categories.every(c => typeof c === 'string')), 'categories 须为文本的数组');
|
|
303
|
+
const category = manifest.categories?.[0]?.trim();
|
|
304
|
+
if (!category) say.warn('manifest.categories', `package.json 没有 categories:Coda 的插件页把它归「其他」;分类用 ${CATEGORIES.join('、')} 之一`);
|
|
305
|
+
else if (!CATEGORIES.includes(category)) say.warn('manifest.categories', `分类「${category}」不是 Coda 自己的插件用的(${CATEGORIES.join('、')}):插件页把它排在这些之后`);
|
|
306
|
+
return category;
|
|
307
|
+
}, category => category && `分类 ${category}`);
|
|
284
308
|
const name = id ?? String(declaredId);
|
|
285
309
|
|
|
286
310
|
const settings = part('manifest.settings', () => {
|