@omnicross/core 0.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.
- package/LICENSE +21 -0
- package/NOTICE +57 -0
- package/README.md +15 -0
- package/dist/ApiKeyPoolService-BmMkau07.d.cts +170 -0
- package/dist/ApiKeyPoolService-BmMkau07.d.ts +170 -0
- package/dist/ProviderProxy-f_8ziIhW.d.cts +120 -0
- package/dist/ProviderProxy-vjt8sQQk.d.ts +120 -0
- package/dist/SubscriptionAuthSource-Cr4fVEYY.d.cts +264 -0
- package/dist/SubscriptionAuthSource-D89zmiSS.d.ts +264 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +218 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.d.cts +68 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.d.ts +68 -0
- package/dist/auth/GeminiCodeAssistProjectResolver.js +189 -0
- package/dist/completion/ApiKeyPoolService.cjs +331 -0
- package/dist/completion/ApiKeyPoolService.d.cts +2 -0
- package/dist/completion/ApiKeyPoolService.d.ts +2 -0
- package/dist/completion/ApiKeyPoolService.js +306 -0
- package/dist/completion.cjs +4027 -0
- package/dist/completion.d.cts +17 -0
- package/dist/completion.d.ts +17 -0
- package/dist/completion.js +3983 -0
- package/dist/index-BTSmc9Sm.d.ts +645 -0
- package/dist/index-DXazdTzZ.d.cts +645 -0
- package/dist/index.cjs +10428 -0
- package/dist/index.d.cts +128 -0
- package/dist/index.d.ts +128 -0
- package/dist/index.js +10339 -0
- package/dist/outbound-api/subscriptionRegistryPort.cjs +38 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +73 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +73 -0
- package/dist/outbound-api/subscriptionRegistryPort.js +12 -0
- package/dist/outbound-api.cjs +5264 -0
- package/dist/outbound-api.d.cts +320 -0
- package/dist/outbound-api.d.ts +320 -0
- package/dist/outbound-api.js +5218 -0
- package/dist/pipeline/SubscriptionAuthSource.cjs +131 -0
- package/dist/pipeline/SubscriptionAuthSource.d.cts +3 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +3 -0
- package/dist/pipeline/SubscriptionAuthSource.js +103 -0
- package/dist/pipeline/SubscriptionAuthStrategy.cjs +18 -0
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +61 -0
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +61 -0
- package/dist/pipeline/SubscriptionAuthStrategy.js +0 -0
- package/dist/ports/gemini-code-assist-resolver.cjs +38 -0
- package/dist/ports/gemini-code-assist-resolver.d.cts +26 -0
- package/dist/ports/gemini-code-assist-resolver.d.ts +26 -0
- package/dist/ports/gemini-code-assist-resolver.js +12 -0
- package/dist/ports.cjs +18 -0
- package/dist/ports.d.cts +15 -0
- package/dist/ports.d.ts +15 -0
- package/dist/ports.js +0 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +2958 -0
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +77 -0
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +77 -0
- package/dist/provider-proxy/ingress/providerProxyShared.js +2925 -0
- package/dist/provider-proxy/matchText.cjs +73 -0
- package/dist/provider-proxy/matchText.d.cts +47 -0
- package/dist/provider-proxy/matchText.d.ts +47 -0
- package/dist/provider-proxy/matchText.js +45 -0
- package/dist/provider-proxy/types.cjs +18 -0
- package/dist/provider-proxy/types.d.cts +12 -0
- package/dist/provider-proxy/types.d.ts +12 -0
- package/dist/provider-proxy/types.js +0 -0
- package/dist/provider-proxy.cjs +4667 -0
- package/dist/provider-proxy.d.cts +69 -0
- package/dist/provider-proxy.d.ts +69 -0
- package/dist/provider-proxy.js +4636 -0
- package/dist/serializeError.cjs +82 -0
- package/dist/serializeError.d.cts +24 -0
- package/dist/serializeError.d.ts +24 -0
- package/dist/serializeError.js +57 -0
- package/dist/sse-parser.cjs +456 -0
- package/dist/sse-parser.d.cts +143 -0
- package/dist/sse-parser.d.ts +143 -0
- package/dist/sse-parser.js +430 -0
- package/dist/transformer/TransformerChainExecutor.cjs +321 -0
- package/dist/transformer/TransformerChainExecutor.d.cts +104 -0
- package/dist/transformer/TransformerChainExecutor.d.ts +104 -0
- package/dist/transformer/TransformerChainExecutor.js +294 -0
- package/dist/transformer/TransformerService.cjs +290 -0
- package/dist/transformer/TransformerService.d.cts +138 -0
- package/dist/transformer/TransformerService.d.ts +138 -0
- package/dist/transformer/TransformerService.js +265 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +1115 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.cts +102 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.ts +102 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +1085 -0
- package/dist/transformer/transformers/GeminiTransformer.cjs +1013 -0
- package/dist/transformer/transformers/GeminiTransformer.d.cts +70 -0
- package/dist/transformer/transformers/GeminiTransformer.d.ts +70 -0
- package/dist/transformer/transformers/GeminiTransformer.js +986 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +538 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +53 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +53 -0
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +513 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +73 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +51 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +51 -0
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +48 -0
- package/dist/transformer/types.cjs +18 -0
- package/dist/transformer/types.d.cts +405 -0
- package/dist/transformer/types.d.ts +405 -0
- package/dist/transformer/types.js +0 -0
- package/dist/transformer.cjs +3736 -0
- package/dist/transformer.d.cts +33 -0
- package/dist/transformer.d.ts +33 -0
- package/dist/transformer.js +3712 -0
- package/dist/types-CGGrKqC_.d.cts +142 -0
- package/dist/types-CbCN2NQP.d.ts +142 -0
- package/dist/types-DCzHkhJt.d.ts +467 -0
- package/dist/types-DZIQbgp0.d.cts +467 -0
- package/dist/usage-event-sink-BX7FE1NL.d.cts +59 -0
- package/dist/usage-event-sink-BX7FE1NL.d.ts +59 -0
- package/package.json +62 -0
|
@@ -0,0 +1,1013 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/transformer/transformers/GeminiTransformer.ts
|
|
21
|
+
var GeminiTransformer_exports = {};
|
|
22
|
+
__export(GeminiTransformer_exports, {
|
|
23
|
+
GeminiTransformer: () => GeminiTransformer
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(GeminiTransformer_exports);
|
|
26
|
+
|
|
27
|
+
// src/transformer/transformers/utils/gemini.response-in.ts
|
|
28
|
+
var FINISH_REASON_TO_GEMINI = {
|
|
29
|
+
stop: "STOP",
|
|
30
|
+
length: "MAX_TOKENS",
|
|
31
|
+
tool_calls: "STOP",
|
|
32
|
+
content_filter: "SAFETY",
|
|
33
|
+
// Already-lowercased Gemini reasons (pass-through from another transformer)
|
|
34
|
+
max_tokens: "MAX_TOKENS",
|
|
35
|
+
safety: "SAFETY"
|
|
36
|
+
};
|
|
37
|
+
function toGeminiFinishReason(openaiReason) {
|
|
38
|
+
if (!openaiReason) return null;
|
|
39
|
+
return FINISH_REASON_TO_GEMINI[openaiReason] || openaiReason.toUpperCase();
|
|
40
|
+
}
|
|
41
|
+
function convertOpenAIResponseToGemini(openaiData) {
|
|
42
|
+
const choice = openaiData.choices?.[0];
|
|
43
|
+
const message = choice?.message ?? {};
|
|
44
|
+
const usage = openaiData.usage;
|
|
45
|
+
const usagePromptDetails = usage?.prompt_tokens_details;
|
|
46
|
+
const usageOutputDetails = usage?.output_tokens_details;
|
|
47
|
+
const parts = [];
|
|
48
|
+
const thinking = message.thinking;
|
|
49
|
+
if (thinking?.content) {
|
|
50
|
+
parts.push({ text: thinking.content, thought: true });
|
|
51
|
+
}
|
|
52
|
+
if (thinking?.signature) {
|
|
53
|
+
parts.push({ thoughtSignature: thinking.signature });
|
|
54
|
+
}
|
|
55
|
+
if (message.content) {
|
|
56
|
+
parts.push({ text: message.content });
|
|
57
|
+
}
|
|
58
|
+
const toolCalls = message.tool_calls;
|
|
59
|
+
if (toolCalls?.length) {
|
|
60
|
+
for (const tc of toolCalls) {
|
|
61
|
+
const func = tc.function;
|
|
62
|
+
let args = {};
|
|
63
|
+
try {
|
|
64
|
+
const raw = func.arguments;
|
|
65
|
+
args = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
66
|
+
} catch {
|
|
67
|
+
args = {};
|
|
68
|
+
}
|
|
69
|
+
parts.push({
|
|
70
|
+
functionCall: {
|
|
71
|
+
id: tc.id,
|
|
72
|
+
name: func.name,
|
|
73
|
+
args
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (parts.length === 0) {
|
|
79
|
+
parts.push({ text: "" });
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
responseId: openaiData.id || "",
|
|
83
|
+
modelVersion: openaiData.model || "",
|
|
84
|
+
candidates: [{
|
|
85
|
+
content: { parts },
|
|
86
|
+
finishReason: toGeminiFinishReason(choice?.finish_reason)
|
|
87
|
+
}],
|
|
88
|
+
usageMetadata: usage ? {
|
|
89
|
+
promptTokenCount: usage.prompt_tokens || 0,
|
|
90
|
+
candidatesTokenCount: usage.completion_tokens || 0,
|
|
91
|
+
totalTokenCount: usage.total_tokens || 0,
|
|
92
|
+
cachedContentTokenCount: usagePromptDetails?.cached_tokens || 0,
|
|
93
|
+
thoughtsTokenCount: usageOutputDetails?.reasoning_tokens || 0
|
|
94
|
+
} : void 0
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
function convertOpenAIStreamToGemini(openaiStream, logger) {
|
|
98
|
+
const decoder = new TextDecoder();
|
|
99
|
+
const encoder = new TextEncoder();
|
|
100
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
101
|
+
let model = "";
|
|
102
|
+
let responseId = "";
|
|
103
|
+
return new ReadableStream({
|
|
104
|
+
start: async (controller) => {
|
|
105
|
+
const reader = openaiStream.getReader();
|
|
106
|
+
let buffer = "";
|
|
107
|
+
let isClosed = false;
|
|
108
|
+
const emit = (data) => {
|
|
109
|
+
if (isClosed) return;
|
|
110
|
+
try {
|
|
111
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
112
|
+
|
|
113
|
+
`));
|
|
114
|
+
} catch {
|
|
115
|
+
isClosed = true;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
const flushToolCalls = () => {
|
|
119
|
+
if (pendingToolCalls.size === 0) return;
|
|
120
|
+
const parts = [];
|
|
121
|
+
for (const tc of pendingToolCalls.values()) {
|
|
122
|
+
let args = {};
|
|
123
|
+
try {
|
|
124
|
+
args = JSON.parse(tc.args || "{}");
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
parts.push({
|
|
128
|
+
functionCall: { id: tc.id, name: tc.name, args }
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
pendingToolCalls.clear();
|
|
132
|
+
emit({
|
|
133
|
+
responseId,
|
|
134
|
+
modelVersion: model,
|
|
135
|
+
candidates: [{ content: { parts }, finishReason: null }]
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
try {
|
|
139
|
+
while (true) {
|
|
140
|
+
const { done, value } = await reader.read();
|
|
141
|
+
if (done) break;
|
|
142
|
+
buffer += decoder.decode(value, { stream: true });
|
|
143
|
+
const lines = buffer.split("\n");
|
|
144
|
+
buffer = lines.pop() || "";
|
|
145
|
+
for (const line of lines) {
|
|
146
|
+
if (isClosed) break;
|
|
147
|
+
if (!line.startsWith("data:")) continue;
|
|
148
|
+
const data = line.slice(5).trim();
|
|
149
|
+
if (!data || data === "[DONE]") continue;
|
|
150
|
+
try {
|
|
151
|
+
const chunk = JSON.parse(data);
|
|
152
|
+
if (!responseId && chunk.id) responseId = chunk.id;
|
|
153
|
+
if (!model && chunk.model) model = chunk.model;
|
|
154
|
+
const choice = chunk.choices?.[0];
|
|
155
|
+
if (!choice) continue;
|
|
156
|
+
const delta = choice.delta ?? {};
|
|
157
|
+
if (delta.thinking) {
|
|
158
|
+
const parts = [];
|
|
159
|
+
if (delta.thinking.content) {
|
|
160
|
+
parts.push({ text: delta.thinking.content, thought: true });
|
|
161
|
+
}
|
|
162
|
+
if (delta.thinking.signature) {
|
|
163
|
+
parts.push({ thoughtSignature: delta.thinking.signature });
|
|
164
|
+
}
|
|
165
|
+
if (parts.length > 0) {
|
|
166
|
+
emit({
|
|
167
|
+
responseId,
|
|
168
|
+
modelVersion: model,
|
|
169
|
+
candidates: [{ content: { parts }, finishReason: null }]
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (delta.content) {
|
|
174
|
+
emit({
|
|
175
|
+
responseId,
|
|
176
|
+
modelVersion: model,
|
|
177
|
+
candidates: [{
|
|
178
|
+
content: { parts: [{ text: delta.content }] },
|
|
179
|
+
finishReason: null
|
|
180
|
+
}]
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (delta.tool_calls) {
|
|
184
|
+
for (const tc of delta.tool_calls) {
|
|
185
|
+
const idx = tc.index ?? 0;
|
|
186
|
+
const existing = pendingToolCalls.get(idx);
|
|
187
|
+
const func = tc.function;
|
|
188
|
+
if (existing) {
|
|
189
|
+
if (func?.arguments) {
|
|
190
|
+
existing.args += func.arguments;
|
|
191
|
+
}
|
|
192
|
+
} else {
|
|
193
|
+
pendingToolCalls.set(idx, {
|
|
194
|
+
id: tc.id || `tool_${Date.now()}_${idx}`,
|
|
195
|
+
name: func?.name || "",
|
|
196
|
+
args: func?.arguments || ""
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (choice.finish_reason) {
|
|
202
|
+
flushToolCalls();
|
|
203
|
+
const geminiUsage = chunk.usage ? {
|
|
204
|
+
promptTokenCount: chunk.usage.prompt_tokens || 0,
|
|
205
|
+
candidatesTokenCount: chunk.usage.completion_tokens || 0,
|
|
206
|
+
totalTokenCount: chunk.usage.total_tokens || 0,
|
|
207
|
+
cachedContentTokenCount: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
|
208
|
+
thoughtsTokenCount: chunk.usage.output_tokens_details?.reasoning_tokens || 0
|
|
209
|
+
} : void 0;
|
|
210
|
+
emit({
|
|
211
|
+
responseId,
|
|
212
|
+
modelVersion: model,
|
|
213
|
+
candidates: [{
|
|
214
|
+
content: { parts: [{ text: "" }] },
|
|
215
|
+
finishReason: toGeminiFinishReason(choice.finish_reason) || "STOP"
|
|
216
|
+
}],
|
|
217
|
+
usageMetadata: geminiUsage
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
} catch (e) {
|
|
221
|
+
logger?.error(`Error parsing OpenAI stream chunk for Gemini conversion: ${e}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
flushToolCalls();
|
|
226
|
+
} catch (e) {
|
|
227
|
+
if (!isClosed) controller.error(e);
|
|
228
|
+
} finally {
|
|
229
|
+
if (!isClosed) {
|
|
230
|
+
try {
|
|
231
|
+
controller.close();
|
|
232
|
+
} catch {
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
reader.releaseLock();
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async function transformResponseIn(response, logger) {
|
|
241
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
242
|
+
if (contentType.includes("text/event-stream")) {
|
|
243
|
+
if (!response.body) {
|
|
244
|
+
throw new Error("Stream response body is null");
|
|
245
|
+
}
|
|
246
|
+
const geminiStream = convertOpenAIStreamToGemini(response.body, logger);
|
|
247
|
+
return new Response(geminiStream, {
|
|
248
|
+
headers: {
|
|
249
|
+
"Content-Type": "text/event-stream",
|
|
250
|
+
"Cache-Control": "no-cache",
|
|
251
|
+
Connection: "keep-alive"
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
const data = await response.json();
|
|
256
|
+
const geminiResponse = convertOpenAIResponseToGemini(data);
|
|
257
|
+
return new Response(JSON.stringify(geminiResponse), {
|
|
258
|
+
status: response.status,
|
|
259
|
+
statusText: response.statusText,
|
|
260
|
+
headers: { "Content-Type": "application/json" }
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// src/transformer/transformers/utils/gemini.stream.ts
|
|
265
|
+
async function transformResponseOut(response, providerName, logger) {
|
|
266
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
267
|
+
if (contentType.includes("application/json")) {
|
|
268
|
+
return handleJsonResponse(response, providerName, logger);
|
|
269
|
+
} else if (contentType.includes("stream") || contentType.includes("text/event-stream")) {
|
|
270
|
+
return handleStreamResponse(response, providerName, logger);
|
|
271
|
+
}
|
|
272
|
+
return response;
|
|
273
|
+
}
|
|
274
|
+
async function handleJsonResponse(response, providerName, logger) {
|
|
275
|
+
const jsonResponse = await response.json();
|
|
276
|
+
logger?.debug(`${providerName} JSON response received`);
|
|
277
|
+
const parts = jsonResponse.candidates?.[0]?.content?.parts || [];
|
|
278
|
+
let thinkingContent = "";
|
|
279
|
+
let thinkingSignature = "";
|
|
280
|
+
const nonThinkingParts = [];
|
|
281
|
+
for (const part of parts) {
|
|
282
|
+
if (part.text && part.thought === true) {
|
|
283
|
+
thinkingContent += part.text;
|
|
284
|
+
} else {
|
|
285
|
+
nonThinkingParts.push(part);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
thinkingSignature = parts.find((part) => part.thoughtSignature)?.thoughtSignature ?? "";
|
|
289
|
+
const toolCalls = nonThinkingParts.filter((part) => part.functionCall).map((part) => ({
|
|
290
|
+
id: part.functionCall?.id || `tool_${Math.random().toString(36).substring(2, 15)}`,
|
|
291
|
+
type: "function",
|
|
292
|
+
function: {
|
|
293
|
+
name: part.functionCall?.name ?? "",
|
|
294
|
+
arguments: JSON.stringify(part.functionCall?.args || {})
|
|
295
|
+
}
|
|
296
|
+
}));
|
|
297
|
+
const textContent = nonThinkingParts.filter((part) => part.text).map((part) => part.text).join("\n");
|
|
298
|
+
const openAIResponse = {
|
|
299
|
+
id: jsonResponse.responseId ?? "",
|
|
300
|
+
choices: [
|
|
301
|
+
{
|
|
302
|
+
finish_reason: (jsonResponse.candidates?.[0]?.finishReason ?? "").toLowerCase() || null,
|
|
303
|
+
index: 0,
|
|
304
|
+
message: {
|
|
305
|
+
content: textContent,
|
|
306
|
+
role: "assistant",
|
|
307
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
308
|
+
...thinkingSignature && {
|
|
309
|
+
thinking: {
|
|
310
|
+
content: thinkingContent || "(no content)",
|
|
311
|
+
signature: thinkingSignature
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
],
|
|
317
|
+
created: Math.floor(Date.now() / 1e3),
|
|
318
|
+
model: jsonResponse.modelVersion ?? "",
|
|
319
|
+
object: "chat.completion",
|
|
320
|
+
usage: {
|
|
321
|
+
completion_tokens: jsonResponse.usageMetadata?.candidatesTokenCount || 0,
|
|
322
|
+
prompt_tokens: jsonResponse.usageMetadata?.promptTokenCount || 0,
|
|
323
|
+
prompt_tokens_details: {
|
|
324
|
+
cached_tokens: jsonResponse.usageMetadata?.cachedContentTokenCount || 0
|
|
325
|
+
},
|
|
326
|
+
total_tokens: jsonResponse.usageMetadata?.totalTokenCount || 0,
|
|
327
|
+
output_tokens_details: {
|
|
328
|
+
reasoning_tokens: jsonResponse.usageMetadata?.thoughtsTokenCount || 0
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
return new Response(JSON.stringify(openAIResponse), {
|
|
333
|
+
status: response.status,
|
|
334
|
+
statusText: response.statusText,
|
|
335
|
+
headers: response.headers
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
function handleStreamResponse(response, providerName, logger) {
|
|
339
|
+
if (!response.body) {
|
|
340
|
+
return response;
|
|
341
|
+
}
|
|
342
|
+
const decoder = new TextDecoder();
|
|
343
|
+
const encoder = new TextEncoder();
|
|
344
|
+
let signatureSent = false;
|
|
345
|
+
let contentSent = false;
|
|
346
|
+
let hasThinkingContent = false;
|
|
347
|
+
let pendingContent = "";
|
|
348
|
+
let contentIndex = 0;
|
|
349
|
+
let toolCallIndex = -1;
|
|
350
|
+
const stream = new ReadableStream({
|
|
351
|
+
async start(controller) {
|
|
352
|
+
const reader = response.body.getReader();
|
|
353
|
+
let buffer = "";
|
|
354
|
+
const processLine = async (line) => {
|
|
355
|
+
if (!line.startsWith("data: ")) return;
|
|
356
|
+
const chunkStr = line.slice(6).trim();
|
|
357
|
+
if (!chunkStr) return;
|
|
358
|
+
logger?.debug(`${providerName} chunk: ${chunkStr.substring(0, 100)}...`);
|
|
359
|
+
try {
|
|
360
|
+
const chunk = JSON.parse(chunkStr);
|
|
361
|
+
if (!chunk.candidates?.[0]) {
|
|
362
|
+
logger?.debug("Invalid chunk structure");
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const candidate = chunk.candidates[0];
|
|
366
|
+
const parts = candidate.content?.parts || [];
|
|
367
|
+
parts.filter((part) => part.text && part.thought === true).forEach((part) => {
|
|
368
|
+
hasThinkingContent = true;
|
|
369
|
+
const thinkingChunk = createChunk({
|
|
370
|
+
responseId: chunk.responseId,
|
|
371
|
+
modelVersion: chunk.modelVersion,
|
|
372
|
+
contentIndex,
|
|
373
|
+
delta: {
|
|
374
|
+
role: "assistant",
|
|
375
|
+
content: null,
|
|
376
|
+
thinking: { content: part.text }
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(thinkingChunk)}
|
|
380
|
+
|
|
381
|
+
`));
|
|
382
|
+
});
|
|
383
|
+
const signature = parts.find((part) => part.thoughtSignature)?.thoughtSignature;
|
|
384
|
+
if (signature && !signatureSent) {
|
|
385
|
+
if (!hasThinkingContent) {
|
|
386
|
+
const thinkingChunk = createChunk({
|
|
387
|
+
responseId: chunk.responseId,
|
|
388
|
+
modelVersion: chunk.modelVersion,
|
|
389
|
+
contentIndex,
|
|
390
|
+
delta: {
|
|
391
|
+
role: "assistant",
|
|
392
|
+
content: null,
|
|
393
|
+
thinking: { content: "" }
|
|
394
|
+
}
|
|
395
|
+
});
|
|
396
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(thinkingChunk)}
|
|
397
|
+
|
|
398
|
+
`));
|
|
399
|
+
}
|
|
400
|
+
const signatureChunk = createChunk({
|
|
401
|
+
responseId: chunk.responseId,
|
|
402
|
+
modelVersion: chunk.modelVersion,
|
|
403
|
+
contentIndex,
|
|
404
|
+
delta: {
|
|
405
|
+
role: "assistant",
|
|
406
|
+
content: null,
|
|
407
|
+
thinking: { signature }
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(signatureChunk)}
|
|
411
|
+
|
|
412
|
+
`));
|
|
413
|
+
signatureSent = true;
|
|
414
|
+
contentIndex++;
|
|
415
|
+
if (pendingContent) {
|
|
416
|
+
const pendingChunk = createChunk({
|
|
417
|
+
responseId: chunk.responseId,
|
|
418
|
+
modelVersion: chunk.modelVersion,
|
|
419
|
+
contentIndex,
|
|
420
|
+
delta: { role: "assistant", content: pendingContent }
|
|
421
|
+
});
|
|
422
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(pendingChunk)}
|
|
423
|
+
|
|
424
|
+
`));
|
|
425
|
+
pendingContent = "";
|
|
426
|
+
contentSent = true;
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
const toolCalls = parts.filter((part) => part.functionCall).map((part) => ({
|
|
430
|
+
id: part.functionCall?.id || `ccr_tool_${Math.random().toString(36).substring(2, 15)}`,
|
|
431
|
+
type: "function",
|
|
432
|
+
function: {
|
|
433
|
+
name: part.functionCall?.name ?? "",
|
|
434
|
+
arguments: JSON.stringify(part.functionCall?.args || {})
|
|
435
|
+
}
|
|
436
|
+
}));
|
|
437
|
+
const textContent = parts.filter((part) => part.text && part.thought !== true).map((part) => part.text).join("\n");
|
|
438
|
+
if (!textContent && signatureSent && !contentSent) {
|
|
439
|
+
contentSent = true;
|
|
440
|
+
}
|
|
441
|
+
if (hasThinkingContent && textContent && !signatureSent) {
|
|
442
|
+
if (chunk.modelVersion?.includes("3")) {
|
|
443
|
+
pendingContent += textContent;
|
|
444
|
+
return;
|
|
445
|
+
} else {
|
|
446
|
+
const signatureChunk = createChunk({
|
|
447
|
+
responseId: chunk.responseId,
|
|
448
|
+
modelVersion: chunk.modelVersion,
|
|
449
|
+
contentIndex,
|
|
450
|
+
delta: {
|
|
451
|
+
role: "assistant",
|
|
452
|
+
content: null,
|
|
453
|
+
thinking: { signature: `ccr_${Date.now()}` }
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(signatureChunk)}
|
|
457
|
+
|
|
458
|
+
`));
|
|
459
|
+
signatureSent = true;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (textContent) {
|
|
463
|
+
if (!pendingContent) contentIndex++;
|
|
464
|
+
const contentChunk = createChunk({
|
|
465
|
+
responseId: chunk.responseId,
|
|
466
|
+
modelVersion: chunk.modelVersion,
|
|
467
|
+
contentIndex,
|
|
468
|
+
delta: { role: "assistant", content: textContent },
|
|
469
|
+
finishReason: candidate.finishReason,
|
|
470
|
+
usageMetadata: chunk.usageMetadata,
|
|
471
|
+
groundingMetadata: candidate.groundingMetadata
|
|
472
|
+
});
|
|
473
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(contentChunk)}
|
|
474
|
+
|
|
475
|
+
`));
|
|
476
|
+
contentSent = true;
|
|
477
|
+
}
|
|
478
|
+
if (toolCalls.length > 0) {
|
|
479
|
+
for (const tool of toolCalls) {
|
|
480
|
+
contentIndex++;
|
|
481
|
+
toolCallIndex++;
|
|
482
|
+
const toolChunk = createChunk({
|
|
483
|
+
responseId: chunk.responseId,
|
|
484
|
+
modelVersion: chunk.modelVersion,
|
|
485
|
+
contentIndex,
|
|
486
|
+
delta: {
|
|
487
|
+
role: "assistant",
|
|
488
|
+
tool_calls: [{ ...tool, index: toolCallIndex }]
|
|
489
|
+
},
|
|
490
|
+
finishReason: candidate.finishReason,
|
|
491
|
+
usageMetadata: chunk.usageMetadata,
|
|
492
|
+
groundingMetadata: candidate.groundingMetadata
|
|
493
|
+
});
|
|
494
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(toolChunk)}
|
|
495
|
+
|
|
496
|
+
`));
|
|
497
|
+
}
|
|
498
|
+
contentSent = true;
|
|
499
|
+
}
|
|
500
|
+
} catch (_error) {
|
|
501
|
+
logger?.error(`Error parsing ${providerName} stream chunk: ${chunkStr}`);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
try {
|
|
505
|
+
while (true) {
|
|
506
|
+
const { done, value } = await reader.read();
|
|
507
|
+
if (done) {
|
|
508
|
+
if (buffer) await processLine(buffer);
|
|
509
|
+
break;
|
|
510
|
+
}
|
|
511
|
+
buffer += decoder.decode(value, { stream: true });
|
|
512
|
+
const lines = buffer.split("\n");
|
|
513
|
+
buffer = lines.pop() || "";
|
|
514
|
+
for (const line of lines) {
|
|
515
|
+
await processLine(line);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
} catch (error) {
|
|
519
|
+
controller.error(error);
|
|
520
|
+
} finally {
|
|
521
|
+
controller.close();
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
return new Response(stream, {
|
|
526
|
+
status: response.status,
|
|
527
|
+
statusText: response.statusText,
|
|
528
|
+
headers: response.headers
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
function createChunk(options) {
|
|
532
|
+
const {
|
|
533
|
+
responseId,
|
|
534
|
+
modelVersion,
|
|
535
|
+
contentIndex,
|
|
536
|
+
delta,
|
|
537
|
+
finishReason,
|
|
538
|
+
usageMetadata,
|
|
539
|
+
groundingMetadata
|
|
540
|
+
} = options;
|
|
541
|
+
const chunk = {
|
|
542
|
+
choices: [
|
|
543
|
+
{
|
|
544
|
+
delta,
|
|
545
|
+
finish_reason: finishReason?.toLowerCase() || null,
|
|
546
|
+
index: contentIndex,
|
|
547
|
+
logprobs: null
|
|
548
|
+
}
|
|
549
|
+
],
|
|
550
|
+
created: Math.floor(Date.now() / 1e3),
|
|
551
|
+
id: responseId || "",
|
|
552
|
+
model: modelVersion || "",
|
|
553
|
+
object: "chat.completion.chunk",
|
|
554
|
+
system_fingerprint: "fp_a49d71b8a1"
|
|
555
|
+
};
|
|
556
|
+
if (usageMetadata) {
|
|
557
|
+
chunk.usage = {
|
|
558
|
+
completion_tokens: usageMetadata.candidatesTokenCount || 0,
|
|
559
|
+
prompt_tokens: usageMetadata.promptTokenCount || 0,
|
|
560
|
+
prompt_tokens_details: {
|
|
561
|
+
cached_tokens: usageMetadata.cachedContentTokenCount || 0
|
|
562
|
+
},
|
|
563
|
+
total_tokens: usageMetadata.totalTokenCount || 0,
|
|
564
|
+
output_tokens_details: {
|
|
565
|
+
reasoning_tokens: usageMetadata.thoughtsTokenCount || 0
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
if (groundingMetadata?.groundingChunks?.length) {
|
|
570
|
+
const annotations = groundingMetadata.groundingChunks.map((groundingChunk, index) => {
|
|
571
|
+
const support = groundingMetadata.groundingSupports?.find(
|
|
572
|
+
(s) => s.groundingChunkIndices?.includes(index)
|
|
573
|
+
);
|
|
574
|
+
return {
|
|
575
|
+
type: "url_citation",
|
|
576
|
+
url_citation: {
|
|
577
|
+
url: groundingChunk.web?.uri || "",
|
|
578
|
+
title: groundingChunk.web?.title || "",
|
|
579
|
+
content: support?.segment?.text || "",
|
|
580
|
+
start_index: support?.segment?.startIndex || 0,
|
|
581
|
+
end_index: support?.segment?.endIndex || 0
|
|
582
|
+
}
|
|
583
|
+
};
|
|
584
|
+
});
|
|
585
|
+
chunk.choices[0].delta.annotations = annotations;
|
|
586
|
+
}
|
|
587
|
+
return chunk;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// src/transformer/transformers/utils/gemini.schema.ts
|
|
591
|
+
var GeminiType = {
|
|
592
|
+
TYPE_UNSPECIFIED: "TYPE_UNSPECIFIED",
|
|
593
|
+
STRING: "STRING",
|
|
594
|
+
NUMBER: "NUMBER",
|
|
595
|
+
INTEGER: "INTEGER",
|
|
596
|
+
BOOLEAN: "BOOLEAN",
|
|
597
|
+
ARRAY: "ARRAY",
|
|
598
|
+
OBJECT: "OBJECT",
|
|
599
|
+
NULL: "NULL"
|
|
600
|
+
};
|
|
601
|
+
function flattenTypeArrayToAnyOf(typeList, resultingSchema) {
|
|
602
|
+
if (typeList.includes("null")) {
|
|
603
|
+
resultingSchema.nullable = true;
|
|
604
|
+
}
|
|
605
|
+
const listWithoutNull = typeList.filter((type) => type !== "null");
|
|
606
|
+
if (listWithoutNull.length === 1) {
|
|
607
|
+
const upperCaseType = listWithoutNull[0].toUpperCase();
|
|
608
|
+
resultingSchema.type = Object.values(GeminiType).includes(upperCaseType) ? upperCaseType : GeminiType.TYPE_UNSPECIFIED;
|
|
609
|
+
} else {
|
|
610
|
+
resultingSchema.anyOf = listWithoutNull.map((typeName) => {
|
|
611
|
+
const upperCaseType = typeName.toUpperCase();
|
|
612
|
+
return {
|
|
613
|
+
type: Object.values(GeminiType).includes(upperCaseType) ? upperCaseType : GeminiType.TYPE_UNSPECIFIED
|
|
614
|
+
};
|
|
615
|
+
});
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
function processJsonSchema(jsonSchema) {
|
|
619
|
+
const genAISchema = {};
|
|
620
|
+
const schemaFieldNames = ["items"];
|
|
621
|
+
const listSchemaFieldNames = ["anyOf"];
|
|
622
|
+
const dictSchemaFieldNames = ["properties"];
|
|
623
|
+
let workingSchema = jsonSchema;
|
|
624
|
+
if (workingSchema.type && workingSchema.anyOf) {
|
|
625
|
+
throw new Error("type and anyOf cannot be both populated.");
|
|
626
|
+
}
|
|
627
|
+
const incomingAnyOf = workingSchema.anyOf;
|
|
628
|
+
if (incomingAnyOf && Array.isArray(incomingAnyOf) && incomingAnyOf.length === 2) {
|
|
629
|
+
if (incomingAnyOf[0]?.type === "null") {
|
|
630
|
+
genAISchema.nullable = true;
|
|
631
|
+
workingSchema = incomingAnyOf[1];
|
|
632
|
+
} else if (incomingAnyOf[1]?.type === "null") {
|
|
633
|
+
genAISchema.nullable = true;
|
|
634
|
+
workingSchema = incomingAnyOf[0];
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
if (workingSchema.type && Array.isArray(workingSchema.type)) {
|
|
638
|
+
flattenTypeArrayToAnyOf(workingSchema.type, genAISchema);
|
|
639
|
+
}
|
|
640
|
+
for (const [fieldName, fieldValue] of Object.entries(workingSchema)) {
|
|
641
|
+
if (fieldValue == null) {
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
if (fieldName === "type") {
|
|
645
|
+
if (fieldValue === "null") {
|
|
646
|
+
throw new Error("type: null cannot be the only possible type for the field.");
|
|
647
|
+
}
|
|
648
|
+
if (Array.isArray(fieldValue)) {
|
|
649
|
+
continue;
|
|
650
|
+
}
|
|
651
|
+
const upperCaseValue = fieldValue.toUpperCase();
|
|
652
|
+
genAISchema.type = Object.values(GeminiType).includes(upperCaseValue) ? upperCaseValue : GeminiType.TYPE_UNSPECIFIED;
|
|
653
|
+
} else if (schemaFieldNames.includes(fieldName)) {
|
|
654
|
+
genAISchema[fieldName] = processJsonSchema(fieldValue);
|
|
655
|
+
} else if (listSchemaFieldNames.includes(fieldName)) {
|
|
656
|
+
const listValue = [];
|
|
657
|
+
for (const item of fieldValue) {
|
|
658
|
+
if (item.type === "null") {
|
|
659
|
+
genAISchema.nullable = true;
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
listValue.push(processJsonSchema(item));
|
|
663
|
+
}
|
|
664
|
+
genAISchema[fieldName] = listValue;
|
|
665
|
+
} else if (dictSchemaFieldNames.includes(fieldName)) {
|
|
666
|
+
const dictValue = {};
|
|
667
|
+
for (const [key, value] of Object.entries(fieldValue)) {
|
|
668
|
+
dictValue[key] = processJsonSchema(value);
|
|
669
|
+
}
|
|
670
|
+
genAISchema[fieldName] = dictValue;
|
|
671
|
+
} else {
|
|
672
|
+
if (fieldName === "additionalProperties") {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
genAISchema[fieldName] = fieldValue;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
return genAISchema;
|
|
679
|
+
}
|
|
680
|
+
function transformTool(tool) {
|
|
681
|
+
const functionDeclarations = tool.functionDeclarations;
|
|
682
|
+
if (functionDeclarations) {
|
|
683
|
+
for (const functionDeclaration of functionDeclarations) {
|
|
684
|
+
if (functionDeclaration.parameters) {
|
|
685
|
+
const params = functionDeclaration.parameters;
|
|
686
|
+
if (!Object.keys(params).includes("$schema")) {
|
|
687
|
+
functionDeclaration.parameters = processJsonSchema(params);
|
|
688
|
+
} else {
|
|
689
|
+
if (!functionDeclaration.parametersJsonSchema) {
|
|
690
|
+
functionDeclaration.parametersJsonSchema = functionDeclaration.parameters;
|
|
691
|
+
delete functionDeclaration.parameters;
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (functionDeclaration.response) {
|
|
696
|
+
const response = functionDeclaration.response;
|
|
697
|
+
if (!Object.keys(response).includes("$schema")) {
|
|
698
|
+
functionDeclaration.response = processJsonSchema(response);
|
|
699
|
+
} else {
|
|
700
|
+
if (!functionDeclaration.responseJsonSchema) {
|
|
701
|
+
functionDeclaration.responseJsonSchema = functionDeclaration.response;
|
|
702
|
+
delete functionDeclaration.response;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
return tool;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// src/transformer/transformers/utils/gemini.util.ts
|
|
712
|
+
function buildRequestBody(request) {
|
|
713
|
+
const tools = [];
|
|
714
|
+
const functionDeclarations = request.tools?.filter((tool) => tool.function.name !== "web_search")?.map((tool) => ({
|
|
715
|
+
name: tool.function.name,
|
|
716
|
+
description: tool.function.description,
|
|
717
|
+
parametersJsonSchema: tool.function.parameters
|
|
718
|
+
}));
|
|
719
|
+
if (functionDeclarations?.length) {
|
|
720
|
+
tools.push(
|
|
721
|
+
transformTool({
|
|
722
|
+
functionDeclarations
|
|
723
|
+
})
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
const webSearch = request.tools?.find((tool) => tool.function.name === "web_search");
|
|
727
|
+
if (webSearch) {
|
|
728
|
+
tools.push({ googleSearch: {} });
|
|
729
|
+
}
|
|
730
|
+
const contents = [];
|
|
731
|
+
const toolResponses = request.messages.filter((item) => item.role === "tool");
|
|
732
|
+
request.messages.filter((item) => item.role !== "tool").forEach((message) => {
|
|
733
|
+
let role;
|
|
734
|
+
if (message.role === "assistant") {
|
|
735
|
+
role = "model";
|
|
736
|
+
} else if (["user", "system"].includes(message.role)) {
|
|
737
|
+
role = "user";
|
|
738
|
+
} else {
|
|
739
|
+
role = "user";
|
|
740
|
+
}
|
|
741
|
+
const parts = [];
|
|
742
|
+
if (typeof message.content === "string") {
|
|
743
|
+
const part = { text: message.content };
|
|
744
|
+
if (message.thinking?.signature) {
|
|
745
|
+
part.thoughtSignature = message.thinking.signature;
|
|
746
|
+
}
|
|
747
|
+
parts.push(part);
|
|
748
|
+
} else if (Array.isArray(message.content)) {
|
|
749
|
+
for (const content of message.content) {
|
|
750
|
+
if (content.type === "text") {
|
|
751
|
+
parts.push({ text: content.text || "" });
|
|
752
|
+
} else if (content.type === "image_url") {
|
|
753
|
+
const imageUrl = content.image_url?.url ?? "";
|
|
754
|
+
if (imageUrl.startsWith("http")) {
|
|
755
|
+
parts.push({
|
|
756
|
+
file_data: {
|
|
757
|
+
mime_type: content.media_type,
|
|
758
|
+
file_uri: imageUrl
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
} else {
|
|
762
|
+
const data = imageUrl.split(",").pop() || imageUrl;
|
|
763
|
+
parts.push({
|
|
764
|
+
inlineData: {
|
|
765
|
+
mime_type: content.media_type || "image/png",
|
|
766
|
+
data
|
|
767
|
+
}
|
|
768
|
+
});
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
} else if (message.content && typeof message.content === "object") {
|
|
773
|
+
const contentObj = message.content;
|
|
774
|
+
if (contentObj.text) {
|
|
775
|
+
parts.push({ text: contentObj.text });
|
|
776
|
+
} else {
|
|
777
|
+
parts.push({ text: JSON.stringify(message.content) });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (Array.isArray(message.tool_calls)) {
|
|
781
|
+
for (let index = 0; index < message.tool_calls.length; index++) {
|
|
782
|
+
const toolCall = message.tool_calls[index];
|
|
783
|
+
const functionCallPart = {
|
|
784
|
+
functionCall: {
|
|
785
|
+
id: toolCall.id || `tool_${Math.random().toString(36).substring(2, 15)}`,
|
|
786
|
+
name: toolCall.function.name,
|
|
787
|
+
args: JSON.parse(toolCall.function.arguments || "{}")
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
if (index === 0 && message.thinking?.signature) {
|
|
791
|
+
functionCallPart.thoughtSignature = message.thinking.signature;
|
|
792
|
+
}
|
|
793
|
+
parts.push(functionCallPart);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
if (parts.length === 0) {
|
|
797
|
+
parts.push({ text: "" });
|
|
798
|
+
}
|
|
799
|
+
contents.push({ role, parts });
|
|
800
|
+
if (role === "model" && message.tool_calls) {
|
|
801
|
+
const functionResponses = message.tool_calls.map(
|
|
802
|
+
(tool) => {
|
|
803
|
+
const response = toolResponses.find((item) => item.tool_call_id === tool.id);
|
|
804
|
+
return {
|
|
805
|
+
functionResponse: {
|
|
806
|
+
name: tool.function?.name ?? "",
|
|
807
|
+
response: { result: response?.content }
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
);
|
|
812
|
+
contents.push({
|
|
813
|
+
role: "user",
|
|
814
|
+
parts: functionResponses
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
const generationConfig = {};
|
|
819
|
+
if (request.reasoning?.effort && request.reasoning.effort !== "none") {
|
|
820
|
+
generationConfig.thinkingConfig = {
|
|
821
|
+
includeThoughts: true
|
|
822
|
+
};
|
|
823
|
+
if (request.model.includes("gemini-3")) {
|
|
824
|
+
generationConfig.thinkingConfig.thinkingLevel = request.reasoning.effort;
|
|
825
|
+
} else {
|
|
826
|
+
const thinkingBudgets = request.model.includes("pro") ? [128, 32768] : [0, 24576];
|
|
827
|
+
const maxTokens = request.reasoning.max_tokens;
|
|
828
|
+
if (typeof maxTokens !== "undefined") {
|
|
829
|
+
let thinkingBudget;
|
|
830
|
+
if (maxTokens >= thinkingBudgets[0] && maxTokens <= thinkingBudgets[1]) {
|
|
831
|
+
thinkingBudget = maxTokens;
|
|
832
|
+
} else if (maxTokens < thinkingBudgets[0]) {
|
|
833
|
+
thinkingBudget = thinkingBudgets[0];
|
|
834
|
+
} else {
|
|
835
|
+
thinkingBudget = thinkingBudgets[1];
|
|
836
|
+
}
|
|
837
|
+
generationConfig.thinkingConfig.thinkingBudget = thinkingBudget;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
const body = {
|
|
842
|
+
contents,
|
|
843
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
844
|
+
generationConfig: Object.keys(generationConfig).length > 0 ? generationConfig : void 0
|
|
845
|
+
};
|
|
846
|
+
if (request.tool_choice) {
|
|
847
|
+
const toolConfig = {
|
|
848
|
+
functionCallingConfig: {}
|
|
849
|
+
};
|
|
850
|
+
if (request.tool_choice === "auto") {
|
|
851
|
+
toolConfig.functionCallingConfig.mode = "auto";
|
|
852
|
+
} else if (request.tool_choice === "none") {
|
|
853
|
+
toolConfig.functionCallingConfig.mode = "none";
|
|
854
|
+
} else if (request.tool_choice === "required") {
|
|
855
|
+
toolConfig.functionCallingConfig.mode = "any";
|
|
856
|
+
} else if (typeof request.tool_choice === "object" && request.tool_choice.function?.name) {
|
|
857
|
+
toolConfig.functionCallingConfig.mode = "any";
|
|
858
|
+
toolConfig.functionCallingConfig.allowedFunctionNames = [
|
|
859
|
+
request.tool_choice.function.name
|
|
860
|
+
];
|
|
861
|
+
}
|
|
862
|
+
body.toolConfig = toolConfig;
|
|
863
|
+
}
|
|
864
|
+
return body;
|
|
865
|
+
}
|
|
866
|
+
function transformRequestOut(request) {
|
|
867
|
+
const contents = request.contents;
|
|
868
|
+
const tools = request.tools;
|
|
869
|
+
const model = request.model;
|
|
870
|
+
const maxTokens = request.max_tokens;
|
|
871
|
+
const temperature = request.temperature;
|
|
872
|
+
const stream = request.stream;
|
|
873
|
+
const toolChoice = request.tool_choice;
|
|
874
|
+
const unifiedRequest = {
|
|
875
|
+
messages: [],
|
|
876
|
+
model,
|
|
877
|
+
max_tokens: maxTokens,
|
|
878
|
+
temperature,
|
|
879
|
+
stream,
|
|
880
|
+
tool_choice: toolChoice
|
|
881
|
+
};
|
|
882
|
+
if (Array.isArray(contents)) {
|
|
883
|
+
for (const content of contents) {
|
|
884
|
+
if (typeof content === "string") {
|
|
885
|
+
unifiedRequest.messages.push({
|
|
886
|
+
role: "user",
|
|
887
|
+
content
|
|
888
|
+
});
|
|
889
|
+
} else if ("text" in content && typeof content.text === "string") {
|
|
890
|
+
unifiedRequest.messages.push({
|
|
891
|
+
role: "user",
|
|
892
|
+
content: content.text || null
|
|
893
|
+
});
|
|
894
|
+
} else if ("role" in content && content.role === "user") {
|
|
895
|
+
const geminiContent = content;
|
|
896
|
+
unifiedRequest.messages.push({
|
|
897
|
+
role: "user",
|
|
898
|
+
content: geminiContent.parts?.map((part) => ({
|
|
899
|
+
type: "text",
|
|
900
|
+
text: part.text || ""
|
|
901
|
+
})) || []
|
|
902
|
+
});
|
|
903
|
+
} else if (content.role === "model") {
|
|
904
|
+
unifiedRequest.messages.push({
|
|
905
|
+
role: "assistant",
|
|
906
|
+
content: content.parts?.map((part) => ({
|
|
907
|
+
type: "text",
|
|
908
|
+
text: part.text || ""
|
|
909
|
+
})) || []
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
if (Array.isArray(tools)) {
|
|
915
|
+
unifiedRequest.tools = [];
|
|
916
|
+
for (const tool of tools) {
|
|
917
|
+
if (Array.isArray(tool.functionDeclarations)) {
|
|
918
|
+
for (const funcDecl of tool.functionDeclarations) {
|
|
919
|
+
unifiedRequest.tools.push({
|
|
920
|
+
type: "function",
|
|
921
|
+
function: {
|
|
922
|
+
name: funcDecl.name,
|
|
923
|
+
description: funcDecl.description ?? "",
|
|
924
|
+
parameters: funcDecl.parameters ?? {}
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return unifiedRequest;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/transformer/transformers/GeminiTransformer.ts
|
|
935
|
+
var GeminiTransformer = class {
|
|
936
|
+
static TransformerName = "gemini";
|
|
937
|
+
name = "gemini";
|
|
938
|
+
logger;
|
|
939
|
+
/**
|
|
940
|
+
* API endpoint pattern for Gemini
|
|
941
|
+
* :modelAndAction will be replaced with actual model and action
|
|
942
|
+
*/
|
|
943
|
+
endPoint = "/v1beta/models/:modelAndAction";
|
|
944
|
+
/** Use Bearer token instead of x-goog-api-key (for relay providers) */
|
|
945
|
+
useBearer;
|
|
946
|
+
constructor(options) {
|
|
947
|
+
this.useBearer = options?.UseBearer ?? false;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Handle authentication
|
|
951
|
+
* - Official Gemini: x-goog-api-key header
|
|
952
|
+
* - Relay providers: Authorization: Bearer header
|
|
953
|
+
*/
|
|
954
|
+
async auth(request, provider, _context) {
|
|
955
|
+
const headers = {};
|
|
956
|
+
if (this.useBearer) {
|
|
957
|
+
headers["authorization"] = `Bearer ${provider.apiKey}`;
|
|
958
|
+
headers["x-goog-api-key"] = void 0;
|
|
959
|
+
} else {
|
|
960
|
+
headers["x-goog-api-key"] = provider.apiKey;
|
|
961
|
+
headers["authorization"] = void 0;
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
body: request,
|
|
965
|
+
config: { headers }
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Transform request from unified format to Gemini format
|
|
970
|
+
* Also builds the correct URL for the Gemini API
|
|
971
|
+
*/
|
|
972
|
+
async transformRequestIn(request, provider, _context) {
|
|
973
|
+
const body = buildRequestBody(request);
|
|
974
|
+
const action = request.stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
975
|
+
const url = new URL(`./${request.model}:${action}`, provider.baseUrl);
|
|
976
|
+
const headers = {};
|
|
977
|
+
if (this.useBearer) {
|
|
978
|
+
headers["authorization"] = `Bearer ${provider.apiKey}`;
|
|
979
|
+
headers["x-goog-api-key"] = void 0;
|
|
980
|
+
} else {
|
|
981
|
+
headers["x-goog-api-key"] = provider.apiKey;
|
|
982
|
+
headers["Authorization"] = void 0;
|
|
983
|
+
}
|
|
984
|
+
return {
|
|
985
|
+
body,
|
|
986
|
+
config: { url, headers }
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
/**
|
|
990
|
+
* Transform incoming request to unified format
|
|
991
|
+
* (For requests coming into the Gemini endpoint)
|
|
992
|
+
*/
|
|
993
|
+
async transformRequestOut(request, _context) {
|
|
994
|
+
return transformRequestOut(request);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Transform Gemini response to OpenAI-compatible format
|
|
998
|
+
*/
|
|
999
|
+
async transformResponseOut(response, _context) {
|
|
1000
|
+
return transformResponseOut(response, this.name, this.logger);
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Transform OpenAI-compatible response back to Gemini format
|
|
1004
|
+
* (For endpoint mode — returning Gemini-format responses to the client)
|
|
1005
|
+
*/
|
|
1006
|
+
async transformResponseIn(response, _context) {
|
|
1007
|
+
return transformResponseIn(response, this.logger);
|
|
1008
|
+
}
|
|
1009
|
+
};
|
|
1010
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1011
|
+
0 && (module.exports = {
|
|
1012
|
+
GeminiTransformer
|
|
1013
|
+
});
|