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.
Files changed (77) hide show
  1. package/README.md +6 -5
  2. package/UPGRADE.md +5 -6
  3. package/dist/server/coding-plan.js +94 -0
  4. package/dist/server/config-managed-fields.js +1 -0
  5. package/dist/server/conversions/compact.js +613 -0
  6. package/dist/server/conversions/detector.js +70 -0
  7. package/dist/server/conversions/index.js +290 -0
  8. package/dist/server/conversions/pairs/claude-completions/request.js +167 -0
  9. package/dist/server/conversions/pairs/claude-completions/response.js +56 -0
  10. package/dist/server/conversions/pairs/claude-completions/streaming.js +259 -0
  11. package/dist/server/conversions/pairs/claude-gemini/request.js +130 -0
  12. package/dist/server/conversions/pairs/claude-gemini/response.js +65 -0
  13. package/dist/server/conversions/pairs/claude-gemini/streaming.js +199 -0
  14. package/dist/server/conversions/pairs/claude-responses/request.js +190 -0
  15. package/dist/server/conversions/pairs/claude-responses/response.js +89 -0
  16. package/dist/server/conversions/pairs/claude-responses/streaming.js +266 -0
  17. package/dist/server/conversions/pairs/completions-claude/request.js +111 -0
  18. package/dist/server/conversions/pairs/completions-claude/response.js +67 -0
  19. package/dist/server/conversions/pairs/completions-claude/streaming.js +165 -0
  20. package/dist/server/conversions/pairs/completions-gemini/request.js +169 -0
  21. package/dist/server/conversions/pairs/completions-gemini/response.js +70 -0
  22. package/dist/server/conversions/pairs/completions-gemini/streaming.js +132 -0
  23. package/dist/server/conversions/pairs/completions-responses/request.js +149 -0
  24. package/dist/server/conversions/pairs/completions-responses/response.js +74 -0
  25. package/dist/server/conversions/pairs/completions-responses/streaming.js +189 -0
  26. package/dist/server/conversions/pairs/gemini-claude/request.js +118 -0
  27. package/dist/server/conversions/pairs/gemini-claude/response.js +45 -0
  28. package/dist/server/conversions/pairs/gemini-claude/streaming.js +146 -0
  29. package/dist/server/conversions/pairs/gemini-completions/request.js +151 -0
  30. package/dist/server/conversions/pairs/gemini-completions/response.js +54 -0
  31. package/dist/server/conversions/pairs/gemini-completions/streaming.js +108 -0
  32. package/dist/server/conversions/pairs/gemini-responses/request.js +18 -0
  33. package/dist/server/conversions/pairs/gemini-responses/response.js +18 -0
  34. package/dist/server/conversions/pairs/gemini-responses/streaming.js +43 -0
  35. package/dist/server/conversions/pairs/responses-claude/request.js +180 -0
  36. package/dist/server/conversions/pairs/responses-claude/response.js +70 -0
  37. package/dist/server/conversions/pairs/responses-claude/streaming.js +345 -0
  38. package/dist/server/conversions/pairs/responses-completions/request.js +207 -0
  39. package/dist/server/conversions/pairs/responses-completions/response.js +96 -0
  40. package/dist/server/conversions/pairs/responses-completions/streaming.js +344 -0
  41. package/dist/server/conversions/pairs/responses-gemini/request.js +18 -0
  42. package/dist/server/conversions/pairs/responses-gemini/response.js +18 -0
  43. package/dist/server/conversions/pairs/responses-gemini/streaming.js +43 -0
  44. package/dist/server/conversions/pairs/responses-responses/request.js +115 -0
  45. package/dist/server/conversions/pipeline.js +296 -0
  46. package/dist/server/conversions/stream-converter-adapter.js +49 -0
  47. package/dist/server/conversions/thinking/effort.js +61 -0
  48. package/dist/server/conversions/thinking/mapper.js +59 -0
  49. package/dist/server/conversions/thinking/providers.js +80 -0
  50. package/dist/server/conversions/types.js +5 -0
  51. package/dist/server/conversions/url-normalizer.js +58 -0
  52. package/dist/server/conversions/utils/format-mappers.js +57 -0
  53. package/dist/server/conversions/utils/id.js +33 -0
  54. package/dist/server/conversions/utils/stop-reasons.js +95 -0
  55. package/dist/server/conversions/utils/streaming-helpers.js +59 -0
  56. package/dist/server/conversions/utils/tool-schema.js +169 -0
  57. package/dist/server/conversions/utils/usage.js +82 -0
  58. package/dist/server/fs-database.js +465 -135
  59. package/dist/server/main.js +93 -33
  60. package/dist/server/original-config-reader.js +1 -1
  61. package/dist/server/proxy-server.js +887 -633
  62. package/dist/server/transformers/chunk-collector.js +5 -1
  63. package/dist/server/transformers/streaming.js +6 -3235
  64. package/dist/server/type-migration.js +2 -3
  65. package/dist/server/utils.js +5 -0
  66. package/dist/ui/assets/{index-C7G0whng.css → index-BHR12ImE.css} +1 -1
  67. package/dist/ui/assets/index-Rwiqttz-.js +517 -0
  68. package/dist/ui/index.html +2 -2
  69. package/package.json +1 -1
  70. package/dist/server/transformers/transformers.js +0 -1767
  71. package/dist/ui/assets/index-Dl-B9pXM.js +0 -514
  72. package/schema/claude.schema.md +0 -946
  73. package/schema/deepseek-chat.schema.md +0 -799
  74. package/schema/gemini.schema.md +0 -1408
  75. package/schema/openai-chat-completions.schema.md +0 -1088
  76. package/schema/openai-responses.schema.md +0 -226196
  77. 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;