@wallbreakerno4/opencode-commandcode 0.1.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.
Files changed (76) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +54 -0
  3. package/dist/disguise/backoff.d.ts +33 -0
  4. package/dist/disguise/backoff.js +43 -0
  5. package/dist/disguise/config-block.d.ts +71 -0
  6. package/dist/disguise/config-block.js +166 -0
  7. package/dist/disguise/config-freeze.d.ts +27 -0
  8. package/dist/disguise/config-freeze.js +87 -0
  9. package/dist/disguise/fingerprint.d.ts +44 -0
  10. package/dist/disguise/fingerprint.js +127 -0
  11. package/dist/disguise/hash.d.ts +8 -0
  12. package/dist/disguise/hash.js +13 -0
  13. package/dist/disguise/headers.d.ts +25 -0
  14. package/dist/disguise/headers.js +25 -0
  15. package/dist/disguise/logger.d.ts +14 -0
  16. package/dist/disguise/logger.js +18 -0
  17. package/dist/disguise/preflight.d.ts +74 -0
  18. package/dist/disguise/preflight.js +139 -0
  19. package/dist/disguise/redact.d.ts +12 -0
  20. package/dist/disguise/redact.js +17 -0
  21. package/dist/disguise/session.d.ts +11 -0
  22. package/dist/disguise/session.js +24 -0
  23. package/dist/disguise/slug.d.ts +12 -0
  24. package/dist/disguise/slug.js +47 -0
  25. package/dist/disguise/state.d.ts +62 -0
  26. package/dist/disguise/state.js +157 -0
  27. package/dist/disguise/traceparent.d.ts +7 -0
  28. package/dist/disguise/traceparent.js +10 -0
  29. package/dist/disguise/version-cache.d.ts +27 -0
  30. package/dist/disguise/version-cache.js +69 -0
  31. package/dist/disguise/version-runtime.d.ts +42 -0
  32. package/dist/disguise/version-runtime.js +137 -0
  33. package/dist/disguise/version.d.ts +21 -0
  34. package/dist/disguise/version.js +17 -0
  35. package/dist/host/constants.d.ts +14 -0
  36. package/dist/host/constants.js +14 -0
  37. package/dist/host/v1.d.ts +85 -0
  38. package/dist/host/v1.js +118 -0
  39. package/dist/host/v2.d.ts +33 -0
  40. package/dist/host/v2.js +110 -0
  41. package/dist/index.d.ts +47 -0
  42. package/dist/index.js +33 -0
  43. package/dist/models/api.d.ts +23 -0
  44. package/dist/models/api.js +44 -0
  45. package/dist/models/artifact.d.ts +45 -0
  46. package/dist/models/artifact.js +108 -0
  47. package/dist/models/cascade.d.ts +47 -0
  48. package/dist/models/cascade.js +40 -0
  49. package/dist/models/mapping.d.ts +66 -0
  50. package/dist/models/mapping.js +47 -0
  51. package/dist/models/pipeline.d.ts +89 -0
  52. package/dist/models/pipeline.js +331 -0
  53. package/dist/models/signature.d.ts +20 -0
  54. package/dist/models/signature.js +29 -0
  55. package/dist/models/snapshot.d.ts +15 -0
  56. package/dist/models/snapshot.js +16 -0
  57. package/dist/models/snapshot.json +535 -0
  58. package/dist/models/urls.d.ts +39 -0
  59. package/dist/models/urls.js +96 -0
  60. package/dist/protocol/envelope.d.ts +118 -0
  61. package/dist/protocol/envelope.js +331 -0
  62. package/dist/protocol/errors.d.ts +97 -0
  63. package/dist/protocol/errors.js +281 -0
  64. package/dist/protocol/generate.d.ts +53 -0
  65. package/dist/protocol/generate.js +173 -0
  66. package/dist/protocol/images.d.ts +45 -0
  67. package/dist/protocol/images.js +115 -0
  68. package/dist/protocol/json.d.ts +11 -0
  69. package/dist/protocol/json.js +8 -0
  70. package/dist/protocol/ndjson.d.ts +41 -0
  71. package/dist/protocol/ndjson.js +321 -0
  72. package/dist/protocol/watchdog.d.ts +37 -0
  73. package/dist/protocol/watchdog.js +61 -0
  74. package/dist/provider/model.d.ts +95 -0
  75. package/dist/provider/model.js +323 -0
  76. package/package.json +46 -0
@@ -0,0 +1,118 @@
1
+ /**
2
+ * 协议核心·请求方向:把 AI SDK 标准调用(LanguageModelV3CallOptions)翻译成
3
+ * `/alpha/generate` 的 CLI 信封(契约:docs/spec/protocol.md §1)。
4
+ *
5
+ * 伪装字段只留填充点:`config` 块与顶层 `permissionMode` 由伪装模块提供(disguise.md
6
+ * §9),本模块对其内容零知识、原样搬运;`memory` / `taste` / `skills` 是官方 CLI
7
+ * 硬编码的 null 死键(#25 源码定案),在此写死;`threadId` 与 `x-session-id` 同值,
8
+ * 由调用方从伪装模块的会话身份取。
9
+ *
10
+ * 越权参数策略(§1.3):信封 `params` 没有的调用参数既不塞进信封也不静默吞掉,
11
+ * 一律以 AI SDK call warnings 上报,由调用方在结果中可见。
12
+ */
13
+ import type { LanguageModelV3CallOptions, LanguageModelV3Prompt, SharedV3Warning } from "@ai-sdk/provider";
14
+ /** provider 唯一标识:v2 插件 id、v1 config 注入键、integrationID、模型 id 前缀四处同名 */
15
+ export declare const PROVIDER_ID = "commandcode-go";
16
+ /** 模型 reference(`provider/wire`)解析:按首个 `/` 切分,wire id 可含 `/` 原样透传 */
17
+ export declare function splitModelReference(reference: string): {
18
+ providerId: string;
19
+ wireId: string;
20
+ };
21
+ export interface EnvelopeTool {
22
+ type: "function";
23
+ name: string;
24
+ description?: string;
25
+ input_schema: Record<string, unknown>;
26
+ }
27
+ export type EnvelopeToolChoice = {
28
+ type: "auto";
29
+ } | {
30
+ type: "none";
31
+ } | {
32
+ type: "any";
33
+ } | {
34
+ type: "tool";
35
+ name: string;
36
+ };
37
+ export type EnvelopeTextBlock = {
38
+ type: "text";
39
+ text: string;
40
+ };
41
+ export type EnvelopeImageBlock = {
42
+ type: "image";
43
+ image: string;
44
+ };
45
+ export type EnvelopeAssistantBlock = EnvelopeTextBlock | {
46
+ type: "reasoning";
47
+ text: string;
48
+ } | {
49
+ type: "tool-call";
50
+ toolCallId: string;
51
+ toolName: string;
52
+ input: unknown;
53
+ };
54
+ export type EnvelopeToolResultBlock = {
55
+ type: "tool-result";
56
+ toolCallId: string;
57
+ toolName: string;
58
+ output: {
59
+ type: "text" | "error-text";
60
+ value: string;
61
+ };
62
+ };
63
+ export type EnvelopeMessage = {
64
+ role: "user";
65
+ content: Array<EnvelopeTextBlock | EnvelopeImageBlock>;
66
+ } | {
67
+ role: "assistant";
68
+ content: EnvelopeAssistantBlock[];
69
+ } | {
70
+ role: "tool";
71
+ content: EnvelopeToolResultBlock[];
72
+ };
73
+ export interface EnvelopeParams {
74
+ model: string;
75
+ messages: EnvelopeMessage[];
76
+ system?: string;
77
+ tools?: EnvelopeTool[];
78
+ tool_choice?: EnvelopeToolChoice;
79
+ max_tokens: number;
80
+ stream: true;
81
+ temperature?: number;
82
+ top_p?: number;
83
+ top_k?: number;
84
+ reasoning_effort?: string;
85
+ }
86
+ /** 七键骨架;键序即规格 §1 列举序,构造时按此序赋值保持 wire 形状稳定 */
87
+ export interface CommandCodeEnvelope {
88
+ config: Record<string, unknown>;
89
+ memory: null;
90
+ taste: null;
91
+ skills: null;
92
+ permissionMode: string;
93
+ threadId: string;
94
+ params: EnvelopeParams;
95
+ }
96
+ /** 协议核心之外的调用上下文:模型管线与伪装模块的输入 */
97
+ export interface EnvelopeCallContext {
98
+ /** wire id(provider 前缀剥除后),原样进 `params.model` */
99
+ modelId: string;
100
+ /** 模型管线级联 `maxOutput`:`max_tokens` 的裁剪参考(models.dev 第三方视角值,
101
+ * 非网关真值;仅当小于官方缺省 64e3 时生效,§1.2 / ADR 0002) */
102
+ maxOutput: number;
103
+ /** 与 `x-session-id` 同值(伪装模块会话身份) */
104
+ threadId: string;
105
+ /** 伪装模块提供的填充点内容,本模块零知识 */
106
+ disguise: {
107
+ config: Record<string, unknown>;
108
+ permissionMode: string;
109
+ };
110
+ }
111
+ export declare function buildEnvelope(options: LanguageModelV3CallOptions, context: EnvelopeCallContext): {
112
+ body: CommandCodeEnvelope;
113
+ warnings: SharedV3Warning[];
114
+ };
115
+ export declare function convertPrompt(prompt: LanguageModelV3Prompt, warnings: SharedV3Warning[]): {
116
+ system: string | undefined;
117
+ messages: EnvelopeMessage[];
118
+ };
@@ -0,0 +1,331 @@
1
+ /**
2
+ * 协议核心·请求方向:把 AI SDK 标准调用(LanguageModelV3CallOptions)翻译成
3
+ * `/alpha/generate` 的 CLI 信封(契约:docs/spec/protocol.md §1)。
4
+ *
5
+ * 伪装字段只留填充点:`config` 块与顶层 `permissionMode` 由伪装模块提供(disguise.md
6
+ * §9),本模块对其内容零知识、原样搬运;`memory` / `taste` / `skills` 是官方 CLI
7
+ * 硬编码的 null 死键(#25 源码定案),在此写死;`threadId` 与 `x-session-id` 同值,
8
+ * 由调用方从伪装模块的会话身份取。
9
+ *
10
+ * 越权参数策略(§1.3):信封 `params` 没有的调用参数既不塞进信封也不静默吞掉,
11
+ * 一律以 AI SDK call warnings 上报,由调用方在结果中可见。
12
+ */
13
+ import { asRecord } from "./json.js";
14
+ /** provider 唯一标识:v2 插件 id、v1 config 注入键、integrationID、模型 id 前缀四处同名 */
15
+ export const PROVIDER_ID = "commandcode-go";
16
+ /** 模型 reference(`provider/wire`)解析:按首个 `/` 切分,wire id 可含 `/` 原样透传 */
17
+ export function splitModelReference(reference) {
18
+ const slash = reference.indexOf("/");
19
+ if (slash === -1)
20
+ return { providerId: PROVIDER_ID, wireId: reference };
21
+ return { providerId: reference.slice(0, slash), wireId: reference.slice(slash + 1) };
22
+ }
23
+ // ---------------------------------------------------------------------------
24
+ // 信封构造
25
+ // ---------------------------------------------------------------------------
26
+ export function buildEnvelope(options, context) {
27
+ const warnings = [];
28
+ warnUnsupportedCallParams(options, warnings);
29
+ const { system, messages } = convertPrompt(options.prompt, warnings);
30
+ const tools = convertTools(options.tools, warnings);
31
+ const toolChoice = resolveToolChoice(options.toolChoice, tools !== undefined);
32
+ const reasoningEffort = resolveReasoningEffort(options.providerOptions);
33
+ const params = {
34
+ model: context.modelId,
35
+ messages,
36
+ ...(system !== undefined ? { system } : {}),
37
+ ...(tools !== undefined ? { tools } : {}),
38
+ ...(toolChoice !== undefined ? { tool_choice: toolChoice } : {}),
39
+ max_tokens: resolveMaxTokens(options.maxOutputTokens, context.maxOutput),
40
+ stream: true,
41
+ ...(options.temperature != null ? { temperature: options.temperature } : {}),
42
+ ...(options.topP != null ? { top_p: options.topP } : {}),
43
+ ...(options.topK != null ? { top_k: options.topK } : {}),
44
+ ...(reasoningEffort !== undefined ? { reasoning_effort: reasoningEffort } : {}),
45
+ };
46
+ const body = {
47
+ config: context.disguise.config,
48
+ memory: null,
49
+ taste: null,
50
+ skills: null,
51
+ permissionMode: context.disguise.permissionMode,
52
+ threadId: context.threadId,
53
+ params,
54
+ };
55
+ return { body, warnings };
56
+ }
57
+ /** `max_tokens` 缺省:官方 CLI 主对话路径常量 `tk = 64e3`(command-code 1.49.1
58
+ * `dist/cli.mjs`,`max_tokens: t.maxOutputTokens ?? tk`),与 capture/samples/generate.json
59
+ * 抓包(deepseek-v4-flash 实发 64000)互证(ADR 0002)。 */
60
+ const OFFICIAL_DEFAULT_MAX_TOKENS = 64_000;
61
+ /** 网关对 `params.max_tokens` 的 zod 校验硬上限:#42 冒烟 400 实证
62
+ * `Too big: expected number to be <=200000`;官方 CLI 缺省 64e3 永不触及,故线上未暴露。 */
63
+ const GATEWAY_MAX_TOKENS_CAP = 200_000;
64
+ /** `max_tokens` = min(调用方值 ?? 官方缺省 64e3, 级联 maxOutput, 网关墙 200000)
65
+ * (§1.2 / ADR 0002):缺省复刻官方 CLI(伪装口径——官方从不发 models.dev 理论值);
66
+ * 级联值降级为裁剪参考(models.dev 第三方视角值,非网关真值);墙防调用方显式
67
+ * 传大值撞 400。 */
68
+ function resolveMaxTokens(callerValue, maxOutput) {
69
+ return Math.min(callerValue ?? OFFICIAL_DEFAULT_MAX_TOKENS, maxOutput, GATEWAY_MAX_TOKENS_CAP);
70
+ }
71
+ /** `tool_choice` 四态映射;tools 非空且调用方未指定时显式发 `{type:"auto"}`
72
+ * (MAXeaglet 抓包验证的信封形状,不赌网关默认值);无 tools 则不发。 */
73
+ function resolveToolChoice(choice, hasTools) {
74
+ if (choice !== undefined) {
75
+ switch (choice.type) {
76
+ case "auto":
77
+ return { type: "auto" };
78
+ case "none":
79
+ return { type: "none" };
80
+ case "required":
81
+ return { type: "any" };
82
+ case "tool":
83
+ return { type: "tool", name: choice.toolName };
84
+ }
85
+ }
86
+ return hasTools ? { type: "auto" } : undefined;
87
+ }
88
+ /** `reasoning_effort` 仅变体被选中时发送;接收通道为 `providerOptions.reasoningEffort`
89
+ * ——OpenCode 把 variant 档位合并进 providerOptions 传入 LanguageModel(协议唯一路径)。
90
+ * 命名空间键随宿主而异(本插件为 commandcode-go,官方 CLI 抓包里出现过 deepseek /
91
+ * openaiCompatible 等),故按确定性优先序全命名空间扫描。 */
92
+ function resolveReasoningEffort(providerOptions) {
93
+ if (providerOptions === undefined)
94
+ return undefined;
95
+ const namespaces = Object.keys(providerOptions).sort((a, b) => {
96
+ if (a === PROVIDER_ID)
97
+ return -1;
98
+ if (b === PROVIDER_ID)
99
+ return 1;
100
+ return 0;
101
+ });
102
+ for (const namespace of namespaces) {
103
+ const value = providerOptions[namespace]?.["reasoningEffort"];
104
+ if (typeof value === "string" && value.length > 0)
105
+ return value;
106
+ }
107
+ return undefined;
108
+ }
109
+ /** §1.3:OpenCode 实证当前不传这些参数;真传来时不塞信封、不吞,warnings 上报 */
110
+ function warnUnsupportedCallParams(options, warnings) {
111
+ const unsupported = [
112
+ ["stopSequences", options.stopSequences],
113
+ ["presencePenalty", options.presencePenalty],
114
+ ["frequencyPenalty", options.frequencyPenalty],
115
+ ["seed", options.seed],
116
+ ["responseFormat", options.responseFormat],
117
+ ];
118
+ for (const [feature, value] of unsupported) {
119
+ if (value == null)
120
+ continue;
121
+ warnings.push({
122
+ type: "unsupported",
123
+ feature,
124
+ details: `CommandCode 网关信封不接收 ${feature},已按协议忽略`,
125
+ });
126
+ }
127
+ }
128
+ // ---------------------------------------------------------------------------
129
+ // 消息转换(§1.1 全表)
130
+ // ---------------------------------------------------------------------------
131
+ export function convertPrompt(prompt, warnings) {
132
+ const systemParts = [];
133
+ const messages = [];
134
+ for (const message of prompt) {
135
+ if (message.role === "system") {
136
+ if (message.content.length > 0)
137
+ systemParts.push(message.content);
138
+ continue;
139
+ }
140
+ if (message.role === "user") {
141
+ const content = [];
142
+ for (const part of message.content) {
143
+ if (part.type === "text") {
144
+ content.push({ type: "text", text: part.text });
145
+ continue;
146
+ }
147
+ const image = convertImageFilePart(part, warnings);
148
+ if (image !== null)
149
+ content.push(image);
150
+ }
151
+ if (content.length > 0)
152
+ messages.push({ role: "user", content });
153
+ continue;
154
+ }
155
+ if (message.role === "assistant") {
156
+ const assistantBlocks = [];
157
+ const toolResults = [];
158
+ for (const part of message.content) {
159
+ if (part.type === "text") {
160
+ assistantBlocks.push({ type: "text", text: part.text });
161
+ }
162
+ else if (part.type === "reasoning") {
163
+ assistantBlocks.push({ type: "reasoning", text: part.text });
164
+ }
165
+ else if (part.type === "tool-call") {
166
+ assistantBlocks.push({
167
+ type: "tool-call",
168
+ toolCallId: part.toolCallId,
169
+ toolName: part.toolName,
170
+ input: normalizeToolCallInput(part.input),
171
+ });
172
+ }
173
+ else if (part.type === "tool-result") {
174
+ toolResults.push(convertToolResult(part, warnings));
175
+ }
176
+ else {
177
+ warnUnsupportedPart(part.type, warnings);
178
+ }
179
+ }
180
+ if (assistantBlocks.length > 0)
181
+ messages.push({ role: "assistant", content: assistantBlocks });
182
+ if (toolResults.length > 0)
183
+ messages.push({ role: "tool", content: toolResults });
184
+ continue;
185
+ }
186
+ // role: "tool"
187
+ const blocks = [];
188
+ for (const part of message.content) {
189
+ if (part.type === "tool-result") {
190
+ blocks.push(convertToolResult(part, warnings));
191
+ }
192
+ else {
193
+ warnUnsupportedPart(part.type, warnings);
194
+ }
195
+ }
196
+ if (blocks.length > 0)
197
+ messages.push({ role: "tool", content: blocks });
198
+ }
199
+ return { system: systemParts.length > 0 ? systemParts.join("\n\n") : undefined, messages };
200
+ }
201
+ /** 图片 part → `data:<mediaType>;base64,...` data URL(§1.1 / §5 上游格式)。
202
+ * 二进制与 base64 串同步可转;`data:` URL 原样透传;URL 引用无法同步转成 base64
203
+ * data URL,按 §1.3 走 warnings 通道,不静默吞。 */
204
+ function convertImageFilePart(part, warnings) {
205
+ if (part.data instanceof Uint8Array) {
206
+ return { type: "image", image: `data:${part.mediaType};base64,${bytesToBase64(part.data)}` };
207
+ }
208
+ if (typeof part.data === "string") {
209
+ if (part.data.startsWith("data:"))
210
+ return { type: "image", image: part.data };
211
+ if (/^https?:\/\//i.test(part.data)) {
212
+ warnUnsupportedImageSource(warnings);
213
+ return null;
214
+ }
215
+ return { type: "image", image: `data:${part.mediaType};base64,${part.data}` };
216
+ }
217
+ warnUnsupportedImageSource(warnings);
218
+ return null;
219
+ }
220
+ function warnUnsupportedImageSource(warnings) {
221
+ warnings.push({
222
+ type: "unsupported",
223
+ feature: "image URL input",
224
+ details: "以 URL 引用的图片无法直接放入信封的 base64 data URL,已丢弃",
225
+ });
226
+ }
227
+ function bytesToBase64(bytes) {
228
+ let binary = "";
229
+ const chunkSize = 0x8000;
230
+ for (let offset = 0; offset < bytes.length; offset += chunkSize) {
231
+ binary += String.fromCharCode(...bytes.subarray(offset, offset + chunkSize));
232
+ }
233
+ return btoa(binary);
234
+ }
235
+ /** 信封 tool-call 的 `input` 为对象;宿主传 JSON 字符串时解析还原,解析失败保留原串 */
236
+ function normalizeToolCallInput(input) {
237
+ if (typeof input !== "string")
238
+ return input;
239
+ try {
240
+ return JSON.parse(input);
241
+ }
242
+ catch {
243
+ return input;
244
+ }
245
+ }
246
+ function convertToolResult(part, warnings) {
247
+ return { type: "tool-result", toolCallId: part.toolCallId, toolName: part.toolName, output: convertToolResultOutput(part.output, warnings) };
248
+ }
249
+ /** AI SDK 六种 tool 输出 → 信封二态(text / error-text)。
250
+ * `isError` 语义(error-text / error-json / execution-denied)一律 `error-text`;
251
+ * json 类输出序列化为文本进信封。 */
252
+ function convertToolResultOutput(output, warnings) {
253
+ switch (output.type) {
254
+ case "text":
255
+ return { type: "text", value: output.value };
256
+ case "json":
257
+ return { type: "text", value: safeStringify(output.value) };
258
+ case "error-text":
259
+ return { type: "error-text", value: output.value };
260
+ case "error-json":
261
+ return { type: "error-text", value: safeStringify(output.value) };
262
+ case "execution-denied":
263
+ return { type: "error-text", value: output.reason ?? "Execution denied" };
264
+ case "content": {
265
+ const hasNonText = output.value.some((block) => block.type !== "text");
266
+ if (hasNonText) {
267
+ warnings.push({
268
+ type: "unsupported",
269
+ feature: "tool result content blocks",
270
+ details: "信封 tool 结果仅支持文本,非文本块已丢弃",
271
+ });
272
+ }
273
+ return {
274
+ type: "text",
275
+ value: output.value
276
+ .filter((block) => block.type === "text")
277
+ .map((block) => block.text)
278
+ .join("\n"),
279
+ };
280
+ }
281
+ }
282
+ }
283
+ /** tools 定义 → Anthropic 风格 `{type:"function", name, description, input_schema}`;
284
+ * provider 工具无信封表达,warnings 上报后略去 */
285
+ function convertTools(tools, warnings) {
286
+ if (tools === undefined || tools.length === 0)
287
+ return undefined;
288
+ const converted = [];
289
+ for (const tool of tools) {
290
+ if (tool.type !== "function") {
291
+ warnings.push({
292
+ type: "unsupported",
293
+ feature: `${tool.type} tool "${tool.name}"`,
294
+ details: "信封 tools 仅支持 function 定义,该工具已略去",
295
+ });
296
+ continue;
297
+ }
298
+ converted.push({
299
+ type: "function",
300
+ name: tool.name,
301
+ ...(tool.description !== undefined ? { description: tool.description } : {}),
302
+ input_schema: canonicalizeJson(asRecord(tool.inputSchema) ?? {}),
303
+ });
304
+ }
305
+ converted.sort((left, right) => left.name.localeCompare(right.name));
306
+ return converted.length > 0 ? converted : undefined;
307
+ }
308
+ function warnUnsupportedPart(partType, warnings) {
309
+ warnings.push({
310
+ type: "unsupported",
311
+ feature: partType,
312
+ details: `信封消息不支持 ${partType} 内容块,已丢弃`,
313
+ });
314
+ }
315
+ function canonicalizeJson(value) {
316
+ if (Array.isArray(value))
317
+ return value.map(canonicalizeJson);
318
+ if (typeof value !== "object" || value === null)
319
+ return value;
320
+ return Object.fromEntries(Object.entries(value)
321
+ .sort(([left], [right]) => left.localeCompare(right))
322
+ .map(([key, item]) => [key, canonicalizeJson(item)]));
323
+ }
324
+ function safeStringify(value) {
325
+ try {
326
+ return JSON.stringify(value);
327
+ }
328
+ catch {
329
+ return String(value);
330
+ }
331
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 协议核心·错误映射(契约:docs/spec/protocol.md §3 十一行表)。
3
+ *
4
+ * 错误对象一律是 AI SDK `APICallError` 形态:透传/合成 statusCode、标注
5
+ * isRetryable(重试由 OpenCode 决定,provider 不自行重试)、message 含
6
+ * model id 与上游 message。宿主对 provider 错误的分类靠
7
+ * `APICallError.isInstance`——AI SDK 用全局 Symbol 标记跨副本识别
8
+ * (`Symbol.for("vercel.ai.error.AI_APICallError")`,provider 3.0.8 实测),
9
+ * 本模块零运行时依赖,只按同款机制在实例上打标记,宿主即可正确分类
10
+ * (测试用 devDep @ai-sdk/provider 的 isInstance 断言验证)。
11
+ *
12
+ * 错误信封统一按 `{"error":{"message":"..."}}` 解析,兜底 `parsed.message`
13
+ * (brent 实证),两处都拿不到时回退 HTTP 状态文本。
14
+ */
15
+ /** AI SDK APICallError 的公开字段(provider 3.0.8 形状,零依赖复刻) */
16
+ export interface ApiCallErrorFields {
17
+ /** 人类可读错误消息:必含 model id,尽量含上游 message(§3 总则) */
18
+ message: string;
19
+ url?: string;
20
+ requestBodyValues?: unknown;
21
+ statusCode?: number;
22
+ responseHeaders?: Record<string, string>;
23
+ responseBody?: string;
24
+ isRetryable: boolean;
25
+ /** 本插件附加:合成错误的推荐退避秒数(调用方/宿主可读) */
26
+ retryAfterSeconds?: number;
27
+ cause?: unknown;
28
+ }
29
+ /** 十一行表错误信封载体:AI SDK APICallError 形态(含跨包识别标记) */
30
+ export declare class CommandCodeApiCallError extends Error implements ApiCallErrorFields {
31
+ readonly url?: string;
32
+ readonly requestBodyValues?: unknown;
33
+ readonly statusCode?: number;
34
+ readonly responseHeaders?: Record<string, string>;
35
+ readonly responseBody?: string;
36
+ readonly isRetryable: boolean;
37
+ readonly retryAfterSeconds?: number;
38
+ /** 合成错误的语义码(如零输出/看门狗超时),透传错误时为 undefined */
39
+ readonly code?: string;
40
+ constructor(fields: ApiCallErrorFields & {
41
+ code?: string;
42
+ });
43
+ }
44
+ /** 十一行表行 7:零输出合成 429(retry-after 10s),防白跑一回合 */
45
+ export declare function zeroOutputError(modelId: string, url: string, requestBodyValues: unknown): CommandCodeApiCallError;
46
+ /** 看门狗到点(§4):上游 300s 无事件 = 失联,可重试(retry-after 5s) */
47
+ export declare function watchdogTimeoutError(modelId: string, url: string, requestBodyValues: unknown): CommandCodeApiCallError;
48
+ /** 行 11:响应前网络错误(DNS / 连接拒绝 / TLS),可重试 */
49
+ export declare function networkError(modelId: string, url: string, requestBodyValues: unknown, cause: unknown): CommandCodeApiCallError;
50
+ /**
51
+ * 流内错误(无 HTTP 状态可透传):可重试性由错误自身语义决定(行 8 截断可
52
+ * 重试;行 9 error 事件交 OpenCode 判断)。错误对象同为 APICallError 形态,
53
+ * 宿主 fromError 分类与 message/responseBody 匹配照常生效。
54
+ */
55
+ export declare function streamError(modelId: string, message: string, opts?: {
56
+ isRetryable?: boolean;
57
+ code?: string;
58
+ cause?: unknown;
59
+ }): CommandCodeApiCallError;
60
+ /**
61
+ * 流关闭但未收到 `finish-step`:无法确认回复完整(§3 行 8,jiesou 的
62
+ * STREAM_CLOSED)。APICallError 形态、可重试;`partialLine` 供排查。
63
+ * (ndjson.ts re-export 本类,流解析层错误统一由此构造。)
64
+ */
65
+ export declare class StreamTruncatedError extends CommandCodeApiCallError {
66
+ /** 早期解析层消费者读取的兼容别名(与 isRetryable 同值) */
67
+ readonly retriable = true;
68
+ /** EOF 时未完结的半行残片,并入错误供排查 */
69
+ readonly partialLine: string;
70
+ constructor(partialLine: string, modelId: string);
71
+ }
72
+ /**
73
+ * 十一行表行 1–6 的 HTTP 层映射:把非 2xx 响应翻译成 APICallError 形态。
74
+ * 401/403 按 body 是否含 MODEL_NOT_IN_PLAN 区分「模型不在套餐」与认证错误;
75
+ * 402 按配额窗口假设合成 retry-after 60s;429 透传上游 retry-after(缺省不合成);
76
+ * 400 命中上下文超限特征时明确报「上下文窗口超限」;5xx 透传状态码。
77
+ * 表外状态码:4xx 透传不可重试,5xx 可重试(真变更走规格修订,不在此发散)。
78
+ */
79
+ export declare function apiCallErrorFromHttpResponse(input: {
80
+ modelId: string;
81
+ url: string;
82
+ requestBodyValues: unknown;
83
+ status: number;
84
+ responseHeaders: Headers;
85
+ responseBody: string;
86
+ }): CommandCodeApiCallError;
87
+ /** 错误信封解析:`{"error":{"message":...}}` 优先,兜底 `parsed.message`,再兜底截断原文 */
88
+ export declare function extractErrorMessage(responseBody: string): string;
89
+ /**
90
+ * 上下文超限特征(400 body 文本判定):命中即行 5「上下文窗口超限」。
91
+ * 词表覆盖网关常见表达(context length / window / token limit / prompt 过长等),
92
+ * 从 jiesou/brent 的判据与宿主 opencode 的 isContextOverflow 词表归纳。
93
+ * 收敛到与「上下文容量」强相关的短语:宽泛的 413 类文案(request entity too
94
+ * large 等)不放进来——图片超限的 400 不该被误报成上下文超限(§5 提示用户
95
+ * 该缩图而非压缩上下文)。
96
+ */
97
+ export declare function isContextOverflowText(bodyText: string): boolean;