@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,11 @@
1
+ /**
2
+ * 协议核心内部共用的 JSON 结构小工具。上游 wire 数据一律经 `JSON.parse` 到达,
3
+ * 这里只做形状守卫,不做语义解释。
4
+ */
5
+ /** 宽松对象守卫:非 null 非 数组的对象才可按字段读取 */
6
+ export declare function asRecord(value: unknown): Record<string, unknown> | null;
7
+ /**
8
+ * 注入式 fetch 接缝类型(工厂 options.fetch 实例;不用 `typeof fetch`——
9
+ * bun 的 fetch 带 preconnect 静态属性,mock 实例难以满足其完整签名)。
10
+ */
11
+ export type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * 协议核心内部共用的 JSON 结构小工具。上游 wire 数据一律经 `JSON.parse` 到达,
3
+ * 这里只做形状守卫,不做语义解释。
4
+ */
5
+ /** 宽松对象守卫:非 null 非 数组的对象才可按字段读取 */
6
+ export function asRecord(value) {
7
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
8
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * 协议核心·响应方向:`/alpha/generate` 的 NDJSON 事件流 → AI SDK stream part
3
+ * (契约:docs/spec/protocol.md §2)。
4
+ *
5
+ * 严格 NDJSON:按行切分、逐行 `JSON.parse`;跳过空行与 `:` 注释行。不做 SSE 双兼容
6
+ * (不剥 `data:`、不认 `[DONE]`、不做 `\r\n` 归一——§6 否决项:上游格式真变更时
7
+ * 错误浮出、修改本项目适配,不预做兼容)。
8
+ *
9
+ * 事件消费全表:`start`/`start-step`(流开始,由 head 的 stream-start 承担)、
10
+ * text/reasoning/tool-input 三组块生命周期(`tool-input-*` 增量必做)、
11
+ * `tool-call`(字段名兼容 `input│args│arguments` 与 `toolCallId│id`)、
12
+ * `finish-step` 收据、`finish` 总账单、`response-metadata` 照收、
13
+ * `error` 不吞错、未知 type 静默忽略。
14
+ *
15
+ * 半行残片两结局(§2 / §3 行 8):EOF 时已收 `finish-step` 则忽略残片并补发收据
16
+ * finish part;未收到则并入截断错误(errors.ts 的 StreamTruncatedError,APICallError
17
+ * 形态、可重试,本模块 re-export 保持消费面)。
18
+ */
19
+ import type { LanguageModelV3FinishReason, LanguageModelV3StreamPart, LanguageModelV3Usage, SharedV3Warning } from "@ai-sdk/provider";
20
+ export { StreamTruncatedError } from "./errors.js";
21
+ export interface NdjsonStreamContext {
22
+ /** wire id——错误 message 需含 model id(§3) */
23
+ modelId: string;
24
+ /** 调用 warnings(buildEnvelope 产出),随首个 stream-start part 上报 */
25
+ warnings: SharedV3Warning[];
26
+ }
27
+ export interface NdjsonEventStream {
28
+ /** 流的第一个 part:`stream-start`(AI SDK 约定 warnings 随首个 part 交给调用方)。
29
+ * 仅首次调用产出,之后返回空。 */
30
+ head(): LanguageModelV3StreamPart[];
31
+ /** 喂一段响应体字节(UTF-8 解码可跨 chunk、按 `\n` 切行、逐行翻译) */
32
+ push(chunk: Uint8Array): LanguageModelV3StreamPart[];
33
+ /** EOF:已收 finish-step → 补发收据 finish part;未收 → 截断错误 part */
34
+ end(): LanguageModelV3StreamPart[];
35
+ }
36
+ export declare function createNdjsonEventStream(context: NdjsonStreamContext): NdjsonEventStream;
37
+ /** usage 双 case 兼容(camelCase / snake_case,brent 实证两种都出现过)。
38
+ * AI SDK `inputTokens.noCache` 取 `noCacheTokens` 优先、否则 total − cacheRead
39
+ * (jiesou 规则);cacheRead / reasoning 映射到对应字段;原始对象进 `raw` 保真。 */
40
+ export declare function normalizeUsage(raw: unknown): LanguageModelV3Usage;
41
+ export declare function normalizeFinishReason(raw: string | undefined): LanguageModelV3FinishReason;
@@ -0,0 +1,321 @@
1
+ /**
2
+ * 协议核心·响应方向:`/alpha/generate` 的 NDJSON 事件流 → AI SDK stream part
3
+ * (契约:docs/spec/protocol.md §2)。
4
+ *
5
+ * 严格 NDJSON:按行切分、逐行 `JSON.parse`;跳过空行与 `:` 注释行。不做 SSE 双兼容
6
+ * (不剥 `data:`、不认 `[DONE]`、不做 `\r\n` 归一——§6 否决项:上游格式真变更时
7
+ * 错误浮出、修改本项目适配,不预做兼容)。
8
+ *
9
+ * 事件消费全表:`start`/`start-step`(流开始,由 head 的 stream-start 承担)、
10
+ * text/reasoning/tool-input 三组块生命周期(`tool-input-*` 增量必做)、
11
+ * `tool-call`(字段名兼容 `input│args│arguments` 与 `toolCallId│id`)、
12
+ * `finish-step` 收据、`finish` 总账单、`response-metadata` 照收、
13
+ * `error` 不吞错、未知 type 静默忽略。
14
+ *
15
+ * 半行残片两结局(§2 / §3 行 8):EOF 时已收 `finish-step` 则忽略残片并补发收据
16
+ * finish part;未收到则并入截断错误(errors.ts 的 StreamTruncatedError,APICallError
17
+ * 形态、可重试,本模块 re-export 保持消费面)。
18
+ */
19
+ import { asRecord } from "./json.js";
20
+ import { streamError, StreamTruncatedError } from "./errors.js";
21
+ export { StreamTruncatedError } from "./errors.js";
22
+ export function createNdjsonEventStream(context) {
23
+ const decoder = new TextDecoder();
24
+ let buffer = "";
25
+ let receipt = null;
26
+ let headEmitted = false;
27
+ let finishEmitted = false;
28
+ let abortedByError = false;
29
+ function errorPart(error) {
30
+ return { type: "error", error };
31
+ }
32
+ /**
33
+ * §3 总则:流内错误一律 APICallError 形态。`streamError` 工厂负责打形态标记
34
+ * 与拼 model id 前缀,本层只供错误正文。
35
+ */
36
+ function streamErrorPart(message, opts) {
37
+ return errorPart(streamError(context.modelId, message, opts));
38
+ }
39
+ function translateLine(line) {
40
+ if (line === "" || line.startsWith(":"))
41
+ return [];
42
+ let parsed;
43
+ try {
44
+ parsed = JSON.parse(line);
45
+ }
46
+ catch (error) {
47
+ abortedByError = true;
48
+ const reason = error instanceof Error ? error.message : String(error);
49
+ return [streamErrorPart(`上游 NDJSON 行无法解析:${clip(line, 200)}(${reason})`)];
50
+ }
51
+ const record = asRecord(parsed);
52
+ const type = record === null ? undefined : str(record["type"]);
53
+ if (record === null || type === undefined) {
54
+ // 无事件形状的 JSON(数组、标量、缺 type 的对象):严格 NDJSON 下视为行级损坏
55
+ abortedByError = true;
56
+ return [streamErrorPart(`上游 NDJSON 行不是事件对象:${clip(line, 200)}`)];
57
+ }
58
+ switch (type) {
59
+ // 流开始由 head() 的 stream-start 承担(AI SDK 契约:warnings 必须随首个 part)
60
+ case "start":
61
+ case "start-step":
62
+ return [];
63
+ case "text-start":
64
+ return [{ type: "text-start", id: str(record["id"]) ?? "", ...providerMeta(record) }];
65
+ case "text-delta":
66
+ return [{ type: "text-delta", id: str(record["id"]) ?? "", delta: str(record["text"]) ?? "", ...providerMeta(record) }];
67
+ case "text-end":
68
+ return [{ type: "text-end", id: str(record["id"]) ?? "", ...providerMeta(record) }];
69
+ case "reasoning-start":
70
+ return [{ type: "reasoning-start", id: str(record["id"]) ?? "", ...providerMeta(record) }];
71
+ case "reasoning-delta":
72
+ return [{ type: "reasoning-delta", id: str(record["id"]) ?? "", delta: str(record["text"]) ?? "", ...providerMeta(record) }];
73
+ case "reasoning-end":
74
+ return [{ type: "reasoning-end", id: str(record["id"]) ?? "", ...providerMeta(record) }];
75
+ case "tool-input-start":
76
+ return [
77
+ {
78
+ type: "tool-input-start",
79
+ id: str(record["id"]) ?? "",
80
+ toolName: str(record["toolName"]) ?? str(record["name"]) ?? "",
81
+ ...providerMeta(record),
82
+ },
83
+ ];
84
+ case "tool-input-delta":
85
+ return [
86
+ {
87
+ type: "tool-input-delta",
88
+ id: str(record["id"]) ?? "",
89
+ delta: str(record["delta"]) ?? str(record["text"]) ?? "",
90
+ ...providerMeta(record),
91
+ },
92
+ ];
93
+ case "tool-input-end":
94
+ return [{ type: "tool-input-end", id: str(record["id"]) ?? "", ...providerMeta(record) }];
95
+ case "tool-call": {
96
+ // 字段名兼容(§2):input│args│arguments 与 toolCallId│id
97
+ const input = record["input"] ?? record["args"] ?? record["arguments"];
98
+ return [
99
+ {
100
+ type: "tool-call",
101
+ toolCallId: str(record["toolCallId"]) ?? str(record["id"]) ?? "",
102
+ toolName: str(record["toolName"]) ?? str(record["name"]) ?? "",
103
+ input: toolCallInputToString(input),
104
+ ...providerMeta(record),
105
+ },
106
+ ];
107
+ }
108
+ case "finish-step": {
109
+ receipt = {
110
+ finishReason: str(record["finishReason"]) ?? str(record["rawFinishReason"]),
111
+ usage: record["usage"],
112
+ providerMetadata: providerMetaOf(record),
113
+ };
114
+ const parts = [];
115
+ const responseMetadata = responseMetadataPart(record["response"]);
116
+ if (responseMetadata !== null)
117
+ parts.push(responseMetadata);
118
+ return parts;
119
+ }
120
+ case "finish": {
121
+ if (finishEmitted)
122
+ return [];
123
+ finishEmitted = true;
124
+ const usage = normalizeUsage(record["totalUsage"] ?? receipt?.usage);
125
+ const finishReason = normalizeFinishReason(str(record["finishReason"]) ?? str(record["rawFinishReason"]) ?? receipt?.finishReason);
126
+ const providerMetadata = providerMetaOf(record) ?? receipt?.providerMetadata;
127
+ return [{ type: "finish", usage, finishReason, ...(providerMetadata !== undefined ? { providerMetadata } : {}) }];
128
+ }
129
+ case "response-metadata": {
130
+ const part = responseMetadataPart(record);
131
+ return part === null ? [] : [part];
132
+ }
133
+ case "error": {
134
+ // §3 行 9:流中 error 事件不吞错,已收 delta 保留,错误浮出后终止翻译
135
+ abortedByError = true;
136
+ return [streamErrorPart(extractErrorMessage(record))];
137
+ }
138
+ default:
139
+ // 未知 type 静默忽略——消费端向前兼容基本功(§2)
140
+ return [];
141
+ }
142
+ }
143
+ function extractErrorMessage(record) {
144
+ const error = record["error"];
145
+ const errorRecord = asRecord(error);
146
+ if (errorRecord !== null && typeof errorRecord["message"] === "string")
147
+ return errorRecord["message"];
148
+ if (typeof error === "string" && error)
149
+ return error;
150
+ const message = record["message"];
151
+ if (typeof message === "string" && message)
152
+ return message;
153
+ return "上游流中报告了 error 事件,但未携带可读信息";
154
+ }
155
+ return {
156
+ head() {
157
+ if (headEmitted)
158
+ return [];
159
+ headEmitted = true;
160
+ return [{ type: "stream-start", warnings: context.warnings }];
161
+ },
162
+ push(chunk) {
163
+ if (abortedByError)
164
+ return [];
165
+ buffer += decoder.decode(chunk, { stream: true });
166
+ const parts = [];
167
+ let newline;
168
+ while ((newline = buffer.indexOf("\n")) !== -1) {
169
+ const line = buffer.slice(0, newline);
170
+ buffer = buffer.slice(newline + 1);
171
+ parts.push(...translateLine(line));
172
+ if (abortedByError)
173
+ break;
174
+ }
175
+ return parts;
176
+ },
177
+ end() {
178
+ if (abortedByError)
179
+ return [];
180
+ buffer += decoder.decode();
181
+ const partial = /\S/.test(buffer) ? buffer : "";
182
+ buffer = "";
183
+ if (receipt === null)
184
+ return [errorPart(new StreamTruncatedError(partial, context.modelId))];
185
+ // 已收 finish-step:半行残片按规格忽略,流视为完整收尾,补发收据 finish part
186
+ if (finishEmitted)
187
+ return [];
188
+ finishEmitted = true;
189
+ return [finishPartFromReceipt(receipt)];
190
+ },
191
+ };
192
+ }
193
+ function finishPartFromReceipt(receipt) {
194
+ const providerMetadata = receipt.providerMetadata;
195
+ return {
196
+ type: "finish",
197
+ usage: normalizeUsage(receipt.usage),
198
+ finishReason: normalizeFinishReason(receipt.finishReason),
199
+ ...(providerMetadata !== undefined ? { providerMetadata } : {}),
200
+ };
201
+ }
202
+ // ---------------------------------------------------------------------------
203
+ // usage 与 finishReason 归一
204
+ // ---------------------------------------------------------------------------
205
+ /** usage 双 case 兼容(camelCase / snake_case,brent 实证两种都出现过)。
206
+ * AI SDK `inputTokens.noCache` 取 `noCacheTokens` 优先、否则 total − cacheRead
207
+ * (jiesou 规则);cacheRead / reasoning 映射到对应字段;原始对象进 `raw` 保真。 */
208
+ export function normalizeUsage(raw) {
209
+ const record = asRecord(raw);
210
+ if (record === null)
211
+ return zeroUsage();
212
+ const num = (...values) => {
213
+ for (const value of values) {
214
+ if (typeof value === "number" && Number.isFinite(value))
215
+ return value;
216
+ }
217
+ return undefined;
218
+ };
219
+ // 每个槽位按「camelCase 细节 → snake_case 细节 → 顶层 camelCase → 顶层 snake_case」取第一个数值
220
+ const pick = (source, ...keys) => {
221
+ if (source === null)
222
+ return undefined;
223
+ for (const key of keys) {
224
+ const value = source[key];
225
+ if (value !== undefined)
226
+ return value;
227
+ }
228
+ return undefined;
229
+ };
230
+ const inputDetails = asRecord(record["inputTokenDetails"]) ?? asRecord(record["input_token_details"]) ?? asRecord(record["prompt_tokens_details"]);
231
+ const outputDetails = asRecord(record["outputTokenDetails"]) ?? asRecord(record["output_token_details"]) ?? asRecord(record["completion_tokens_details"]);
232
+ const inputTotal = num(pick(record, "inputTokens", "input_tokens", "prompt_tokens", "totalTokens", "total_tokens"));
233
+ const cacheRead = num(pick(inputDetails, "cacheReadTokens", "cache_read_tokens"), pick(record, "cachedInputTokens", "cached_input_tokens"), pick(inputDetails, "cached_tokens"), record["prompt_cache_hit_tokens"]);
234
+ const noCacheExplicit = num(pick(inputDetails, "noCacheTokens", "no_cache_tokens"), record["prompt_cache_miss_tokens"]);
235
+ return {
236
+ inputTokens: {
237
+ total: inputTotal,
238
+ noCache: noCacheExplicit ?? (inputTotal !== undefined ? inputTotal - (cacheRead ?? 0) : undefined),
239
+ cacheRead,
240
+ cacheWrite: num(pick(inputDetails, "cacheWriteTokens", "cache_write_tokens")),
241
+ },
242
+ outputTokens: {
243
+ total: num(pick(record, "outputTokens", "output_tokens", "completion_tokens")),
244
+ text: num(pick(outputDetails, "textTokens", "text_tokens")),
245
+ reasoning: num(pick(outputDetails, "reasoningTokens", "reasoning_tokens"), pick(record, "reasoningTokens", "reasoning_tokens")),
246
+ },
247
+ raw: record,
248
+ };
249
+ }
250
+ function zeroUsage() {
251
+ return {
252
+ inputTokens: { total: undefined, noCache: undefined, cacheRead: undefined, cacheWrite: undefined },
253
+ outputTokens: { total: undefined, text: undefined, reasoning: undefined },
254
+ };
255
+ }
256
+ /** finishReason 词表归一(§2):`stop│end_turn`、`tool_calls│tool-calls`、
257
+ * `length│max_tokens│max_output_tokens`;表外值归 other、原文进 raw 不丢失 */
258
+ const FINISH_REASON_VOCABULARY = {
259
+ stop: "stop",
260
+ end_turn: "stop",
261
+ tool_calls: "tool-calls",
262
+ "tool-calls": "tool-calls",
263
+ length: "length",
264
+ max_tokens: "length",
265
+ max_output_tokens: "length",
266
+ };
267
+ export function normalizeFinishReason(raw) {
268
+ const unified = (raw !== undefined ? FINISH_REASON_VOCABULARY[raw] : undefined) ?? "other";
269
+ return { unified, raw };
270
+ }
271
+ // ---------------------------------------------------------------------------
272
+ // 小工具
273
+ // ---------------------------------------------------------------------------
274
+ /** 信封 tool-call 的 `input`(AI SDK 要求 JSON 字符串):对象序列化、字符串原样 */
275
+ function toolCallInputToString(input) {
276
+ if (input == null)
277
+ return "{}";
278
+ if (typeof input === "string")
279
+ return input;
280
+ try {
281
+ return JSON.stringify(input);
282
+ }
283
+ catch {
284
+ return "{}";
285
+ }
286
+ }
287
+ function responseMetadataPart(value) {
288
+ const record = asRecord(value);
289
+ if (record === null)
290
+ return null;
291
+ const id = str(record["id"]);
292
+ const timestamp = toDate(record["timestamp"]);
293
+ const modelId = str(record["modelId"]);
294
+ if (id === undefined && timestamp === undefined && modelId === undefined)
295
+ return null;
296
+ return {
297
+ type: "response-metadata",
298
+ ...(id !== undefined ? { id } : {}),
299
+ ...(timestamp !== undefined ? { timestamp } : {}),
300
+ ...(modelId !== undefined ? { modelId } : {}),
301
+ };
302
+ }
303
+ function providerMetaOf(record) {
304
+ return asRecord(record["providerMetadata"]) !== null ? record["providerMetadata"] : undefined;
305
+ }
306
+ function providerMeta(record) {
307
+ const providerMetadata = providerMetaOf(record);
308
+ return providerMetadata !== undefined ? { providerMetadata } : {};
309
+ }
310
+ function toDate(value) {
311
+ if (typeof value !== "string")
312
+ return undefined;
313
+ const date = new Date(value);
314
+ return Number.isNaN(date.getTime()) ? undefined : date;
315
+ }
316
+ function str(value) {
317
+ return typeof value === "string" ? value : undefined;
318
+ }
319
+ function clip(text, max) {
320
+ return text.length > max ? `${text.slice(0, max)}…` : text;
321
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * 协议核心·超时与取消(契约:docs/spec/protocol.md §4)。
3
+ *
4
+ * - 300 秒空闲看门狗(常量,暂不配置化):持续未收到任何新事件即判定上游
5
+ * 失联 → abort 上游 + 抛可重试错误(retry-after 5s,见 errors.ts
6
+ * `watchdogTimeoutError`)。连接阶段计入空闲(「连首字节都没等到」本身
7
+ * 就是最大的空闲),因此看门狗从 fetch 发起前启动。
8
+ * - 无总超时——健康长回合不被固定时限误杀,只有真失联才断开。本模块只有
9
+ * 「空闲计时」,不累计任何总时长。
10
+ * - 取消合并由调用方用 `AbortSignal.any([watchdog.signal, 调用方 abortSignal])`
11
+ * 完成(本文件导出 `mergeCancellation` 辅助,谁先响都生效)。
12
+ *
13
+ * 实现用 setTimeout + AbortController 而非 `AbortSignal.timeout`:看门狗要
14
+ * 「每次收到事件重置计时」,timeout 信号是一次性的、无法重置。
15
+ */
16
+ /** 空闲看门狗时长:300 秒(§4 常量,暂不配置化) */
17
+ export declare const IDLE_WATCHDOG_TIMEOUT_MS = 300000;
18
+ export interface IdleWatchdog {
19
+ /** abort 时置位;调用方经 `AbortSignal.any` 与自己的取消信号合并后控制上游 fetch */
20
+ readonly signal: AbortSignal;
21
+ /** 是否已到点触发(区分「看门狗断开」与「调用方取消」两条 abort 路径) */
22
+ readonly fired: boolean;
23
+ /** 每次收到上游新事件调用:重置空闲计时 */
24
+ reset(): void;
25
+ /** 流正常收尾后调用:停止计时并释放定时器(避免残留 timer 悬挂进程) */
26
+ clear(): void;
27
+ }
28
+ /** 到点触发时 abort 的 reason 标识:fetch 侧据此可区分失联断开 */
29
+ export declare const WATCHDOG_ABORT_REASON: unique symbol;
30
+ /** 创建空闲看门狗;创建即开始计时(连接阶段计入空闲,§4)。时长即常量。 */
31
+ export declare function createIdleWatchdog(): IdleWatchdog;
32
+ /**
33
+ * 取消合并:`AbortSignal.any` 的封装——看门狗 signal 与调用方 abortSignal
34
+ * 谁先 abort 谁生效(§4 取消合并条款;两路信号的行为差异见执行层:
35
+ * 看门狗到点 → 合成可重试错误,调用方取消 → 传播 abort 不合成错误)。
36
+ */
37
+ export declare function mergeCancellation(watchdog: IdleWatchdog, callerSignal?: AbortSignal): AbortSignal;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * 协议核心·超时与取消(契约:docs/spec/protocol.md §4)。
3
+ *
4
+ * - 300 秒空闲看门狗(常量,暂不配置化):持续未收到任何新事件即判定上游
5
+ * 失联 → abort 上游 + 抛可重试错误(retry-after 5s,见 errors.ts
6
+ * `watchdogTimeoutError`)。连接阶段计入空闲(「连首字节都没等到」本身
7
+ * 就是最大的空闲),因此看门狗从 fetch 发起前启动。
8
+ * - 无总超时——健康长回合不被固定时限误杀,只有真失联才断开。本模块只有
9
+ * 「空闲计时」,不累计任何总时长。
10
+ * - 取消合并由调用方用 `AbortSignal.any([watchdog.signal, 调用方 abortSignal])`
11
+ * 完成(本文件导出 `mergeCancellation` 辅助,谁先响都生效)。
12
+ *
13
+ * 实现用 setTimeout + AbortController 而非 `AbortSignal.timeout`:看门狗要
14
+ * 「每次收到事件重置计时」,timeout 信号是一次性的、无法重置。
15
+ */
16
+ /** 空闲看门狗时长:300 秒(§4 常量,暂不配置化) */
17
+ export const IDLE_WATCHDOG_TIMEOUT_MS = 300_000;
18
+ /** 到点触发时 abort 的 reason 标识:fetch 侧据此可区分失联断开 */
19
+ export const WATCHDOG_ABORT_REASON = Symbol("commandcode.idle-watchdog");
20
+ /** 创建空闲看门狗;创建即开始计时(连接阶段计入空闲,§4)。时长即常量。 */
21
+ export function createIdleWatchdog() {
22
+ const controller = new AbortController();
23
+ let timer;
24
+ let fired = false;
25
+ function arm() {
26
+ clear();
27
+ timer = setTimeout(() => {
28
+ fired = true;
29
+ controller.abort(WATCHDOG_ABORT_REASON);
30
+ }, IDLE_WATCHDOG_TIMEOUT_MS);
31
+ }
32
+ function clear() {
33
+ if (timer !== undefined) {
34
+ clearTimeout(timer);
35
+ timer = undefined;
36
+ }
37
+ }
38
+ arm();
39
+ return {
40
+ signal: controller.signal,
41
+ get fired() {
42
+ return fired;
43
+ },
44
+ reset() {
45
+ if (fired)
46
+ return;
47
+ arm();
48
+ },
49
+ clear() {
50
+ clear();
51
+ },
52
+ };
53
+ }
54
+ /**
55
+ * 取消合并:`AbortSignal.any` 的封装——看门狗 signal 与调用方 abortSignal
56
+ * 谁先 abort 谁生效(§4 取消合并条款;两路信号的行为差异见执行层:
57
+ * 看门狗到点 → 合成可重试错误,调用方取消 → 传播 abort 不合成错误)。
58
+ */
59
+ export function mergeCancellation(watchdog, callerSignal) {
60
+ return callerSignal === undefined ? watchdog.signal : AbortSignal.any([watchdog.signal, callerSignal]);
61
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * 工厂装配(#35 弹道合拢点):协议核心(执行内核 generate.ts)× 伪装模块
3
+ * (会话身份 / config 块 / 版本头 / 伪装头)× 模型管线(级联 limit 数据)组装成
4
+ * 包导出的 `createCommandCode({name, apiKey, headers, fetch})` 工厂,内部实现
5
+ * LanguageModelV3(契约形状:docs/research/v2-provider-contract.md §2 实测——
6
+ * `finishReason` 为 `{unified, raw}` 对象、`usage` 嵌套结构、上游恒为流式 NDJSON、
7
+ * `doGenerate` = `doStream` 聚合)。
8
+ *
9
+ * 工厂签名四键无 body;宿主按「模块第一个 `create*` 前缀导出」判据发现(v1/v2
10
+ * 共用),返回 `{ languageModel(modelID) → LanguageModelV3 }`。模型 reference 按
11
+ * 首个 `/` 切分 provider 与 wire id——wire id 自身可含 `/`(如
12
+ * `deepseek/deepseek-v4-flash`),故仅 provider 段与本插件同名时才剥前缀,无前缀
13
+ * reference 原样透传。
14
+ *
15
+ * doStream 主路径:预请求惰性检查(到点后台发射,主请求零等待)→ 会话身份派生
16
+ * (从调用 headers 的 OpenCode 会话头确定性派生,缺失落 per-key 回退轮换)→ 图片
17
+ * 预处理(模态过滤 + 外链下载)→ config 块与版本头并行就绪 → 信封构造(伪装字段
18
+ * 填充点)→ 伪装头组装(`threadId` 与 `x-session-id` 同值)→ 300s 看门狗执行内核。
19
+ * HTTP 层错误(非 2xx / 响应前网络错误 / 看门狗连接期到点)在 doStream await 时
20
+ * 抛出(AI SDK 惯例,宿主 catch 分类);流内错误(截断 / error 事件 / 零输出 /
21
+ * 流中看门狗到点)以 error part 浮现(protocol.md §3)。
22
+ *
23
+ * 运行时共享:宿主每次模型请求都会重新调用工厂(v2 实测两遍/请求),预请求节奏
24
+ * (D2 每进程一次)与模型管线刷新节奏(model-pipeline.md §4 TTL)必须跨工厂调用
25
+ * 存活——伪装状态与管线为模块级单例;宿主逐次注入的 fetch / 自定义头 / logger 经
26
+ * 接缝重指向最新值;key 相关状态(回退会话、预请求退避)由 DisguiseState 内部
27
+ * per-key Map 承载。调用 headers 只作会话派生种子、不透传上游(`X-Session-Id` 等
28
+ * 是宿主内部头,非 CLI 指纹);工厂 options.headers(用户显式配置)透传且被伪装
29
+ * 键覆盖防冲突。
30
+ */
31
+ import type { LanguageModelV3 } from "@ai-sdk/provider";
32
+ import { type DisguiseLogger } from "../disguise/logger.js";
33
+ import { type CascadeResult } from "../models/cascade.js";
34
+ import type { FetchLike } from "../protocol/json.js";
35
+ /**
36
+ * 工厂入参:宿主调工厂时注入的 options(v2-provider-contract.md §1 实测形状)。
37
+ * `fetch` 是宿主包装过的实例(超时与 chunk 处理由宿主侧完成),全部出网路径——
38
+ * generate、models、产物 URL、版本查询、预请求——统一经它注入(testing.md §2)。
39
+ */
40
+ export interface CommandCodeFactoryOptions {
41
+ /** 宿主传入的 provider id;本插件四处同名常量(PROVIDER_ID),不用此值 */
42
+ readonly name?: string;
43
+ readonly apiKey?: string;
44
+ /** 宿主/用户显式配置的 provider 级请求头,逐请求透传(伪装键优先,防冲突覆盖) */
45
+ readonly headers?: Record<string, string>;
46
+ /** 出网接缝(json.ts FetchLike 形状,bun fetch 带 preconnect 静态属性、mock 实例难以满足其完整签名);缺省 globalThis.fetch */
47
+ readonly fetch?: FetchLike;
48
+ /**
49
+ * modelsUrls 覆盖通道原样值(model-pipeline.md §1.3):v1 `options.modelsUrls` /
50
+ * v2 `settings.modelsUrls` 经宿主透传后在工厂 options 中以顶层键出现。构造时作
51
+ * 初值;其后的调用经管线 rebindModelsUrls 重绑定(v2 的 config settings 首次
52
+ * 工厂调用才可见,#36)。
53
+ */
54
+ readonly modelsUrls?: unknown;
55
+ /**
56
+ * 注入式 logger(disguise.md §7 官方接缝;通道结论以 logger.ts 头注为准)。
57
+ * 逐次工厂调用重指向最新注入值。
58
+ */
59
+ readonly logger?: DisguiseLogger;
60
+ }
61
+ /** 工厂返回形状(v2-provider-contract.md §1:宿主经 languageModel(modelID) 取模型实例) */
62
+ export interface CommandCodeProvider {
63
+ languageModel(modelID: string): LanguageModelV3;
64
+ }
65
+ /** 运行时构造参数:全部仅首次构造生效(幂等构造,后续调用忽略);fetch/headers/
66
+ * logger 构造后仍有逐次调用重指向的活接缝,modelsUrls 没有(管线构造时解析一次) */
67
+ export interface RuntimeInit {
68
+ /** modelsUrls 的 config 通道原样值(v2 = settings.modelsUrls,glue 从 transform 捕获) */
69
+ readonly modelsUrls?: unknown;
70
+ readonly fetch?: FetchLike;
71
+ readonly headers?: Record<string, string>;
72
+ readonly logger?: DisguiseLogger;
73
+ /** 模型数据变更回调(v2 glue 接 catalog.reload());不受幂等构造限制,任意时刻可重设 */
74
+ readonly onModelDataChange?: (cascade: CascadeResult) => void;
75
+ }
76
+ /**
77
+ * v2 glue(#36)的运行时入口:幂等构造 + 变更回调注册。setup 语义下 modelsUrls
78
+ * 的 config 通道(settings.modelsUrls)由 glue 在 transform 内捕获后传入;工厂侧
79
+ * 宿主透传值只在工厂先行构造(独立调用 / v1 形态)时作构造初值。
80
+ */
81
+ export declare function ensureProviderRuntime(init: RuntimeInit): void;
82
+ /**
83
+ * v1 glue(#37)的运行时入口:v1 启动协商——幂等构造(不预启动)+ 启动拉取一次
84
+ *(15s 总预算、失败用快照、此后无后台刷新,model-pipeline.md §4)。v1 config hook
85
+ * 在注入模型清单前 await 本函数;运行时已被先行构造时(v2 setup / 工厂独立调用),
86
+ * initializeOnce 的幂等守卫使本调用退化为「返回当前级联」。
87
+ */
88
+ export declare function ensureV1ProviderRuntime(init: RuntimeInit): Promise<CascadeResult>;
89
+ /**
90
+ * 当前级联(v2 glue 的 transform 回放数据源):运行时已构造时读管线实时值(读时
91
+ * 惰性检查 TTL,reload 回放顺带充当显式到期触发点);未构造时为纯快照层——启动
92
+ * 零阻塞的注册序:transform 先注册快照,后台拉取的变更经 reload 回放本函数更新。
93
+ */
94
+ export declare function latestCascade(): CascadeResult;
95
+ export declare function createCommandCode(options: CommandCodeFactoryOptions): CommandCodeProvider;