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