aicodeswitch 4.0.4 → 5.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.
- package/README.md +6 -5
- package/UPGRADE.md +5 -6
- package/dist/server/coding-plan.js +94 -0
- package/dist/server/config-managed-fields.js +1 -0
- package/dist/server/conversions/compact.js +613 -0
- package/dist/server/conversions/detector.js +70 -0
- package/dist/server/conversions/index.js +290 -0
- package/dist/server/conversions/pairs/claude-completions/request.js +167 -0
- package/dist/server/conversions/pairs/claude-completions/response.js +56 -0
- package/dist/server/conversions/pairs/claude-completions/streaming.js +259 -0
- package/dist/server/conversions/pairs/claude-gemini/request.js +130 -0
- package/dist/server/conversions/pairs/claude-gemini/response.js +65 -0
- package/dist/server/conversions/pairs/claude-gemini/streaming.js +199 -0
- package/dist/server/conversions/pairs/claude-responses/request.js +190 -0
- package/dist/server/conversions/pairs/claude-responses/response.js +89 -0
- package/dist/server/conversions/pairs/claude-responses/streaming.js +266 -0
- package/dist/server/conversions/pairs/completions-claude/request.js +111 -0
- package/dist/server/conversions/pairs/completions-claude/response.js +67 -0
- package/dist/server/conversions/pairs/completions-claude/streaming.js +165 -0
- package/dist/server/conversions/pairs/completions-gemini/request.js +169 -0
- package/dist/server/conversions/pairs/completions-gemini/response.js +70 -0
- package/dist/server/conversions/pairs/completions-gemini/streaming.js +132 -0
- package/dist/server/conversions/pairs/completions-responses/request.js +149 -0
- package/dist/server/conversions/pairs/completions-responses/response.js +74 -0
- package/dist/server/conversions/pairs/completions-responses/streaming.js +189 -0
- package/dist/server/conversions/pairs/gemini-claude/request.js +118 -0
- package/dist/server/conversions/pairs/gemini-claude/response.js +45 -0
- package/dist/server/conversions/pairs/gemini-claude/streaming.js +146 -0
- package/dist/server/conversions/pairs/gemini-completions/request.js +151 -0
- package/dist/server/conversions/pairs/gemini-completions/response.js +54 -0
- package/dist/server/conversions/pairs/gemini-completions/streaming.js +108 -0
- package/dist/server/conversions/pairs/gemini-responses/request.js +18 -0
- package/dist/server/conversions/pairs/gemini-responses/response.js +18 -0
- package/dist/server/conversions/pairs/gemini-responses/streaming.js +43 -0
- package/dist/server/conversions/pairs/responses-claude/request.js +180 -0
- package/dist/server/conversions/pairs/responses-claude/response.js +70 -0
- package/dist/server/conversions/pairs/responses-claude/streaming.js +345 -0
- package/dist/server/conversions/pairs/responses-completions/request.js +207 -0
- package/dist/server/conversions/pairs/responses-completions/response.js +96 -0
- package/dist/server/conversions/pairs/responses-completions/streaming.js +344 -0
- package/dist/server/conversions/pairs/responses-gemini/request.js +18 -0
- package/dist/server/conversions/pairs/responses-gemini/response.js +18 -0
- package/dist/server/conversions/pairs/responses-gemini/streaming.js +43 -0
- package/dist/server/conversions/pairs/responses-responses/request.js +115 -0
- package/dist/server/conversions/pipeline.js +296 -0
- package/dist/server/conversions/stream-converter-adapter.js +49 -0
- package/dist/server/conversions/thinking/effort.js +61 -0
- package/dist/server/conversions/thinking/mapper.js +59 -0
- package/dist/server/conversions/thinking/providers.js +80 -0
- package/dist/server/conversions/types.js +5 -0
- package/dist/server/conversions/url-normalizer.js +58 -0
- package/dist/server/conversions/utils/format-mappers.js +57 -0
- package/dist/server/conversions/utils/id.js +33 -0
- package/dist/server/conversions/utils/stop-reasons.js +95 -0
- package/dist/server/conversions/utils/streaming-helpers.js +59 -0
- package/dist/server/conversions/utils/tool-schema.js +169 -0
- package/dist/server/conversions/utils/usage.js +82 -0
- package/dist/server/fs-database.js +465 -135
- package/dist/server/main.js +93 -33
- package/dist/server/original-config-reader.js +1 -1
- package/dist/server/proxy-server.js +887 -633
- package/dist/server/transformers/chunk-collector.js +5 -1
- package/dist/server/transformers/streaming.js +6 -3235
- package/dist/server/type-migration.js +2 -3
- package/dist/server/utils.js +5 -0
- package/dist/ui/assets/{index-C7G0whng.css → index-BHR12ImE.css} +1 -1
- package/dist/ui/assets/index-Rwiqttz-.js +517 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/server/transformers/transformers.js +0 -1767
- package/dist/ui/assets/index-Dl-B9pXM.js +0 -514
- package/schema/claude.schema.md +0 -946
- package/schema/deepseek-chat.schema.md +0 -799
- package/schema/gemini.schema.md +0 -1408
- package/schema/openai-chat-completions.schema.md +0 -1088
- package/schema/openai-responses.schema.md +0 -226196
- package/schema/stream.md +0 -2592
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Composite streaming converter: Gemini SSE → Responses API SSE.
|
|
4
|
+
*
|
|
5
|
+
* Chains: GeminiToCompletionsConverter → CompletionsToResponsesConverter
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.GeminiToResponsesConverter = void 0;
|
|
9
|
+
const streaming_js_1 = require("../completions-gemini/streaming.js");
|
|
10
|
+
const streaming_js_2 = require("../responses-completions/streaming.js");
|
|
11
|
+
const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
|
|
12
|
+
/**
|
|
13
|
+
* Composite converter: Gemini SSE → Completions SSE → Responses API SSE
|
|
14
|
+
*/
|
|
15
|
+
class GeminiToResponsesConverter {
|
|
16
|
+
constructor() {
|
|
17
|
+
Object.defineProperty(this, "first", {
|
|
18
|
+
enumerable: true,
|
|
19
|
+
configurable: true,
|
|
20
|
+
writable: true,
|
|
21
|
+
value: void 0
|
|
22
|
+
});
|
|
23
|
+
Object.defineProperty(this, "second", {
|
|
24
|
+
enumerable: true,
|
|
25
|
+
configurable: true,
|
|
26
|
+
writable: true,
|
|
27
|
+
value: void 0
|
|
28
|
+
});
|
|
29
|
+
this.first = new streaming_js_1.GeminiToCompletionsConverter();
|
|
30
|
+
this.second = new streaming_js_2.CompletionsToResponsesConverter();
|
|
31
|
+
}
|
|
32
|
+
convertEvent(event) {
|
|
33
|
+
const intermediate = this.first.convertEvent(event);
|
|
34
|
+
return intermediate.flatMap((e) => this.second.convertEvent(e));
|
|
35
|
+
}
|
|
36
|
+
flush() {
|
|
37
|
+
const intermediate = (0, streaming_helpers_js_1.flushConverter)(this.first);
|
|
38
|
+
const secondFromIntermediate = intermediate.flatMap((e) => this.second.convertEvent(e));
|
|
39
|
+
const secondFlush = (0, streaming_helpers_js_1.flushConverter)(this.second);
|
|
40
|
+
return [...secondFromIntermediate, ...secondFlush];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
exports.GeminiToResponsesConverter = GeminiToResponsesConverter;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Responses → Responses 同格式降级兼容转换。
|
|
4
|
+
*
|
|
5
|
+
* 当 Codex 等客户端向非 OpenAI 的第三方 Responses API 提供商(如火山方舟/豆包)发送请求时,
|
|
6
|
+
* 需要清理 OpenAI 私有扩展、转换消息格式,以确保兼容性。
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.downgradeResponsesRequest = downgradeResponsesRequest;
|
|
10
|
+
/**
|
|
11
|
+
* Responses API 非标准 tool 类型集合。
|
|
12
|
+
* OpenAI 私有扩展(custom、tool_search、web_search 等),
|
|
13
|
+
* 其他 Responses API 提供商不支持这些类型,直接转发会导致 400 错误。
|
|
14
|
+
*/
|
|
15
|
+
const NON_STANDARD_TOOL_TYPES = new Set([
|
|
16
|
+
'custom', 'tool_search', 'web_search', 'file_search', 'code_interpreter',
|
|
17
|
+
]);
|
|
18
|
+
/**
|
|
19
|
+
* Responses API 降级兼容时需移除的顶层字段集合。
|
|
20
|
+
* 这些字段非所有 Responses API 提供商都支持,开启降级兼容时会被移除以避免 400 错误。
|
|
21
|
+
* - reasoning: { effort } — OpenAI 推理努力程度控制(火山方舟不支持)
|
|
22
|
+
* - text: { verbosity } — OpenAI 响应 verbosity 控制(火山方舟用 text.format 做结构化输出)
|
|
23
|
+
* - prompt_cache_key — OpenAI 提示缓存键
|
|
24
|
+
* - client_metadata — OpenAI 客户端元数据
|
|
25
|
+
* - include — OpenAI 响应包含项(如 reasoning.encrypted_content)
|
|
26
|
+
* - parallel_tool_calls — OpenAI 并行工具调用控制(火山方舟未明确支持)
|
|
27
|
+
*/
|
|
28
|
+
const DOWNGRADE_STRIP_FIELDS = new Set([
|
|
29
|
+
'reasoning', 'text', 'prompt_cache_key', 'client_metadata',
|
|
30
|
+
'include', 'parallel_tool_calls',
|
|
31
|
+
]);
|
|
32
|
+
/**
|
|
33
|
+
* 将 input message 的 content 规范化为 ContentItem 数组。
|
|
34
|
+
* 火山方舟等第三方 Responses API 提供商要求 content 为 []*responses.ContentItem 数组格式,
|
|
35
|
+
* 不接受纯字符串。因此需要确保 content 始终为数组:
|
|
36
|
+
* - 字符串 → [{type: "input_text"/"output_text", text: "..."}](根据 role 选择类型)
|
|
37
|
+
* - 数组 → 保持不变
|
|
38
|
+
*/
|
|
39
|
+
function normalizeInputContent(content, role) {
|
|
40
|
+
// 已经是数组,保持不变
|
|
41
|
+
if (Array.isArray(content))
|
|
42
|
+
return content;
|
|
43
|
+
// 字符串:根据 role 转为对应的 ContentItem 数组
|
|
44
|
+
if (typeof content === 'string') {
|
|
45
|
+
const type = role === 'assistant' ? 'output_text' : 'input_text';
|
|
46
|
+
return [{ type, text: content }];
|
|
47
|
+
}
|
|
48
|
+
return content;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* 对 Responses API 请求体执行降级兼容转换。
|
|
52
|
+
*
|
|
53
|
+
* 处理项:
|
|
54
|
+
* 1. 过滤非标准 tool 类型(custom/tool_search/web_search 等),仅保留 function
|
|
55
|
+
* 2. 移除非标准顶层字段(reasoning/text/include/parallel_tool_calls 等)
|
|
56
|
+
* 3. 转换 input 消息格式:
|
|
57
|
+
* - role: "developer" → role: "system"
|
|
58
|
+
* - content 字符串 → content: [{type:"input_text"/"output_text", text:"..."}]
|
|
59
|
+
* - 补全 status: "completed"
|
|
60
|
+
*/
|
|
61
|
+
function downgradeResponsesRequest(body) {
|
|
62
|
+
if (!body || typeof body !== 'object')
|
|
63
|
+
return body;
|
|
64
|
+
let sanitized = body;
|
|
65
|
+
const ensureCopy = () => {
|
|
66
|
+
if (sanitized === body)
|
|
67
|
+
sanitized = Object.assign({}, sanitized);
|
|
68
|
+
return sanitized;
|
|
69
|
+
};
|
|
70
|
+
// 1. 过滤掉非标准 tool 类型,仅保留 function 类型
|
|
71
|
+
if (Array.isArray(body.tools)) {
|
|
72
|
+
const filteredTools = body.tools.filter((t) => !NON_STANDARD_TOOL_TYPES.has(t.type));
|
|
73
|
+
if (filteredTools.length !== body.tools.length) {
|
|
74
|
+
sanitized = Object.assign(Object.assign({}, sanitized), { tools: filteredTools });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// 2. 移除非标准顶层字段
|
|
78
|
+
for (const field of DOWNGRADE_STRIP_FIELDS) {
|
|
79
|
+
if (field in sanitized) {
|
|
80
|
+
ensureCopy();
|
|
81
|
+
delete sanitized[field];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// 3. 转换 input 消息格式
|
|
85
|
+
if (Array.isArray(sanitized.input)) {
|
|
86
|
+
let patched = false;
|
|
87
|
+
const patchedInput = sanitized.input.map((item) => {
|
|
88
|
+
if (!item || typeof item !== 'object')
|
|
89
|
+
return item;
|
|
90
|
+
let modified = Object.assign({}, item);
|
|
91
|
+
// 3a. developer → system
|
|
92
|
+
if (modified.role === 'developer') {
|
|
93
|
+
modified.role = 'system';
|
|
94
|
+
patched = true;
|
|
95
|
+
}
|
|
96
|
+
// 3b. 规范化 content 为 ContentItem 数组
|
|
97
|
+
const normalized = normalizeInputContent(modified.content, modified.role);
|
|
98
|
+
if (normalized !== modified.content) {
|
|
99
|
+
modified.content = normalized;
|
|
100
|
+
patched = true;
|
|
101
|
+
}
|
|
102
|
+
// 3c. 补全 status 字段
|
|
103
|
+
if (modified.type === 'message' && !('status' in modified)) {
|
|
104
|
+
modified.status = 'completed';
|
|
105
|
+
patched = true;
|
|
106
|
+
}
|
|
107
|
+
return modified;
|
|
108
|
+
});
|
|
109
|
+
if (patched) {
|
|
110
|
+
ensureCopy();
|
|
111
|
+
sanitized.input = patchedInput;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return sanitized;
|
|
115
|
+
}
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* SSE streaming pipeline for the conversion system.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the conversion StreamConverter in an async generator pipeline
|
|
6
|
+
* that handles SSE parsing, conversion, and serialization.
|
|
7
|
+
*/
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
18
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
19
|
+
var m = o[Symbol.asyncIterator], i;
|
|
20
|
+
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
|
21
|
+
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
|
22
|
+
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
|
23
|
+
};
|
|
24
|
+
var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
|
|
25
|
+
var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
|
|
26
|
+
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
|
27
|
+
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
|
28
|
+
return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
|
|
29
|
+
function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
|
|
30
|
+
function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
|
|
31
|
+
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
|
32
|
+
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
|
33
|
+
function fulfill(value) { resume("next", value); }
|
|
34
|
+
function reject(value) { resume("throw", value); }
|
|
35
|
+
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
|
36
|
+
};
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.SSEEventParser = void 0;
|
|
39
|
+
exports.serializeSSE = serializeSSE;
|
|
40
|
+
exports.createStreamPipeline = createStreamPipeline;
|
|
41
|
+
const stream_1 = require("stream");
|
|
42
|
+
const index_js_1 = require("./index.js");
|
|
43
|
+
/**
|
|
44
|
+
* Serialize an SSEEvent to wire format.
|
|
45
|
+
*/
|
|
46
|
+
function serializeSSE(event) {
|
|
47
|
+
let result = '';
|
|
48
|
+
if (event.event)
|
|
49
|
+
result += `event: ${event.event}\n`;
|
|
50
|
+
if (event.id)
|
|
51
|
+
result += `id: ${event.id}\n`;
|
|
52
|
+
const dataStr = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);
|
|
53
|
+
result += `data: ${dataStr}\n\n`;
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Lightweight SSE event parser.
|
|
58
|
+
*/
|
|
59
|
+
class SSEEventParser {
|
|
60
|
+
constructor() {
|
|
61
|
+
Object.defineProperty(this, "buffer", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
writable: true,
|
|
65
|
+
value: ''
|
|
66
|
+
});
|
|
67
|
+
Object.defineProperty(this, "currentEvent", {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
writable: true,
|
|
71
|
+
value: {}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
pushChunk(chunk) {
|
|
75
|
+
this.buffer += chunk.toString();
|
|
76
|
+
const lines = this.buffer.split('\n');
|
|
77
|
+
this.buffer = lines.pop() || '';
|
|
78
|
+
return this.consumeLines(lines);
|
|
79
|
+
}
|
|
80
|
+
flush() {
|
|
81
|
+
if (!this.buffer) {
|
|
82
|
+
return this.finishCurrentEvent();
|
|
83
|
+
}
|
|
84
|
+
const lines = this.buffer.split('\n');
|
|
85
|
+
this.buffer = '';
|
|
86
|
+
const events = this.consumeLines(lines);
|
|
87
|
+
return events.concat(this.finishCurrentEvent());
|
|
88
|
+
}
|
|
89
|
+
consumeLines(lines) {
|
|
90
|
+
const events = [];
|
|
91
|
+
for (const rawLine of lines) {
|
|
92
|
+
const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
|
|
93
|
+
if (line === '') {
|
|
94
|
+
events.push(...this.finishCurrentEvent());
|
|
95
|
+
}
|
|
96
|
+
else if (line.startsWith('event: ')) {
|
|
97
|
+
this.currentEvent.event = line.substring(7);
|
|
98
|
+
}
|
|
99
|
+
else if (line.startsWith('id: ')) {
|
|
100
|
+
this.currentEvent.id = line.substring(4);
|
|
101
|
+
}
|
|
102
|
+
else if (line.startsWith('data: ')) {
|
|
103
|
+
this.currentEvent.data = (this.currentEvent.data || '') + line.substring(6);
|
|
104
|
+
}
|
|
105
|
+
else if (line.startsWith('data:')) {
|
|
106
|
+
this.currentEvent.data = (this.currentEvent.data || '') + line.substring(5);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return events;
|
|
110
|
+
}
|
|
111
|
+
finishCurrentEvent() {
|
|
112
|
+
if (this.currentEvent.data === undefined) {
|
|
113
|
+
this.currentEvent = {};
|
|
114
|
+
return [];
|
|
115
|
+
}
|
|
116
|
+
const event = this.currentEvent;
|
|
117
|
+
this.currentEvent = {};
|
|
118
|
+
return [event];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.SSEEventParser = SSEEventParser;
|
|
122
|
+
/**
|
|
123
|
+
* Node.js Transform stream that parses SSE bytes into SSEEvent objects.
|
|
124
|
+
*/
|
|
125
|
+
class SSEParserTransform extends stream_1.Transform {
|
|
126
|
+
constructor() {
|
|
127
|
+
super({ objectMode: true });
|
|
128
|
+
Object.defineProperty(this, "parser", {
|
|
129
|
+
enumerable: true,
|
|
130
|
+
configurable: true,
|
|
131
|
+
writable: true,
|
|
132
|
+
value: new SSEEventParser()
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
_transform(chunk, _encoding, callback) {
|
|
136
|
+
const events = this.parser.pushChunk(chunk);
|
|
137
|
+
for (const event of events) {
|
|
138
|
+
this.push(event);
|
|
139
|
+
}
|
|
140
|
+
callback();
|
|
141
|
+
}
|
|
142
|
+
_flush(callback) {
|
|
143
|
+
const events = this.parser.flush();
|
|
144
|
+
for (const event of events) {
|
|
145
|
+
this.push(event);
|
|
146
|
+
}
|
|
147
|
+
callback();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Convert a Web ReadableStream to a Node.js Readable.
|
|
152
|
+
*/
|
|
153
|
+
function toNodeReadable(stream) {
|
|
154
|
+
if (stream instanceof ReadableStream) {
|
|
155
|
+
const reader = stream.getReader();
|
|
156
|
+
return new stream_1.Readable({
|
|
157
|
+
read() {
|
|
158
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
159
|
+
try {
|
|
160
|
+
const { done, value } = yield reader.read();
|
|
161
|
+
if (done)
|
|
162
|
+
this.push(null);
|
|
163
|
+
else
|
|
164
|
+
this.push(value);
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
this.destroy(e);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return stream;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Check if the format pair should use raw passthrough (no conversion needed).
|
|
177
|
+
*/
|
|
178
|
+
function shouldPassthroughRaw(fromFormat, toFormat) {
|
|
179
|
+
return fromFormat === toFormat;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Creates a streaming pipeline that converts SSE events from one format to another.
|
|
183
|
+
*
|
|
184
|
+
* @param upstreamBody - The upstream response body stream
|
|
185
|
+
* @param fromFormat - The upstream format (source)
|
|
186
|
+
* @param toFormat - The client format (target)
|
|
187
|
+
* @param onEvent - Optional callback for monitoring parsed events (e.g., usage tracking)
|
|
188
|
+
* @returns AsyncGenerator yielding SSE-formatted string chunks
|
|
189
|
+
*/
|
|
190
|
+
function createStreamPipeline(upstreamBody, fromFormat, toFormat, onEvent) {
|
|
191
|
+
return __asyncGenerator(this, arguments, function* createStreamPipeline_1() {
|
|
192
|
+
var _a, e_1, _b, _c;
|
|
193
|
+
const nodeStream = toNodeReadable(upstreamBody);
|
|
194
|
+
// Raw passthrough: same format, no conversion needed
|
|
195
|
+
if (shouldPassthroughRaw(fromFormat, toFormat)) {
|
|
196
|
+
const parser = onEvent ? new SSEEventParser() : null;
|
|
197
|
+
try {
|
|
198
|
+
for (var _d = true, _e = __asyncValues(nodeStream), _f; _f = yield __await(_e.next()), _a = _f.done, !_a; _d = true) {
|
|
199
|
+
_c = _f.value;
|
|
200
|
+
_d = false;
|
|
201
|
+
const chunk = _c;
|
|
202
|
+
if (parser) {
|
|
203
|
+
const parsedEvents = parser.pushChunk(typeof chunk === 'string' ? chunk : chunk.toString());
|
|
204
|
+
for (const event of parsedEvents) {
|
|
205
|
+
try {
|
|
206
|
+
const parsed = event.data ? JSON.parse(event.data) : null;
|
|
207
|
+
onEvent(parsed);
|
|
208
|
+
}
|
|
209
|
+
catch ( /* ignore */_g) { /* ignore */ }
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
yield yield __await(chunk);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (e_1_1) { e_1 = { error: e_1_1 }; }
|
|
216
|
+
finally {
|
|
217
|
+
try {
|
|
218
|
+
if (!_d && !_a && (_b = _e.return)) yield __await(_b.call(_e));
|
|
219
|
+
}
|
|
220
|
+
finally { if (e_1) throw e_1.error; }
|
|
221
|
+
}
|
|
222
|
+
if (parser) {
|
|
223
|
+
for (const event of parser.flush()) {
|
|
224
|
+
try {
|
|
225
|
+
const parsed = event.data ? JSON.parse(event.data) : null;
|
|
226
|
+
onEvent(parsed);
|
|
227
|
+
}
|
|
228
|
+
catch ( /* ignore */_h) { /* ignore */ }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return yield __await(void 0);
|
|
232
|
+
}
|
|
233
|
+
// Conversion path: parse SSE → convert → serialize
|
|
234
|
+
const parser = new SSEParserTransform();
|
|
235
|
+
const eventStream = nodeStream.pipe(parser);
|
|
236
|
+
const eventQueue = [];
|
|
237
|
+
let resolveEvent = null;
|
|
238
|
+
let done = false;
|
|
239
|
+
eventStream.on('data', (event) => {
|
|
240
|
+
if (resolveEvent) {
|
|
241
|
+
resolveEvent(event);
|
|
242
|
+
resolveEvent = null;
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
eventQueue.push(event);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
eventStream.on('end', () => {
|
|
249
|
+
done = true;
|
|
250
|
+
if (resolveEvent) {
|
|
251
|
+
resolveEvent(null);
|
|
252
|
+
resolveEvent = null;
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
eventStream.on('error', () => {
|
|
256
|
+
done = true;
|
|
257
|
+
if (resolveEvent) {
|
|
258
|
+
resolveEvent(null);
|
|
259
|
+
resolveEvent = null;
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
const getNextEvent = () => {
|
|
263
|
+
if (eventQueue.length > 0)
|
|
264
|
+
return Promise.resolve(eventQueue.shift());
|
|
265
|
+
if (done)
|
|
266
|
+
return Promise.resolve(null);
|
|
267
|
+
return new Promise((resolve) => {
|
|
268
|
+
resolveEvent = resolve;
|
|
269
|
+
});
|
|
270
|
+
};
|
|
271
|
+
const converter = (0, index_js_1.createStreamConverter)({ fromFormat, toFormat });
|
|
272
|
+
while (true) {
|
|
273
|
+
const event = yield __await(getNextEvent());
|
|
274
|
+
if (!event)
|
|
275
|
+
break;
|
|
276
|
+
if (onEvent) {
|
|
277
|
+
try {
|
|
278
|
+
const parsed = event.data ? JSON.parse(event.data) : null;
|
|
279
|
+
onEvent(parsed);
|
|
280
|
+
}
|
|
281
|
+
catch ( /* ignore */_j) { /* ignore */ }
|
|
282
|
+
}
|
|
283
|
+
const convertedEvents = converter.convertEvent(event);
|
|
284
|
+
for (const converted of convertedEvents) {
|
|
285
|
+
yield yield __await(serializeSSE(converted));
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
// Flush any remaining state from the converter
|
|
289
|
+
if (converter.flush) {
|
|
290
|
+
const finalEvents = converter.flush();
|
|
291
|
+
for (const converted of finalEvents) {
|
|
292
|
+
yield yield __await(serializeSSE(converted));
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* StreamConverterAdapter — 将 conversions StreamConverter 桥接为 Node.js Transform 流。
|
|
4
|
+
*
|
|
5
|
+
* 新系统的 StreamConverter 是纯对象接口(convertEvent/flush),
|
|
6
|
+
* 而代理管道使用 Node.js Transform 流通过 stream.pipeline() 串联。
|
|
7
|
+
* 此 adapter 在两者之间做透明桥接。
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.StreamConverterAdapter = void 0;
|
|
11
|
+
const stream_1 = require("stream");
|
|
12
|
+
class StreamConverterAdapter extends stream_1.Transform {
|
|
13
|
+
constructor(converter) {
|
|
14
|
+
super({ objectMode: true });
|
|
15
|
+
Object.defineProperty(this, "converter", {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
configurable: true,
|
|
18
|
+
writable: true,
|
|
19
|
+
value: converter
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
_transform(event, _encoding, callback) {
|
|
23
|
+
try {
|
|
24
|
+
const convertedEvents = this.converter.convertEvent(event);
|
|
25
|
+
for (const converted of convertedEvents) {
|
|
26
|
+
this.push(converted);
|
|
27
|
+
}
|
|
28
|
+
callback();
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
callback(err);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
_flush(callback) {
|
|
35
|
+
try {
|
|
36
|
+
if (this.converter.flush) {
|
|
37
|
+
const finalEvents = this.converter.flush();
|
|
38
|
+
for (const converted of finalEvents) {
|
|
39
|
+
this.push(converted);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
callback();
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
callback(err);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
exports.StreamConverterAdapter = StreamConverterAdapter;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Thinking parameter mapping across API formats.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.claudeThinkingToReasoningEffort = claudeThinkingToReasoningEffort;
|
|
7
|
+
exports.claudeThinkingToResponsesReasoning = claudeThinkingToResponsesReasoning;
|
|
8
|
+
exports.reasoningEffortToClaudeThinking = reasoningEffortToClaudeThinking;
|
|
9
|
+
exports.isOSeriesModel = isOSeriesModel;
|
|
10
|
+
/** Maps Claude thinking config to reasoning_effort string */
|
|
11
|
+
function claudeThinkingToReasoningEffort(thinking) {
|
|
12
|
+
var _a;
|
|
13
|
+
if (!thinking || thinking.type === 'disabled')
|
|
14
|
+
return null;
|
|
15
|
+
// Priority: output_config.effort
|
|
16
|
+
if ((_a = thinking.output_config) === null || _a === void 0 ? void 0 : _a.effort) {
|
|
17
|
+
return thinking.output_config.effort;
|
|
18
|
+
}
|
|
19
|
+
// Fallback: derive from type + budget_tokens
|
|
20
|
+
if (thinking.type === 'adaptive')
|
|
21
|
+
return 'xhigh';
|
|
22
|
+
if (thinking.budget_tokens !== undefined) {
|
|
23
|
+
if (thinking.budget_tokens < 4000)
|
|
24
|
+
return 'low';
|
|
25
|
+
if (thinking.budget_tokens < 16000)
|
|
26
|
+
return 'medium';
|
|
27
|
+
return 'high';
|
|
28
|
+
}
|
|
29
|
+
return 'medium';
|
|
30
|
+
}
|
|
31
|
+
/** Maps Claude thinking config to Responses API reasoning parameter */
|
|
32
|
+
function claudeThinkingToResponsesReasoning(thinking) {
|
|
33
|
+
const effort = claudeThinkingToReasoningEffort(thinking);
|
|
34
|
+
if (!effort)
|
|
35
|
+
return null;
|
|
36
|
+
return { effort };
|
|
37
|
+
}
|
|
38
|
+
/** Reverse: reasoning_effort string → Claude thinking config */
|
|
39
|
+
function reasoningEffortToClaudeThinking(effort) {
|
|
40
|
+
if (!effort)
|
|
41
|
+
return undefined;
|
|
42
|
+
switch (effort) {
|
|
43
|
+
case 'low':
|
|
44
|
+
return { type: 'enabled', budget_tokens: 2048 };
|
|
45
|
+
case 'medium':
|
|
46
|
+
return { type: 'enabled', budget_tokens: 8192 };
|
|
47
|
+
case 'high':
|
|
48
|
+
return { type: 'enabled', budget_tokens: 32000 };
|
|
49
|
+
case 'xhigh':
|
|
50
|
+
return { type: 'adaptive' };
|
|
51
|
+
default:
|
|
52
|
+
return { type: 'enabled', budget_tokens: 8192 };
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Check if a model is an OpenAI o-series reasoning model */
|
|
56
|
+
function isOSeriesModel(model) {
|
|
57
|
+
if (!model)
|
|
58
|
+
return false;
|
|
59
|
+
const lower = model.toLowerCase();
|
|
60
|
+
return /\bo[1-9]\b/.test(lower) || /\bo4\b/.test(lower) || /\bgpt-?5\b/.test(lower);
|
|
61
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Thinking content mapping across API formats.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.thinkingToReasoningContent = thinkingToReasoningContent;
|
|
7
|
+
exports.reasoningContentToThinking = reasoningContentToThinking;
|
|
8
|
+
exports.reasoningToThinking = reasoningToThinking;
|
|
9
|
+
exports.thinkingToReasoningSummary = thinkingToReasoningSummary;
|
|
10
|
+
exports.fixThinkingHistory = fixThinkingHistory;
|
|
11
|
+
exports.redactedThinkingPlaceholder = redactedThinkingPlaceholder;
|
|
12
|
+
/** Claude thinking text → reasoning_content string */
|
|
13
|
+
function thinkingToReasoningContent(thinking) {
|
|
14
|
+
return thinking;
|
|
15
|
+
}
|
|
16
|
+
/** reasoning_content string → Claude thinking block */
|
|
17
|
+
function reasoningContentToThinking(content) {
|
|
18
|
+
return { type: 'thinking', thinking: content };
|
|
19
|
+
}
|
|
20
|
+
/** Responses API reasoning summary → Claude thinking block */
|
|
21
|
+
function reasoningToThinking(summary) {
|
|
22
|
+
const text = summary
|
|
23
|
+
.filter((s) => s.type === 'summary_text')
|
|
24
|
+
.map((s) => s.text || '')
|
|
25
|
+
.join('');
|
|
26
|
+
return { type: 'thinking', thinking: text || '' };
|
|
27
|
+
}
|
|
28
|
+
/** Claude thinking text → Responses API reasoning summary array */
|
|
29
|
+
function thinkingToReasoningSummary(thinking) {
|
|
30
|
+
return [{ type: 'summary_text', text: thinking }];
|
|
31
|
+
}
|
|
32
|
+
/** Fix history messages: ensure thinking/reasoning_content is present alongside tool use */
|
|
33
|
+
function fixThinkingHistory(messages, format) {
|
|
34
|
+
return messages.map(msg => {
|
|
35
|
+
var _a, _b, _c, _d, _e;
|
|
36
|
+
if (msg.role !== 'assistant')
|
|
37
|
+
return msg;
|
|
38
|
+
const hasToolUse = (format === 'claude' && ((_b = (_a = msg.content) === null || _a === void 0 ? void 0 : _a.some) === null || _b === void 0 ? void 0 : _b.call(_a, (b) => b.type === 'tool_use'))) ||
|
|
39
|
+
(format === 'completions' && (((_c = msg.tool_calls) === null || _c === void 0 ? void 0 : _c.length) > 0));
|
|
40
|
+
if (!hasToolUse)
|
|
41
|
+
return msg;
|
|
42
|
+
if (format === 'claude') {
|
|
43
|
+
const hasThinking = (_e = (_d = msg.content) === null || _d === void 0 ? void 0 : _d.some) === null || _e === void 0 ? void 0 : _e.call(_d, (b) => b.type === 'thinking');
|
|
44
|
+
if (!hasThinking) {
|
|
45
|
+
return Object.assign(Object.assign({}, msg), { content: [{ type: 'thinking', thinking: 'tool call' }, ...(msg.content || [])] });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
if (!msg.reasoning_content) {
|
|
50
|
+
return Object.assign(Object.assign({}, msg), { reasoning_content: 'tool call' });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return msg;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
/** Placeholder for redacted thinking blocks */
|
|
57
|
+
function redactedThinkingPlaceholder() {
|
|
58
|
+
return '[redacted thinking]';
|
|
59
|
+
}
|