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,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Messages → OpenAI Chat Completions response conversion.
|
|
4
|
+
*
|
|
5
|
+
* Converts a Claude Messages response into a Chat Completions response.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.claudeResponseToCompletions = claudeResponseToCompletions;
|
|
9
|
+
const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
|
|
10
|
+
const usage_js_1 = require("../../utils/usage.js");
|
|
11
|
+
const id_js_1 = require("../../utils/id.js");
|
|
12
|
+
/**
|
|
13
|
+
* Convert a Claude Messages response into an OpenAI Chat Completions response.
|
|
14
|
+
*/
|
|
15
|
+
function claudeResponseToCompletions(response) {
|
|
16
|
+
const content = response.content || [];
|
|
17
|
+
let textContent = '';
|
|
18
|
+
let reasoningContent;
|
|
19
|
+
const toolCalls = [];
|
|
20
|
+
for (const block of content) {
|
|
21
|
+
if (block.type === 'thinking') {
|
|
22
|
+
// Extract thinking text into reasoning_content field
|
|
23
|
+
reasoningContent = (reasoningContent || '') + (block.thinking || '');
|
|
24
|
+
}
|
|
25
|
+
else if (block.type === 'text') {
|
|
26
|
+
textContent += block.text;
|
|
27
|
+
}
|
|
28
|
+
else if (block.type === 'tool_use') {
|
|
29
|
+
toolCalls.push({
|
|
30
|
+
id: block.id,
|
|
31
|
+
type: 'function',
|
|
32
|
+
function: {
|
|
33
|
+
name: block.name,
|
|
34
|
+
arguments: typeof block.input === 'string'
|
|
35
|
+
? block.input
|
|
36
|
+
: JSON.stringify(block.input),
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const message = {
|
|
42
|
+
role: 'assistant',
|
|
43
|
+
content: textContent || null,
|
|
44
|
+
};
|
|
45
|
+
if (reasoningContent) {
|
|
46
|
+
message.reasoning_content = reasoningContent;
|
|
47
|
+
}
|
|
48
|
+
if (toolCalls.length > 0) {
|
|
49
|
+
message.tool_calls = toolCalls;
|
|
50
|
+
}
|
|
51
|
+
const finishReason = (0, stop_reasons_js_1.claudeToCompletionsStopReason)(response.stop_reason);
|
|
52
|
+
const usage = (0, usage_js_1.claudeToCompletionsUsage)(response.usage);
|
|
53
|
+
return {
|
|
54
|
+
id: response.id || (0, id_js_1.generateCompletionsId)(),
|
|
55
|
+
object: 'chat.completion',
|
|
56
|
+
created: Math.floor(Date.now() / 1000),
|
|
57
|
+
model: response.model,
|
|
58
|
+
choices: [
|
|
59
|
+
{
|
|
60
|
+
index: 0,
|
|
61
|
+
message,
|
|
62
|
+
finish_reason: finishReason,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
usage,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Claude Messages → OpenAI Chat Completions streaming conversion.
|
|
4
|
+
*
|
|
5
|
+
* Stateless converter that processes Claude SSE events and produces
|
|
6
|
+
* OpenAI Chat Completions SSE chunks.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ClaudeToCompletionsConverter = void 0;
|
|
10
|
+
const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
|
|
11
|
+
const id_js_1 = require("../../utils/id.js");
|
|
12
|
+
const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
|
|
13
|
+
/**
|
|
14
|
+
* Convert Claude Messages SSE events into OpenAI Chat Completions SSE chunks.
|
|
15
|
+
*/
|
|
16
|
+
class ClaudeToCompletionsConverter {
|
|
17
|
+
convertEvent(event) {
|
|
18
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
19
|
+
if (!event.data || event.data === '[DONE]' || ((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done') {
|
|
20
|
+
return [{ data: '[DONE]', event: '' }];
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const parsed = (0, streaming_helpers_js_1.parseEventData)(event.data);
|
|
24
|
+
const chunks = [];
|
|
25
|
+
switch (parsed.type) {
|
|
26
|
+
case 'message_start': {
|
|
27
|
+
chunks.push({
|
|
28
|
+
id: ((_b = parsed.message) === null || _b === void 0 ? void 0 : _b.id) || (0, id_js_1.generateCompletionsId)(),
|
|
29
|
+
object: 'chat.completion.chunk',
|
|
30
|
+
created: Math.floor(Date.now() / 1000),
|
|
31
|
+
model: ((_c = parsed.message) === null || _c === void 0 ? void 0 : _c.model) || '',
|
|
32
|
+
choices: [
|
|
33
|
+
{
|
|
34
|
+
index: 0,
|
|
35
|
+
delta: { role: 'assistant', content: '' },
|
|
36
|
+
finish_reason: null,
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
case 'content_block_start': {
|
|
43
|
+
if (((_d = parsed.content_block) === null || _d === void 0 ? void 0 : _d.type) === 'tool_use') {
|
|
44
|
+
chunks.push({
|
|
45
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
46
|
+
object: 'chat.completion.chunk',
|
|
47
|
+
created: Math.floor(Date.now() / 1000),
|
|
48
|
+
model: '',
|
|
49
|
+
choices: [
|
|
50
|
+
{
|
|
51
|
+
index: 0,
|
|
52
|
+
delta: {
|
|
53
|
+
tool_calls: [
|
|
54
|
+
{
|
|
55
|
+
index: parsed.index || 0,
|
|
56
|
+
id: parsed.content_block.id,
|
|
57
|
+
type: 'function',
|
|
58
|
+
function: {
|
|
59
|
+
name: parsed.content_block.name || '',
|
|
60
|
+
arguments: '',
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
finish_reason: null,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
// text and thinking blocks do not emit on start — deltas follow
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
case 'content_block_delta': {
|
|
74
|
+
if (((_e = parsed.delta) === null || _e === void 0 ? void 0 : _e.type) === 'thinking_delta') {
|
|
75
|
+
// thinking delta → reasoning_content
|
|
76
|
+
chunks.push({
|
|
77
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
78
|
+
object: 'chat.completion.chunk',
|
|
79
|
+
created: Math.floor(Date.now() / 1000),
|
|
80
|
+
model: '',
|
|
81
|
+
choices: [
|
|
82
|
+
{
|
|
83
|
+
index: 0,
|
|
84
|
+
delta: { reasoning_content: parsed.delta.thinking },
|
|
85
|
+
finish_reason: null,
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
else if (((_f = parsed.delta) === null || _f === void 0 ? void 0 : _f.type) === 'text_delta') {
|
|
91
|
+
chunks.push({
|
|
92
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
93
|
+
object: 'chat.completion.chunk',
|
|
94
|
+
created: Math.floor(Date.now() / 1000),
|
|
95
|
+
model: '',
|
|
96
|
+
choices: [
|
|
97
|
+
{
|
|
98
|
+
index: 0,
|
|
99
|
+
delta: { content: parsed.delta.text },
|
|
100
|
+
finish_reason: null,
|
|
101
|
+
},
|
|
102
|
+
],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
else if (((_g = parsed.delta) === null || _g === void 0 ? void 0 : _g.type) === 'input_json_delta') {
|
|
106
|
+
chunks.push({
|
|
107
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
108
|
+
object: 'chat.completion.chunk',
|
|
109
|
+
created: Math.floor(Date.now() / 1000),
|
|
110
|
+
model: '',
|
|
111
|
+
choices: [
|
|
112
|
+
{
|
|
113
|
+
index: 0,
|
|
114
|
+
delta: {
|
|
115
|
+
tool_calls: [
|
|
116
|
+
{
|
|
117
|
+
index: parsed.index || 0,
|
|
118
|
+
function: { arguments: parsed.delta.partial_json },
|
|
119
|
+
},
|
|
120
|
+
],
|
|
121
|
+
},
|
|
122
|
+
finish_reason: null,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
case 'message_delta': {
|
|
130
|
+
const finishReason = (0, stop_reasons_js_1.claudeToCompletionsStopReason)((_h = parsed.delta) === null || _h === void 0 ? void 0 : _h.stop_reason);
|
|
131
|
+
chunks.push({
|
|
132
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
133
|
+
object: 'chat.completion.chunk',
|
|
134
|
+
created: Math.floor(Date.now() / 1000),
|
|
135
|
+
model: '',
|
|
136
|
+
choices: [
|
|
137
|
+
{
|
|
138
|
+
index: 0,
|
|
139
|
+
delta: {},
|
|
140
|
+
finish_reason: finishReason,
|
|
141
|
+
},
|
|
142
|
+
],
|
|
143
|
+
usage: parsed.usage
|
|
144
|
+
? {
|
|
145
|
+
prompt_tokens: 0,
|
|
146
|
+
completion_tokens: parsed.usage.output_tokens || 0,
|
|
147
|
+
total_tokens: parsed.usage.output_tokens || 0,
|
|
148
|
+
}
|
|
149
|
+
: undefined,
|
|
150
|
+
});
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
// message_stop, content_block_stop, ping — no OpenAI equivalent
|
|
154
|
+
default:
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
// OpenAI SSE does not use named events (event: is empty string)
|
|
158
|
+
return chunks.map((data) => ({ data, event: '' }));
|
|
159
|
+
}
|
|
160
|
+
catch (_j) {
|
|
161
|
+
return [event];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
exports.ClaudeToCompletionsConverter = ClaudeToCompletionsConverter;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Chat Completions → Gemini generateContent request conversion.
|
|
4
|
+
*
|
|
5
|
+
* Direct mapping without Claude intermediate — preserves all fields:
|
|
6
|
+
* - function_call / tool_calls ↔ functionCall / functionResponse
|
|
7
|
+
* - system message → systemInstruction
|
|
8
|
+
* - generationConfig mapping
|
|
9
|
+
* - tool_choice ↔ toolConfig
|
|
10
|
+
* - images (inlineData) ↔ base64 image content
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.completionsToGemini = completionsToGemini;
|
|
14
|
+
const tool_schema_js_1 = require("../../utils/tool-schema.js");
|
|
15
|
+
/**
|
|
16
|
+
* Convert a Chat Completions request body to a Gemini generateContent request body.
|
|
17
|
+
*/
|
|
18
|
+
function completionsToGemini(body) {
|
|
19
|
+
var _a;
|
|
20
|
+
const contents = [];
|
|
21
|
+
let systemInstruction = undefined;
|
|
22
|
+
for (const msg of body.messages || []) {
|
|
23
|
+
if (msg.role === 'system' || msg.role === 'developer') {
|
|
24
|
+
// System messages → systemInstruction (merge multiple)
|
|
25
|
+
const text = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
26
|
+
if (systemInstruction) {
|
|
27
|
+
systemInstruction.parts[0].text += '\n\n' + text;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
systemInstruction = { parts: [{ text }] };
|
|
31
|
+
}
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
const role = msg.role === 'assistant' ? 'model' : 'user';
|
|
35
|
+
const parts = convertCompletionsMessageToGeminiParts(msg);
|
|
36
|
+
contents.push({ role, parts });
|
|
37
|
+
}
|
|
38
|
+
// --- Generation config ---
|
|
39
|
+
const generationConfig = {};
|
|
40
|
+
if (body.max_tokens)
|
|
41
|
+
generationConfig.maxOutputTokens = body.max_tokens;
|
|
42
|
+
if (body.max_completion_tokens)
|
|
43
|
+
generationConfig.maxOutputTokens = body.max_completion_tokens;
|
|
44
|
+
if (body.temperature !== undefined)
|
|
45
|
+
generationConfig.temperature = body.temperature;
|
|
46
|
+
if (body.top_p !== undefined)
|
|
47
|
+
generationConfig.topP = body.top_p;
|
|
48
|
+
if (body.stop) {
|
|
49
|
+
generationConfig.stopSequences = Array.isArray(body.stop) ? body.stop : [body.stop];
|
|
50
|
+
}
|
|
51
|
+
// --- Build result ---
|
|
52
|
+
const result = {
|
|
53
|
+
contents,
|
|
54
|
+
generationConfig,
|
|
55
|
+
};
|
|
56
|
+
if (systemInstruction)
|
|
57
|
+
result.systemInstruction = systemInstruction;
|
|
58
|
+
if (body.model)
|
|
59
|
+
result.model = body.model;
|
|
60
|
+
if (body.stream !== undefined)
|
|
61
|
+
result.stream = body.stream;
|
|
62
|
+
// --- Tools ---
|
|
63
|
+
if (body.tools && body.tools.length > 0) {
|
|
64
|
+
const functionDeclarations = (0, tool_schema_js_1.completionsToGeminiTools)(body.tools);
|
|
65
|
+
if (functionDeclarations.length > 0) {
|
|
66
|
+
result.tools = [{ functionDeclarations }];
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// --- Tool choice → toolConfig ---
|
|
70
|
+
if (body.tool_choice !== undefined) {
|
|
71
|
+
const tc = body.tool_choice;
|
|
72
|
+
if (tc === 'required' || tc === 'any') {
|
|
73
|
+
result.toolConfig = { functionCallingConfig: { mode: 'ANY' } };
|
|
74
|
+
}
|
|
75
|
+
else if (tc === 'auto') {
|
|
76
|
+
result.toolConfig = { functionCallingConfig: { mode: 'AUTO' } };
|
|
77
|
+
}
|
|
78
|
+
else if (tc === 'none') {
|
|
79
|
+
result.toolConfig = { functionCallingConfig: { mode: 'NONE' } };
|
|
80
|
+
}
|
|
81
|
+
else if (typeof tc === 'object') {
|
|
82
|
+
if (tc.type === 'function' && ((_a = tc.function) === null || _a === void 0 ? void 0 : _a.name)) {
|
|
83
|
+
result.toolConfig = {
|
|
84
|
+
functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [tc.function.name] },
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
else if (tc.type === 'required' || tc.type === 'any') {
|
|
88
|
+
result.toolConfig = { functionCallingConfig: { mode: 'ANY' } };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// --- Reasoning effort → thinking config ---
|
|
93
|
+
if (body.reasoning_effort) {
|
|
94
|
+
result.generationConfig.thinkingConfig = {
|
|
95
|
+
thinkingBudget: mapEffortToThinkingBudget(body.reasoning_effort),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Convert a single Completions message to Gemini parts.
|
|
102
|
+
*/
|
|
103
|
+
function convertCompletionsMessageToGeminiParts(msg) {
|
|
104
|
+
var _a, _b, _c, _d;
|
|
105
|
+
const parts = [];
|
|
106
|
+
// reasoning_content → skip (no Gemini equivalent, but we don't lose it
|
|
107
|
+
// because it stays in the request context as history)
|
|
108
|
+
// Text content
|
|
109
|
+
if (msg.content && typeof msg.content === 'string') {
|
|
110
|
+
parts.push({ text: msg.content });
|
|
111
|
+
}
|
|
112
|
+
else if (msg.content && typeof msg.content === 'object') {
|
|
113
|
+
// Array content (multimodal)
|
|
114
|
+
for (const item of Array.isArray(msg.content) ? msg.content : [msg.content]) {
|
|
115
|
+
if (item.type === 'text') {
|
|
116
|
+
parts.push({ text: item.text });
|
|
117
|
+
}
|
|
118
|
+
else if (item.type === 'image_url') {
|
|
119
|
+
// data URL → inlineData
|
|
120
|
+
const url = ((_a = item.image_url) === null || _a === void 0 ? void 0 : _a.url) || '';
|
|
121
|
+
if (url.startsWith('data:')) {
|
|
122
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
123
|
+
if (match) {
|
|
124
|
+
parts.push({ inlineData: { mimeType: match[1], data: match[2] } });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
parts.push({ text: url }); // fallback: pass URL as text
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Tool calls → functionCall parts
|
|
134
|
+
if (msg.tool_calls) {
|
|
135
|
+
for (const tc of msg.tool_calls) {
|
|
136
|
+
const args = typeof ((_b = tc.function) === null || _b === void 0 ? void 0 : _b.arguments) === 'string'
|
|
137
|
+
? JSON.parse(tc.function.arguments)
|
|
138
|
+
: ((_c = tc.function) === null || _c === void 0 ? void 0 : _c.arguments) || {};
|
|
139
|
+
parts.push({
|
|
140
|
+
functionCall: {
|
|
141
|
+
name: ((_d = tc.function) === null || _d === void 0 ? void 0 : _d.name) || '',
|
|
142
|
+
args,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// Tool result → functionResponse part
|
|
148
|
+
if (msg.role === 'tool') {
|
|
149
|
+
const content = typeof msg.content === 'string' ? msg.content : JSON.stringify(msg.content);
|
|
150
|
+
parts.push({
|
|
151
|
+
functionResponse: {
|
|
152
|
+
name: msg.name || msg.tool_call_id || '',
|
|
153
|
+
response: { content },
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return parts.length > 0 ? parts : [{ text: '' }];
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Map reasoning_effort string to Gemini thinkingBudget number.
|
|
161
|
+
*/
|
|
162
|
+
function mapEffortToThinkingBudget(effort) {
|
|
163
|
+
switch (effort) {
|
|
164
|
+
case 'low': return 2048;
|
|
165
|
+
case 'medium': return 8192;
|
|
166
|
+
case 'high': return 24576;
|
|
167
|
+
default: return 8192;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gemini generateContent → OpenAI Chat Completions response conversion.
|
|
4
|
+
*
|
|
5
|
+
* Converts a Gemini response into a Chat Completions response.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.geminiToCompletionsResponse = geminiToCompletionsResponse;
|
|
9
|
+
const id_js_1 = require("../../utils/id.js");
|
|
10
|
+
const format_mappers_js_1 = require("../../utils/format-mappers.js");
|
|
11
|
+
/**
|
|
12
|
+
* Convert a Gemini generateContent response to a Chat Completions response.
|
|
13
|
+
*/
|
|
14
|
+
function geminiToCompletionsResponse(response) {
|
|
15
|
+
var _a, _b;
|
|
16
|
+
const candidate = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0];
|
|
17
|
+
if (!candidate) {
|
|
18
|
+
return {
|
|
19
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
20
|
+
object: 'chat.completion',
|
|
21
|
+
created: Math.floor(Date.now() / 1000),
|
|
22
|
+
model: response.modelVersion || '',
|
|
23
|
+
choices: [{
|
|
24
|
+
index: 0,
|
|
25
|
+
message: { role: 'assistant', content: '' },
|
|
26
|
+
finish_reason: 'stop',
|
|
27
|
+
}],
|
|
28
|
+
usage: { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 },
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const parts = ((_b = candidate.content) === null || _b === void 0 ? void 0 : _b.parts) || [];
|
|
32
|
+
const message = { role: 'assistant', content: null };
|
|
33
|
+
const texts = [];
|
|
34
|
+
const toolCalls = [];
|
|
35
|
+
for (const part of parts) {
|
|
36
|
+
if (part.text !== undefined && part.text !== null) {
|
|
37
|
+
texts.push(part.text);
|
|
38
|
+
}
|
|
39
|
+
else if (part.functionCall) {
|
|
40
|
+
toolCalls.push({
|
|
41
|
+
id: `call_${Math.random().toString(36).slice(2, 14)}`,
|
|
42
|
+
type: 'function',
|
|
43
|
+
function: {
|
|
44
|
+
name: part.functionCall.name || '',
|
|
45
|
+
arguments: typeof part.functionCall.args === 'string'
|
|
46
|
+
? part.functionCall.args
|
|
47
|
+
: JSON.stringify(part.functionCall.args || {}),
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
message.content = texts.join('') || null;
|
|
53
|
+
if (toolCalls.length > 0) {
|
|
54
|
+
message.tool_calls = toolCalls;
|
|
55
|
+
}
|
|
56
|
+
const finishReason = (0, format_mappers_js_1.mapGeminiFinishReason)(candidate.finishReason);
|
|
57
|
+
const usage = (0, format_mappers_js_1.mapGeminiUsage)(response.usageMetadata);
|
|
58
|
+
return {
|
|
59
|
+
id: (0, id_js_1.generateCompletionsId)(),
|
|
60
|
+
object: 'chat.completion',
|
|
61
|
+
created: Math.floor(Date.now() / 1000),
|
|
62
|
+
model: response.modelVersion || '',
|
|
63
|
+
choices: [{
|
|
64
|
+
index: 0,
|
|
65
|
+
message,
|
|
66
|
+
finish_reason: finishReason,
|
|
67
|
+
}],
|
|
68
|
+
usage,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Gemini SSE → Completions SSE streaming converter.
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.GeminiToCompletionsConverter = void 0;
|
|
7
|
+
const id_js_1 = require("../../utils/id.js");
|
|
8
|
+
const format_mappers_js_1 = require("../../utils/format-mappers.js");
|
|
9
|
+
const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
|
|
10
|
+
/**
|
|
11
|
+
* Stateful converter that transforms Gemini streaming chunks into
|
|
12
|
+
* Chat Completions SSE events.
|
|
13
|
+
*/
|
|
14
|
+
class GeminiToCompletionsConverter {
|
|
15
|
+
constructor() {
|
|
16
|
+
Object.defineProperty(this, "started", {
|
|
17
|
+
enumerable: true,
|
|
18
|
+
configurable: true,
|
|
19
|
+
writable: true,
|
|
20
|
+
value: false
|
|
21
|
+
});
|
|
22
|
+
Object.defineProperty(this, "chatId", {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
configurable: true,
|
|
25
|
+
writable: true,
|
|
26
|
+
value: (0, id_js_1.generateCompletionsId)()
|
|
27
|
+
});
|
|
28
|
+
Object.defineProperty(this, "model", {
|
|
29
|
+
enumerable: true,
|
|
30
|
+
configurable: true,
|
|
31
|
+
writable: true,
|
|
32
|
+
value: ''
|
|
33
|
+
});
|
|
34
|
+
Object.defineProperty(this, "textContent", {
|
|
35
|
+
enumerable: true,
|
|
36
|
+
configurable: true,
|
|
37
|
+
writable: true,
|
|
38
|
+
value: ''
|
|
39
|
+
});
|
|
40
|
+
Object.defineProperty(this, "toolCallIndex", {
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
writable: true,
|
|
44
|
+
value: 0
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
convertEvent(event) {
|
|
48
|
+
var _a, _b, _c, _d, _e;
|
|
49
|
+
if (!event.data)
|
|
50
|
+
return [];
|
|
51
|
+
try {
|
|
52
|
+
const parsed = (0, streaming_helpers_js_1.parseEventData)(event.data);
|
|
53
|
+
const chunks = [];
|
|
54
|
+
// Gemini streams candidates as cumulative snapshots
|
|
55
|
+
const candidate = (_a = parsed.candidates) === null || _a === void 0 ? void 0 : _a[0];
|
|
56
|
+
if (!candidate)
|
|
57
|
+
return [];
|
|
58
|
+
const parts = ((_b = candidate.content) === null || _b === void 0 ? void 0 : _b.parts) || [];
|
|
59
|
+
if (!this.started) {
|
|
60
|
+
this.model = parsed.modelVersion || '';
|
|
61
|
+
// Emit initial chunk with role
|
|
62
|
+
chunks.push(this.makeChunk({ role: 'assistant', content: '' }, null));
|
|
63
|
+
this.started = true;
|
|
64
|
+
}
|
|
65
|
+
// Process parts (diff against what we've already emitted)
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
if (part.text !== undefined && part.text !== null) {
|
|
68
|
+
const newText = part.text.substring(this.textContent.length);
|
|
69
|
+
if (newText) {
|
|
70
|
+
chunks.push(this.makeChunk({ content: newText }, null));
|
|
71
|
+
}
|
|
72
|
+
this.textContent = part.text;
|
|
73
|
+
}
|
|
74
|
+
else if (part.functionCall) {
|
|
75
|
+
// Emit tool call start
|
|
76
|
+
const tcId = `call_${Math.random().toString(36).slice(2, 14)}`;
|
|
77
|
+
chunks.push(this.makeChunk({
|
|
78
|
+
tool_calls: [{
|
|
79
|
+
index: this.toolCallIndex,
|
|
80
|
+
id: tcId,
|
|
81
|
+
type: 'function',
|
|
82
|
+
function: {
|
|
83
|
+
name: part.functionCall.name || '',
|
|
84
|
+
arguments: typeof part.functionCall.args === 'string'
|
|
85
|
+
? part.functionCall.args
|
|
86
|
+
: JSON.stringify(part.functionCall.args || {}),
|
|
87
|
+
},
|
|
88
|
+
}],
|
|
89
|
+
}, null));
|
|
90
|
+
this.toolCallIndex++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// Finish reason
|
|
94
|
+
if (candidate.finishReason) {
|
|
95
|
+
const finishReason = (0, format_mappers_js_1.mapGeminiFinishReason)(candidate.finishReason);
|
|
96
|
+
const usage = parsed.usageMetadata
|
|
97
|
+
? {
|
|
98
|
+
prompt_tokens: (_c = parsed.usageMetadata.promptTokenCount) !== null && _c !== void 0 ? _c : 0,
|
|
99
|
+
completion_tokens: (_d = parsed.usageMetadata.candidatesTokenCount) !== null && _d !== void 0 ? _d : 0,
|
|
100
|
+
total_tokens: (_e = parsed.usageMetadata.totalTokenCount) !== null && _e !== void 0 ? _e : 0,
|
|
101
|
+
}
|
|
102
|
+
: undefined;
|
|
103
|
+
chunks.push(this.makeChunk({}, finishReason, usage));
|
|
104
|
+
}
|
|
105
|
+
return chunks;
|
|
106
|
+
}
|
|
107
|
+
catch (_f) {
|
|
108
|
+
return [event];
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
flush() {
|
|
112
|
+
// Gemini streaming is cumulative, no state to flush
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
makeChunk(delta, finishReason, usage) {
|
|
116
|
+
const chunk = {
|
|
117
|
+
id: this.chatId,
|
|
118
|
+
object: 'chat.completion.chunk',
|
|
119
|
+
created: Math.floor(Date.now() / 1000),
|
|
120
|
+
model: this.model,
|
|
121
|
+
choices: [{
|
|
122
|
+
index: 0,
|
|
123
|
+
delta,
|
|
124
|
+
finish_reason: finishReason,
|
|
125
|
+
}],
|
|
126
|
+
};
|
|
127
|
+
if (usage)
|
|
128
|
+
chunk.usage = usage;
|
|
129
|
+
return { data: chunk, event: '' };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
exports.GeminiToCompletionsConverter = GeminiToCompletionsConverter;
|