vanspod 0.0.0-stage → 0.1.2

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 (79) hide show
  1. package/README.md +74 -2
  2. package/bin/register.cjs +11 -0
  3. package/bin/vanspod.cjs +2 -0
  4. package/dist/adapterSettings.js +37 -0
  5. package/dist/adapters/chat-output.js +69 -0
  6. package/dist/adapters/chat-stream.js +44 -0
  7. package/dist/adapters/chat.js +63 -0
  8. package/dist/adapters/claude-reasoning.js +118 -0
  9. package/dist/adapters/claude-request.js +190 -0
  10. package/dist/adapters/claude-response.js +79 -0
  11. package/dist/adapters/index.js +19 -0
  12. package/dist/adapters/messages-stream.js +126 -0
  13. package/dist/adapters/messages.js +21 -0
  14. package/dist/adapters/responses-stream.js +70 -0
  15. package/dist/adapters/responses.js +28 -0
  16. package/dist/adapters/stream.js +99 -0
  17. package/dist/adapters/transport.js +26 -0
  18. package/dist/adapters/types.js +35 -0
  19. package/dist/appearance.js +10 -0
  20. package/dist/classroomApp.js +1814 -0
  21. package/dist/classroomUpdate.js +155 -0
  22. package/dist/claudeWebSearch.js +248 -0
  23. package/dist/clientDetection.js +35 -0
  24. package/dist/clientIcons.js +33 -0
  25. package/dist/clientRouteRuntime.js +149 -0
  26. package/dist/clientRoutes.js +118 -0
  27. package/dist/clientSettingsBackup.js +110 -0
  28. package/dist/clientTraffic.js +26 -0
  29. package/dist/clipboard.js +32 -0
  30. package/dist/codexWebSearch.js +80 -0
  31. package/dist/codexWire/apply-patch-envelope.js +139 -0
  32. package/dist/codexWire/chat-options.js +51 -0
  33. package/dist/codexWire/chat-tools.js +80 -0
  34. package/dist/codexWire/freeform-wrapper-scan.js +326 -0
  35. package/dist/codexWire/progressive-freeform-input.js +133 -0
  36. package/dist/codexWire/request.js +73 -0
  37. package/dist/codexWire/responses-tools.js +88 -0
  38. package/dist/codexWire/tool-arguments.js +39 -0
  39. package/dist/codexWire/tool-guidance.js +77 -0
  40. package/dist/commandRunner.js +100 -0
  41. package/dist/connectionMascot.js +235 -0
  42. package/dist/courseCatalog.js +131 -0
  43. package/dist/desktop.js +25 -0
  44. package/dist/entry.js +17 -0
  45. package/dist/environmentIcons.js +11 -0
  46. package/dist/exitGuard.js +17 -0
  47. package/dist/folderPicker.js +35 -0
  48. package/dist/installProgress.js +171 -0
  49. package/dist/installations.js +72 -0
  50. package/dist/instance.js +160 -0
  51. package/dist/launcher.js +155 -0
  52. package/dist/legacyMigration.js +87 -0
  53. package/dist/lifecyclePage.js +41 -0
  54. package/dist/modelProxy.js +105 -0
  55. package/dist/modelStatus.js +42 -0
  56. package/dist/page.js +1016 -0
  57. package/dist/pagePrototype.js +528 -0
  58. package/dist/platformStorage.js +91 -0
  59. package/dist/removal.js +41 -0
  60. package/dist/responsesChat.js +835 -0
  61. package/dist/routeFiles.js +646 -0
  62. package/dist/routerSessionModels.js +32 -0
  63. package/dist/runtimeOptions.js +14 -0
  64. package/dist/secureStorage.js +63 -0
  65. package/dist/settingsActions.js +100 -0
  66. package/dist/start.js +822 -0
  67. package/dist/storageErrors.js +17 -0
  68. package/dist/storageLocation.js +58 -0
  69. package/dist/version.js +4 -0
  70. package/dist/vsCodeRouteSettings.js +208 -0
  71. package/dist/webSearch.js +56 -0
  72. package/dist/windowsSecureStorage.js +163 -0
  73. package/media/brand-icon.ico +0 -0
  74. package/media/brand-logo.png +0 -0
  75. package/media/fadou-superhero-blink.png +0 -0
  76. package/media/fadou-superhero.png +0 -0
  77. package/package.json +27 -4
  78. package/scripts/tray.ps1 +40 -0
  79. package/scripts/uninstall.ps1 +133 -0
@@ -0,0 +1,835 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResponsesSse = void 0;
4
+ exports.decodeRequestBody = decodeRequestBody;
5
+ exports.toolKindsFromResponses = toolKindsFromResponses;
6
+ exports.responsesToChatBody = responsesToChatBody;
7
+ const request_1 = require("./codexWire/request");
8
+ const tool_guidance_1 = require("./codexWire/tool-guidance");
9
+ const node_buffer_1 = require("node:buffer");
10
+ const node_zlib_1 = require("node:zlib");
11
+ const apply_patch_envelope_1 = require("./codexWire/apply-patch-envelope");
12
+ const progressive_freeform_input_1 = require("./codexWire/progressive-freeform-input");
13
+ const chat_tools_1 = require("./codexWire/chat-tools");
14
+ const chat_options_1 = require("./codexWire/chat-options");
15
+ const types_1 = require("./adapters/types");
16
+ const tool_arguments_1 = require("./codexWire/tool-arguments");
17
+ const ZSTD_MAGIC = node_buffer_1.Buffer.from([0x28, 0xb5, 0x2f, 0xfd]);
18
+ function decodeRequestBody(bytes, encoding) {
19
+ const enc = (encoding ?? "").toLowerCase();
20
+ let decoded = bytes;
21
+ if (enc.includes("zstd") || bytes.subarray(0, 4).equals(ZSTD_MAGIC)) {
22
+ decoded = (0, node_zlib_1.zstdDecompressSync)(bytes);
23
+ }
24
+ else if (enc.includes("gzip")) {
25
+ decoded = (0, node_zlib_1.gunzipSync)(bytes);
26
+ }
27
+ else if (enc.includes("deflate")) {
28
+ decoded = (0, node_zlib_1.inflateSync)(bytes);
29
+ }
30
+ return decoded.toString("utf8");
31
+ }
32
+ function textFromContent(content) {
33
+ if (typeof content === "string") {
34
+ return content;
35
+ }
36
+ if (!Array.isArray(content)) {
37
+ return "";
38
+ }
39
+ return content
40
+ .map((part) => {
41
+ if (typeof part === "string") {
42
+ return part;
43
+ }
44
+ if (!part || typeof part !== "object") {
45
+ return "";
46
+ }
47
+ const text = part.text;
48
+ return typeof text === "string" ? text : "";
49
+ })
50
+ .join("");
51
+ }
52
+ function toolKindsFromResponses(raw) {
53
+ const kinds = new Map();
54
+ let parsed = {};
55
+ try {
56
+ const value = JSON.parse(raw);
57
+ parsed = (0, types_1.object)(value, "Responses 正文");
58
+ }
59
+ catch {
60
+ return kinds;
61
+ }
62
+ parsed = (0, chat_tools_1.codexChatTools)(parsed).body;
63
+ if (!Array.isArray(parsed.tools)) {
64
+ return kinds;
65
+ }
66
+ for (const tool of parsed.tools) {
67
+ if (!tool || typeof tool !== "object") {
68
+ continue;
69
+ }
70
+ const record = tool;
71
+ const fn = record.function && typeof record.function === "object"
72
+ ? record.function
73
+ : record;
74
+ const name = typeof fn.name === "string" ? fn.name : "";
75
+ if (!name) {
76
+ continue;
77
+ }
78
+ kinds.set(name, record.type === "custom" ? "custom" : "function");
79
+ }
80
+ return kinds;
81
+ }
82
+ function chatTool(tool) {
83
+ if (!tool || typeof tool !== "object") {
84
+ return undefined;
85
+ }
86
+ const record = tool;
87
+ if (record.type !== "function" && record.type !== "custom") {
88
+ return undefined;
89
+ }
90
+ const fn = record.function && typeof record.function === "object"
91
+ ? record.function
92
+ : record;
93
+ const name = typeof fn.name === "string" ? fn.name : "";
94
+ if (!name) {
95
+ return undefined;
96
+ }
97
+ return {
98
+ type: "function",
99
+ function: {
100
+ name,
101
+ ...(typeof fn.strict === "boolean" ? { strict: fn.strict } : {}),
102
+ ...(typeof fn.description === "string"
103
+ ? { description: fn.description }
104
+ : {}),
105
+ parameters: record.type === "custom"
106
+ ? {
107
+ type: "object",
108
+ properties: {
109
+ input: { type: "string", description: record.format ? `The tool input must follow this format definition: ${JSON.stringify(record.format)}` : "The tool input." },
110
+ },
111
+ required: ["input"],
112
+ }
113
+ : fn.parameters ?? { type: "object", properties: {} },
114
+ },
115
+ };
116
+ }
117
+ // Content parts are read by projection, as opencodex does: unknown fields on a
118
+ // known part are dropped. Part types Chat cannot carry still fail, so a file,
119
+ // audio or video attachment never vanishes silently.
120
+ function chatContent(content, toolOutput = false) {
121
+ if (typeof content === "string")
122
+ return content;
123
+ const parts = (0, types_1.list)(content, "Responses content").map(part => {
124
+ const value = (0, types_1.object)(part, "Responses content part");
125
+ if (value.type === "input_text" || value.type === "output_text") {
126
+ if (Array.isArray(value.annotations) && value.annotations.length)
127
+ throw new types_1.AdapterError("Chat 無法保留文字 annotations。");
128
+ return { type: "text", text: (0, types_1.string)(value.text, "text") };
129
+ }
130
+ if (value.type === "input_image") {
131
+ return { type: "image_url", image_url: { url: (0, types_1.string)(value.image_url, "image_url"), detail: value.detail === "original" ? "high" : value.detail ?? "auto" } };
132
+ }
133
+ // codex-rs FunctionCallOutputContentItem::EncryptedContent is opaque to a
134
+ // routed model; say so instead of failing the whole turn.
135
+ if (toolOutput && value.type === "encrypted_content")
136
+ return { type: "text", text: "[encrypted content omitted]" };
137
+ throw new types_1.AdapterError(`Chat 無法轉換內容類型 ${String(value.type)}。`);
138
+ });
139
+ return parts.every(part => part.type === "text") ? parts.map(part => part.text).join("") : parts;
140
+ }
141
+ function chatToolArguments(raw, custom) {
142
+ if (!raw) {
143
+ return "{}";
144
+ }
145
+ if (!custom) {
146
+ try {
147
+ JSON.parse(raw);
148
+ return raw;
149
+ }
150
+ catch {
151
+ return JSON.stringify({ input: raw });
152
+ }
153
+ }
154
+ try {
155
+ const parsed = JSON.parse(raw);
156
+ if (parsed &&
157
+ typeof parsed === "object" &&
158
+ !Array.isArray(parsed) &&
159
+ typeof parsed.input === "string") {
160
+ return raw;
161
+ }
162
+ }
163
+ catch {
164
+ // 腳本原文,包回模型看過的 input 欄。
165
+ }
166
+ return JSON.stringify({ input: raw });
167
+ }
168
+ function pushFunctionCall(messages, record) {
169
+ const id = typeof record.call_id === "string" ? record.call_id : "";
170
+ const name = typeof record.name === "string" ? record.name : "";
171
+ if (!id || !name) {
172
+ return;
173
+ }
174
+ const rawArguments = typeof record.arguments === "string" && record.arguments
175
+ ? record.arguments
176
+ : typeof record.input === "string"
177
+ ? record.input
178
+ : "";
179
+ const call = {
180
+ id,
181
+ type: "function",
182
+ function: {
183
+ name,
184
+ arguments: chatToolArguments(rawArguments, record.type === "custom_tool_call"),
185
+ },
186
+ };
187
+ const last = messages[messages.length - 1];
188
+ if (last?.role === "assistant" && last.tool_calls) {
189
+ last.tool_calls.push(call);
190
+ return;
191
+ }
192
+ messages.push({ role: "assistant", content: null, tool_calls: [call] });
193
+ }
194
+ // Replayed reasoning rides on the assistant message it preceded, in the same
195
+ // reasoning_content field the upstream returned it in. It must never be written
196
+ // into content: a model that sees tagged reasoning in its own earlier answers
197
+ // starts writing its reasoning into the visible answer the same way. Reasoning
198
+ // with no assistant message after it has nothing to ride on and is dropped.
199
+ function foldReasoning(messages) {
200
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
201
+ const held = messages[index];
202
+ if (held.role !== "assistant" || held.content !== null || held.tool_calls || held.reasoning_content === undefined)
203
+ continue;
204
+ const next = messages[index + 1];
205
+ if (next?.role === "assistant")
206
+ next.reasoning_content = held.reasoning_content + (next.reasoning_content ?? "");
207
+ messages.splice(index, 1);
208
+ }
209
+ }
210
+ const COMPACTION_ITEM_TYPES = new Set(["compaction", "compaction_summary", "context_compaction"]);
211
+ const OPAQUE_COMPACTION_NOTE = "[earlier conversation was compacted; the summary is stored in a format this model cannot read]";
212
+ const UNPAIRABLE_ITEM_TYPES = new Set(["local_shell_call", "tool_search_call", "tool_search_output"]);
213
+ // Chat tool messages are text-only, so an image returned by a tool (such as
214
+ // Codex view_image) cannot ride in role:"tool" content. As opencodex does, keep
215
+ // the text in the tool message and carry the images in a user message placed
216
+ // after the run of tool results they belong to.
217
+ function liftToolResultImages(messages) {
218
+ let pending = [];
219
+ for (let index = 0; index <= messages.length; index++) {
220
+ const message = messages[index];
221
+ if (message?.role === "tool") {
222
+ if (!Array.isArray(message.content))
223
+ continue;
224
+ const parts = message.content;
225
+ const images = parts.filter(part => part.type === "image_url");
226
+ pending.push(...images);
227
+ message.content = parts.filter(part => part.type === "text").map(part => part.text).join("") || "[image]".repeat(images.length);
228
+ continue;
229
+ }
230
+ if (!pending.length)
231
+ continue;
232
+ messages.splice(index, 0, { role: "user", content: [{ type: "text", text: "[VansPod] image output from the preceding tool result(s):" }, ...pending] });
233
+ pending = [];
234
+ }
235
+ }
236
+ function responsesToChatBody(raw, model) {
237
+ let parsed = {};
238
+ try {
239
+ const value = JSON.parse(raw);
240
+ parsed = (0, types_1.object)(value, "Responses 正文");
241
+ }
242
+ catch {
243
+ throw new types_1.AdapterError("Responses 正文必須是 JSON 物件。");
244
+ }
245
+ const source = (0, request_1.normalizeCodexRequest)(parsed);
246
+ parsed = (0, chat_tools_1.codexChatTools)(source).body;
247
+ const guidance = (0, tool_guidance_1.codexToolGuidance)(source, Array.isArray(parsed.tools) ? parsed.tools.map(value => String((0, types_1.object)(value, "tool").name)) : []);
248
+ const options = (0, chat_options_1.codexChatOptions)(parsed, chatResponseFormat);
249
+ if (parsed.tools !== undefined)
250
+ for (const tool of (0, types_1.list)(parsed.tools, "tools")) {
251
+ const record = (0, types_1.object)(tool, "tool");
252
+ if (record.type !== "function" && record.type !== "custom")
253
+ throw new types_1.AdapterError(`Chat 無法轉換工具類型 ${String(record.type)}。`);
254
+ (0, types_1.allowedFields)(record, record.type === "custom" ? ["type", "name", "description", "format"] : ["type", "name", "description", "parameters", "strict", "function"], "Chat tool");
255
+ if (record.function)
256
+ (0, types_1.allowedFields)((0, types_1.object)(record.function, "function"), ["name", "description", "parameters", "strict"], "Chat function");
257
+ const fn = record.function ? (0, types_1.object)(record.function, "function") : record;
258
+ (0, types_1.string)(fn.name, "tool name");
259
+ if (record.type === "custom" && record.format !== undefined) {
260
+ const format = (0, types_1.object)(record.format, "custom format");
261
+ if (format.type === "grammar") {
262
+ (0, types_1.allowedFields)(format, ["type", "syntax", "definition"], "custom grammar");
263
+ if (format.syntax !== "lark" && format.syntax !== "regex")
264
+ throw new types_1.AdapterError("Chat 無法轉換此自訂 grammar syntax。");
265
+ (0, types_1.string)(format.definition, "grammar definition");
266
+ }
267
+ else if (format.type === "text")
268
+ (0, types_1.allowedFields)(format, ["type"], "custom format");
269
+ else
270
+ throw new types_1.AdapterError("Chat 無法轉換此自訂工具格式。");
271
+ }
272
+ }
273
+ const messages = [];
274
+ if (typeof parsed.instructions === "string" && parsed.instructions) {
275
+ messages.push({ role: "system", content: parsed.instructions });
276
+ }
277
+ const input = parsed.input;
278
+ if (typeof input === "string") {
279
+ messages.push({ role: "user", content: input });
280
+ }
281
+ else if (Array.isArray(input)) {
282
+ // Match opencodex's Responses parser: each input item is read by projection.
283
+ // Only the fields Chat can express are taken; unknown item-level fields
284
+ // (ids, status, phase, client metadata, future extensions) neither reach
285
+ // Chat nor reject the turn. Content parts are projected the same way.
286
+ for (const item of input) {
287
+ const record = (0, types_1.object)(item, "Responses input item");
288
+ if (record.type === "reasoning") {
289
+ // Readable summary/content text is replayed. Native encrypted_content is
290
+ // opaque to a Chat upstream: an encrypted-only item is skipped and no
291
+ // plaintext is invented from it.
292
+ const summary = (record.summary == null ? [] : (0, types_1.list)(record.summary, "reasoning summary")).map(value => {
293
+ const part = (0, types_1.object)(value, "reasoning summary part");
294
+ if (part.type !== "summary_text")
295
+ throw new types_1.AdapterError("Chat 無法轉換此 reasoning summary 類型。");
296
+ return (0, types_1.string)(part.text, "reasoning summary text");
297
+ }).join("") + (record.content == null ? "" : (0, types_1.list)(record.content, "reasoning content").map(value => {
298
+ const part = (0, types_1.object)(value, "reasoning content part");
299
+ if (part.type !== "reasoning_text")
300
+ throw new types_1.AdapterError("Chat 無法轉換此 reasoning content 類型。");
301
+ return (0, types_1.string)(part.text, "reasoning text");
302
+ }).join(""));
303
+ // Held as a content-less assistant message until foldReasoning attaches
304
+ // it to the turn it belongs to.
305
+ if (summary)
306
+ messages.push({ role: "assistant", content: null, reasoning_content: summary });
307
+ continue;
308
+ }
309
+ if (record.type === "input_text" || record.type === "output_text") {
310
+ const text = (0, types_1.string)(record.text, "text");
311
+ if (text) {
312
+ messages.push({
313
+ role: record.type === "output_text" ? "assistant" : "user",
314
+ content: text,
315
+ });
316
+ }
317
+ continue;
318
+ }
319
+ if (record.type === "function_call" || record.type === "custom_tool_call") {
320
+ if (!record.call_id || !record.name)
321
+ throw new types_1.AdapterError("工具呼叫缺少 call_id 或 name。");
322
+ pushFunctionCall(messages, record);
323
+ continue;
324
+ }
325
+ if (record.type === "function_call_output" ||
326
+ record.type === "custom_tool_call_output") {
327
+ const id = (0, types_1.string)(record.call_id, "call_id");
328
+ const output = typeof record.output === "string" ? record.output : chatContent(record.output, true);
329
+ if (id) {
330
+ messages.push({ role: "tool", tool_call_id: id, content: output });
331
+ }
332
+ continue;
333
+ }
334
+ if (COMPACTION_ITEM_TYPES.has(String(record.type))) {
335
+ // A stored summary from an earlier compaction. Its encrypted_content can
336
+ // only be read by the backend that minted it, so the model is told the
337
+ // earlier conversation was compacted. A context_compaction without a
338
+ // payload is a pure marker: the summary follows as its own user message.
339
+ if (record.type === "context_compaction" && typeof record.encrypted_content !== "string")
340
+ continue;
341
+ messages.push({ role: "user", content: OPAQUE_COMPACTION_NOTE });
342
+ continue;
343
+ }
344
+ if (record.type === "agent_message") {
345
+ // External input delivered to the parent agent; keep it as a user turn.
346
+ const content = record.content == null ? "" : chatContent(record.content);
347
+ messages.push({ role: "user", content: content.length ? content : "(sub-agent message received)" });
348
+ continue;
349
+ }
350
+ // A typeless item with a role is a message, as in opencodex. The type, not
351
+ // an incidental role field, decides everything else.
352
+ if (record.type === undefined ? typeof record.role !== "string" : record.type !== "message") {
353
+ // These would leave a tool call or result Chat cannot pair up.
354
+ if (record.type === undefined || UNPAIRABLE_ITEM_TYPES.has(String(record.type)))
355
+ throw new types_1.AdapterError(`Chat 無法轉換輸入類型 ${String(record.type)}。`);
356
+ // Anything else (web_search_call, future item types) has no Chat form and
357
+ // is skipped, as opencodex does, so one item cannot block every later turn.
358
+ continue;
359
+ }
360
+ if (!["assistant", "system", "developer", "user"].includes(String(record.role)))
361
+ throw new types_1.AdapterError("Chat 無法轉換此 message role。");
362
+ const role = record.role === "assistant"
363
+ ? "assistant"
364
+ : record.role === "system" || record.role === "developer"
365
+ ? "system"
366
+ : "user";
367
+ const content = chatContent(record.content);
368
+ if (!content) {
369
+ continue;
370
+ }
371
+ messages.push({ role, content });
372
+ }
373
+ }
374
+ else if (input !== undefined)
375
+ throw new types_1.AdapterError("Responses input 必須是文字或陣列。");
376
+ foldReasoning(messages);
377
+ liftToolResultImages(messages);
378
+ const tools = Array.isArray(parsed.tools)
379
+ ? parsed.tools.flatMap((tool) => {
380
+ const converted = chatTool(tool);
381
+ return converted ? [converted] : [];
382
+ })
383
+ : [];
384
+ if (guidance.catalog) {
385
+ const instructions = typeof parsed.instructions === "string" ? parsed.instructions : "";
386
+ const guided = (0, tool_guidance_1.appendToolInstructions)(instructions, [guidance.catalog]);
387
+ if (instructions)
388
+ messages[0] = { role: "system", content: guided };
389
+ else
390
+ messages.unshift({ role: "system", content: guided });
391
+ }
392
+ for (const tool of tools)
393
+ if (guidance.execNames.has(tool.function.name)) {
394
+ const parameters = (0, types_1.object)(tool.function.parameters, "parameters");
395
+ const input = (0, types_1.object)((0, types_1.object)(parameters.properties, "properties").input, "input");
396
+ input.description = (0, tool_guidance_1.codeModeInputDescription)(typeof input.description === "string" ? input.description : undefined);
397
+ }
398
+ return JSON.stringify({
399
+ model,
400
+ messages,
401
+ stream: parsed.stream ?? true,
402
+ ...options,
403
+ ...(parsed.tool_choice !== undefined ? { tool_choice: chatToolChoice(parsed.tool_choice) } : {}),
404
+ ...(tools.length > 0 ? { tools } : {}),
405
+ });
406
+ }
407
+ function chatToolChoice(value) {
408
+ if (typeof value === "string" && ["auto", "none", "required"].includes(value))
409
+ return value;
410
+ const choice = (0, types_1.object)(value, "tool_choice");
411
+ (0, types_1.allowedFields)(choice, ["type", "name"], "Chat tool_choice");
412
+ if (choice.type === "function" || choice.type === "custom")
413
+ return { type: "function", function: { name: (0, types_1.string)(choice.name, "tool_choice.name") } };
414
+ throw new types_1.AdapterError("Chat 無法轉換 tool_choice。");
415
+ }
416
+ function chatResponseFormat(value) {
417
+ const format = (0, types_1.object)(value, "text.format");
418
+ if (format.type === "text" || format.type === "json_object") {
419
+ (0, types_1.allowedFields)(format, ["type"], "text.format");
420
+ return format;
421
+ }
422
+ if (format.type === "json_schema") {
423
+ (0, types_1.allowedFields)(format, ["type", "name", "description", "schema", "strict"], "text.format");
424
+ const { type, ...schema } = format;
425
+ return { type, json_schema: schema };
426
+ }
427
+ throw new types_1.AdapterError("Chat 無法轉換 text.format。");
428
+ }
429
+ function randomId() {
430
+ return Math.random().toString(16).slice(2, 14);
431
+ }
432
+ function sse(name, sequence, data) {
433
+ return `event: ${name}\ndata: ${JSON.stringify({
434
+ type: name,
435
+ sequence_number: sequence,
436
+ ...data,
437
+ })}\n\n`;
438
+ }
439
+ class ResponsesSse {
440
+ model;
441
+ toolKinds;
442
+ toolIdentity;
443
+ hideReasoningSummary;
444
+ reasoning = "";
445
+ reasoningIndex = -1;
446
+ reasoningId = `rs_${randomId()}`;
447
+ finishReason;
448
+ usage = null;
449
+ sequence = 0;
450
+ opened = false;
451
+ textClosed = false;
452
+ text = "";
453
+ textIndex = 0;
454
+ nextIndex = 0;
455
+ responseId = `resp_${randomId()}`;
456
+ itemId = `msg_${randomId()}`;
457
+ createdAt = Math.floor(Date.now() / 1000);
458
+ buffer = "";
459
+ tools = new Map();
460
+ constructor(model, toolKinds = new Map(), toolIdentity = name => ({ name }), hideReasoningSummary = false) {
461
+ this.model = model;
462
+ this.toolKinds = toolKinds;
463
+ this.toolIdentity = toolIdentity;
464
+ this.hideReasoningSummary = hideReasoningSummary;
465
+ }
466
+ created() {
467
+ return this.frame("response.created", {
468
+ response: this.snapshot("in_progress", []),
469
+ });
470
+ }
471
+ pushChatChunk(chunk) {
472
+ this.buffer += chunk;
473
+ const lines = this.buffer.split("\n");
474
+ this.buffer = lines.pop() ?? "";
475
+ let out = "";
476
+ for (const line of lines) {
477
+ out += this.pushChatLine(line);
478
+ }
479
+ return out;
480
+ }
481
+ finish() {
482
+ if (this.buffer.trim()) {
483
+ const tail = this.pushChatLine(this.buffer);
484
+ this.buffer = "";
485
+ return tail + this.completed();
486
+ }
487
+ return this.completed();
488
+ }
489
+ pushChatLine(line) {
490
+ const trimmed = line.trim();
491
+ if (!trimmed.startsWith("data:")) {
492
+ return "";
493
+ }
494
+ const data = trimmed.slice(5).trim();
495
+ if (!data || data === "[DONE]") {
496
+ return "";
497
+ }
498
+ let parsed;
499
+ try {
500
+ parsed = JSON.parse(data);
501
+ }
502
+ catch {
503
+ return "";
504
+ }
505
+ const choice = parsed.choices?.[0];
506
+ if (choice?.finish_reason != null)
507
+ this.finishReason = choice.finish_reason;
508
+ const usage = parsed.usage;
509
+ if (usage)
510
+ this.usage = { input_tokens: usage.prompt_tokens ?? 0, output_tokens: usage.completion_tokens ?? 0, total_tokens: usage.total_tokens ?? 0 };
511
+ let out = "";
512
+ const reasoning = choice?.delta?.reasoning ?? choice?.delta?.reasoning_content;
513
+ if (typeof reasoning === "string" && reasoning) {
514
+ if (this.reasoningIndex < 0) {
515
+ this.reasoningIndex = this.nextIndex++;
516
+ out += this.frame("response.output_item.added", { output_index: this.reasoningIndex, item: { type: "reasoning", id: this.reasoningId, summary: [], status: "in_progress" } });
517
+ if (!this.hideReasoningSummary)
518
+ out += this.frame("response.reasoning_summary_part.added", { item_id: this.reasoningId, output_index: this.reasoningIndex, summary_index: 0, part: { type: "summary_text", text: "" } });
519
+ }
520
+ this.reasoning += reasoning;
521
+ if (!this.hideReasoningSummary)
522
+ out += this.frame("response.reasoning_summary_text.delta", { item_id: this.reasoningId, output_index: this.reasoningIndex, summary_index: 0, delta: reasoning });
523
+ }
524
+ const content = choice?.delta?.content;
525
+ if (typeof content === "string" && content) {
526
+ out += this.delta(content);
527
+ }
528
+ for (const call of choice?.delta?.tool_calls ?? []) {
529
+ out += this.toolDelta(call);
530
+ }
531
+ return out;
532
+ }
533
+ toolDelta(call) {
534
+ const index = call.index ?? 0;
535
+ const byId = call.id
536
+ ? [...this.tools.values()].find((tool) => tool.id === call.id)
537
+ : undefined;
538
+ let tool = byId ?? this.tools.get(index);
539
+ if (!tool) {
540
+ tool = {
541
+ id: call.id || `call_${randomId()}`,
542
+ name: "",
543
+ args: "",
544
+ inputEmitted: "",
545
+ itemId: `fc_${randomId()}`,
546
+ outputIndex: 0,
547
+ started: false,
548
+ custom: false,
549
+ };
550
+ this.tools.set(index, tool);
551
+ }
552
+ if (call.id) {
553
+ tool.id = call.id;
554
+ }
555
+ if (call.function?.name) {
556
+ tool.name = tool.name
557
+ ? call.function.name.startsWith(tool.name)
558
+ ? call.function.name
559
+ : tool.name + call.function.name
560
+ : call.function.name;
561
+ }
562
+ if (call.function?.arguments) {
563
+ tool.args += call.function.arguments;
564
+ }
565
+ let out = "";
566
+ if (!tool.started && tool.name) {
567
+ tool.started = true;
568
+ tool.custom = this.toolKinds.get(tool.name) === "custom";
569
+ tool.outputIndex = this.nextIndex;
570
+ this.nextIndex += 1;
571
+ out += this.frame("response.output_item.added", {
572
+ output_index: tool.outputIndex,
573
+ item: tool.custom
574
+ ? {
575
+ type: "custom_tool_call",
576
+ id: tool.itemId,
577
+ call_id: tool.id,
578
+ name: tool.name,
579
+ input: "",
580
+ status: "in_progress",
581
+ }
582
+ : {
583
+ type: "function_call",
584
+ id: tool.itemId,
585
+ call_id: tool.id,
586
+ name: tool.name,
587
+ arguments: "",
588
+ status: "in_progress",
589
+ },
590
+ });
591
+ }
592
+ if (tool.started &&
593
+ !tool.custom &&
594
+ call.function?.arguments &&
595
+ (0, tool_arguments_1.toolCallArgumentsCouldBeJson)(tool.args)) {
596
+ out += this.frame("response.function_call_arguments.delta", {
597
+ item_id: tool.itemId,
598
+ output_index: tool.outputIndex,
599
+ delta: call.function.arguments,
600
+ });
601
+ }
602
+ if (tool.started && tool.custom) {
603
+ const identity = this.toolIdentity(tool.name);
604
+ const ownsGrammar = identity.namespace === undefined || identity.namespace === "functions";
605
+ const full = (0, progressive_freeform_input_1.progressiveFreeformInput)(tool.args, ownsGrammar ? identity.name : "");
606
+ if (full !== null) {
607
+ const holdPatch = ownsGrammar && identity.name === "apply_patch" && (0, apply_patch_envelope_1.mayBecomePatchEnvelope)(full);
608
+ if (!holdPatch &&
609
+ full.startsWith(tool.inputEmitted) &&
610
+ full.length > tool.inputEmitted.length) {
611
+ const delta = full.slice(tool.inputEmitted.length);
612
+ tool.inputEmitted = full;
613
+ out += this.frame("response.custom_tool_call_input.delta", {
614
+ item_id: tool.itemId,
615
+ output_index: tool.outputIndex,
616
+ delta,
617
+ });
618
+ }
619
+ }
620
+ }
621
+ return out;
622
+ }
623
+ delta(piece) {
624
+ let out = this.ensureOpen();
625
+ this.text += piece;
626
+ out += this.frame("response.output_text.delta", {
627
+ item_id: this.itemId,
628
+ output_index: this.textIndex,
629
+ content_index: 0,
630
+ delta: piece,
631
+ });
632
+ return out;
633
+ }
634
+ ensureOpen() {
635
+ if (this.opened) {
636
+ return "";
637
+ }
638
+ this.opened = true;
639
+ this.textIndex = this.nextIndex;
640
+ this.nextIndex += 1;
641
+ return (this.frame("response.output_item.added", {
642
+ output_index: this.textIndex,
643
+ item: {
644
+ type: "message",
645
+ id: this.itemId,
646
+ status: "in_progress",
647
+ role: "assistant",
648
+ content: [],
649
+ },
650
+ }) +
651
+ this.frame("response.content_part.added", {
652
+ item_id: this.itemId,
653
+ output_index: this.textIndex,
654
+ content_index: 0,
655
+ part: { type: "output_text", text: "", annotations: [] },
656
+ }));
657
+ }
658
+ closeText() {
659
+ if (!this.opened || this.textClosed) {
660
+ return "";
661
+ }
662
+ this.textClosed = true;
663
+ const item = {
664
+ type: "message",
665
+ id: this.itemId,
666
+ status: "completed",
667
+ role: "assistant",
668
+ content: [{ type: "output_text", text: this.text, annotations: [] }],
669
+ };
670
+ return (this.frame("response.output_text.done", {
671
+ item_id: this.itemId,
672
+ output_index: this.textIndex,
673
+ content_index: 0,
674
+ text: this.text,
675
+ }) +
676
+ this.frame("response.content_part.done", {
677
+ item_id: this.itemId,
678
+ output_index: this.textIndex,
679
+ content_index: 0,
680
+ part: { type: "output_text", text: this.text, annotations: [] },
681
+ }) +
682
+ this.frame("response.output_item.done", {
683
+ output_index: this.textIndex,
684
+ item,
685
+ }));
686
+ }
687
+ closeTools() {
688
+ let frames = "";
689
+ const items = [];
690
+ const seen = new Set();
691
+ for (const tool of this.tools.values()) {
692
+ if (!tool.name || seen.has(tool.id)) {
693
+ continue;
694
+ }
695
+ seen.add(tool.id);
696
+ if (!tool.custom && !(0, tool_arguments_1.toolCallArgumentsUsable)(tool.args)) {
697
+ frames += this.frame("response.failed", {
698
+ response: {
699
+ ...this.snapshot("failed", items),
700
+ error: {
701
+ type: "upstream_error",
702
+ message: "upstream stream produced malformed tool call arguments",
703
+ },
704
+ },
705
+ });
706
+ return { frames, items, failed: true };
707
+ }
708
+ const args = tool.custom
709
+ ? (0, apply_patch_envelope_1.repairFreeformToolInput)(tool.args, this.toolIdentity(tool.name).name, this.toolIdentity(tool.name).namespace)
710
+ : tool.args || "{}";
711
+ const item = tool.custom
712
+ ? {
713
+ type: "custom_tool_call",
714
+ id: tool.itemId,
715
+ call_id: tool.id,
716
+ name: tool.name,
717
+ input: args,
718
+ status: "completed",
719
+ }
720
+ : {
721
+ type: "function_call",
722
+ id: tool.itemId,
723
+ call_id: tool.id,
724
+ name: tool.name,
725
+ arguments: args,
726
+ status: "completed",
727
+ };
728
+ let framesForTool = "";
729
+ if (!tool.started) {
730
+ tool.custom = this.toolKinds.get(tool.name) === "custom";
731
+ tool.outputIndex = this.nextIndex;
732
+ this.nextIndex += 1;
733
+ framesForTool += this.frame("response.output_item.added", {
734
+ output_index: tool.outputIndex,
735
+ item: tool.custom
736
+ ? { ...item, status: "in_progress", input: "" }
737
+ : { ...item, status: "in_progress", arguments: "" },
738
+ });
739
+ }
740
+ framesForTool += this.frame(tool.custom
741
+ ? "response.custom_tool_call_input.done"
742
+ : "response.function_call_arguments.done", tool.custom
743
+ ? {
744
+ item_id: tool.itemId,
745
+ output_index: tool.outputIndex,
746
+ input: args,
747
+ }
748
+ : {
749
+ item_id: tool.itemId,
750
+ output_index: tool.outputIndex,
751
+ arguments: args,
752
+ });
753
+ framesForTool += this.frame("response.output_item.done", {
754
+ output_index: tool.outputIndex,
755
+ item,
756
+ });
757
+ frames += framesForTool;
758
+ items.push(item);
759
+ }
760
+ return { frames, items, failed: false };
761
+ }
762
+ completed() {
763
+ const status = this.finishReason === "length" ? "incomplete" : "completed";
764
+ const terminalEvent = status === "incomplete" ? "response.incomplete" : "response.completed";
765
+ const textFrames = this.opened ? this.closeText() : "";
766
+ const reasoningItem = { type: "reasoning", id: this.reasoningId, status: "completed", summary: this.hideReasoningSummary ? [] : [{ type: "summary_text", text: this.reasoning }],
767
+ ...(this.hideReasoningSummary ? { content: [{ type: "reasoning_text", text: this.reasoning }] } : {}) };
768
+ const reasoningFrames = this.reasoningIndex < 0 ? "" :
769
+ (this.hideReasoningSummary ? "" : this.frame("response.reasoning_summary_text.done", { item_id: this.reasoningId, output_index: this.reasoningIndex, summary_index: 0, text: this.reasoning }) +
770
+ this.frame("response.reasoning_summary_part.done", { item_id: this.reasoningId, output_index: this.reasoningIndex, summary_index: 0, part: reasoningItem.summary[0] })) +
771
+ this.frame("response.output_item.done", { output_index: this.reasoningIndex, item: reasoningItem });
772
+ const tools = this.closeTools();
773
+ if (tools.failed) {
774
+ return textFrames + tools.frames;
775
+ }
776
+ const output = [
777
+ ...(this.reasoningIndex >= 0 ? [reasoningItem] : []),
778
+ ...(this.opened
779
+ ? [
780
+ {
781
+ type: "message",
782
+ id: this.itemId,
783
+ status: "completed",
784
+ role: "assistant",
785
+ content: [{ type: "output_text", text: this.text, annotations: [] }],
786
+ },
787
+ ]
788
+ : []),
789
+ ...tools.items,
790
+ ];
791
+ const indexOf = (item) => item.id === this.reasoningId ? this.reasoningIndex : item.id === this.itemId ? this.textIndex : [...this.tools.values()].find(tool => tool.itemId === item.id)?.outputIndex ?? 0;
792
+ output.sort((left, right) => indexOf(left) - indexOf(right));
793
+ if (output.length === 0) {
794
+ return (this.ensureOpen() +
795
+ this.closeText() +
796
+ this.frame(terminalEvent, {
797
+ response: this.snapshot(status, [
798
+ {
799
+ type: "message",
800
+ id: this.itemId,
801
+ status: "completed",
802
+ role: "assistant",
803
+ content: [{ type: "output_text", text: "", annotations: [] }],
804
+ },
805
+ ]),
806
+ }) +
807
+ "data: [DONE]\n\n");
808
+ }
809
+ return (reasoningFrames +
810
+ textFrames +
811
+ tools.frames +
812
+ this.frame(terminalEvent, {
813
+ response: this.snapshot(status, output),
814
+ }) +
815
+ "data: [DONE]\n\n");
816
+ }
817
+ snapshot(status, output) {
818
+ return {
819
+ id: this.responseId,
820
+ object: "response",
821
+ created_at: this.createdAt,
822
+ status,
823
+ model: this.model,
824
+ output,
825
+ usage: this.usage,
826
+ ...(status === "incomplete" ? { incomplete_details: { reason: "max_output_tokens" } } : {}),
827
+ };
828
+ }
829
+ frame(name, data) {
830
+ const sequence = this.sequence;
831
+ this.sequence += 1;
832
+ return sse(name, sequence, data);
833
+ }
834
+ }
835
+ exports.ResponsesSse = ResponsesSse;