@ycodium-ai/plugin-api 0.2.2169 → 0.2.2510

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.
@@ -10,7 +10,8 @@
10
10
  *
11
11
  * @module @ycodium-ai/plugin-api/executor
12
12
  */
13
- /** 与 `ExecutorModelConnectivity.dialects` 同一闭集;协议转换对复用它。 */
13
+ import type { PluginAccountUsageProbe } from "./account-usage.ts";
14
+ /** `ExecutorModelConnectivity.dialects` 的闭集:CLI 会说的模型请求格式。 */
14
15
  export type ExecutorModelDialect = "anthropic-messages" | "openai-responses" | "openai-chat-completions" | "google-cloud-code";
15
16
  export type ExecutorMcpTransport = "stdio" | "http" | "sse";
16
17
  export type ExecutorInstall = {
@@ -36,6 +37,26 @@ export type ExecutorSignInMethod = {
36
37
  readonly kind: "api-key";
37
38
  readonly variable: string;
38
39
  };
40
+ /**
41
+ * 这双手在磁盘上的账户与转录布局。缺席 = 宿主读不出它的布局,相关功能如实缺席,
42
+ * 不猜目录。
43
+ *
44
+ * 宿主有三件横切功能必须知道「某个实例的账户目录在哪」:维护命令要把账户目录变量
45
+ * 投对(否则配了三个账号,点哪个的登录都写进同一个目录)、`usage` 插件要扫转录、
46
+ * 插件的只读凭据白名单要放行那一个文件。宿主不能为此写 `if (driver === "codex")`,
47
+ * 所以布局作为事实写在名片上。
48
+ */
49
+ export type ExecutorHomeLayout = {
50
+ /**
51
+ * 账户目录另存一处时,实例配置里存它的字段名(codex 的 `shadowHomePath`)。
52
+ * 空串 = 账户目录就是 `homePath` 那一个。
53
+ */
54
+ readonly accountConfigKey: string;
55
+ /** 凭据文件相对账户目录的路径。空串 = 凭据不在文件里(钥匙串之类),宿主不猜。 */
56
+ readonly credentialFile: string;
57
+ /** 会话转录相对**共用** home 的子目录。空串 = 这双手不在磁盘上留转录。 */
58
+ readonly transcriptSubdir: string;
59
+ };
39
60
  export type ExecutorSessionModelSwitch = "in-session" | "unsupported";
40
61
  export type ExecutorContextCompaction = "native" | "unsupported";
41
62
  export type ExecutorRuntimeMode = "approval-required" | "auto-accept-edits" | "auto" | "full-access";
@@ -48,7 +69,6 @@ export type ExecutorCapabilities = {
48
69
  readonly contextCompaction?: ExecutorContextCompaction;
49
70
  readonly nativeModeSwitch?: boolean;
50
71
  readonly turnSteering?: boolean;
51
- readonly feedback?: boolean;
52
72
  /**
53
73
  * 这只手会不会把它派生的 agent(子代理、workflow 与其成员)经 `task.*` 报给宿主,
54
74
  * 并守 {@link ExecutorTaskAgentLinkage} 上的身份约定。缺席即不报;当前只声明
@@ -56,105 +76,99 @@ export type ExecutorCapabilities = {
56
76
  */
57
77
  readonly reportsSpawnedAgents?: boolean;
58
78
  };
79
+ /**
80
+ * 「这只 CLI 怎么连模型」的说明——说哪种请求格式、哪几个环境变量装地址与钥匙、会不会登录
81
+ * 自家订阅。宿主不往手里注地址或凭据,也不翻译请求;人要把 CLI 指到别的模型服务,就在手
82
+ * 实例上设这里点名的变量。
83
+ *
84
+ * 读它的都走 {@link setsOwnModelConnection} 判断一个实例是不是连自己的地址或钥匙:设置页
85
+ * 据此给它单独的账户目录,免得和已登录的实例共用同一份登录;额度显示(宿主的账号用量
86
+ * 引擎、`PluginProviderInstance.ownModelConnection`)据此不把订阅额度算到它头上。所以
87
+ * `baseUrlEnvKey`、`credentialEnvKey`、`alternateCredentialEnvKeys`、`backendSwitchEnvKeys`
88
+ * 要写全:漏一个,填了那个变量的实例就会落进默认账户目录、显示别人的额度。
89
+ */
59
90
  type ExecutorModelConnectivityFields = {
60
91
  /**
61
- * 绑定的默认入口方言:手的主请求打本机模型端点走的那一种。接外部端点的手**恰好一项**,
62
- * 而且必须是 `nativePathSuffix` 打到的那一种:`/v1/messages` → `anthropic-messages`,
63
- * `/responses` → `openai-responses`,`/chat/completions` → `openai-chat-completions`。
92
+ * CLI 会说的请求格式。能指到外部地址的手**恰好一项**,而且必须是 `nativePathSuffix`
93
+ * 打到的那一种:`/v1/messages` → `anthropic-messages`,`/responses` → `openai-responses`,
94
+ * `/chat/completions` → `openai-chat-completions`。
64
95
  *
65
- * 不是「CLI 换了配置能说哪几种」:投递从不替 CLI 换配置,宿主按后缀规划绑定、挑直通还是
66
- * 翻译,从不按这一栏路由。手在同一个 base 下另打别的路由时(grok 的标题请求走
67
- * `/responses`),宿主按那一笔的路径另挑翻译对。别家方言的脑由宿主翻译接上,不需要在这里
68
- * 多报;多报只会骗到读这一栏的人。
69
- *
70
- * 不接外部端点的手(通用 ACP)这里报这一族可能说的方言,认出是哪一家之前兜底用。
96
+ * 不是「CLI 换了配置能说哪几种」:只报它按这里写的变量连外部地址时说的那一种,多报只会
97
+ * 骗到读这一栏的人。不能指到外部地址的手(通用 ACP)这里报这一族可能说的格式。
71
98
  */
72
99
  readonly dialects: ReadonlyArray<ExecutorModelDialect>;
73
- /** CLI 在注入的 base 后固定追加的那一截。它决定绑定的默认入口方言,见 `dialects`。 */
100
+ /** CLI 在设置的 base 地址后固定追加的那一截。它决定 `dialects` 报哪一种。 */
74
101
  readonly nativePathSuffix?: "/v1/messages" | "/responses" | "/chat/completions";
75
102
  readonly credentialEnvKey?: string;
76
103
  /**
77
- * 代理地址投到哪个环境变量(`ANTHROPIC_BASE_URL`、`DEEPSEEK_BASE_URL` 这类)。
104
+ * CLI 从哪个环境变量读模型服务地址(`ANTHROPIC_BASE_URL`、`DEEPSEEK_BASE_URL` 这类)。
78
105
  *
79
- * 缺席说明这双手不靠环境变量换端点——有的只认配置文件(hermes),有的要塞一整份
80
- * JSON(opencode),有的压根接不了(cursor)。各家的实际途径与前提条件见提案 0025。
106
+ * 缺席说明这双手不靠环境变量换地址——有的只认配置文件,有的要塞一整份
107
+ * JSON(opencode),有的压根接不了。
81
108
  *
82
- * 与 `credentialEnvKey` **一起声明才有用**:只给凭据不给地址,手会拿着代理签发的
83
- * 一次性凭据去打厂商真地址,那个凭据在真地址上无效,而报错来自厂商、不指向这里。
109
+ * 与 `credentialEnvKey` **一起声明才有用**:只换地址不换凭据,CLI 会拿自己登录的凭据
110
+ * 去打那个地址。
84
111
  */
85
112
  readonly baseUrlEnvKey?: string;
86
113
  /**
87
- * CLI 还认的其它凭据变量名。
88
- *
89
- * `credentialEnvKey` 只有一个含义:宿主给绑定注入的那个变量(Claude 是
90
- * `ANTHROPIC_AUTH_TOKEN`)。用户在实例上配成这双手也认的另一个变量时,识别靠
91
- * 这一栏;投递不变。认证形态按变量名查脑插件预设,不按这一栏。
114
+ * CLI 还认的其它凭据变量名。`credentialEnvKey` 是首选的那个(Claude 是
115
+ * `ANTHROPIC_AUTH_TOKEN`);这一栏列 CLI 也认的其它变量(如 `ANTHROPIC_API_KEY`)。
92
116
  */
93
117
  readonly alternateCredentialEnvKeys?: readonly string[];
94
118
  /**
95
- * 除地址与凭据之外,这双手还需要哪些固定环境变量才肯用外部端点。
119
+ * 整套换后端的开关变量:设成真值(`1` / `true` / `yes` / `on`,不分大小写)CLI 就不走
120
+ * 自家订阅,改连云厂商托管的模型(claude 的 `CLAUDE_CODE_USE_BEDROCK` 这一类)。
121
+ * 与地址、凭据变量一样,设了就算这个实例连自己的模型服务。
122
+ */
123
+ readonly backendSwitchEnvKeys?: readonly string[];
124
+ /**
125
+ * 除地址与凭据之外,这双手还需要哪些固定环境变量才肯用外部地址。
96
126
  *
97
127
  * 有几家不是「设个地址就行」:kimi 要 `KIMI_MODEL_NAME` 当门闩(不设则整套
98
- * `KIMI_MODEL_*` 全不生效)加 `KIMI_MODEL_PROVIDER_TYPE` 选协议;
99
- *
100
- * `value` 是模板,四个占位符由宿主替换:
128
+ * `KIMI_MODEL_*` 全不生效)加 `KIMI_MODEL_PROVIDER_TYPE` 选协议。
101
129
  *
102
- * - `$model` — 本次选中的模型 id
103
- * - `$baseUrl` — 代理地址
104
- * - `$credential` — 代理签发的一次性凭据
105
- * - `$absentPath` — 按这次绑定算出的、**必定不存在**的文件路径。给那些只能靠
106
- * 「读不到自己的登录文件」才肯改用环境变量凭据的 CLI 用(grok 的
107
- * `GROK_AUTH_PATH`)。宿主不创建它、也不建父目录,所以 CLI 写也写不成;用它而
108
- * 不是空设备,是因为空设备名各平台不同(`/dev/null` / `NUL`),而「读不到」
109
- * 两边只有一种行为。
130
+ * `value` 是模板,占位符:
110
131
  *
111
- * 后两个是给 opencode 那类手的:它要的不是「地址一个变量、凭据一个变量」,而是一整段
112
- * JSON 塞进 `OPENCODE_CONFIG_CONTENT`,地址与凭据嵌在里面。
132
+ * - `$model` — 模型 id
133
+ * - `$baseUrl` — 模型服务地址
134
+ * - `$credential` — 凭据
135
+ * - `$absentPath` — 一个**必定不存在**的文件路径。给那些只能靠「读不到自己的登录文件」
136
+ * 才肯改用环境变量凭据的 CLI 用(grok 的 `GROK_AUTH_PATH`)。宿主不替换它;要用的手
137
+ * 自己在起进程时填(grok 在实例设了 `baseUrlEnvKey` 时填)。
113
138
  *
114
- * **`$credential` 出现在哪里,那个变量就装着一把真钥匙。** 它只在这一次进程的生命
115
- * 周期内有效,但仍然不该写进任何日志或磁盘文件。
139
+ * `$baseUrl` / `$credential` 是给 opencode 那类手的:它要的不是「地址一个变量、凭据一个
140
+ * 变量」,而是一整段 JSON 塞进 `OPENCODE_CONFIG_CONTENT`,地址与凭据嵌在里面。
116
141
  */
117
142
  readonly extraEnv?: ReadonlyArray<{
118
143
  readonly name: string;
119
144
  readonly value: string;
120
145
  }>;
121
146
  /**
122
- * 不认环境变量的手:把地址与凭据注在 argv 上(codex 的 `-c key=value`)。
147
+ * 不认环境变量的手:地址与凭据写在 argv 上(codex 的 `-c key=value`)。
123
148
  *
124
- * key 与字符串 value 都是模板,占位符在 `extraEnv` 三个之外另加两个:
125
- * `$providerId` 宿主为这次绑定生成的唯一 provider 名,`$credentialEnvKey` 承载
126
- * 凭据的那个变量名(同样按绑定唯一,两份绑定不抢同一个变量)。声明了这一栏的手,
127
- * 凭据由宿主投进 `$credentialEnvKey` 这个变量,不再看 `credentialEnvKey`。
149
+ * key 与字符串 value 都是模板,占位符在 `extraEnv` 那几个之外另加两个:`$providerId`
150
+ * 一个 provider 名,`$credentialEnvKey` 承载凭据的那个变量名。
128
151
  */
129
152
  readonly extraLaunchArgs?: ReadonlyArray<{
130
153
  readonly key: string;
131
154
  readonly value: string | boolean;
132
155
  }>;
133
156
  /**
134
- * 这双手能不能用自家订阅账户跑模型(CLI 自己登录、token 存在它自己的 home 里,
135
- * 宿主只记录目录与 revision)。缺席等同 `false`。
136
- *
137
- * 是手的事实,不是脑的:订阅凭据由 CLI 保管,宿主拿不到 token(见 ADR-0023 §2、§7)。
138
- * 绑定编译读这一栏判断 `native-subscription` / `runtime-managed` 来源能否派给它,
139
- * 不再按 driverKind 写死家族名。
157
+ * 这双手能不能用自家订阅账户跑模型(CLI 自己登录、token 存在它自己的 home 里)。
158
+ * 缺席等同 `false`。订阅凭据由 CLI 保管;宿主只在读用量时按名片上的 `accountUsage`
159
+ * 只读它。
140
160
  */
141
161
  readonly supportsNativeSubscription?: boolean;
142
162
  };
143
163
  /**
144
- * 说「我能接外部端点」就必须同时给出**地址往哪送**和**钥匙往哪送**,三种送法任选:
164
+ * 说「我能接外部地址」就必须同时写明**地址放哪**和**钥匙放哪**,三种放法任选:
145
165
  *
146
166
  * 1. 两个环境变量(claude、dsh、grok、kimi……);
147
167
  * 2. 一段 `extraEnv` 模板,地址与凭据嵌在里面(opencode 的整段 JSON);
148
168
  * 3. 启动参数(codex 的 `-c model_providers.*`)。
149
169
  *
150
- * **为什么要在类型上卡。** 宿主把手指到本机代理,靠的就是这两样;少一样,手就拿自己
151
- * 那个账号去打厂商真地址,而这一轮照样成功——绕过是默认行为。两样都不给却写 `true`,
152
- * 以前只在绑定时报一个含糊的 `driver-unknown`,而名片仍然对外宣称能接(通用 ACP 名片
153
- * 就是这么挂着的,五家厂商因此一个开关都没有)。接一双新手时这个问题必须当场回答,
154
- * 而不是等用户绑定失败。
155
- *
156
- * 类型只保证**栏位在**;模板里真写了 `$baseUrl` / `$credential` 由 `compileBinding`
157
- * 逐个核,核不过同样拒绝编译绑定——字符串内容进不了类型。
170
+ * 在类型上卡,是为了接一双新手时当场回答「怎么把它指到别的服务」,不留一张只写 `true`
171
+ * 却说不出怎么接的名片。类型只保证**栏位在**,模板内容没人核。
158
172
  */
159
173
  type ExecutorEndpointDelivery = {
160
174
  readonly baseUrlEnvKey: string;
@@ -174,7 +188,7 @@ export type ExecutorModelConnectivity = ExecutorModelConnectivityFields & ({
174
188
  readonly acceptsExternalEndpoints: false;
175
189
  } | ({
176
190
  readonly acceptsExternalEndpoints: true;
177
- /** 接得了就一定知道 CLI 会在 base 后追加什么,否则算不出该注什么地址。 */
191
+ /** 接得了就一定知道 CLI 会在 base 后追加什么,否则说不清该填什么地址。 */
178
192
  readonly nativePathSuffix: NonNullable<ExecutorModelConnectivityFields["nativePathSuffix"]>;
179
193
  } & ExecutorEndpointDelivery));
180
194
  export type ExecutorCredentialSource = {
@@ -194,7 +208,8 @@ export type ExecutorCredentialSource = {
194
208
  /**
195
209
  * 加载时即可聚合的静态名片。一张名片一个 `driverKind`。
196
210
  *
197
- * `iconUrl` 是完整 URL,由宿主改写自插件的 `assets/icon.svg` 路径或内置 glyph 名称。
211
+ * `iconUrl` 写插件相对路径(`./assets/icon.svg`)。宿主在目录出口把它核实后改写成资产路由
212
+ * 地址;文件缺失、超限或不安全时目录里是空串(明确的「缺失」),宿主不替换成别的图标。
198
213
  */
199
214
  export type ExecutorCard = {
200
215
  readonly driverKind: string;
@@ -203,6 +218,7 @@ export type ExecutorCard = {
203
218
  readonly commandName: string;
204
219
  readonly launchArgs: string;
205
220
  readonly homeEnvVar: string;
221
+ readonly homeLayout?: ExecutorHomeLayout;
206
222
  readonly environment: ReadonlyArray<{
207
223
  readonly name: string;
208
224
  readonly sensitive: boolean;
@@ -219,9 +235,6 @@ export type ExecutorCard = {
219
235
  readonly defaultTextGenerationModel: string;
220
236
  readonly customModelIdExample: string;
221
237
  readonly modelSlugAliases: Readonly<Record<string, string>> | null;
222
- readonly modelsDevProviderIds: ReadonlyArray<string>;
223
- readonly modelsDevSlugPrefixed: boolean;
224
- readonly modelSlugPrefix: string;
225
238
  readonly mcpTransports: ReadonlyArray<ExecutorMcpTransport> | null;
226
239
  readonly pluginManaged: boolean;
227
240
  readonly verifiedSkills: boolean;
@@ -232,44 +245,67 @@ export type ExecutorCard = {
232
245
  readonly id: string;
233
246
  readonly labelKey: string;
234
247
  }>;
235
- /** 与 `PluginJsonSchema` 同形:draft-07 白名单子集,顶层必须 `"type": "object"`。 */
236
- readonly configSchema: Record<string, unknown>;
237
- readonly modelConnectivity: ExecutorModelConnectivity;
238
- readonly maxConcurrentProcesses?: number;
239
- readonly legacyAcpClaim?: {
240
- readonly commandName: string;
241
- readonly homeEnvVar: string;
242
- };
243
- };
244
- export type ExecutorLaunchBinding = {
245
248
  /**
246
- * 宿主的最终层,不是基线。装的是宿主拿实例环境变量表算过之后的结论:绑定注入的
247
- * 端点地址与一次性凭据、名片 `extraEnv`(含让手不走自己登录的变量)、请求观测改写
248
- * 的代理变量。
249
+ * 与 `PluginJsonSchema` 同形:draft-07 白名单子集,顶层必须 `"type": "object"`。
249
250
  *
250
- * 手合成子进程环境时**最后铺**这一层:实例环境变量表、账号目录注入、手自己的默认
251
- * 值都不得盖过它——盖掉凭据,本机端点一律 401;盖掉代理,观测整段落空。显式
252
- * `undefined` 与 `spawn` 的 `env` 同义,是压掉继承值。
251
+ * 设置表单也照它画(ADR-0033 第 4 条):属性上带 {@link EXECUTOR_FORM_FIELD_KEYWORD}
252
+ * 才进表单,顺序由顶层 {@link EXECUTOR_FORM_ORDER_KEYWORD} 给。见
253
+ * {@link ExecutorFormFieldAnnotation}。
253
254
  */
254
- readonly processEnv: Readonly<Record<string, string | undefined>>;
255
- readonly launchArgOverrides: ReadonlyArray<{
256
- readonly key: string;
257
- readonly value: string | boolean;
258
- }>;
259
- /**
260
- * 绑定的脑要跑的型号。多半不在手的目录里:递给 CLI 之前过 {@link matchCatalogModel},
261
- * 对不上就不设型号。
262
- */
263
- readonly requestedModelId?: string;
264
- readonly homePath?: string;
265
- readonly localBridgeUrl?: string;
255
+ readonly configSchema: Record<string, unknown>;
256
+ readonly modelConnectivity: ExecutorModelConnectivity;
266
257
  /**
267
- * 这一轮走的是这双手自家的订阅账户:凭据在 `homePath` 那个独立账户目录里,宿主
268
- * 不投密钥、不开本地端点。怎么让 CLI 认这条路(强制订阅登录、压掉继承来的 API
269
- * key、绕开实例自己的影子目录)是手的事——宿主不认各家的配置键名。
258
+ * 怎么读这只手自己登录的账号用量(输入区与用量页的额度)。纯声明:宿主按
259
+ * `credentialKind` 读这只手登录目录里的凭据,只发往该凭据的受众 origin,不刷新。
260
+ * 没有自管登录额度的手不填。
270
261
  */
271
- readonly nativeSubscription?: boolean;
262
+ readonly accountUsage?: PluginAccountUsageProbe;
263
+ readonly maxConcurrentProcesses?: number;
272
264
  };
265
+ /** 属性级:这个配置字段要不要进设置表单、怎么画。没有它的属性不进表单。 */
266
+ export declare const EXECUTOR_FORM_FIELD_KEYWORD = "x-ycodium-form";
267
+ /** schema 顶层:表单里字段的先后,值是属性名数组。没列出的字段按声明顺序排在后面。 */
268
+ export declare const EXECUTOR_FORM_ORDER_KEYWORD = "x-ycodium-form-order";
269
+ export type ExecutorFormControl = "text" | "password" | "textarea" | "executable-path";
270
+ /**
271
+ * 名片 `configSchema` 里一个属性上的 `x-ycodium-form`。
272
+ *
273
+ * 文案一律是**键**,在这个插件自己的 `locales/<locale>.json`(宿主命名空间
274
+ * `plugin:<pluginId>`)里解析;宿主不带任何兜底文案。键缺失,界面就把那一处明确画成缺失。
275
+ *
276
+ * - `control`:缺省 `text`;`executable-path` 是必须绝对路径、并带「按 PATH 探测」按钮的
277
+ * 文本框。`type: "boolean"` 的属性一律画开关,默认值取属性上的 `default`(缺省 false)。
278
+ * - `clearWhenEmpty`:缺省 `omit`,即清空时把这个键从实例配置里删掉;`persist` 保留空值。
279
+ */
280
+ export type ExecutorFormFieldAnnotation = {
281
+ readonly titleKey: string;
282
+ readonly descriptionKey?: string;
283
+ readonly placeholderKey?: string;
284
+ readonly control?: ExecutorFormControl;
285
+ readonly clearWhenEmpty?: "omit" | "persist";
286
+ };
287
+ /** {@link deriveExecutorFormFields} 的一行:配置里的键 + 怎么画它。 */
288
+ export type ExecutorFormField = {
289
+ readonly key: string;
290
+ readonly control: "text" | "password" | "textarea" | "switch";
291
+ /** 必须是绝对路径,并带探测按钮。 */
292
+ readonly executablePath: boolean;
293
+ /** 注解写坏了(不是对象、没给 `titleKey`)时是 `undefined`:界面明确显示缺失。 */
294
+ readonly titleKey: string | undefined;
295
+ readonly descriptionKey: string | undefined;
296
+ readonly placeholderKey: string | undefined;
297
+ readonly clearWhenEmpty: "omit" | "persist";
298
+ /** 只有开关有:属性上声明的 `default`,没声明是 `undefined`。 */
299
+ readonly defaultBoolean: boolean | undefined;
300
+ };
301
+ /**
302
+ * 名片 `configSchema` 要在设置表单里画哪些字段,按什么顺序。宿主的添加与编辑表单、
303
+ * 各插件的名片测试读的都是这一份,不各写一遍。
304
+ *
305
+ * 不认识的 `control`、写坏的注解都不抛:那是第三方插件的数据,抛了整张表单就没了。
306
+ * 坏的地方原样带出去(`titleKey` 为 `undefined`),由界面画成缺失。
307
+ */
308
+ export declare function deriveExecutorFormFields(configSchema: Readonly<Record<string, unknown>>): ReadonlyArray<ExecutorFormField>;
273
309
  /**
274
310
  * 从 `ModelSelection` 形状里读出某个字符串选项的值。
275
311
  *
@@ -284,19 +320,17 @@ export declare function modelSelectionStringOption(selection: unknown, optionId:
284
320
  /**
285
321
  * 宿主要的型号在这只手的型号目录里对应哪一个取值;对不上返回 `undefined`。
286
322
  *
287
- * 宿主递来的型号(`modelSelection.model`、`launch.requestedModelId`)绑了脑时是**脑的**
288
- * 型号,比如 `deepseek-flash`,多半不在手的目录里。上游跑哪个型号只由绑定决定——本机
289
- * 模型端点发往上游那一处按绑定改写(`docs/internals/agent-model-management.md`),手侧
290
- * 型号只是手肯收的占位。而目录外的 id 递给 CLI 会出事:dsh / grok 回 -32602 会话起不来,
291
- * opencode 要 `provider/model`,Claude Code 连请求都不发。
323
+ * 输入区递来的型号(`modelSelection.model`)可能是裸 id(`deepseek-flash`),而手的目录
324
+ * 里是另一种写法(dsh 的 JSON 元组、opencode 的 `provider/model`);也可能是一个根本不在
325
+ * 目录里的 id。目录外的 id 递给 CLI 会出事:dsh / grok 回 -32602 会话起不来,opencode
326
+ * 要 `provider/model`,Claude Code 连请求都不发。
292
327
  *
293
328
  * 所以只在目录里找:先精确匹配,再按最后一段唯一匹配;两个取值最后一段相同就不猜——
294
- * 不同 provider 走的地址与凭据不同,猜错了就绕开了本机端点。
329
+ * 不同 provider 走的地址与凭据不同,猜错了就花了用户另一份凭据。
295
330
  *
296
331
  * 拿到 `undefined` 的手**不设型号**(不发 set_model、不带 `--model`、提示里不填
297
- * model),CLI 用自己的默认——绑定注入的配置让默认那一条就走本机端点;**不报错**。
298
- * 要留日志用 {@link createCatalogModelMatcher}。CLI 本来就收任意 id 的手(codex 自定义
299
- * provider 就是这么用的)不必过这一道。
332
+ * model),CLI 用自己的默认;**不报错**。要留日志用 {@link createCatalogModelMatcher}。
333
+ * CLI 本来就收任意 id 的手(codex 自定义 provider 就是这么用的)不必过这一道。
300
334
  *
301
335
  * ACP 纯库不许依赖本包,`ycodium-acp-executor` 的 `matchAdvertisedModelValue` 是同一条
302
336
  * 规则的一份副本;两份在 `plugins/agent-acp/src/catalogModelParity.test.ts` 对拍。
@@ -321,52 +355,39 @@ export declare function createCatalogModelMatcher(log: {
321
355
  * 更明确,所以赢过上一层。
322
356
  * 4. `handOverrides` — 手自己必须赢过实例表的那几个键(握手来源标注、压掉继承来的
323
357
  * 凭据)。
324
- * 5. `launch` — 宿主的最终层(`ExecutorLaunchBinding.processEnv`)。它是宿主拿实例表
325
- * 算过之后的结论,谁都不得盖。
358
+ * 5. `launch` — 这一次会话特有、谁都不得盖的覆盖:宿主挑好的 MCP server 带来的变量
359
+ * (codex)铺在这里。
326
360
  *
327
361
  * 值为 `undefined` 表示压掉继承值,与 `spawn` 的 `env` 同义;自己 spawn 的手要把
328
362
  * 这些键真的从环境里删掉。
329
363
  */
330
- /**
331
- * 名片给**脑**留的那几个环境变量名。实例表里出现它们时不交给 CLI。
332
- *
333
- * 判据全在名片上,不写死名单:`credentialEnvKey` / `baseUrlEnvKey` /
334
- * `alternateCredentialEnvKeys` 是宿主绑定一个脑时要注入的槽位。同一个名字如果名片
335
- * **同时**声明成用户可填(`environment`)或环境式登录(`signIn` 的 `kind:
336
- * "environment"`),那它就是这双手的登录方式本身——dsh 的 `DEEPSEEK_API_KEY` 正是
337
- * 这种,它没有别的登录路径——照旧放行。
338
- *
339
- * 为什么要闸:手上填着地址和凭据时,CLI 直连厂商,不经本机代理,于是没有记账、没有
340
- * 录音、没有按模型冷却、没有协议翻译——「手 → 本机代理 → 脑凭据」那条唯一链路被绕开,
341
- * 而手又变回了脑。
342
- *
343
- * 加第七只手时只改它自己的名片,这里不用动。
344
- */
345
- export declare function brainSlotEnvKeys(card: {
346
- readonly environment: ReadonlyArray<{
347
- readonly name: string;
348
- }>;
349
- readonly signIn: ReadonlyArray<ExecutorSignInMethod>;
350
- readonly modelConnectivity: {
351
- readonly credentialEnvKey?: string | undefined;
352
- readonly baseUrlEnvKey?: string | undefined;
353
- readonly alternateCredentialEnvKeys?: readonly string[] | undefined;
354
- };
355
- }): ReadonlyArray<string>;
356
- /** 从一张环境变量表里摘掉脑的插口。键不出现,而不是置 undefined——继承来的那份不是我们该管的。 */
357
- export declare function withoutBrainSlots(table: Readonly<Record<string, string | undefined>> | undefined, brainSlots: ReadonlyArray<string>): Record<string, string | undefined>;
358
364
  export declare function mergeLaunchEnv(input: {
359
365
  readonly inherited?: Readonly<Record<string, string | undefined>> | undefined;
360
366
  readonly accountHome?: Readonly<Record<string, string | undefined>> | undefined;
361
367
  readonly instanceTable?: Readonly<Record<string, string | undefined>> | undefined;
362
- /**
363
- * 这双手的脑插口(`brainSlotEnvKeys(card)`)。**必填**:漏传就等于把闸门悄悄
364
- * 拆掉,而那种漏接没有任何症状——手照旧直连厂商,一切看着正常。没有插口的手传 `[]`。
365
- */
366
- readonly brainSlots: ReadonlyArray<string>;
367
368
  readonly handOverrides?: Readonly<Record<string, string | undefined>> | undefined;
368
369
  readonly launch?: Readonly<Record<string, string | undefined>> | undefined;
369
370
  }): Record<string, string | undefined>;
371
+ /**
372
+ * 实例的环境变量表有没有把这只 CLI 指到它自己的模型地址或钥匙上:名片
373
+ * `modelConnectivity` 点名的地址变量、凭据变量有非空值,或后端开关是真值。
374
+ *
375
+ * 这样的实例不该和已登录的实例共用账户目录:grok 会把登录拿到的 x.ai 会话 token 发给
376
+ * 自定义地址,claude 会把订阅额度算到它头上。设置页据此给它单独的账户目录。
377
+ *
378
+ * `valueRedacted` 的行是存进密钥库、客户端读不到值的那种,按「有值」算。只认名片点名的
379
+ * 变量,不按变量名猜;名片没声明任何变量的手恒为 false。
380
+ */
381
+ export declare function setsOwnModelConnection(connectivity: {
382
+ readonly baseUrlEnvKey?: string | undefined;
383
+ readonly credentialEnvKey?: string | undefined;
384
+ readonly alternateCredentialEnvKeys?: ReadonlyArray<string> | undefined;
385
+ readonly backendSwitchEnvKeys?: ReadonlyArray<string> | undefined;
386
+ }, environment: ReadonlyArray<{
387
+ readonly name: string;
388
+ readonly value: string;
389
+ readonly valueRedacted?: boolean | undefined;
390
+ }>): boolean;
370
391
  /**
371
392
  * 宿主这一轮要挂给 CLI 的 MCP server。谁该挂由宿主决定(内置编排那台加插件中心里
372
393
  * 对这双手开着的条目),怎么告诉自己的 CLI 由手决定——各家格式差太远,宿主翻不了。
@@ -388,9 +409,8 @@ export type ExecutorSessionStartInput = {
388
409
  readonly threadId: string;
389
410
  readonly cwd?: string;
390
411
  readonly title?: string;
391
- /** 同 {@link ExecutorSendTurnInput.modelSelection}:绑了脑时 `model` 是脑的型号。 */
412
+ /** 同 {@link ExecutorSendTurnInput.modelSelection}。 */
392
413
  readonly modelSelection?: unknown;
393
- readonly executionSelection?: unknown;
394
414
  readonly bindingGeneration?: number;
395
415
  readonly resumeCursor?: unknown;
396
416
  readonly discardResumeCursor?: boolean;
@@ -403,7 +423,6 @@ export type ExecutorSessionStartInput = {
403
423
  * 那时座位画的是四档,手按 `runtimeMode` 跑。
404
424
  */
405
425
  readonly nativeModeId?: string;
406
- readonly launch: ExecutorLaunchBinding;
407
426
  /**
408
427
  * 这次会话要挂的 MCP server,内置编排那一条排第一(宿主 `resolveMcpLaunchList` 的
409
428
  * 输出原样搬过来)。缺席或空数组就是不挂——手据此决定要不要传自己 SDK 的那一栏。
@@ -413,6 +432,15 @@ export type ExecutorSessionStartInput = {
413
432
  * 工具一起丢,所以补的是插口而不是给某一家开例外。
414
433
  */
415
434
  readonly mcpServers?: ReadonlyArray<ExecutorMcpServer>;
435
+ /**
436
+ * 宿主已决定允许读取的目录(绝对路径,只读)。目前是用户附件所在的目录:
437
+ * 附件落在 cwd 之外,不放行的话 CLI 每读一次都要弹授权。
438
+ *
439
+ * 只表达"可读",不含写:手必须把它翻成自家 CLI 的语汇,并保证这些目录里的写入
440
+ * 在非 bypass 档仍然被拦或要批准。缺席或空数组就是不额外放行。读本来就不受限的
441
+ * 手可以忽略这一栏。
442
+ */
443
+ readonly readOnlyDirectories?: ReadonlyArray<string>;
416
444
  };
417
445
  export type ExecutorSendTurnInput = {
418
446
  readonly threadId: string;
@@ -420,11 +448,10 @@ export type ExecutorSendTurnInput = {
420
448
  readonly input?: string;
421
449
  readonly attachments?: ReadonlyArray<unknown>;
422
450
  /**
423
- * 绑了脑时,宿主已把其中的 `model` 换成脑的型号(同 `launch.requestedModelId`)。
424
- * 递给 CLI 之前过 {@link matchCatalogModel},对不上就不设型号、不报错。
451
+ * 输入区选的型号与档位。型号递给 CLI 之前过 {@link matchCatalogModel},对不上就不设
452
+ * 型号、不报错。
425
453
  */
426
454
  readonly modelSelection?: unknown;
427
- readonly executionSelection?: unknown;
428
455
  readonly interactionMode?: unknown;
429
456
  };
430
457
  export type ExecutorTurnStartResult = {
@@ -469,9 +496,8 @@ export type ExecutorProbeResult = {
469
496
  /**
470
497
  * 这个账户目录能跑哪些模型,以及每个模型接受哪些档位。
471
498
  *
472
- * **订阅脑的清单只有这只 CLI 够得着,所以由手报**:型号、思考档位、服务档都写在
473
- * 用户那份订阅账户里,宿主没有别的路问得到。**API 脑的清单不走这里**——那边的模型
474
- * 由来源自己声明(`model-<vendor>` 与用户手填),手不必也不该重复报一遍。
499
+ * 这是 CLI 自己账户的目录:型号、思考档位、服务档都写在用户那份账户里,只有这只 CLI
500
+ * 够得着,宿主没有别的路问得到,所以由手报。
475
501
  *
476
502
  * 只有真问过 CLI 的探测才填,填不出就**缺席**;空数组是「问过了,一个都没有」,
477
503
  * 两者对调用方不是一回事。
@@ -500,6 +526,27 @@ export type ExecutorProbeResult = {
500
526
  readonly nativeModeByRuntimeMode?: {
501
527
  readonly [Mode in ExecutorRuntimeMode]: string;
502
528
  };
529
+ /**
530
+ * 这只手能用的技能:输入区的 `$` 菜单和回复里的技能标记都读它。
531
+ *
532
+ * **缺席即不支持**:这只手说不出自己有哪些技能。空数组是「问过了,一个都没有」。
533
+ */
534
+ readonly skills?: ReadonlyArray<ExecutorProbeSkill>;
535
+ };
536
+ /**
537
+ * 探测报上来的一个技能。字段与宿主 `ServerProviderSkill` 逐字对齐,宿主原样落进快照。
538
+ *
539
+ * `path` 必填:界面按技能文件所在的位置分来源(插件带的、个人的、项目的),同名技能也靠它
540
+ * 区分。报不出位置的技能就不报。
541
+ */
542
+ export type ExecutorProbeSkill = {
543
+ readonly name: string;
544
+ readonly description?: string;
545
+ readonly path: string;
546
+ readonly scope?: string;
547
+ readonly enabled: boolean;
548
+ readonly displayName?: string;
549
+ readonly shortDescription?: string;
503
550
  };
504
551
  /**
505
552
  * 探测报上来的一档「原生模式」——这只手自己的审批档,不是宿主那四档。
@@ -525,7 +572,6 @@ export type ExecutorProbeModel = {
525
572
  readonly slug: string;
526
573
  readonly name?: string;
527
574
  readonly isDefault?: boolean;
528
- readonly isLegacy?: boolean;
529
575
  /** CLI 自己就把它藏起来了(codex 的 `hidden`)。宿主照样收,由客户端决定显不显。 */
530
576
  readonly hidden?: boolean;
531
577
  /**
@@ -535,7 +581,7 @@ export type ExecutorProbeModel = {
535
581
  readonly capabilities?: ExecutorModelCapabilities | null;
536
582
  /**
537
583
  * 上下文窗口 token 数。**多数 CLI 报不出这个**(codex 的 `model/list` 就没有这一栏),
538
- * 报不出就缺席,宿主另走 models.dev;别在这里填一个猜的数。
584
+ * 报不出就缺席,界面显示「未知」;别在这里填一个猜的数。用户手填的窗口由宿主叠在上面。
539
585
  */
540
586
  readonly contextWindow?: number;
541
587
  };
@@ -688,6 +734,7 @@ export type ExecutorEventRaw = {
688
734
  readonly payload: unknown;
689
735
  };
690
736
  export type ExecutorEventBase = {
737
+ /** 跨服务器重启、跨线程都不能重复:宿主拿它当活动 id 全局落库,重复会覆盖已有的旧活动。 */
691
738
  readonly eventId: string;
692
739
  readonly provider: string;
693
740
  readonly providerInstanceId?: string;
@@ -695,6 +742,10 @@ export type ExecutorEventBase = {
695
742
  readonly createdAt: string;
696
743
  readonly turnId?: string;
697
744
  readonly itemId?: string;
745
+ /**
746
+ * 同一线程里跨会话重启也不能重复:宿主按它记「已接受」并在重启后恢复,撞号的新请求会被
747
+ * 当成答过的旧请求拒掉。协议自带的序号(如每个进程从 0 数起的 JSON-RPC id)要先加会话级前缀。
748
+ */
698
749
  readonly requestId?: string;
699
750
  readonly providerRefs?: ExecutorEventProviderRefs;
700
751
  readonly raw?: ExecutorEventRaw;
@@ -849,12 +900,6 @@ export type ExecutorInstance = {
849
900
  readonly readThread: (threadId: string) => Promise<ExecutorThreadSnapshot>;
850
901
  readonly events: AsyncIterable<ExecutorEvent>;
851
902
  readonly dispose: () => Promise<void>;
852
- readonly uploadFeedback?: (input: {
853
- readonly threadId: string;
854
- readonly reason?: string;
855
- }, signal?: AbortSignal) => Promise<{
856
- readonly feedbackId: string;
857
- }>;
858
903
  readonly textGeneration?: ExecutorTextGeneration;
859
904
  readonly sessions?: {
860
905
  readonly scan: (input: {
@@ -911,9 +956,55 @@ export type ExecutorInstance = {
911
956
  */
912
957
  readonly continuationKey?: string;
913
958
  };
959
+ /**
960
+ * 候选账户目录里的登录状态。`unreadable` 是「文件在,但读不懂」,不是「那里没有账号」。
961
+ * 宿主另有 `unsupported`,只由宿主给出(手没实现 {@link ExecutorContribution.probeAccount},
962
+ * 或这次问询失败、超时、答非所问),手不报它。
963
+ */
964
+ export type ExecutorAccountState = "signed-in" | "expired" | "signed-out" | "unreadable";
965
+ export type ExecutorAccountProbeInput = {
966
+ /**
967
+ * 添加流程给出的账户目录原文:可能带 `~`,可能是相对路径;空串指「实例配置留空时
968
+ * CLI 实际用的那个默认目录」。与实例配置里那个账户目录字段同一种写法、同一套解析。
969
+ */
970
+ readonly accountPath: string;
971
+ /** 宿主等不及时 abort。只读几个文件的实现可以不理它。 */
972
+ readonly signal?: AbortSignal;
973
+ };
974
+ export type ExecutorAccountProbeResult = {
975
+ /**
976
+ * 解析后的绝对路径,也就是 CLI 真会读的那个目录。宿主拿它判断目录在不在(这一位
977
+ * 不由手报)、比较两种写法是不是同一个目录;不是绝对路径的回答整条作废。
978
+ */
979
+ readonly accountPath: string;
980
+ readonly state: ExecutorAccountState;
981
+ /** 凭据自带的身份。说不出就缺席,不要编。 */
982
+ readonly email?: string;
983
+ readonly name?: string;
984
+ /** 凭据自报的过期时刻(ISO 8601)。解不出就缺席。 */
985
+ readonly expiresAt?: string;
986
+ /**
987
+ * 同一个 CLI 既能订阅登录、又能填 API key 时,这份凭据是哪一种。取值与过线契约
988
+ * `ProviderAccountProbe.authType` 相同;只有一种凭据的手不报。
989
+ */
990
+ readonly authType?: "chatgpt" | "apiKey";
991
+ };
914
992
  export type ExecutorContribution = {
915
993
  readonly card: ExecutorCard;
916
994
  readonly createInstance: (config: unknown, host: ExecutorHost) => Promise<ExecutorInstance>;
995
+ /**
996
+ * 还没建实例时,这个候选账户目录里登着谁。添加实例的对话框在问用户之前先问它:
997
+ * 默认目录里已经登着账号,就点名并让用户选「用这个」还是「再加一个」;起新目录名
998
+ * 时拿它的 `accountPath` 比掉已被占的候选。
999
+ *
1000
+ * 只读磁盘上 CLI 自己写的凭据文件,**不要起 CLI**,不要写任何东西。读不到、读不懂
1001
+ * 都收敛成一个 state,不要抛;宿主对抛错、超时和不合形状的回答一律当 `unsupported`,
1002
+ * 对话框据此闭嘴。
1003
+ *
1004
+ * 可选:不实现的手,添加流程不提账户目录。与 `ExecutorInstance.signIn.verify` 不是
1005
+ * 一回事——那个验的是已经存在的实例。
1006
+ */
1007
+ readonly probeAccount?: (input: ExecutorAccountProbeInput) => Promise<ExecutorAccountProbeResult>;
917
1008
  };
918
1009
  export {};
919
1010
  //# sourceMappingURL=executor.d.ts.map