aicodeswitch 4.0.3 → 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 +7 -6
- 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 +1102 -804
- 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-Nl6yJxrc.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,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
|
+
}
|