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,259 @@
1
+ "use strict";
2
+ /**
3
+ * OpenAI Chat Completions SSE → Claude Messages SSE streaming conversion.
4
+ *
5
+ * Stateful converter that processes SSE events from an OpenAI Chat Completions
6
+ * upstream and emits Claude Messages SSE events, enhanced with reasoning_content /
7
+ * thinking block support.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.CompletionsToClaudeConverter = void 0;
11
+ const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
12
+ const id_js_1 = require("../../utils/id.js");
13
+ const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
14
+ /**
15
+ * Stateful converter: OpenAI Chat SSE → Claude Messages SSE.
16
+ */
17
+ class CompletionsToClaudeConverter {
18
+ constructor() {
19
+ Object.defineProperty(this, "started", {
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true,
23
+ value: false
24
+ });
25
+ Object.defineProperty(this, "messageStopped", {
26
+ enumerable: true,
27
+ configurable: true,
28
+ writable: true,
29
+ value: false
30
+ });
31
+ /** Tracks which non-tool content block is currently open: null | 'thinking' | 'text' */
32
+ Object.defineProperty(this, "currentBlockType", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: null
37
+ });
38
+ /** Index of the currently open block; -1 when no block is open */
39
+ Object.defineProperty(this, "currentBlockIndex", {
40
+ enumerable: true,
41
+ configurable: true,
42
+ writable: true,
43
+ value: -1
44
+ });
45
+ /** Monotonically increasing block index counter */
46
+ Object.defineProperty(this, "nextBlockIndex", {
47
+ enumerable: true,
48
+ configurable: true,
49
+ writable: true,
50
+ value: 0
51
+ });
52
+ /** Accumulated tool calls keyed by their OpenAI index */
53
+ Object.defineProperty(this, "toolCalls", {
54
+ enumerable: true,
55
+ configurable: true,
56
+ writable: true,
57
+ value: new Map()
58
+ });
59
+ Object.defineProperty(this, "nextToolOrder", {
60
+ enumerable: true,
61
+ configurable: true,
62
+ writable: true,
63
+ value: 0
64
+ });
65
+ Object.defineProperty(this, "outputTokens", {
66
+ enumerable: true,
67
+ configurable: true,
68
+ writable: true,
69
+ value: 0
70
+ });
71
+ Object.defineProperty(this, "pendingStopReason", {
72
+ enumerable: true,
73
+ configurable: true,
74
+ writable: true,
75
+ value: null
76
+ });
77
+ Object.defineProperty(this, "messageId", {
78
+ enumerable: true,
79
+ configurable: true,
80
+ writable: true,
81
+ value: (0, id_js_1.generateMessageId)()
82
+ });
83
+ }
84
+ // ---- StreamConverter interface ------------------------------------------
85
+ convertEvent(event) {
86
+ var _a, _b, _c, _d, _e, _f, _g, _h;
87
+ if (!event.data)
88
+ return [];
89
+ if (event.data === '[DONE]' || ((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done')
90
+ return this.flush();
91
+ try {
92
+ const chunk = (0, streaming_helpers_js_1.parseEventData)(event.data);
93
+ const events = [];
94
+ const delta = (_c = (_b = chunk.choices) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.delta;
95
+ const finishReason = (_e = (_d = chunk.choices) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.finish_reason;
96
+ const messageToolCalls = (_h = (_g = (_f = chunk.choices) === null || _f === void 0 ? void 0 : _f[0]) === null || _g === void 0 ? void 0 : _g.message) === null || _h === void 0 ? void 0 : _h.tool_calls;
97
+ const usage = chunk.usage;
98
+ if (usage === null || usage === void 0 ? void 0 : usage.completion_tokens) {
99
+ this.outputTokens = usage.completion_tokens;
100
+ }
101
+ // Emit message_start on first chunk
102
+ if (!this.started) {
103
+ events.push({
104
+ type: 'message_start',
105
+ message: {
106
+ id: this.messageId,
107
+ type: 'message',
108
+ role: 'assistant',
109
+ content: [],
110
+ model: chunk.model || '',
111
+ stop_reason: null,
112
+ stop_sequence: null,
113
+ usage: { input_tokens: 0, output_tokens: 0 },
114
+ },
115
+ });
116
+ this.started = true;
117
+ }
118
+ // --- Reasoning content (NEW) ----------------------------------------
119
+ if (!this.messageStopped && (delta === null || delta === void 0 ? void 0 : delta.reasoning_content)) {
120
+ if (this.currentBlockType !== 'thinking') {
121
+ this.closeCurrentBlock(events);
122
+ this.openBlock(events, 'thinking');
123
+ }
124
+ events.push({
125
+ type: 'content_block_delta',
126
+ index: this.currentBlockIndex,
127
+ delta: { type: 'thinking_delta', thinking: delta.reasoning_content },
128
+ });
129
+ }
130
+ // --- Text content ---------------------------------------------------
131
+ if (!this.messageStopped && (delta === null || delta === void 0 ? void 0 : delta.content)) {
132
+ if (this.currentBlockType !== 'text') {
133
+ this.closeCurrentBlock(events);
134
+ this.openBlock(events, 'text');
135
+ }
136
+ events.push({
137
+ type: 'content_block_delta',
138
+ index: this.currentBlockIndex,
139
+ delta: { type: 'text_delta', text: delta.content },
140
+ });
141
+ }
142
+ // --- Tool calls (from delta) ----------------------------------------
143
+ if (!this.messageStopped &&
144
+ Array.isArray(delta === null || delta === void 0 ? void 0 : delta.tool_calls) &&
145
+ delta.tool_calls.length > 0) {
146
+ this.closeCurrentBlock(events);
147
+ this.captureToolCalls(delta.tool_calls);
148
+ }
149
+ // --- Tool calls (from message-level, some providers) ----------------
150
+ if (!this.messageStopped &&
151
+ Array.isArray(messageToolCalls) &&
152
+ messageToolCalls.length > 0) {
153
+ this.closeCurrentBlock(events);
154
+ this.captureToolCalls(messageToolCalls);
155
+ }
156
+ // --- Finish reason (dedup — skip if already set) --------------------
157
+ if (finishReason && !this.messageStopped && this.pendingStopReason === null) {
158
+ this.pendingStopReason = (0, stop_reasons_js_1.completionsToClaudeStopReason)(finishReason);
159
+ }
160
+ return events.length > 0
161
+ ? events.map((data) => ({
162
+ data,
163
+ event: data.type || event.event,
164
+ }))
165
+ : [];
166
+ }
167
+ catch (_j) {
168
+ return [event];
169
+ }
170
+ }
171
+ flush() {
172
+ if (this.messageStopped || !this.started)
173
+ return [];
174
+ const events = [];
175
+ this.closeCurrentBlock(events);
176
+ this.flushToolCalls(events);
177
+ events.push({
178
+ type: 'message_delta',
179
+ delta: {
180
+ stop_reason: this.pendingStopReason || 'end_turn',
181
+ stop_sequence: null,
182
+ },
183
+ usage: { output_tokens: this.outputTokens },
184
+ });
185
+ events.push({ type: 'message_stop' });
186
+ this.messageStopped = true;
187
+ return events.map((data) => ({ data, event: data.type }));
188
+ }
189
+ // ---- Private helpers ---------------------------------------------------
190
+ /** Open a new content block of the given type */
191
+ openBlock(events, type) {
192
+ this.currentBlockIndex = this.nextBlockIndex++;
193
+ this.currentBlockType = type;
194
+ if (type === 'thinking') {
195
+ events.push({
196
+ type: 'content_block_start',
197
+ index: this.currentBlockIndex,
198
+ content_block: { type: 'thinking', thinking: '' },
199
+ });
200
+ }
201
+ else {
202
+ events.push({
203
+ type: 'content_block_start',
204
+ index: this.currentBlockIndex,
205
+ content_block: { type: 'text', text: '' },
206
+ });
207
+ }
208
+ }
209
+ /** Close the currently open content block, if any */
210
+ closeCurrentBlock(events) {
211
+ if (this.currentBlockIndex >= 0) {
212
+ events.push({ type: 'content_block_stop', index: this.currentBlockIndex });
213
+ this.currentBlockIndex = -1;
214
+ this.currentBlockType = null;
215
+ }
216
+ }
217
+ /** Accumulate tool call fragments from delta */
218
+ captureToolCalls(toolCalls) {
219
+ var _a, _b, _c;
220
+ for (const tc of toolCalls) {
221
+ const toolIdx = (_a = tc.index) !== null && _a !== void 0 ? _a : 0;
222
+ const current = this.toolCalls.get(toolIdx) || {
223
+ argumentsText: '',
224
+ order: this.nextToolOrder++,
225
+ };
226
+ if (tc.id)
227
+ current.id = tc.id;
228
+ if ((_b = tc.function) === null || _b === void 0 ? void 0 : _b.name)
229
+ current.name = tc.function.name;
230
+ const fragment = (0, streaming_helpers_js_1.normalizeToolArgumentsFragment)((_c = tc.function) === null || _c === void 0 ? void 0 : _c.arguments);
231
+ if (fragment)
232
+ current.argumentsText += fragment;
233
+ this.toolCalls.set(toolIdx, current);
234
+ }
235
+ }
236
+ /** Emit complete tool_use blocks for all accumulated tool calls */
237
+ flushToolCalls(events) {
238
+ const sorted = [...this.toolCalls.entries()].sort((a, b) => a[1].order - b[1].order);
239
+ for (const [, toolCall] of sorted) {
240
+ const idx = this.nextBlockIndex++;
241
+ const id = toolCall.id || (0, id_js_1.generateToolUseId)();
242
+ events.push({
243
+ type: 'content_block_start',
244
+ index: idx,
245
+ content_block: { type: 'tool_use', id, name: toolCall.name || '', input: {} },
246
+ });
247
+ if (toolCall.argumentsText) {
248
+ events.push({
249
+ type: 'content_block_delta',
250
+ index: idx,
251
+ delta: { type: 'input_json_delta', partial_json: toolCall.argumentsText },
252
+ });
253
+ }
254
+ events.push({ type: 'content_block_stop', index: idx });
255
+ }
256
+ this.toolCalls.clear();
257
+ }
258
+ }
259
+ exports.CompletionsToClaudeConverter = CompletionsToClaudeConverter;
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * Claude Messages → Gemini generateContent request conversion.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.claudeToGemini = claudeToGemini;
7
+ const tool_schema_js_1 = require("../../utils/tool-schema.js");
8
+ /**
9
+ * Convert a Claude Messages request body to a Gemini generateContent request body.
10
+ */
11
+ function claudeToGemini(body) {
12
+ const contents = [];
13
+ // --- System instruction ---------------------------------------------------
14
+ const systemInstruction = body.system
15
+ ? {
16
+ parts: [
17
+ {
18
+ text: typeof body.system === 'string'
19
+ ? body.system
20
+ : Array.isArray(body.system)
21
+ ? body.system
22
+ .filter((s) => s.type === 'text' || typeof s.text === 'string')
23
+ .map((s) => s.text || '')
24
+ .join('\n\n')
25
+ : '',
26
+ },
27
+ ],
28
+ }
29
+ : undefined;
30
+ // --- Messages → contents --------------------------------------------------
31
+ if (body.messages) {
32
+ for (const msg of body.messages) {
33
+ const role = msg.role === 'assistant' ? 'model' : 'user';
34
+ const parts = convertClaudeContentToGeminiParts(msg.content);
35
+ contents.push({ role, parts });
36
+ }
37
+ }
38
+ // --- Generation config ----------------------------------------------------
39
+ const generationConfig = {};
40
+ if (body.max_tokens)
41
+ generationConfig.maxOutputTokens = body.max_tokens;
42
+ if (body.temperature !== undefined)
43
+ generationConfig.temperature = body.temperature;
44
+ if (body.top_p !== undefined)
45
+ generationConfig.topP = body.top_p;
46
+ if (body.stop_sequences)
47
+ generationConfig.stopSequences = body.stop_sequences;
48
+ // --- Build result ---------------------------------------------------------
49
+ const result = {
50
+ contents,
51
+ generationConfig,
52
+ };
53
+ if (systemInstruction)
54
+ result.systemInstruction = systemInstruction;
55
+ // --- Tools ----------------------------------------------------------------
56
+ if (body.tools && body.tools.length > 0) {
57
+ const functionDeclarations = (0, tool_schema_js_1.claudeToGeminiTools)(body.tools);
58
+ if (functionDeclarations.length > 0) {
59
+ result.tools = [{ functionDeclarations }];
60
+ }
61
+ }
62
+ // --- Tool choice → toolConfig ---------------------------------------------
63
+ if (body.tool_choice) {
64
+ const tc = body.tool_choice;
65
+ if (tc.type === 'any') {
66
+ result.toolConfig = { functionCallingConfig: { mode: 'ANY' } };
67
+ }
68
+ else if (tc.type === 'auto') {
69
+ result.toolConfig = { functionCallingConfig: { mode: 'AUTO' } };
70
+ }
71
+ else if (tc.type === 'none') {
72
+ result.toolConfig = { functionCallingConfig: { mode: 'NONE' } };
73
+ }
74
+ else if (tc.type === 'tool' && tc.name) {
75
+ result.toolConfig = {
76
+ functionCallingConfig: { mode: 'ANY', allowedFunctionNames: [tc.name] },
77
+ };
78
+ }
79
+ }
80
+ return result;
81
+ }
82
+ /**
83
+ * Convert Claude content blocks (or string) into Gemini parts array.
84
+ */
85
+ function convertClaudeContentToGeminiParts(content) {
86
+ if (typeof content === 'string') {
87
+ return [{ text: content }];
88
+ }
89
+ if (Array.isArray(content)) {
90
+ const parts = [];
91
+ for (const block of content) {
92
+ if (block.type === 'text') {
93
+ parts.push({ text: block.text });
94
+ }
95
+ else if (block.type === 'image') {
96
+ parts.push({
97
+ inlineData: {
98
+ mimeType: block.source.media_type,
99
+ data: block.source.data,
100
+ },
101
+ });
102
+ }
103
+ else if (block.type === 'tool_use') {
104
+ parts.push({
105
+ functionCall: {
106
+ name: block.name,
107
+ args: block.input,
108
+ },
109
+ });
110
+ }
111
+ else if (block.type === 'tool_result') {
112
+ const resultContent = typeof block.content === 'string'
113
+ ? block.content
114
+ : JSON.stringify(block.content);
115
+ parts.push({
116
+ functionResponse: {
117
+ name: block.tool_use_id,
118
+ response: { content: resultContent },
119
+ },
120
+ });
121
+ }
122
+ else if (block.type === 'thinking') {
123
+ // Skip thinking blocks
124
+ continue;
125
+ }
126
+ }
127
+ return parts.length > 0 ? parts : [{ text: '' }];
128
+ }
129
+ return [{ text: String(content) }];
130
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ /**
3
+ * Gemini generateContent → Claude Messages response conversion.
4
+ */
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.geminiToClaudeResponse = geminiToClaudeResponse;
7
+ const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
8
+ const usage_js_1 = require("../../utils/usage.js");
9
+ const id_js_1 = require("../../utils/id.js");
10
+ /**
11
+ * Convert a Gemini generateContent response into a Claude Messages response.
12
+ */
13
+ function geminiToClaudeResponse(response) {
14
+ var _a, _b, _c;
15
+ const candidate = (_a = response.candidates) === null || _a === void 0 ? void 0 : _a[0];
16
+ // Handle prompt feedback / blocked response
17
+ if (!candidate) {
18
+ const blockReason = (_b = response.promptFeedback) === null || _b === void 0 ? void 0 : _b.blockReason;
19
+ if (blockReason) {
20
+ return {
21
+ id: (0, id_js_1.generateMessageId)(),
22
+ type: 'message',
23
+ role: 'assistant',
24
+ content: [
25
+ {
26
+ type: 'text',
27
+ text: `Response blocked: ${blockReason}`,
28
+ },
29
+ ],
30
+ model: '',
31
+ stop_reason: 'end_turn',
32
+ stop_sequence: null,
33
+ usage: { input_tokens: 0, output_tokens: 0 },
34
+ };
35
+ }
36
+ return response;
37
+ }
38
+ const content = [];
39
+ const parts = ((_c = candidate.content) === null || _c === void 0 ? void 0 : _c.parts) || [];
40
+ for (const part of parts) {
41
+ if (part.text) {
42
+ content.push({ type: 'text', text: part.text });
43
+ }
44
+ if (part.functionCall) {
45
+ content.push({
46
+ type: 'tool_use',
47
+ id: (0, id_js_1.generateToolUseId)(),
48
+ name: part.functionCall.name,
49
+ input: part.functionCall.args || {},
50
+ });
51
+ }
52
+ }
53
+ const stopReason = (0, stop_reasons_js_1.geminiToClaudeStopReason)(candidate.finishReason);
54
+ const usage = (0, usage_js_1.geminiToClaudeUsage)(response.usageMetadata);
55
+ return {
56
+ id: (0, id_js_1.generateMessageId)(),
57
+ type: 'message',
58
+ role: 'assistant',
59
+ content,
60
+ model: response.modelVersion || '',
61
+ stop_reason: stopReason,
62
+ stop_sequence: null,
63
+ usage,
64
+ };
65
+ }
@@ -0,0 +1,199 @@
1
+ "use strict";
2
+ /**
3
+ * Gemini SSE → Claude Messages SSE streaming conversion.
4
+ *
5
+ * Stateful converter that transforms Gemini streaming chunks into Claude SSE events.
6
+ * Gemini streams using cumulative snapshots (each chunk contains the full text so
7
+ * far), so the converter must track the previously-seen text and emit only the diff
8
+ * as `text_delta` events in the Claude protocol.
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.GeminiToClaudeConverter = void 0;
12
+ const stop_reasons_js_1 = require("../../utils/stop-reasons.js");
13
+ const id_js_1 = require("../../utils/id.js");
14
+ const streaming_helpers_js_1 = require("../../utils/streaming-helpers.js");
15
+ /**
16
+ * Stateful converter: Gemini SSE → Claude Messages SSE.
17
+ */
18
+ class GeminiToClaudeConverter {
19
+ constructor() {
20
+ Object.defineProperty(this, "started", {
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true,
24
+ value: false
25
+ });
26
+ Object.defineProperty(this, "messageStopped", {
27
+ enumerable: true,
28
+ configurable: true,
29
+ writable: true,
30
+ value: false
31
+ });
32
+ Object.defineProperty(this, "currentBlockType", {
33
+ enumerable: true,
34
+ configurable: true,
35
+ writable: true,
36
+ value: null
37
+ });
38
+ Object.defineProperty(this, "currentBlockIndex", {
39
+ enumerable: true,
40
+ configurable: true,
41
+ writable: true,
42
+ value: -1
43
+ });
44
+ Object.defineProperty(this, "nextBlockIndex", {
45
+ enumerable: true,
46
+ configurable: true,
47
+ writable: true,
48
+ value: 0
49
+ });
50
+ Object.defineProperty(this, "outputTokens", {
51
+ enumerable: true,
52
+ configurable: true,
53
+ writable: true,
54
+ value: 0
55
+ });
56
+ Object.defineProperty(this, "messageId", {
57
+ enumerable: true,
58
+ configurable: true,
59
+ writable: true,
60
+ value: (0, id_js_1.generateMessageId)()
61
+ });
62
+ Object.defineProperty(this, "previousText", {
63
+ enumerable: true,
64
+ configurable: true,
65
+ writable: true,
66
+ value: ''
67
+ });
68
+ }
69
+ convertEvent(event) {
70
+ var _a, _b, _c;
71
+ // [DONE] passthrough
72
+ if (!event.data || event.data === '[DONE]' || ((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === 'done') {
73
+ return [{ data: '[DONE]', event: event.event }];
74
+ }
75
+ try {
76
+ const chunk = (0, streaming_helpers_js_1.parseEventData)(event.data);
77
+ const events = [];
78
+ const candidates = chunk.candidates || [];
79
+ const usage = chunk.usageMetadata || {};
80
+ if (usage.candidatesTokenCount) {
81
+ this.outputTokens = usage.candidatesTokenCount;
82
+ }
83
+ // Emit message_start on first chunk
84
+ if (!this.started) {
85
+ events.push({
86
+ type: 'message_start',
87
+ message: {
88
+ id: this.messageId,
89
+ type: 'message',
90
+ role: 'assistant',
91
+ content: [],
92
+ model: chunk.modelVersion || '',
93
+ stop_reason: null,
94
+ stop_sequence: null,
95
+ usage: { input_tokens: 0, output_tokens: 0 },
96
+ },
97
+ });
98
+ this.started = true;
99
+ }
100
+ for (const candidate of candidates) {
101
+ const parts = ((_b = candidate.content) === null || _b === void 0 ? void 0 : _b.parts) || [];
102
+ for (const part of parts) {
103
+ // Text part — handle cumulative snapshots via diffing
104
+ if (!this.messageStopped && part.text !== undefined && part.text !== null) {
105
+ const diff = part.text.substring(this.previousText.length);
106
+ this.previousText = part.text;
107
+ if (diff) {
108
+ this.ensureTextBlock(events);
109
+ events.push({
110
+ type: 'content_block_delta',
111
+ index: this.currentBlockIndex,
112
+ delta: { type: 'text_delta', text: diff },
113
+ });
114
+ }
115
+ }
116
+ // Function call — close text block first, then emit complete tool_use block
117
+ if (!this.messageStopped && part.functionCall) {
118
+ this.closeTextBlock(events);
119
+ emitToolUseBlock(events, {
120
+ index: this.nextBlockIndex++,
121
+ id: (0, id_js_1.generateToolUseId)(),
122
+ name: part.functionCall.name || '',
123
+ inputJson: JSON.stringify((_c = part.functionCall.args) !== null && _c !== void 0 ? _c : {}),
124
+ });
125
+ }
126
+ }
127
+ // Finish reason → close and finalize
128
+ if (candidate.finishReason && !this.messageStopped) {
129
+ this.closeTextBlock(events);
130
+ events.push({
131
+ type: 'message_delta',
132
+ delta: {
133
+ stop_reason: (0, stop_reasons_js_1.geminiToClaudeStopReason)(candidate.finishReason),
134
+ stop_sequence: null,
135
+ },
136
+ usage: { output_tokens: this.outputTokens },
137
+ });
138
+ events.push({ type: 'message_stop' });
139
+ this.messageStopped = true;
140
+ }
141
+ }
142
+ return events.length > 0
143
+ ? events.map((data) => ({
144
+ data,
145
+ event: data.type || event.event,
146
+ }))
147
+ : [];
148
+ }
149
+ catch (_d) {
150
+ return [event];
151
+ }
152
+ }
153
+ // --- Helpers --------------------------------------------------------------
154
+ ensureTextBlock(events) {
155
+ if (this.currentBlockType === 'text' && this.currentBlockIndex >= 0)
156
+ return;
157
+ this.currentBlockIndex = this.nextBlockIndex++;
158
+ this.currentBlockType = 'text';
159
+ events.push({
160
+ type: 'content_block_start',
161
+ index: this.currentBlockIndex,
162
+ content_block: { type: 'text', text: '' },
163
+ });
164
+ }
165
+ closeTextBlock(events) {
166
+ if (this.currentBlockType === 'text' && this.currentBlockIndex >= 0) {
167
+ events.push({ type: 'content_block_stop', index: this.currentBlockIndex });
168
+ this.currentBlockType = null;
169
+ this.currentBlockIndex = -1;
170
+ }
171
+ }
172
+ }
173
+ exports.GeminiToClaudeConverter = GeminiToClaudeConverter;
174
+ // ---------------------------------------------------------------------------
175
+ // Shared helpers
176
+ // ---------------------------------------------------------------------------
177
+ /**
178
+ * Emit a complete tool_use block (start + delta + stop) into the events array.
179
+ */
180
+ function emitToolUseBlock(events, tool) {
181
+ events.push({
182
+ type: 'content_block_start',
183
+ index: tool.index,
184
+ content_block: {
185
+ type: 'tool_use',
186
+ id: tool.id,
187
+ name: tool.name,
188
+ input: {},
189
+ },
190
+ });
191
+ if (tool.inputJson) {
192
+ events.push({
193
+ type: 'content_block_delta',
194
+ index: tool.index,
195
+ delta: { type: 'input_json_delta', partial_json: tool.inputJson },
196
+ });
197
+ }
198
+ events.push({ type: 'content_block_stop', index: tool.index });
199
+ }