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,79 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.responsesToClaude = responsesToClaude;
4
+ exports.chatToClaude = chatToClaude;
5
+ const types_1 = require("./types");
6
+ const claude_reasoning_1 = require("./claude-reasoning");
7
+ const chat_output_1 = require("./chat-output");
8
+ function responsesToClaude(body, model, mode = "off") {
9
+ if (body.status !== "completed" && body.status !== "incomplete")
10
+ throw new types_1.AdapterError("Responses 未正常完成。", 502);
11
+ if (body.status === "incomplete") {
12
+ const reason = (0, types_1.object)(body.incomplete_details, "incomplete_details").reason;
13
+ if (reason !== "max_output_tokens")
14
+ throw new types_1.AdapterError(`Responses 未完成,原因 ${String(reason)} 無法轉換為 Claude stop_reason。`, 502);
15
+ }
16
+ const content = [];
17
+ for (const value of (0, types_1.list)(body.output, "Responses output")) {
18
+ const item = (0, types_1.object)(value, "output item");
19
+ if (item.type === "message")
20
+ for (const raw of (0, types_1.list)(item.content, "message content")) {
21
+ const part = (0, types_1.object)(raw, "output part");
22
+ if (part.type !== "output_text" || (Array.isArray(part.annotations) && part.annotations.length))
23
+ throw new types_1.AdapterError("Claude 無法轉換此 Responses 輸出內容。", 502);
24
+ content.push({ type: "text", text: (0, types_1.string)(part.text, "output text") });
25
+ }
26
+ else if (item.type === "function_call") {
27
+ let input;
28
+ try {
29
+ input = JSON.parse((0, types_1.string)(item.arguments, "arguments"));
30
+ }
31
+ catch {
32
+ throw new types_1.AdapterError("上游工具參數不是有效 JSON。", 502);
33
+ }
34
+ content.push({ type: "tool_use", id: (0, types_1.string)(item.call_id, "call_id"), name: (0, types_1.string)(item.name, "tool name"), input: (0, types_1.object)(input, "tool input") });
35
+ }
36
+ else if (item.type === "reasoning") {
37
+ const reasoning = mode === "off" ? undefined : (0, claude_reasoning_1.responsesReasoning)(item);
38
+ if (reasoning)
39
+ content.push((0, claude_reasoning_1.thinkingBlock)(reasoning, mode, model));
40
+ }
41
+ else
42
+ throw new types_1.AdapterError(`Claude 無法保留 Responses 輸出 ${String(item.type)}。`, 502);
43
+ }
44
+ const usage = body.usage ? (0, types_1.object)(body.usage, "usage") : {};
45
+ return { id: body.id ?? "msg_vanspod", type: "message", role: "assistant", model, content,
46
+ stop_reason: body.status === "incomplete" ? "max_tokens" : content.some(c => c.type === "tool_use") ? "tool_use" : "end_turn",
47
+ stop_sequence: null, usage: { input_tokens: usage.input_tokens ?? 0, output_tokens: usage.output_tokens ?? 0 } };
48
+ }
49
+ function chatToClaude(body, model, mode = "off") {
50
+ const choice = (0, types_1.object)((0, types_1.list)(body.choices, "choices")[0], "choice");
51
+ const message = (0, types_1.object)(choice.message, "Chat message");
52
+ (0, chat_output_1.validateChatContent)(message, true);
53
+ const content = [];
54
+ const reasoning = message.reasoning_content ?? message.reasoning;
55
+ if (mode !== "off" && typeof reasoning === "string" && reasoning)
56
+ content.push((0, claude_reasoning_1.thinkingBlock)({ text: reasoning }, mode, model));
57
+ if (message.content !== null && message.content !== undefined)
58
+ content.push({ type: "text", text: (0, types_1.string)(message.content, "Chat content") });
59
+ const tools = new chat_output_1.ChatToolCalls();
60
+ tools.add(message.tool_calls === undefined ? [] : (0, types_1.list)(message.tool_calls, "tool_calls").map((call, index) => ({ ...(0, types_1.object)(call, "tool call"), index })));
61
+ const calls = tools.completed();
62
+ if (choice.finish_reason === "tool_calls" && !calls.length)
63
+ throw new types_1.AdapterError("上游工具呼叫缺少識別資訊。", 502);
64
+ for (const value of calls) {
65
+ const call = (0, types_1.object)(value, "tool call"), fn = (0, types_1.object)(call.function, "tool function");
66
+ let input;
67
+ try {
68
+ input = JSON.parse((0, types_1.string)(fn.arguments, "arguments"));
69
+ }
70
+ catch {
71
+ throw new types_1.AdapterError("上游工具參數不是有效 JSON。", 502);
72
+ }
73
+ content.push({ type: "tool_use", id: (0, types_1.string)(call.id, "tool id"), name: (0, types_1.string)(fn.name, "tool name"), input: (0, types_1.object)(input, "tool input") });
74
+ }
75
+ const usage = body.usage ? (0, types_1.object)(body.usage, "usage") : {};
76
+ (0, chat_output_1.chatFinishReason)(choice.finish_reason);
77
+ return { id: body.id ?? "msg_vanspod", type: "message", role: "assistant", model, content, stop_reason: choice.finish_reason === "length" ? "max_tokens" : choice.finish_reason === "tool_calls" ? "tool_use" : "end_turn",
78
+ stop_sequence: null, usage: { input_tokens: usage.prompt_tokens ?? 0, output_tokens: usage.completion_tokens ?? 0 } };
79
+ }
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.modelApiAdapter = modelApiAdapter;
4
+ const responses_1 = require("./responses");
5
+ const chat_1 = require("./chat");
6
+ const messages_1 = require("./messages");
7
+ const types_1 = require("./types");
8
+ const responses = new responses_1.ResponsesAdapter();
9
+ const chat = new chat_1.ChatAdapter();
10
+ const messages = new messages_1.MessagesAdapter();
11
+ function modelApiAdapter(id) {
12
+ if (id === "responses")
13
+ return responses;
14
+ if (id === "chat")
15
+ return chat;
16
+ if (id === "messages")
17
+ return messages;
18
+ throw new types_1.AdapterError("無效的 model API adapter。");
19
+ }
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessagesStream = void 0;
4
+ exports.chatToMessagesStream = chatToMessagesStream;
5
+ const node_crypto_1 = require("node:crypto");
6
+ const types_1 = require("./types");
7
+ const stream_1 = require("./stream");
8
+ const claude_reasoning_1 = require("./claude-reasoning");
9
+ const chat_output_1 = require("./chat-output");
10
+ class MessagesStream {
11
+ model;
12
+ blocks = new Map();
13
+ inputTokens = 0;
14
+ outputTokens = 0;
15
+ constructor(model) {
16
+ this.model = model;
17
+ }
18
+ start() { return (0, stream_1.event)("message_start", { message: { id: `msg_${(0, node_crypto_1.randomUUID)()}`, type: "message", role: "assistant", model: this.model, content: [], stop_reason: null, stop_sequence: null, usage: { input_tokens: 0, output_tokens: 0 } } }); }
19
+ usage(input, output) { if (typeof input === "number")
20
+ this.inputTokens = input; if (typeof output === "number")
21
+ this.outputTokens = output; }
22
+ text(key, delta) {
23
+ let block = this.blocks.get(key), out = "";
24
+ if (!block) {
25
+ block = { index: this.blocks.size, kind: "text", value: "", closed: false };
26
+ this.blocks.set(key, block);
27
+ out += (0, stream_1.event)("content_block_start", { index: block.index, content_block: { type: "text", text: "" } });
28
+ }
29
+ block.value += delta;
30
+ return out + (0, stream_1.event)("content_block_delta", { index: block.index, delta: { type: "text_delta", text: delta } });
31
+ }
32
+ tool(key, id, name) {
33
+ if (this.blocks.has(key))
34
+ return "";
35
+ const block = { index: this.blocks.size, kind: "tool", value: "", closed: false };
36
+ this.blocks.set(key, block);
37
+ return (0, stream_1.event)("content_block_start", { index: block.index, content_block: { type: "tool_use", id, name, input: {} } });
38
+ }
39
+ // The signature only exists once the upstream has finished the reasoning, so the block is sent whole.
40
+ thinking(key, reasoning, mode) {
41
+ if (mode === "off" || this.blocks.has(key))
42
+ return "";
43
+ const index = this.blocks.size, block = (0, claude_reasoning_1.thinkingBlock)(reasoning, mode, this.model);
44
+ this.blocks.set(key, { index, kind: "thinking", value: "", closed: true });
45
+ return (0, stream_1.event)("content_block_start", { index, content_block: { type: "thinking", thinking: "", signature: "" } })
46
+ + (block.thinking ? (0, stream_1.event)("content_block_delta", { index, delta: { type: "thinking_delta", thinking: block.thinking } }) : "")
47
+ + (0, stream_1.event)("content_block_delta", { index, delta: { type: "signature_delta", signature: block.signature } }) + (0, stream_1.event)("content_block_stop", { index });
48
+ }
49
+ arguments(key, delta) {
50
+ const block = this.blocks.get(key);
51
+ if (!block || block.kind !== "tool")
52
+ throw new types_1.AdapterError("上游工具串流缺少識別資訊。", 502);
53
+ block.value += delta;
54
+ return (0, stream_1.event)("content_block_delta", { index: block.index, delta: { type: "input_json_delta", partial_json: delta } });
55
+ }
56
+ reconcile(key, value) {
57
+ const previous = this.blocks.get(key)?.value ?? "";
58
+ if (!value.startsWith(previous))
59
+ throw new types_1.AdapterError("上游完成內容與串流內容不一致。", 502);
60
+ const rest = value.slice(previous.length);
61
+ return rest ? this.blocks.get(key)?.kind === "tool" ? this.arguments(key, rest) : this.text(key, rest) : "";
62
+ }
63
+ finish(reason) {
64
+ let out = "";
65
+ for (const block of this.blocks.values()) {
66
+ if (block.kind === "tool") {
67
+ try {
68
+ (0, types_1.object)(JSON.parse(block.value || "{}"), "tool input");
69
+ }
70
+ catch {
71
+ throw new types_1.AdapterError("上游工具參數不是有效 JSON 物件。", 502);
72
+ }
73
+ }
74
+ if (!block.closed) {
75
+ out += (0, stream_1.event)("content_block_stop", { index: block.index });
76
+ block.closed = true;
77
+ }
78
+ }
79
+ return out + (0, stream_1.event)("message_delta", { delta: { stop_reason: reason, stop_sequence: null }, usage: { input_tokens: this.inputTokens, output_tokens: this.outputTokens } }) + (0, stream_1.event)("message_stop", {});
80
+ }
81
+ }
82
+ exports.MessagesStream = MessagesStream;
83
+ async function* chatToMessagesStream(response, model, mode = "off") {
84
+ const output = new MessagesStream(model);
85
+ yield output.start();
86
+ const tools = new chat_output_1.ChatToolCalls();
87
+ let finish, reasoning = "", thoughts = 0;
88
+ // Reasoning comes before the answer it led to; it becomes one block when that answer starts.
89
+ const thought = () => { const text = reasoning; reasoning = ""; return text ? output.thinking(`thinking:${thoughts++}`, { text }, mode) : ""; };
90
+ for await (const frame of (0, stream_1.frames)(response)) {
91
+ if (frame.data === "[DONE]")
92
+ continue;
93
+ const body = frame.data;
94
+ if (body.error)
95
+ throw new types_1.AdapterError("Chat 上游串流回報失敗。", 502);
96
+ if (body.usage) {
97
+ const usage = (0, types_1.object)(body.usage, "usage");
98
+ output.usage(usage.prompt_tokens, usage.completion_tokens);
99
+ }
100
+ for (const raw of (body.choices ?? [])) {
101
+ const choice = (0, types_1.object)(raw, "choice"), delta = (0, types_1.object)(choice.delta ?? {}, "delta");
102
+ (0, chat_output_1.validateChatContent)(delta, true);
103
+ const reasoned = delta.reasoning_content ?? delta.reasoning;
104
+ if (typeof reasoned === "string")
105
+ reasoning += reasoned;
106
+ if (typeof delta.content === "string" && delta.content) {
107
+ yield thought();
108
+ yield output.text("text", delta.content);
109
+ }
110
+ if (delta.tool_calls !== undefined)
111
+ tools.add(delta.tool_calls);
112
+ if (choice.finish_reason != null)
113
+ finish = choice.finish_reason;
114
+ }
115
+ }
116
+ finish = (0, chat_output_1.chatFinishReason)(finish);
117
+ const calls = tools.completed();
118
+ if (finish === "tool_calls" && !calls.length)
119
+ throw new types_1.AdapterError("上游工具呼叫缺少識別資訊。", 502);
120
+ yield thought();
121
+ for (const call of calls) {
122
+ yield output.tool(`tool:${call.index}`, call.id, call.function.name);
123
+ yield output.arguments(`tool:${call.index}`, call.function.arguments);
124
+ }
125
+ yield output.finish(finish === "tool_calls" ? "tool_use" : finish === "length" ? "max_tokens" : "end_turn");
126
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MessagesAdapter = void 0;
4
+ const types_1 = require("./types");
5
+ const stream_1 = require("./stream");
6
+ const transport_1 = require("./transport");
7
+ const claude_reasoning_1 = require("./claude-reasoning");
8
+ class MessagesAdapter {
9
+ id = "messages";
10
+ async send(request) {
11
+ if (request.client !== "claude")
12
+ throw new types_1.AdapterError("Messages adapter 僅支援 Claude Code。");
13
+ const headers = { "Content-Type": "application/json", "anthropic-version": "2023-06-01", Authorization: `Bearer ${request.apiKey}` };
14
+ for (const name of ["anthropic-version", "anthropic-beta"])
15
+ if (request.headers?.[name])
16
+ headers[name] = request.headers[name];
17
+ const { response, abort } = await (0, transport_1.upstreamRequest)(request, "/v1/messages", { ...(0, claude_reasoning_1.withoutReplayedThinking)(request.body), model: request.model }, headers);
18
+ return response.ok && request.body.stream === true ? (0, stream_1.streamResponse)((0, stream_1.nativeStream)(response, "messages"), abort) : response;
19
+ }
20
+ }
21
+ exports.MessagesAdapter = MessagesAdapter;
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.responsesToMessagesStream = responsesToMessagesStream;
4
+ const messages_stream_1 = require("./messages-stream");
5
+ const claude_response_1 = require("./claude-response");
6
+ const types_1 = require("./types");
7
+ const stream_1 = require("./stream");
8
+ const claude_reasoning_1 = require("./claude-reasoning");
9
+ async function* responsesToMessagesStream(response, model, mode = "off") {
10
+ const output = new messages_stream_1.MessagesStream(model);
11
+ yield output.start();
12
+ let terminal;
13
+ for await (const frame of (0, stream_1.frames)(response)) {
14
+ if (frame.data === "[DONE]")
15
+ continue;
16
+ const body = frame.data, type = String(body.type);
17
+ if (type === "error" || type === "response.failed")
18
+ throw new types_1.AdapterError("Responses 上游串流回報失敗。", 502);
19
+ if (type === "response.output_text.delta")
20
+ yield output.text(`text:${body.item_id}:${body.content_index ?? 0}`, (0, types_1.string)(body.delta, "text delta"));
21
+ else if (type === "response.output_item.added") {
22
+ const item = (0, types_1.object)(body.item, "output item");
23
+ if (item.type === "function_call") {
24
+ const key = `tool:${(0, types_1.string)(item.id, "item id")}`;
25
+ yield output.tool(key, (0, types_1.string)(item.call_id, "call id"), (0, types_1.string)(item.name, "tool name"));
26
+ if (typeof item.arguments === "string" && item.arguments)
27
+ yield output.arguments(key, item.arguments);
28
+ }
29
+ else if (item.type !== "message" && item.type !== "reasoning")
30
+ throw new types_1.AdapterError(`Claude 無法保留 Responses 輸出 ${String(item.type)}。`, 502);
31
+ }
32
+ else if (type === "response.output_item.done") {
33
+ const item = (0, types_1.object)(body.item, "output item"), reasoning = item.type === "reasoning" ? (0, claude_reasoning_1.responsesReasoning)(item) : undefined;
34
+ if (reasoning)
35
+ yield output.thinking(`reasoning:${item.id ?? body.output_index}`, reasoning, mode);
36
+ }
37
+ else if (type === "response.function_call_arguments.delta")
38
+ yield output.arguments(`tool:${body.item_id}`, (0, types_1.string)(body.delta, "arguments delta"));
39
+ else if (type === "response.completed" || type === "response.incomplete")
40
+ terminal = (0, types_1.object)(body.response, "response");
41
+ else if (/^response\.(refusal|custom_tool_call)/.test(type))
42
+ throw new types_1.AdapterError("Claude 無法保留此 Responses 串流內容。", 502);
43
+ }
44
+ if (!terminal)
45
+ throw new types_1.AdapterError("Responses 串流中斷,未收到完成事件。", 502);
46
+ const message = (0, claude_response_1.responsesToClaude)(terminal, model, mode);
47
+ for (const [position, value] of (0, types_1.list)(terminal.output, "output").entries()) {
48
+ const item = (0, types_1.object)(value, "output item");
49
+ if (item.type === "message") {
50
+ const parts = (0, types_1.list)(item.content, "content");
51
+ for (let index = 0; index < parts.length; index++) {
52
+ const part = (0, types_1.object)(parts[index], "content part");
53
+ yield output.reconcile(`text:${item.id}:${index}`, (0, types_1.string)(part.text, "output text"));
54
+ }
55
+ }
56
+ else if (item.type === "reasoning") {
57
+ const reasoning = (0, claude_reasoning_1.responsesReasoning)(item);
58
+ if (reasoning)
59
+ yield output.thinking(`reasoning:${item.id ?? position}`, reasoning, mode);
60
+ }
61
+ else if (item.type === "function_call") {
62
+ const key = `tool:${item.id}`;
63
+ yield output.tool(key, (0, types_1.string)(item.call_id, "call_id"), (0, types_1.string)(item.name, "name"));
64
+ yield output.reconcile(key, (0, types_1.string)(item.arguments, "arguments"));
65
+ }
66
+ }
67
+ const usage = (0, types_1.object)(message.usage, "usage");
68
+ output.usage(usage.input_tokens, usage.output_tokens);
69
+ yield output.finish((0, types_1.string)(message.stop_reason, "stop_reason"));
70
+ }
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ResponsesAdapter = void 0;
4
+ const claude_request_1 = require("./claude-request");
5
+ const claude_reasoning_1 = require("./claude-reasoning");
6
+ const claude_response_1 = require("./claude-response");
7
+ const responses_stream_1 = require("./responses-stream");
8
+ const stream_1 = require("./stream");
9
+ const transport_1 = require("./transport");
10
+ const request_1 = require("../codexWire/request");
11
+ const responses_tools_1 = require("../codexWire/responses-tools");
12
+ class ResponsesAdapter {
13
+ id = "responses";
14
+ async send(request) {
15
+ const normalized = request.client === "codex" ? (0, request_1.normalizeCodexRequest)(request.body) : undefined;
16
+ const tools = normalized ? new responses_tools_1.CodexResponsesTools(normalized) : undefined;
17
+ const body = normalized ? { ...(0, request_1.codexResponsesUpstreamBody)(normalized), model: request.model } : (0, claude_request_1.claudeToResponses)(request.body, request.model);
18
+ const { response, abort } = await (normalized ? transport_1.upstreamRequest : claude_reasoning_1.upstreamRequestWithReplay)(request, "/v1/responses", body);
19
+ if (!response.ok)
20
+ return response;
21
+ if (request.body.stream === true)
22
+ return (0, stream_1.streamResponse)(request.client === "codex" ? (0, stream_1.nativeStream)(response, "responses", tools?.hasCustomTools ? data => tools.events(data) : undefined) : (0, responses_stream_1.responsesToMessagesStream)(response, request.model, (0, claude_reasoning_1.thinkingMode)(request.body)), abort);
23
+ if (request.client === "codex")
24
+ return tools?.hasCustomTools ? (0, transport_1.jsonResponse)(response, body => tools.response(body)) : response;
25
+ return (0, transport_1.jsonResponse)(response, body => (0, claude_response_1.responsesToClaude)(body, request.model, (0, claude_reasoning_1.thinkingMode)(request.body)));
26
+ }
27
+ }
28
+ exports.ResponsesAdapter = ResponsesAdapter;
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.event = event;
4
+ exports.frames = frames;
5
+ exports.streamResponse = streamResponse;
6
+ exports.nativeStream = nativeStream;
7
+ const types_1 = require("./types");
8
+ function event(type, body) {
9
+ return `event: ${type}\ndata: ${JSON.stringify({ type, ...body })}\n\n`;
10
+ }
11
+ async function* frames(response) {
12
+ if (!response.body)
13
+ throw new types_1.AdapterError("上游串流沒有正文。", 502);
14
+ const reader = response.body.getReader(), decoder = new TextDecoder();
15
+ let buffer = "";
16
+ try {
17
+ while (true) {
18
+ const next = await reader.read();
19
+ buffer += next.done ? decoder.decode() : decoder.decode(next.value, { stream: true });
20
+ let match;
21
+ while ((match = /\r?\n\r?\n/.exec(buffer))) {
22
+ const raw = buffer.slice(0, match.index + match[0].length);
23
+ buffer = buffer.slice(raw.length);
24
+ const data = raw.split(/\r?\n/).filter(line => line.startsWith("data:")).map(line => line.slice(5).trimStart()).join("\n");
25
+ if (!data)
26
+ continue;
27
+ if (data === "[DONE]")
28
+ yield { raw, data };
29
+ else {
30
+ let parsed;
31
+ try {
32
+ parsed = JSON.parse(data);
33
+ }
34
+ catch {
35
+ throw new types_1.AdapterError("上游 SSE 資料不是有效 JSON。", 502);
36
+ }
37
+ yield { raw, data: (0, types_1.object)(parsed, "SSE data") };
38
+ }
39
+ }
40
+ if (buffer.length > 1_048_576)
41
+ throw new types_1.AdapterError("上游 SSE 事件過大。", 502);
42
+ if (next.done) {
43
+ if (buffer.trim())
44
+ throw new types_1.AdapterError("上游 SSE 事件未完整結束。", 502);
45
+ break;
46
+ }
47
+ }
48
+ }
49
+ finally {
50
+ await reader.cancel().catch(() => { });
51
+ reader.releaseLock();
52
+ }
53
+ }
54
+ // Pull-based transformation preserves backpressure; cancellation releases the upstream reader.
55
+ function streamResponse(source, abortUpstream) {
56
+ const encoder = new TextEncoder();
57
+ return new Response(new ReadableStream({
58
+ async pull(controller) {
59
+ try {
60
+ const next = await source.next();
61
+ if (next.done)
62
+ controller.close();
63
+ else
64
+ controller.enqueue(encoder.encode(next.value));
65
+ }
66
+ catch (error) {
67
+ abortUpstream();
68
+ controller.error(error);
69
+ }
70
+ },
71
+ async cancel() { abortUpstream(); await source.return(undefined).catch(() => { }); },
72
+ }), { headers: { "Content-Type": "text/event-stream; charset=utf-8", "Cache-Control": "no-cache" } });
73
+ }
74
+ async function* nativeStream(response, wire, transform) {
75
+ let completed = false;
76
+ let sequence = 0;
77
+ for await (const frame of frames(response)) {
78
+ if (frame.data !== "[DONE]") {
79
+ const type = frame.data.type;
80
+ if (type === "error" || type === "response.failed")
81
+ throw new types_1.AdapterError("上游串流回報失敗。", 502);
82
+ if (wire === "responses" ? type === "response.completed" || type === "response.incomplete" : type === "message_stop")
83
+ completed = true;
84
+ }
85
+ if (transform && frame.data !== "[DONE]") {
86
+ for (const data of transform(frame.data))
87
+ yield event(stringEventType(data.type), { ...data, sequence_number: sequence++ });
88
+ }
89
+ else
90
+ yield frame.raw;
91
+ }
92
+ if (!completed)
93
+ throw new types_1.AdapterError("上游串流中斷,未收到完成事件。", 502);
94
+ }
95
+ function stringEventType(type) {
96
+ if (typeof type !== "string")
97
+ throw new types_1.AdapterError("上游 SSE 缺少事件類型。", 502);
98
+ return type;
99
+ }
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.upstreamRequest = upstreamRequest;
4
+ exports.jsonResponse = jsonResponse;
5
+ const types_1 = require("./types");
6
+ async function upstreamRequest(request, path, body, extraHeaders = {}) {
7
+ const controller = new AbortController();
8
+ const response = await fetch(`${request.routerBaseUrl.replace(/\/+$/, "")}${path}`, {
9
+ method: "POST", signal: AbortSignal.any([request.signal, controller.signal]),
10
+ headers: { ...extraHeaders, "Content-Type": "application/json", Authorization: `Bearer ${request.apiKey}` },
11
+ body: JSON.stringify(body),
12
+ });
13
+ if (response.ok && body.stream === true && !response.headers.get("content-type")?.includes("text/event-stream")) {
14
+ controller.abort();
15
+ throw new types_1.AdapterError("上游沒有回傳所要求的 SSE 串流。", 502);
16
+ }
17
+ return { response, abort: () => controller.abort() };
18
+ }
19
+ async function jsonResponse(response, convert) {
20
+ try {
21
+ return Response.json(convert((0, types_1.object)(await response.json(), "upstream response")));
22
+ }
23
+ catch (error) {
24
+ throw new types_1.AdapterError(error instanceof types_1.AdapterError ? error.message : "上游 JSON 回應格式無效。", 502);
25
+ }
26
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AdapterError = void 0;
4
+ exports.object = object;
5
+ exports.list = list;
6
+ exports.string = string;
7
+ exports.allowedFields = allowedFields;
8
+ class AdapterError extends Error {
9
+ status;
10
+ constructor(message, status = 400) {
11
+ super(message);
12
+ this.status = status;
13
+ }
14
+ }
15
+ exports.AdapterError = AdapterError;
16
+ function object(value, context) {
17
+ if (!value || typeof value !== "object" || Array.isArray(value))
18
+ throw new AdapterError(`${context} 必須是物件。`);
19
+ return value;
20
+ }
21
+ function list(value, context) {
22
+ if (!Array.isArray(value))
23
+ throw new AdapterError(`${context} 必須是陣列。`);
24
+ return value;
25
+ }
26
+ function string(value, context) {
27
+ if (typeof value !== "string")
28
+ throw new AdapterError(`${context} 必須是文字。`);
29
+ return value;
30
+ }
31
+ function allowedFields(value, fields, context) {
32
+ const unsupported = Object.keys(value).find(key => value[key] !== undefined && !fields.includes(key));
33
+ if (unsupported)
34
+ throw new AdapterError(`${context} 無法轉換欄位 ${unsupported}。`);
35
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveAppearance = resolveAppearance;
4
+ /** 這台電腦選過的外觀蓋過系統。沒選過才跟系統。 */
5
+ function resolveAppearance(stored, systemDark) {
6
+ if (stored === "light" || stored === "dark") {
7
+ return stored;
8
+ }
9
+ return systemDark ? "dark" : "light";
10
+ }