aicodeswitch 4.0.4 → 5.0.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 +285 -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 +155 -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 +76 -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-DjdBW1yu.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,76 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Multi-provider reasoning configuration.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DEFAULT_CONFIG = void 0;
|
|
7
|
+
exports.getReasoningConfig = getReasoningConfig;
|
|
8
|
+
exports.applyReasoningConfig = applyReasoningConfig;
|
|
9
|
+
const PROVIDER_CONFIGS = [
|
|
10
|
+
{ patterns: ['deepseek'], config: { supportsThinking: true, supportsEffort: true, thinkingParam: 'thinking', effortParam: 'reasoning_effort', effortValueMode: 'deepseek', outputFormat: 'reasoning_content' } },
|
|
11
|
+
{ patterns: ['moonshot', 'kimi'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
|
|
12
|
+
{ patterns: ['qwen', 'dashscope'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'enable_thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
|
|
13
|
+
{ patterns: ['zhipu', 'glm', 'bigmodel'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
|
|
14
|
+
{ patterns: ['minimax'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'reasoning_split', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_details' } },
|
|
15
|
+
{ patterns: ['mimo', 'xiaomimimo'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
|
|
16
|
+
{ patterns: ['openrouter'], config: { supportsThinking: false, supportsEffort: true, thinkingParam: 'none', effortParam: 'reasoning.effort', effortValueMode: 'openrouter', outputFormat: 'reasoning' } },
|
|
17
|
+
{ patterns: ['siliconflow'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'enable_thinking', effortParam: 'none', effortValueMode: 'passthrough', outputFormat: 'reasoning_content' } },
|
|
18
|
+
{ patterns: ['stepfun', 'step'], config: { supportsThinking: true, supportsEffort: false, thinkingParam: 'none', effortParam: 'reasoning_effort', effortValueMode: 'low_high', outputFormat: 'reasoning' } },
|
|
19
|
+
];
|
|
20
|
+
const DEFAULT_CONFIG = {
|
|
21
|
+
supportsThinking: false,
|
|
22
|
+
supportsEffort: false,
|
|
23
|
+
thinkingParam: 'none',
|
|
24
|
+
effortParam: 'none',
|
|
25
|
+
effortValueMode: 'passthrough',
|
|
26
|
+
outputFormat: 'reasoning_content',
|
|
27
|
+
};
|
|
28
|
+
exports.DEFAULT_CONFIG = DEFAULT_CONFIG;
|
|
29
|
+
/** Detect reasoning config for a given provider */
|
|
30
|
+
function getReasoningConfig(providerName, baseUrl, model) {
|
|
31
|
+
const haystack = `${providerName} ${baseUrl} ${model}`.toLowerCase();
|
|
32
|
+
for (const { patterns, config } of PROVIDER_CONFIGS) {
|
|
33
|
+
if (patterns.some(p => haystack.includes(p))) {
|
|
34
|
+
return config;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return DEFAULT_CONFIG;
|
|
38
|
+
}
|
|
39
|
+
/** Apply reasoning config to a request body */
|
|
40
|
+
function applyReasoningConfig(body, config, effort) {
|
|
41
|
+
if (!effort)
|
|
42
|
+
return body;
|
|
43
|
+
const result = Object.assign({}, body);
|
|
44
|
+
if (config.supportsThinking && config.thinkingParam !== 'none') {
|
|
45
|
+
switch (config.thinkingParam) {
|
|
46
|
+
case 'thinking':
|
|
47
|
+
result.thinking = { type: 'enabled' };
|
|
48
|
+
break;
|
|
49
|
+
case 'enable_thinking':
|
|
50
|
+
result.enable_thinking = true;
|
|
51
|
+
break;
|
|
52
|
+
case 'reasoning_split':
|
|
53
|
+
result.reasoning_split = true;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (config.supportsEffort && config.effortParam !== 'none' && effort) {
|
|
58
|
+
const mappedEffort = mapEffortValue(effort, config.effortValueMode);
|
|
59
|
+
if (config.effortParam === 'reasoning.effort') {
|
|
60
|
+
result.reasoning = result.reasoning || {};
|
|
61
|
+
result.reasoning.effort = mappedEffort;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
result[config.effortParam] = mappedEffort;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
function mapEffortValue(effort, mode) {
|
|
70
|
+
switch (mode) {
|
|
71
|
+
case 'deepseek': return 'high'; // DeepSeek only supports high/max
|
|
72
|
+
case 'low_high': return ['low', 'medium'].includes(effort) ? 'low' : 'high';
|
|
73
|
+
case 'openrouter': return ['xhigh', 'max'].includes(effort) ? 'xhigh' : effort;
|
|
74
|
+
default: return effort;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* API URL 规范化工具
|
|
4
|
+
*
|
|
5
|
+
* 解决用户填写的 apiUrl 可能已包含版本路径(如 /v1、/v3、/v4、/v1beta),
|
|
6
|
+
* 而系统又会硬编码拼接版本路径,导致出现双重版本路径的问题。
|
|
7
|
+
*
|
|
8
|
+
* 例如:
|
|
9
|
+
* - https://xxx.com/api/anthropic/v1 + /v1/messages → https://xxx.com/api/anthropic/v1/messages
|
|
10
|
+
* - https://xxx.com/api/anthropic/v4 + /v1/messages → https://xxx.com/api/anthropic/v4/messages
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.buildUpstreamUrl = buildUpstreamUrl;
|
|
14
|
+
exports.normalizeApiUrl = normalizeApiUrl;
|
|
15
|
+
// 匹配末尾的版本路径,如 /v1, /v4, /v1beta, /v2alpha 等
|
|
16
|
+
const VERSION_SUFFIX_REGEX = /(\/v\d+[a-z]*)$/;
|
|
17
|
+
// 匹配路径开头的版本段,如 /v1/, /v4/, /v1beta/ 等
|
|
18
|
+
const VERSION_PREFIX_REGEX = /^\/v\d+[a-z]*\//;
|
|
19
|
+
/**
|
|
20
|
+
* 智能构建上游请求 URL
|
|
21
|
+
*
|
|
22
|
+
* 检测 apiUrl 末尾的版本路径,智能处理版本冲突:
|
|
23
|
+
* - 若 apiUrl 无版本后缀:直接拼接 appendPath
|
|
24
|
+
* - 若 apiUrl 有版本后缀:剥离后用该版本替换 appendPath 中的版本段
|
|
25
|
+
*
|
|
26
|
+
* @param apiUrl 用户配置的 API 地址(可能包含末尾版本路径)
|
|
27
|
+
* @param appendPath 需要拼接的路径(如 /v1/messages, /v1/chat/completions)
|
|
28
|
+
* @returns 规范化后的完整 URL
|
|
29
|
+
*/
|
|
30
|
+
function buildUpstreamUrl(apiUrl, appendPath) {
|
|
31
|
+
// 去除末尾斜杠
|
|
32
|
+
const url = apiUrl.replace(/\/+$/, '');
|
|
33
|
+
// 检测 apiUrl 末尾是否包含版本路径
|
|
34
|
+
const versionMatch = url.match(VERSION_SUFFIX_REGEX);
|
|
35
|
+
if (!versionMatch) {
|
|
36
|
+
// 无版本后缀,直接拼接
|
|
37
|
+
return `${url}${appendPath}`;
|
|
38
|
+
}
|
|
39
|
+
// 剥离版本后缀得到 baseUrl
|
|
40
|
+
const baseUrl = url.slice(0, -versionMatch[1].length);
|
|
41
|
+
const extractedVersion = versionMatch[1]; // 如 '/v4'
|
|
42
|
+
// 将 appendPath 中开头的版本段替换为提取到的版本
|
|
43
|
+
const normalizedPath = appendPath.replace(VERSION_PREFIX_REGEX, `${extractedVersion}/`);
|
|
44
|
+
return `${baseUrl}${normalizedPath}`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 规范化 API URL,去除末尾斜杠和版本后缀
|
|
48
|
+
*
|
|
49
|
+
* @param apiUrl 用户配置的 API 地址
|
|
50
|
+
* @returns 去除末尾斜杠和版本路径后的基础 URL
|
|
51
|
+
*/
|
|
52
|
+
function normalizeApiUrl(apiUrl) {
|
|
53
|
+
// 去除末尾斜杠
|
|
54
|
+
let url = apiUrl.replace(/\/+$/, '');
|
|
55
|
+
// 去除末尾版本路径
|
|
56
|
+
url = url.replace(VERSION_SUFFIX_REGEX, '');
|
|
57
|
+
return url;
|
|
58
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared format mapping helpers for Gemini ↔ Completions conversions.
|
|
4
|
+
* Eliminates duplication between response.ts and streaming.ts within the pair.
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.mapGeminiFinishReason = mapGeminiFinishReason;
|
|
8
|
+
exports.mapCompletionsFinishReason = mapCompletionsFinishReason;
|
|
9
|
+
exports.mapGeminiUsage = mapGeminiUsage;
|
|
10
|
+
exports.mapCompletionsUsage = mapCompletionsUsage;
|
|
11
|
+
/** Map Gemini finishReason to Completions finish_reason. */
|
|
12
|
+
function mapGeminiFinishReason(reason) {
|
|
13
|
+
switch (reason) {
|
|
14
|
+
case 'STOP': return 'stop';
|
|
15
|
+
case 'MAX_TOKENS': return 'length';
|
|
16
|
+
case 'SAFETY': return 'content_filter';
|
|
17
|
+
case 'RECITATION': return 'content_filter';
|
|
18
|
+
case 'FINISH_REASON_UNSPECIFIED': return 'stop';
|
|
19
|
+
default: return 'stop';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/** Map Completions finish_reason to Gemini finishReason. */
|
|
23
|
+
function mapCompletionsFinishReason(reason) {
|
|
24
|
+
switch (reason) {
|
|
25
|
+
case 'stop': return 'STOP';
|
|
26
|
+
case 'length': return 'MAX_TOKENS';
|
|
27
|
+
case 'content_filter': return 'SAFETY';
|
|
28
|
+
case 'tool_calls': return 'STOP';
|
|
29
|
+
default: return 'STOP';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/** Map Gemini usageMetadata to Completions usage. */
|
|
33
|
+
function mapGeminiUsage(metadata) {
|
|
34
|
+
var _a, _b, _c;
|
|
35
|
+
if (!metadata)
|
|
36
|
+
return { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
37
|
+
const prompt = (_a = metadata.promptTokenCount) !== null && _a !== void 0 ? _a : 0;
|
|
38
|
+
const completion = (_b = metadata.candidatesTokenCount) !== null && _b !== void 0 ? _b : 0;
|
|
39
|
+
return {
|
|
40
|
+
prompt_tokens: prompt,
|
|
41
|
+
completion_tokens: completion,
|
|
42
|
+
total_tokens: (_c = metadata.totalTokenCount) !== null && _c !== void 0 ? _c : (prompt + completion),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/** Map Completions usage to Gemini usageMetadata. */
|
|
46
|
+
function mapCompletionsUsage(usage) {
|
|
47
|
+
var _a, _b, _c;
|
|
48
|
+
if (!usage)
|
|
49
|
+
return { promptTokenCount: 0, candidatesTokenCount: 0, totalTokenCount: 0 };
|
|
50
|
+
const prompt = (_a = usage.prompt_tokens) !== null && _a !== void 0 ? _a : 0;
|
|
51
|
+
const completion = (_b = usage.completion_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
52
|
+
return {
|
|
53
|
+
promptTokenCount: prompt,
|
|
54
|
+
candidatesTokenCount: completion,
|
|
55
|
+
totalTokenCount: (_c = usage.total_tokens) !== null && _c !== void 0 ? _c : (prompt + completion),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateMessageId = generateMessageId;
|
|
4
|
+
exports.generateToolUseId = generateToolUseId;
|
|
5
|
+
exports.generateCompletionsId = generateCompletionsId;
|
|
6
|
+
exports.generateResponseId = generateResponseId;
|
|
7
|
+
exports.generateCallId = generateCallId;
|
|
8
|
+
exports.generateGeminiSynthId = generateGeminiSynthId;
|
|
9
|
+
const crypto_1 = require("crypto");
|
|
10
|
+
/** Generate a Claude message ID: msg_<uuid> */
|
|
11
|
+
function generateMessageId() {
|
|
12
|
+
return `msg_${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 24)}`;
|
|
13
|
+
}
|
|
14
|
+
/** Generate a Claude tool use ID: toolu_<uuid> */
|
|
15
|
+
function generateToolUseId() {
|
|
16
|
+
return `toolu_${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 24)}`;
|
|
17
|
+
}
|
|
18
|
+
/** Generate an OpenAI Chat completion ID: chatcmpl-<uuid> */
|
|
19
|
+
function generateCompletionsId() {
|
|
20
|
+
return `chatcmpl-${(0, crypto_1.randomUUID)().replace(/-/g, '')}`;
|
|
21
|
+
}
|
|
22
|
+
/** Generate an OpenAI Responses API ID: resp_<uuid> */
|
|
23
|
+
function generateResponseId() {
|
|
24
|
+
return `resp_${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 24)}`;
|
|
25
|
+
}
|
|
26
|
+
/** Generate a generic call ID for Responses API function calls */
|
|
27
|
+
function generateCallId() {
|
|
28
|
+
return `call_${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 24)}`;
|
|
29
|
+
}
|
|
30
|
+
/** Generate a Gemini synthetic tool call ID */
|
|
31
|
+
function generateGeminiSynthId() {
|
|
32
|
+
return `gemini_synth_${(0, crypto_1.randomUUID)().replace(/-/g, '').slice(0, 16)}`;
|
|
33
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Stop reason / finish reason / status mapping across all API formats.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.completionsToClaudeStopReason = completionsToClaudeStopReason;
|
|
7
|
+
exports.claudeToCompletionsStopReason = claudeToCompletionsStopReason;
|
|
8
|
+
exports.geminiToClaudeStopReason = geminiToClaudeStopReason;
|
|
9
|
+
exports.claudeToGeminiStopReason = claudeToGeminiStopReason;
|
|
10
|
+
exports.responsesToClaudeStopReason = responsesToClaudeStopReason;
|
|
11
|
+
exports.claudeToResponsesStatus = claudeToResponsesStatus;
|
|
12
|
+
exports.completionsToResponsesFinishReason = completionsToResponsesFinishReason;
|
|
13
|
+
exports.responsesToCompletionsFinishReason = responsesToCompletionsFinishReason;
|
|
14
|
+
// --- OpenAI Chat Completions ↔ Claude Messages ---
|
|
15
|
+
function completionsToClaudeStopReason(reason) {
|
|
16
|
+
if (!reason)
|
|
17
|
+
return 'end_turn';
|
|
18
|
+
switch (reason) {
|
|
19
|
+
case 'tool_calls':
|
|
20
|
+
case 'function_call':
|
|
21
|
+
return 'tool_use';
|
|
22
|
+
case 'length':
|
|
23
|
+
return 'max_tokens';
|
|
24
|
+
default:
|
|
25
|
+
return 'end_turn';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function claudeToCompletionsStopReason(reason) {
|
|
29
|
+
if (!reason)
|
|
30
|
+
return 'stop';
|
|
31
|
+
switch (reason) {
|
|
32
|
+
case 'tool_use':
|
|
33
|
+
return 'tool_calls';
|
|
34
|
+
case 'max_tokens':
|
|
35
|
+
return 'length';
|
|
36
|
+
case 'end_turn':
|
|
37
|
+
return 'stop';
|
|
38
|
+
default:
|
|
39
|
+
return 'stop';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
// --- Gemini ↔ Claude Messages ---
|
|
43
|
+
function geminiToClaudeStopReason(reason) {
|
|
44
|
+
if (!reason)
|
|
45
|
+
return 'end_turn';
|
|
46
|
+
switch (reason) {
|
|
47
|
+
case 'MAX_TOKENS':
|
|
48
|
+
return 'max_tokens';
|
|
49
|
+
case 'STOP':
|
|
50
|
+
default:
|
|
51
|
+
return 'end_turn';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function claudeToGeminiStopReason(reason) {
|
|
55
|
+
if (!reason)
|
|
56
|
+
return 'STOP';
|
|
57
|
+
switch (reason) {
|
|
58
|
+
case 'max_tokens':
|
|
59
|
+
return 'MAX_TOKENS';
|
|
60
|
+
case 'end_turn':
|
|
61
|
+
case 'tool_use':
|
|
62
|
+
default:
|
|
63
|
+
return 'STOP';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
// --- OpenAI Responses API ↔ Claude Messages ---
|
|
67
|
+
function responsesToClaudeStopReason(status, incompleteReason, hasToolUse) {
|
|
68
|
+
if (!status || status === 'completed') {
|
|
69
|
+
return hasToolUse ? 'tool_use' : 'end_turn';
|
|
70
|
+
}
|
|
71
|
+
if (status === 'incomplete') {
|
|
72
|
+
if (incompleteReason === 'max_output_tokens' || incompleteReason === 'max_tokens') {
|
|
73
|
+
return 'max_tokens';
|
|
74
|
+
}
|
|
75
|
+
return 'end_turn';
|
|
76
|
+
}
|
|
77
|
+
return 'end_turn';
|
|
78
|
+
}
|
|
79
|
+
function claudeToResponsesStatus(reason) {
|
|
80
|
+
if (reason === 'max_tokens') {
|
|
81
|
+
return { status: 'incomplete', incomplete_details: { reason: 'max_output_tokens' } };
|
|
82
|
+
}
|
|
83
|
+
return { status: 'completed' };
|
|
84
|
+
}
|
|
85
|
+
// --- OpenAI Chat ↔ Responses API ---
|
|
86
|
+
function completionsToResponsesFinishReason(reason) {
|
|
87
|
+
if (reason === 'length')
|
|
88
|
+
return 'incomplete';
|
|
89
|
+
return 'completed';
|
|
90
|
+
}
|
|
91
|
+
function responsesToCompletionsFinishReason(status) {
|
|
92
|
+
if (status === 'incomplete')
|
|
93
|
+
return 'length';
|
|
94
|
+
return 'stop';
|
|
95
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared streaming helper utilities.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.flushConverter = flushConverter;
|
|
7
|
+
exports.normalizeToolArgumentsFragment = normalizeToolArgumentsFragment;
|
|
8
|
+
exports.serializeSSE = serializeSSE;
|
|
9
|
+
exports.parseEventData = parseEventData;
|
|
10
|
+
exports.createOutputEvent = createOutputEvent;
|
|
11
|
+
/** Flush a converter's internal buffer, returning any remaining events. */
|
|
12
|
+
function flushConverter(converter) {
|
|
13
|
+
var _a, _b;
|
|
14
|
+
return (_b = (_a = converter.flush) === null || _a === void 0 ? void 0 : _a.call(converter)) !== null && _b !== void 0 ? _b : [];
|
|
15
|
+
}
|
|
16
|
+
/** Normalize tool arguments fragment to string. */
|
|
17
|
+
function normalizeToolArgumentsFragment(argumentsValue) {
|
|
18
|
+
if (argumentsValue === undefined || argumentsValue === null)
|
|
19
|
+
return '';
|
|
20
|
+
if (typeof argumentsValue === 'string')
|
|
21
|
+
return argumentsValue;
|
|
22
|
+
return JSON.stringify(argumentsValue);
|
|
23
|
+
}
|
|
24
|
+
/** Serialize an SSEEvent into wire-format SSE text block. */
|
|
25
|
+
function serializeSSE(event) {
|
|
26
|
+
let result = '';
|
|
27
|
+
if (event.event !== undefined && event.event !== null) {
|
|
28
|
+
result += `event: ${event.event}\n`;
|
|
29
|
+
}
|
|
30
|
+
if (event.id) {
|
|
31
|
+
result += `id: ${event.id}\n`;
|
|
32
|
+
}
|
|
33
|
+
const dataStr = typeof event.data === 'string' ? event.data : JSON.stringify(event.data);
|
|
34
|
+
result += `data: ${dataStr}\n\n`;
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* 解析 SSE event data:如果已是对象直接返回,否则 JSON.parse。
|
|
39
|
+
* 兼容 legacy SSEParserTransform 输出的对象 data 和字符串 data。
|
|
40
|
+
*/
|
|
41
|
+
function parseEventData(data) {
|
|
42
|
+
if (data === undefined || data === null)
|
|
43
|
+
return data;
|
|
44
|
+
if (typeof data === 'string') {
|
|
45
|
+
if (data === '[DONE]')
|
|
46
|
+
return { type: 'done' };
|
|
47
|
+
try {
|
|
48
|
+
return JSON.parse(data);
|
|
49
|
+
}
|
|
50
|
+
catch (_a) {
|
|
51
|
+
return data;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return data; // 已经是对象
|
|
55
|
+
}
|
|
56
|
+
/** 创建输出 SSEEvent:data 直接设为对象 */
|
|
57
|
+
function createOutputEvent(type, data, id) {
|
|
58
|
+
return { event: type, data, id };
|
|
59
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Tool schema conversion utilities across API formats.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.claudeToCompletionsTools = claudeToCompletionsTools;
|
|
7
|
+
exports.completionsToClaudeTools = completionsToClaudeTools;
|
|
8
|
+
exports.claudeToResponsesTools = claudeToResponsesTools;
|
|
9
|
+
exports.responsesToClaudeTools = responsesToClaudeTools;
|
|
10
|
+
exports.claudeToGeminiTools = claudeToGeminiTools;
|
|
11
|
+
exports.geminiToClaudeTools = geminiToClaudeTools;
|
|
12
|
+
exports.completionsToGeminiTools = completionsToGeminiTools;
|
|
13
|
+
exports.convertSchemaToGemini = convertSchemaToGemini;
|
|
14
|
+
exports.convertSchemaFromGemini = convertSchemaFromGemini;
|
|
15
|
+
/** Claude tools → OpenAI Chat tools */
|
|
16
|
+
function claudeToCompletionsTools(tools) {
|
|
17
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
18
|
+
return [];
|
|
19
|
+
return tools.map(tool => ({
|
|
20
|
+
type: 'function',
|
|
21
|
+
function: {
|
|
22
|
+
name: tool.name,
|
|
23
|
+
description: tool.description || '',
|
|
24
|
+
parameters: tool.input_schema || {},
|
|
25
|
+
},
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
/** OpenAI Chat tools → Claude tools */
|
|
29
|
+
function completionsToClaudeTools(tools) {
|
|
30
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
31
|
+
return [];
|
|
32
|
+
return tools
|
|
33
|
+
.filter(t => t.type === 'function' && t.function)
|
|
34
|
+
.map(t => ({
|
|
35
|
+
name: t.function.name,
|
|
36
|
+
description: t.function.description || '',
|
|
37
|
+
input_schema: t.function.parameters || {},
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
/** Claude tools → Responses API tools */
|
|
41
|
+
function claudeToResponsesTools(tools) {
|
|
42
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
43
|
+
return [];
|
|
44
|
+
return tools.map(tool => ({
|
|
45
|
+
type: 'function',
|
|
46
|
+
name: tool.name,
|
|
47
|
+
description: tool.description || '',
|
|
48
|
+
parameters: cleanSchema(tool.input_schema || {}),
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
/** Responses API tools → Claude tools */
|
|
52
|
+
function responsesToClaudeTools(tools) {
|
|
53
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
54
|
+
return [];
|
|
55
|
+
return tools
|
|
56
|
+
.filter(t => t.type === 'function')
|
|
57
|
+
.map(t => ({
|
|
58
|
+
name: t.name,
|
|
59
|
+
description: t.description || '',
|
|
60
|
+
input_schema: cleanSchema(t.parameters || {}),
|
|
61
|
+
}));
|
|
62
|
+
}
|
|
63
|
+
/** Claude tools → Gemini functionDeclarations */
|
|
64
|
+
function claudeToGeminiTools(tools) {
|
|
65
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
66
|
+
return [];
|
|
67
|
+
return tools.map(tool => ({
|
|
68
|
+
name: tool.name,
|
|
69
|
+
description: tool.description || '',
|
|
70
|
+
parameters: convertSchemaToGemini(tool.input_schema || {}),
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
/** Gemini functionDeclarations → Claude tools */
|
|
74
|
+
function geminiToClaudeTools(tools) {
|
|
75
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
76
|
+
return [];
|
|
77
|
+
return tools.map(tool => ({
|
|
78
|
+
name: tool.name,
|
|
79
|
+
description: tool.description || '',
|
|
80
|
+
input_schema: convertSchemaFromGemini(tool.parameters || {}),
|
|
81
|
+
}));
|
|
82
|
+
}
|
|
83
|
+
/** OpenAI Chat tools → Gemini functionDeclarations */
|
|
84
|
+
function completionsToGeminiTools(tools) {
|
|
85
|
+
if (!(tools === null || tools === void 0 ? void 0 : tools.length))
|
|
86
|
+
return [];
|
|
87
|
+
return tools
|
|
88
|
+
.filter(t => t.type === 'function' && t.function)
|
|
89
|
+
.map(t => ({
|
|
90
|
+
name: t.function.name,
|
|
91
|
+
description: t.function.description || '',
|
|
92
|
+
parameters: convertSchemaToGemini(t.function.parameters || {}),
|
|
93
|
+
}));
|
|
94
|
+
}
|
|
95
|
+
/** Clean/normalize a JSON Schema (strip cache_control, etc.) */
|
|
96
|
+
function cleanSchema(schema) {
|
|
97
|
+
if (!schema || typeof schema !== 'object')
|
|
98
|
+
return schema;
|
|
99
|
+
const result = Array.isArray(schema) ? [] : {};
|
|
100
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
101
|
+
if (key === 'cache_control' || key === 'x-cache-control')
|
|
102
|
+
continue;
|
|
103
|
+
if (key === 'additionalProperties' && value === false)
|
|
104
|
+
continue;
|
|
105
|
+
result[key] = typeof value === 'object' && value !== null ? cleanSchema(value) : value;
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
/** Recursively convert JSON Schema to Gemini format (type strings uppercased) */
|
|
110
|
+
function convertSchemaToGemini(schema) {
|
|
111
|
+
if (!schema || typeof schema !== 'object')
|
|
112
|
+
return schema;
|
|
113
|
+
if (Array.isArray(schema))
|
|
114
|
+
return schema.map(convertSchemaToGemini);
|
|
115
|
+
const result = {};
|
|
116
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
117
|
+
if (key === 'type' && typeof value === 'string') {
|
|
118
|
+
result[key] = value.toUpperCase();
|
|
119
|
+
}
|
|
120
|
+
else if (key === 'properties') {
|
|
121
|
+
result[key] = {};
|
|
122
|
+
for (const [propName, propVal] of Object.entries(value)) {
|
|
123
|
+
result[key][propName] = convertSchemaToGemini(propVal);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
else if (key === 'items') {
|
|
127
|
+
result[key] = convertSchemaToGemini(value);
|
|
128
|
+
}
|
|
129
|
+
else if (key === 'anyOf' || key === 'oneOf' || key === 'allOf') {
|
|
130
|
+
result[key] = value.map(convertSchemaToGemini);
|
|
131
|
+
}
|
|
132
|
+
else if (typeof value === 'object' && value !== null) {
|
|
133
|
+
result[key] = convertSchemaToGemini(value);
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
result[key] = value;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
/** Recursively convert Gemini schema back to JSON Schema (type strings lowercased) */
|
|
142
|
+
function convertSchemaFromGemini(schema) {
|
|
143
|
+
if (!schema || typeof schema !== 'object')
|
|
144
|
+
return schema;
|
|
145
|
+
if (Array.isArray(schema))
|
|
146
|
+
return schema.map(convertSchemaFromGemini);
|
|
147
|
+
const result = {};
|
|
148
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
149
|
+
if (key === 'type' && typeof value === 'string') {
|
|
150
|
+
result[key] = value.toLowerCase();
|
|
151
|
+
}
|
|
152
|
+
else if (key === 'properties') {
|
|
153
|
+
result[key] = {};
|
|
154
|
+
for (const [propName, propVal] of Object.entries(value)) {
|
|
155
|
+
result[key][propName] = convertSchemaFromGemini(propVal);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
else if (key === 'items') {
|
|
159
|
+
result[key] = convertSchemaFromGemini(value);
|
|
160
|
+
}
|
|
161
|
+
else if (typeof value === 'object' && value !== null) {
|
|
162
|
+
result[key] = convertSchemaFromGemini(value);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
result[key] = value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Usage/token count mapping across all API formats.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.completionsToClaudeUsage = completionsToClaudeUsage;
|
|
7
|
+
exports.claudeToCompletionsUsage = claudeToCompletionsUsage;
|
|
8
|
+
exports.geminiToClaudeUsage = geminiToClaudeUsage;
|
|
9
|
+
exports.claudeToGeminiUsage = claudeToGeminiUsage;
|
|
10
|
+
exports.responsesToClaudeUsage = responsesToClaudeUsage;
|
|
11
|
+
exports.completionsToResponsesUsage = completionsToResponsesUsage;
|
|
12
|
+
/** Map OpenAI Chat usage to Claude usage */
|
|
13
|
+
function completionsToClaudeUsage(usage) {
|
|
14
|
+
var _a, _b, _c, _d, _e, _f;
|
|
15
|
+
if (!usage)
|
|
16
|
+
return { input_tokens: 0, output_tokens: 0 };
|
|
17
|
+
return {
|
|
18
|
+
input_tokens: (_b = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : usage.prompt_tokens) !== null && _b !== void 0 ? _b : 0,
|
|
19
|
+
output_tokens: (_d = (_c = usage.output_tokens) !== null && _c !== void 0 ? _c : usage.completion_tokens) !== null && _d !== void 0 ? _d : 0,
|
|
20
|
+
cache_read_input_tokens: (_e = usage.cache_read_input_tokens) !== null && _e !== void 0 ? _e : (_f = usage.prompt_tokens_details) === null || _f === void 0 ? void 0 : _f.cached_tokens,
|
|
21
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** Map Claude usage to OpenAI Chat usage */
|
|
25
|
+
function claudeToCompletionsUsage(usage) {
|
|
26
|
+
var _a, _b;
|
|
27
|
+
if (!usage)
|
|
28
|
+
return { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 };
|
|
29
|
+
const prompt = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
|
|
30
|
+
const completion = (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
31
|
+
return {
|
|
32
|
+
prompt_tokens: prompt,
|
|
33
|
+
completion_tokens: completion,
|
|
34
|
+
total_tokens: prompt + completion,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Map Gemini usageMetadata to Claude usage */
|
|
38
|
+
function geminiToClaudeUsage(metadata) {
|
|
39
|
+
var _a, _b, _c;
|
|
40
|
+
if (!metadata)
|
|
41
|
+
return { input_tokens: 0, output_tokens: 0 };
|
|
42
|
+
return {
|
|
43
|
+
input_tokens: (_a = metadata.promptTokenCount) !== null && _a !== void 0 ? _a : 0,
|
|
44
|
+
output_tokens: ((_b = metadata.totalTokenCount) !== null && _b !== void 0 ? _b : 0) - ((_c = metadata.promptTokenCount) !== null && _c !== void 0 ? _c : 0),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** Map Claude usage to Gemini usageMetadata */
|
|
48
|
+
function claudeToGeminiUsage(usage) {
|
|
49
|
+
var _a, _b;
|
|
50
|
+
if (!usage)
|
|
51
|
+
return {};
|
|
52
|
+
const input = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : 0;
|
|
53
|
+
const output = (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : 0;
|
|
54
|
+
return {
|
|
55
|
+
promptTokenCount: input,
|
|
56
|
+
candidatesTokenCount: output,
|
|
57
|
+
totalTokenCount: input + output,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/** Map Responses API usage to Claude usage */
|
|
61
|
+
function responsesToClaudeUsage(usage) {
|
|
62
|
+
var _a, _b, _c, _d;
|
|
63
|
+
if (!usage)
|
|
64
|
+
return { input_tokens: 0, output_tokens: 0 };
|
|
65
|
+
return {
|
|
66
|
+
input_tokens: (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : 0,
|
|
67
|
+
output_tokens: (_b = usage.output_tokens) !== null && _b !== void 0 ? _b : 0,
|
|
68
|
+
cache_read_input_tokens: (_c = usage.cache_read_input_tokens) !== null && _c !== void 0 ? _c : (_d = usage.input_tokens_details) === null || _d === void 0 ? void 0 : _d.cached_tokens,
|
|
69
|
+
cache_creation_input_tokens: usage.cache_creation_input_tokens,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/** Map OpenAI Chat usage to Responses API usage */
|
|
73
|
+
function completionsToResponsesUsage(usage) {
|
|
74
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
75
|
+
if (!usage)
|
|
76
|
+
return {};
|
|
77
|
+
return {
|
|
78
|
+
input_tokens: (_b = (_a = usage.input_tokens) !== null && _a !== void 0 ? _a : usage.prompt_tokens) !== null && _b !== void 0 ? _b : 0,
|
|
79
|
+
output_tokens: (_d = (_c = usage.output_tokens) !== null && _c !== void 0 ? _c : usage.completion_tokens) !== null && _d !== void 0 ? _d : 0,
|
|
80
|
+
total_tokens: (_e = usage.total_tokens) !== null && _e !== void 0 ? _e : (((_g = (_f = usage.input_tokens) !== null && _f !== void 0 ? _f : usage.prompt_tokens) !== null && _g !== void 0 ? _g : 0) + ((_j = (_h = usage.output_tokens) !== null && _h !== void 0 ? _h : usage.completion_tokens) !== null && _j !== void 0 ? _j : 0)),
|
|
81
|
+
};
|
|
82
|
+
}
|