@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,3736 @@
|
|
|
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/index.ts
|
|
21
|
+
var transformer_exports = {};
|
|
22
|
+
__export(transformer_exports, {
|
|
23
|
+
TransformerChainExecutor: () => TransformerChainExecutor,
|
|
24
|
+
TransformerService: () => TransformerService,
|
|
25
|
+
registerBuiltinTransformers: () => registerBuiltinTransformers
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(transformer_exports);
|
|
28
|
+
|
|
29
|
+
// src/transformer/anthropicBetaInject.ts
|
|
30
|
+
var import_extended_context = require("@omnicross/contracts/extended-context");
|
|
31
|
+
var EXTENDED_CONTEXT_BETA = "context-1m-2025-08-07";
|
|
32
|
+
var ANTHROPIC_BETA_HEADER = "anthropic-beta";
|
|
33
|
+
function injectExtendedContextBeta(headers, model, useExtendedContext) {
|
|
34
|
+
if (!useExtendedContext) return;
|
|
35
|
+
if (!(0, import_extended_context.isExtendedContextCapable)(model)) return;
|
|
36
|
+
let existingValue = "";
|
|
37
|
+
for (const key of Object.keys(headers)) {
|
|
38
|
+
if (key.toLowerCase() === ANTHROPIC_BETA_HEADER) {
|
|
39
|
+
const v = headers[key];
|
|
40
|
+
if (typeof v === "string") existingValue = v;
|
|
41
|
+
if (key !== ANTHROPIC_BETA_HEADER) delete headers[key];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const parts = existingValue.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
45
|
+
if (!parts.includes(EXTENDED_CONTEXT_BETA)) {
|
|
46
|
+
parts.push(EXTENDED_CONTEXT_BETA);
|
|
47
|
+
}
|
|
48
|
+
headers[ANTHROPIC_BETA_HEADER] = parts.join(",");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/transformer/TransformerChainExecutor.ts
|
|
52
|
+
var defaultLogger = {
|
|
53
|
+
debug: (msg, ...args) => console.debug(`[ChainExecutor] ${msg}`, ...args),
|
|
54
|
+
info: (msg, ...args) => console.info(`[ChainExecutor] ${msg}`, ...args),
|
|
55
|
+
warn: (msg, ...args) => console.warn(`[ChainExecutor] ${msg}`, ...args),
|
|
56
|
+
error: (msg, ...args) => console.error(`[ChainExecutor] ${msg}`, ...args)
|
|
57
|
+
};
|
|
58
|
+
var TransformerChainExecutor = class {
|
|
59
|
+
logger;
|
|
60
|
+
constructor(logger) {
|
|
61
|
+
this.logger = logger ?? defaultLogger;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Execute the request transformation chain
|
|
65
|
+
*
|
|
66
|
+
* @param request - Original request body
|
|
67
|
+
* @param provider - LLM provider configuration
|
|
68
|
+
* @param chain - Resolved transformer chain
|
|
69
|
+
* @param options - Execution options
|
|
70
|
+
* @returns Transformed request result
|
|
71
|
+
*/
|
|
72
|
+
async executeRequestChain(request, provider, chain, options = {}) {
|
|
73
|
+
const { endpointTransformer, headers, extendedContext } = options;
|
|
74
|
+
const context = {
|
|
75
|
+
logger: this.logger,
|
|
76
|
+
providerName: provider.name
|
|
77
|
+
};
|
|
78
|
+
let requestBody = request;
|
|
79
|
+
let config = {};
|
|
80
|
+
let bypass = false;
|
|
81
|
+
bypass = this.shouldBypassTransformers(
|
|
82
|
+
chain,
|
|
83
|
+
endpointTransformer,
|
|
84
|
+
requestBody
|
|
85
|
+
);
|
|
86
|
+
if (bypass) {
|
|
87
|
+
if (headers) {
|
|
88
|
+
const cleanHeaders = this.cleanHeaders(headers);
|
|
89
|
+
config.headers = cleanHeaders;
|
|
90
|
+
}
|
|
91
|
+
this.logger.debug("Bypass mode enabled - skipping transformations");
|
|
92
|
+
}
|
|
93
|
+
if (!bypass && endpointTransformer?.transformRequestOut) {
|
|
94
|
+
this.logger.debug("Executing transformRequestOut");
|
|
95
|
+
try {
|
|
96
|
+
const transformOut = await endpointTransformer.transformRequestOut(requestBody, context);
|
|
97
|
+
if (transformOut && typeof transformOut === "object") {
|
|
98
|
+
if ("body" in transformOut) {
|
|
99
|
+
requestBody = transformOut.body;
|
|
100
|
+
config = transformOut.config ?? {};
|
|
101
|
+
} else {
|
|
102
|
+
requestBody = transformOut;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
this.logger.error(`transformRequestOut error: ${this.getErrorMessage(error)}`);
|
|
107
|
+
throw error;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
if (!bypass && chain.providerTransformers.length > 0) {
|
|
111
|
+
this.logger.debug(`Executing ${chain.providerTransformers.length} provider transformers`);
|
|
112
|
+
for (const transformer of chain.providerTransformers) {
|
|
113
|
+
if (transformer.transformRequestIn) {
|
|
114
|
+
try {
|
|
115
|
+
const transformIn = await transformer.transformRequestIn(
|
|
116
|
+
requestBody,
|
|
117
|
+
provider,
|
|
118
|
+
context
|
|
119
|
+
);
|
|
120
|
+
if (transformIn && typeof transformIn === "object") {
|
|
121
|
+
if ("body" in transformIn) {
|
|
122
|
+
requestBody = transformIn.body;
|
|
123
|
+
config = { ...config, ...transformIn.config };
|
|
124
|
+
} else {
|
|
125
|
+
requestBody = transformIn;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
} catch (error) {
|
|
129
|
+
this.logger.error(
|
|
130
|
+
`Provider transformer ${transformer.name} error: ${this.getErrorMessage(error)}`
|
|
131
|
+
);
|
|
132
|
+
throw error;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
if (!bypass && chain.modelTransformers.length > 0) {
|
|
138
|
+
this.logger.debug(`Executing ${chain.modelTransformers.length} model transformers`);
|
|
139
|
+
for (const transformer of chain.modelTransformers) {
|
|
140
|
+
if (transformer.transformRequestIn) {
|
|
141
|
+
try {
|
|
142
|
+
const result = await transformer.transformRequestIn(
|
|
143
|
+
requestBody,
|
|
144
|
+
provider,
|
|
145
|
+
context
|
|
146
|
+
);
|
|
147
|
+
requestBody = result;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
this.logger.error(
|
|
150
|
+
`Model transformer ${transformer.name} error: ${this.getErrorMessage(error)}`
|
|
151
|
+
);
|
|
152
|
+
throw error;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (extendedContext?.enabled) {
|
|
158
|
+
if (!config.headers || typeof config.headers !== "object") {
|
|
159
|
+
config.headers = {};
|
|
160
|
+
}
|
|
161
|
+
injectExtendedContextBeta(
|
|
162
|
+
config.headers,
|
|
163
|
+
extendedContext.model,
|
|
164
|
+
true
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return { requestBody, config, bypass };
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Execute the response transformation chain
|
|
171
|
+
*
|
|
172
|
+
* @param request - Original request (for context)
|
|
173
|
+
* @param response - Response from provider
|
|
174
|
+
* @param provider - LLM provider configuration
|
|
175
|
+
* @param chain - Resolved transformer chain
|
|
176
|
+
* @param options - Execution options
|
|
177
|
+
* @returns Transformed response
|
|
178
|
+
*/
|
|
179
|
+
async executeResponseChain(request, response, provider, chain, options = {}) {
|
|
180
|
+
const { endpointTransformer } = options;
|
|
181
|
+
const context = {
|
|
182
|
+
logger: this.logger,
|
|
183
|
+
providerName: provider.name
|
|
184
|
+
};
|
|
185
|
+
let finalResponse = response;
|
|
186
|
+
const bypass = this.shouldBypassTransformers(chain, endpointTransformer, request);
|
|
187
|
+
if (bypass) {
|
|
188
|
+
this.logger.debug("Bypass mode - skipping response transformations");
|
|
189
|
+
return finalResponse;
|
|
190
|
+
}
|
|
191
|
+
if (chain.modelTransformers.length > 0) {
|
|
192
|
+
const reversedModelTransformers = [...chain.modelTransformers].reverse();
|
|
193
|
+
this.logger.debug(
|
|
194
|
+
`Executing ${reversedModelTransformers.length} model response transformers (reversed)`
|
|
195
|
+
);
|
|
196
|
+
for (const transformer of reversedModelTransformers) {
|
|
197
|
+
if (transformer.transformResponseOut) {
|
|
198
|
+
try {
|
|
199
|
+
finalResponse = await transformer.transformResponseOut(finalResponse, context);
|
|
200
|
+
} catch (error) {
|
|
201
|
+
this.logger.error(
|
|
202
|
+
`Model transformer ${transformer.name} response error: ${this.getErrorMessage(error)}`
|
|
203
|
+
);
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (chain.providerTransformers.length > 0) {
|
|
210
|
+
const reversedProviderTransformers = [...chain.providerTransformers].reverse();
|
|
211
|
+
this.logger.debug(
|
|
212
|
+
`Executing ${reversedProviderTransformers.length} provider response transformers (reversed)`
|
|
213
|
+
);
|
|
214
|
+
for (const transformer of reversedProviderTransformers) {
|
|
215
|
+
if (transformer.transformResponseOut) {
|
|
216
|
+
try {
|
|
217
|
+
finalResponse = await transformer.transformResponseOut(finalResponse, context);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
this.logger.error(
|
|
220
|
+
`Provider transformer ${transformer.name} response error: ${this.getErrorMessage(error)}`
|
|
221
|
+
);
|
|
222
|
+
throw error;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
if (endpointTransformer?.transformResponseIn) {
|
|
228
|
+
this.logger.debug("Executing transformResponseIn");
|
|
229
|
+
try {
|
|
230
|
+
finalResponse = await endpointTransformer.transformResponseIn(finalResponse, context);
|
|
231
|
+
} catch (error) {
|
|
232
|
+
this.logger.error(`transformResponseIn error: ${this.getErrorMessage(error)}`);
|
|
233
|
+
throw error;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return finalResponse;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Execute authentication handler if available
|
|
240
|
+
*
|
|
241
|
+
* @param request - Request body
|
|
242
|
+
* @param provider - LLM provider
|
|
243
|
+
* @param endpointTransformer - Endpoint transformer with auth handler
|
|
244
|
+
* @param context - Transformer context
|
|
245
|
+
* @returns Auth result with potentially modified request and config
|
|
246
|
+
*/
|
|
247
|
+
async executeAuth(request, provider, endpointTransformer, context) {
|
|
248
|
+
let requestBody = request;
|
|
249
|
+
let config = {};
|
|
250
|
+
if (endpointTransformer?.auth) {
|
|
251
|
+
this.logger.debug("Executing auth handler");
|
|
252
|
+
try {
|
|
253
|
+
const auth = await endpointTransformer.auth(requestBody, provider, context);
|
|
254
|
+
if (auth && typeof auth === "object") {
|
|
255
|
+
if ("body" in auth) {
|
|
256
|
+
requestBody = auth.body;
|
|
257
|
+
const authConfig = auth.config;
|
|
258
|
+
if (authConfig) {
|
|
259
|
+
const headers = { ...config.headers ?? {}, ...authConfig.headers ?? {} };
|
|
260
|
+
delete headers["host"];
|
|
261
|
+
config = { ...config, ...authConfig, headers };
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
requestBody = auth;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
} catch (error) {
|
|
268
|
+
this.logger.error(`Auth handler error: ${this.getErrorMessage(error)}`);
|
|
269
|
+
throw error;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { requestBody, config };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Check if transformers should be bypassed (optimization)
|
|
276
|
+
*
|
|
277
|
+
* Bypass is enabled when:
|
|
278
|
+
* - Provider has only one transformer that matches the endpoint transformer
|
|
279
|
+
* - Model has no specific transformers or only the same endpoint transformer
|
|
280
|
+
*/
|
|
281
|
+
shouldBypassTransformers(chain, endpointTransformer, _request) {
|
|
282
|
+
if (!endpointTransformer?.name) {
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
const providerHasOnlyEndpoint = chain.providerTransformers.length === 1 && chain.providerTransformers[0]?.name === endpointTransformer.name;
|
|
286
|
+
const modelHasNoTransformers = chain.modelTransformers.length === 0;
|
|
287
|
+
const modelHasOnlyEndpoint = chain.modelTransformers.length === 1 && chain.modelTransformers[0]?.name === endpointTransformer.name;
|
|
288
|
+
return providerHasOnlyEndpoint && (modelHasNoTransformers || modelHasOnlyEndpoint);
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Clean headers for pass-through
|
|
292
|
+
*/
|
|
293
|
+
cleanHeaders(headers) {
|
|
294
|
+
const result = {};
|
|
295
|
+
if (headers instanceof Headers) {
|
|
296
|
+
headers.forEach((value, key) => {
|
|
297
|
+
if (key.toLowerCase() !== "content-length") {
|
|
298
|
+
result[key] = value;
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
} else {
|
|
302
|
+
for (const [key, value] of Object.entries(headers)) {
|
|
303
|
+
if (key.toLowerCase() !== "content-length") {
|
|
304
|
+
result[key] = value;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
return result;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Get error message from unknown error
|
|
312
|
+
*/
|
|
313
|
+
getErrorMessage(error) {
|
|
314
|
+
if (error instanceof Error) {
|
|
315
|
+
return error.message;
|
|
316
|
+
}
|
|
317
|
+
return String(error);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// src/transformer/TransformerService.ts
|
|
322
|
+
var defaultLogger2 = {
|
|
323
|
+
debug: (msg, ...args) => console.debug(`[TransformerService] ${msg}`, ...args),
|
|
324
|
+
info: (msg, ...args) => console.info(`[TransformerService] ${msg}`, ...args),
|
|
325
|
+
warn: (msg, ...args) => console.warn(`[TransformerService] ${msg}`, ...args),
|
|
326
|
+
error: (msg, ...args) => console.error(`[TransformerService] ${msg}`, ...args)
|
|
327
|
+
};
|
|
328
|
+
var TransformerService = class {
|
|
329
|
+
transformers = /* @__PURE__ */ new Map();
|
|
330
|
+
logger;
|
|
331
|
+
// Performance optimization: Cache instantiated transformers
|
|
332
|
+
instanceCache = /* @__PURE__ */ new Map();
|
|
333
|
+
// Performance optimization: Cache endpoint transformer results
|
|
334
|
+
endpointTransformersCache = null;
|
|
335
|
+
noEndpointTransformersCache = null;
|
|
336
|
+
constructor(logger) {
|
|
337
|
+
this.logger = logger ?? defaultLogger2;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Clear instance caches
|
|
341
|
+
*/
|
|
342
|
+
clearInstanceCache() {
|
|
343
|
+
this.instanceCache.clear();
|
|
344
|
+
this.endpointTransformersCache = null;
|
|
345
|
+
this.noEndpointTransformersCache = null;
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Get cache statistics for diagnostics
|
|
349
|
+
*/
|
|
350
|
+
getCacheStats() {
|
|
351
|
+
return {
|
|
352
|
+
instanceCacheSize: this.instanceCache.size,
|
|
353
|
+
hasEndpointCache: this.endpointTransformersCache !== null
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Register a transformer by name
|
|
358
|
+
* @param name - Unique transformer name
|
|
359
|
+
* @param transformer - Transformer instance or constructor
|
|
360
|
+
*/
|
|
361
|
+
registerTransformer(name, transformer) {
|
|
362
|
+
this.transformers.set(name, transformer);
|
|
363
|
+
this.clearInstanceCache();
|
|
364
|
+
const endpoint = "endPoint" in transformer ? transformer.endPoint : "prototype" in transformer && transformer.prototype?.endPoint ? transformer.prototype.endPoint : void 0;
|
|
365
|
+
this.logger.info(
|
|
366
|
+
`Registered transformer: ${name}${endpoint ? ` (endpoint: ${endpoint})` : " (no endpoint)"}`
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Get a transformer by name
|
|
371
|
+
* @param name - Transformer name
|
|
372
|
+
* @returns Transformer instance/constructor or undefined
|
|
373
|
+
*/
|
|
374
|
+
getTransformer(name) {
|
|
375
|
+
return this.transformers.get(name);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Get all registered transformers
|
|
379
|
+
* @returns Map of all transformers
|
|
380
|
+
*/
|
|
381
|
+
getAllTransformers() {
|
|
382
|
+
return new Map(this.transformers);
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Get transformers that have an endpoint defined
|
|
386
|
+
* Uses cache to avoid redundant traversal
|
|
387
|
+
* @returns Array of transformers with endpoints
|
|
388
|
+
*/
|
|
389
|
+
getTransformersWithEndpoint() {
|
|
390
|
+
if (this.endpointTransformersCache) {
|
|
391
|
+
return this.endpointTransformersCache;
|
|
392
|
+
}
|
|
393
|
+
const result = [];
|
|
394
|
+
this.transformers.forEach((transformer, name) => {
|
|
395
|
+
const instance = this.instantiateIfNeeded(transformer);
|
|
396
|
+
if (instance?.endPoint) {
|
|
397
|
+
result.push({ name, transformer: instance });
|
|
398
|
+
}
|
|
399
|
+
});
|
|
400
|
+
this.endpointTransformersCache = result;
|
|
401
|
+
return result;
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Get transformers without endpoints
|
|
405
|
+
* Uses cache to avoid redundant traversal
|
|
406
|
+
* @returns Array of transformers without endpoints
|
|
407
|
+
*/
|
|
408
|
+
getTransformersWithoutEndpoint() {
|
|
409
|
+
if (this.noEndpointTransformersCache) {
|
|
410
|
+
return this.noEndpointTransformersCache;
|
|
411
|
+
}
|
|
412
|
+
const result = [];
|
|
413
|
+
this.transformers.forEach((transformer, name) => {
|
|
414
|
+
const instance = this.instantiateIfNeeded(transformer);
|
|
415
|
+
if (instance && !instance.endPoint) {
|
|
416
|
+
result.push({ name, transformer: instance });
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
this.noEndpointTransformersCache = result;
|
|
420
|
+
return result;
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Remove a transformer by name
|
|
424
|
+
* @param name - Transformer name
|
|
425
|
+
* @returns true if removed, false if not found
|
|
426
|
+
*/
|
|
427
|
+
removeTransformer(name) {
|
|
428
|
+
const removed = this.transformers.delete(name);
|
|
429
|
+
if (removed) {
|
|
430
|
+
this.clearInstanceCache();
|
|
431
|
+
}
|
|
432
|
+
return removed;
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Check if a transformer is registered
|
|
436
|
+
* @param name - Transformer name
|
|
437
|
+
* @returns true if registered
|
|
438
|
+
*/
|
|
439
|
+
hasTransformer(name) {
|
|
440
|
+
return this.transformers.has(name);
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Initialize the service with default transformers
|
|
444
|
+
* @param defaultTransformers - Map or object of default transformers
|
|
445
|
+
*/
|
|
446
|
+
async initialize(defaultTransformers) {
|
|
447
|
+
try {
|
|
448
|
+
if (defaultTransformers) {
|
|
449
|
+
await this.registerDefaultTransformers(defaultTransformers);
|
|
450
|
+
}
|
|
451
|
+
this.logger.info(
|
|
452
|
+
`TransformerService initialized with ${this.transformers.size} transformers`
|
|
453
|
+
);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
456
|
+
this.logger.error(`TransformerService init error: ${errorMessage}`);
|
|
457
|
+
throw error;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Register default transformers from a map
|
|
462
|
+
* @param transformers - Map of transformer name to transformer
|
|
463
|
+
*/
|
|
464
|
+
async registerDefaultTransformers(transformers) {
|
|
465
|
+
for (const [key, TransformerClass] of Object.entries(transformers)) {
|
|
466
|
+
try {
|
|
467
|
+
if (this.isTransformerConstructor(TransformerClass)) {
|
|
468
|
+
const staticName = TransformerClass.TransformerName ?? key;
|
|
469
|
+
this.registerTransformer(staticName, TransformerClass);
|
|
470
|
+
} else {
|
|
471
|
+
const name = TransformerClass.name ?? key;
|
|
472
|
+
TransformerClass.logger = this.logger;
|
|
473
|
+
this.registerTransformer(name, TransformerClass);
|
|
474
|
+
}
|
|
475
|
+
} catch (error) {
|
|
476
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
477
|
+
this.logger.error(`Failed to register transformer ${key}: ${errorMessage}`);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Resolve a transformer chain configuration to actual transformer instances
|
|
483
|
+
* @param chainConfig - Transformer chain configuration
|
|
484
|
+
* @param modelName - Model name for model-specific transformers
|
|
485
|
+
* @returns Resolved transformer chain with instances
|
|
486
|
+
*/
|
|
487
|
+
resolveTransformerChain(chainConfig, modelName) {
|
|
488
|
+
const result = {
|
|
489
|
+
providerTransformers: [],
|
|
490
|
+
modelTransformers: []
|
|
491
|
+
};
|
|
492
|
+
if (!chainConfig) {
|
|
493
|
+
return result;
|
|
494
|
+
}
|
|
495
|
+
if (chainConfig.use) {
|
|
496
|
+
result.providerTransformers = this.resolveTransformerReferences(chainConfig.use);
|
|
497
|
+
}
|
|
498
|
+
if (modelName && chainConfig[modelName]) {
|
|
499
|
+
const modelConfig = chainConfig[modelName];
|
|
500
|
+
if (Array.isArray(modelConfig)) {
|
|
501
|
+
result.modelTransformers = this.resolveTransformerReferences(modelConfig);
|
|
502
|
+
} else if (modelConfig && typeof modelConfig === "object" && "use" in modelConfig) {
|
|
503
|
+
result.modelTransformers = this.resolveTransformerReferences(modelConfig.use ?? []);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
return result;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Resolve an array of transformer references to instances
|
|
510
|
+
* @param refs - Array of transformer references
|
|
511
|
+
* @returns Array of transformer instances
|
|
512
|
+
*/
|
|
513
|
+
resolveTransformerReferences(refs) {
|
|
514
|
+
const instances = [];
|
|
515
|
+
for (const ref of refs) {
|
|
516
|
+
const [name, options] = Array.isArray(ref) ? ref : [ref, void 0];
|
|
517
|
+
const transformer = this.getTransformer(name);
|
|
518
|
+
if (!transformer) {
|
|
519
|
+
this.logger.warn(`Transformer not found: ${name}`);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
const instance = this.instantiateIfNeeded(transformer, options);
|
|
523
|
+
if (instance) {
|
|
524
|
+
instance.logger = this.logger;
|
|
525
|
+
instances.push(instance);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return instances;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Instantiate a transformer if it's a constructor
|
|
532
|
+
* Uses instance cache to avoid redundant instantiation
|
|
533
|
+
*
|
|
534
|
+
* @param transformer - Transformer instance or constructor
|
|
535
|
+
* @param options - Options to pass to constructor
|
|
536
|
+
* @returns Transformer instance
|
|
537
|
+
*/
|
|
538
|
+
instantiateIfNeeded(transformer, options) {
|
|
539
|
+
if (!this.isTransformerConstructor(transformer)) {
|
|
540
|
+
return transformer;
|
|
541
|
+
}
|
|
542
|
+
const constructorName = transformer.TransformerName || transformer.name || "anonymous";
|
|
543
|
+
const optionsHash = options ? JSON.stringify(options) : "";
|
|
544
|
+
const cacheKey = `${constructorName}:${optionsHash}`;
|
|
545
|
+
if (this.instanceCache.has(cacheKey)) {
|
|
546
|
+
return this.instanceCache.get(cacheKey);
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
const instance = new transformer(options);
|
|
550
|
+
instance.logger = this.logger;
|
|
551
|
+
this.instanceCache.set(cacheKey, instance);
|
|
552
|
+
return instance;
|
|
553
|
+
} catch (error) {
|
|
554
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
555
|
+
this.logger.error(`Failed to instantiate transformer: ${errorMessage}`);
|
|
556
|
+
return void 0;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* Check if a value is a transformer constructor
|
|
561
|
+
* @param value - Value to check
|
|
562
|
+
* @returns true if it's a constructor
|
|
563
|
+
*/
|
|
564
|
+
isTransformerConstructor(value) {
|
|
565
|
+
return typeof value === "function" && value.prototype && (typeof value.prototype.transformRequestIn === "function" || typeof value.prototype.transformRequestOut === "function" || typeof value.prototype.transformResponseIn === "function" || typeof value.prototype.transformResponseOut === "function" || "TransformerName" in value);
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Get list of all registered transformer names
|
|
569
|
+
* @returns Array of transformer names
|
|
570
|
+
*/
|
|
571
|
+
getTransformerNames() {
|
|
572
|
+
return Array.from(this.transformers.keys());
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Clear all registered transformers
|
|
576
|
+
*/
|
|
577
|
+
clear() {
|
|
578
|
+
this.transformers.clear();
|
|
579
|
+
this.clearInstanceCache();
|
|
580
|
+
this.logger.info("All transformers cleared");
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
// src/transformer/transformers/AnthropicToolHandling.ts
|
|
585
|
+
function isServerSideTool(tool) {
|
|
586
|
+
const type = String(tool.type || "");
|
|
587
|
+
return type.startsWith("web_search_") || type.startsWith("code_execution_") || type.startsWith("text_editor_") || type.startsWith("memory_") || type.startsWith("web_fetch_") || type.startsWith("search_tool_");
|
|
588
|
+
}
|
|
589
|
+
function convertAnthropicToolsToOpenAI(tools) {
|
|
590
|
+
return tools.filter((tool) => !isServerSideTool(tool)).map((tool) => ({
|
|
591
|
+
type: "function",
|
|
592
|
+
function: {
|
|
593
|
+
name: String(tool.name),
|
|
594
|
+
description: String(tool.description || ""),
|
|
595
|
+
parameters: tool.input_schema
|
|
596
|
+
}
|
|
597
|
+
}));
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// src/transformer/transformers/AnthropicTypes.ts
|
|
601
|
+
function getThinkLevel(budgetTokens) {
|
|
602
|
+
if (!budgetTokens || budgetTokens <= 0) return "none";
|
|
603
|
+
if (budgetTokens < 4096) return "low";
|
|
604
|
+
if (budgetTokens < 16384) return "medium";
|
|
605
|
+
return "high";
|
|
606
|
+
}
|
|
607
|
+
function formatBase64(data, mediaType) {
|
|
608
|
+
if (data.startsWith("data:")) return data;
|
|
609
|
+
return `data:${mediaType || "image/png"};base64,${data}`;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// src/transformer/transformers/AnthropicRequestBuilder.ts
|
|
613
|
+
function buildAnthropicRequestBody(request) {
|
|
614
|
+
let systemContent;
|
|
615
|
+
const anthropicMessages = [];
|
|
616
|
+
for (let i = 0; i < request.messages.length; i++) {
|
|
617
|
+
const msg = request.messages[i];
|
|
618
|
+
if (msg.role === "system") {
|
|
619
|
+
if (typeof msg.content === "string") {
|
|
620
|
+
systemContent = msg.content;
|
|
621
|
+
} else if (Array.isArray(msg.content)) {
|
|
622
|
+
systemContent = msg.content.filter((c) => c.type === "text").map((c) => ({
|
|
623
|
+
type: "text",
|
|
624
|
+
text: c.text,
|
|
625
|
+
...c.cache_control ? { cache_control: c.cache_control } : {}
|
|
626
|
+
}));
|
|
627
|
+
}
|
|
628
|
+
continue;
|
|
629
|
+
}
|
|
630
|
+
if (msg.role === "assistant") {
|
|
631
|
+
const content = [];
|
|
632
|
+
if (msg.thinking?.content) {
|
|
633
|
+
const block = {
|
|
634
|
+
type: "thinking",
|
|
635
|
+
thinking: msg.thinking.content
|
|
636
|
+
};
|
|
637
|
+
if (msg.thinking.signature) {
|
|
638
|
+
block.signature = msg.thinking.signature;
|
|
639
|
+
}
|
|
640
|
+
content.push(block);
|
|
641
|
+
}
|
|
642
|
+
if (msg.content) {
|
|
643
|
+
const text = typeof msg.content === "string" ? msg.content : msg.content.filter((c) => c.type === "text").map((c) => c.text).join("\n");
|
|
644
|
+
if (text) {
|
|
645
|
+
content.push({ type: "text", text });
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (msg.tool_calls?.length) {
|
|
649
|
+
for (const tc of msg.tool_calls) {
|
|
650
|
+
let input;
|
|
651
|
+
try {
|
|
652
|
+
input = typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments;
|
|
653
|
+
} catch {
|
|
654
|
+
input = { text: tc.function.arguments || "" };
|
|
655
|
+
}
|
|
656
|
+
content.push({
|
|
657
|
+
type: "tool_use",
|
|
658
|
+
id: tc.id,
|
|
659
|
+
name: tc.function.name,
|
|
660
|
+
input
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
anthropicMessages.push({
|
|
665
|
+
role: "assistant",
|
|
666
|
+
content: content.length > 0 ? content : ""
|
|
667
|
+
});
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (msg.role === "tool") {
|
|
671
|
+
const toolResults = [];
|
|
672
|
+
let j = i;
|
|
673
|
+
while (j < request.messages.length && request.messages[j].role === "tool") {
|
|
674
|
+
const t = request.messages[j];
|
|
675
|
+
toolResults.push({
|
|
676
|
+
type: "tool_result",
|
|
677
|
+
tool_use_id: t.tool_call_id || "",
|
|
678
|
+
content: typeof t.content === "string" ? t.content : JSON.stringify(t.content),
|
|
679
|
+
...t.cache_control ? { cache_control: t.cache_control } : {}
|
|
680
|
+
});
|
|
681
|
+
j++;
|
|
682
|
+
}
|
|
683
|
+
anthropicMessages.push({ role: "user", content: toolResults });
|
|
684
|
+
i = j - 1;
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (typeof msg.content === "string") {
|
|
688
|
+
anthropicMessages.push({ role: "user", content: msg.content });
|
|
689
|
+
} else if (Array.isArray(msg.content)) {
|
|
690
|
+
const content = msg.content.map((part) => {
|
|
691
|
+
if (part.type === "image_url") {
|
|
692
|
+
const url = part.image_url.url;
|
|
693
|
+
if (url.startsWith("data:")) {
|
|
694
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
695
|
+
if (match) {
|
|
696
|
+
return {
|
|
697
|
+
type: "image",
|
|
698
|
+
source: { type: "base64", media_type: match[1], data: match[2] }
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return {
|
|
703
|
+
type: "image",
|
|
704
|
+
source: { type: "url", url }
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return { type: "text", text: part.text };
|
|
708
|
+
});
|
|
709
|
+
anthropicMessages.push({ role: "user", content });
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
const body = {
|
|
713
|
+
model: request.model,
|
|
714
|
+
messages: anthropicMessages,
|
|
715
|
+
max_tokens: request.max_tokens || 4096,
|
|
716
|
+
stream: request.stream ?? false
|
|
717
|
+
};
|
|
718
|
+
if (request.temperature !== void 0) {
|
|
719
|
+
body.temperature = request.temperature;
|
|
720
|
+
}
|
|
721
|
+
if (systemContent !== void 0) {
|
|
722
|
+
body.system = systemContent;
|
|
723
|
+
}
|
|
724
|
+
if (request.tools?.length) {
|
|
725
|
+
body.tools = request.tools.map((tool) => ({
|
|
726
|
+
name: tool.function.name,
|
|
727
|
+
description: tool.function.description || "",
|
|
728
|
+
input_schema: tool.function.parameters
|
|
729
|
+
}));
|
|
730
|
+
}
|
|
731
|
+
const serverSideTools = request._serverSideTools;
|
|
732
|
+
if (serverSideTools?.length) {
|
|
733
|
+
body.tools = [...body.tools || [], ...serverSideTools];
|
|
734
|
+
}
|
|
735
|
+
if (request.tool_choice) {
|
|
736
|
+
if (typeof request.tool_choice === "string") {
|
|
737
|
+
if (request.tool_choice === "required") {
|
|
738
|
+
body.tool_choice = { type: "any" };
|
|
739
|
+
} else if (request.tool_choice !== "none") {
|
|
740
|
+
body.tool_choice = { type: request.tool_choice };
|
|
741
|
+
}
|
|
742
|
+
} else if (typeof request.tool_choice === "object" && "function" in request.tool_choice) {
|
|
743
|
+
body.tool_choice = { type: "tool", name: request.tool_choice.function.name };
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (request.reasoning?.enabled) {
|
|
747
|
+
const budgetMap = { low: 2048, medium: 8192, high: 32768 };
|
|
748
|
+
const budget = request.reasoning.max_tokens || budgetMap[request.reasoning.effort || "medium"] || 8192;
|
|
749
|
+
body.thinking = { type: "enabled", budget_tokens: budget };
|
|
750
|
+
body.temperature = 1;
|
|
751
|
+
}
|
|
752
|
+
return body;
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// src/transformer/transformers/AnthropicResponseConversion.ts
|
|
756
|
+
function convertAnthropicResponseToOpenAI(anthropicResponse) {
|
|
757
|
+
const content = anthropicResponse.content || [];
|
|
758
|
+
const textParts = content.filter((c) => c.type === "text").map((c) => c.text);
|
|
759
|
+
const toolUses = content.filter((c) => c.type === "tool_use" || c.type === "server_tool_use");
|
|
760
|
+
const thinkingBlock = content.find((c) => c.type === "thinking");
|
|
761
|
+
const searchResults = content.filter((c) => c.type === "web_search_tool_result");
|
|
762
|
+
for (const sr of searchResults) {
|
|
763
|
+
const searches = sr.content;
|
|
764
|
+
if (searches?.length) {
|
|
765
|
+
const formatted = searches.map((s) => `[${s.title}](${s.url}): ${s.page_content || s.snippet || ""}`).join("\n");
|
|
766
|
+
textParts.push(`
|
|
767
|
+
|
|
768
|
+
**Search Results:**
|
|
769
|
+
${formatted}`);
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
const message = {
|
|
773
|
+
role: "assistant",
|
|
774
|
+
content: textParts.join("") || null
|
|
775
|
+
};
|
|
776
|
+
if (toolUses.length > 0) {
|
|
777
|
+
message.tool_calls = toolUses.map((tc) => ({
|
|
778
|
+
id: tc.id,
|
|
779
|
+
type: "function",
|
|
780
|
+
function: {
|
|
781
|
+
name: tc.name,
|
|
782
|
+
arguments: JSON.stringify(tc.input || {})
|
|
783
|
+
}
|
|
784
|
+
}));
|
|
785
|
+
}
|
|
786
|
+
if (thinkingBlock) {
|
|
787
|
+
message.thinking = {
|
|
788
|
+
content: thinkingBlock.thinking,
|
|
789
|
+
signature: thinkingBlock.signature
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
const stopReasonMapping = {
|
|
793
|
+
end_turn: "stop",
|
|
794
|
+
max_tokens: "length",
|
|
795
|
+
tool_use: "tool_calls",
|
|
796
|
+
stop_sequence: "stop"
|
|
797
|
+
};
|
|
798
|
+
const usage = anthropicResponse.usage;
|
|
799
|
+
return {
|
|
800
|
+
id: anthropicResponse.id || `chatcmpl-${Date.now()}`,
|
|
801
|
+
object: "chat.completion",
|
|
802
|
+
created: Math.floor(Date.now() / 1e3),
|
|
803
|
+
model: anthropicResponse.model || "unknown",
|
|
804
|
+
choices: [{
|
|
805
|
+
index: 0,
|
|
806
|
+
message,
|
|
807
|
+
finish_reason: stopReasonMapping[anthropicResponse.stop_reason] || "stop"
|
|
808
|
+
}],
|
|
809
|
+
usage: usage ? {
|
|
810
|
+
prompt_tokens: usage.input_tokens || 0,
|
|
811
|
+
completion_tokens: usage.output_tokens || 0,
|
|
812
|
+
total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
|
813
|
+
} : void 0
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
function convertOpenAIResponseToAnthropic(openaiResponse) {
|
|
817
|
+
const choice = openaiResponse.choices?.[0];
|
|
818
|
+
if (!choice) {
|
|
819
|
+
throw new Error("No choices found in OpenAI response");
|
|
820
|
+
}
|
|
821
|
+
const message = choice.message;
|
|
822
|
+
const content = [];
|
|
823
|
+
if (message.content) {
|
|
824
|
+
content.push({
|
|
825
|
+
type: "text",
|
|
826
|
+
text: message.content
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
const toolCalls = message.tool_calls;
|
|
830
|
+
if (toolCalls?.length) {
|
|
831
|
+
for (const toolCall of toolCalls) {
|
|
832
|
+
const func = toolCall.function;
|
|
833
|
+
let parsedInput = {};
|
|
834
|
+
try {
|
|
835
|
+
const args = func.arguments;
|
|
836
|
+
parsedInput = typeof args === "string" ? JSON.parse(args) : args;
|
|
837
|
+
} catch {
|
|
838
|
+
parsedInput = { text: func.arguments || "" };
|
|
839
|
+
}
|
|
840
|
+
content.push({
|
|
841
|
+
type: "tool_use",
|
|
842
|
+
id: toolCall.id,
|
|
843
|
+
name: func.name,
|
|
844
|
+
input: parsedInput
|
|
845
|
+
});
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
const thinking = message.thinking;
|
|
849
|
+
if (thinking?.content) {
|
|
850
|
+
content.push({
|
|
851
|
+
type: "thinking",
|
|
852
|
+
thinking: thinking.content,
|
|
853
|
+
signature: thinking.signature
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
const finishReason = choice.finish_reason;
|
|
857
|
+
const stopReasonMapping = {
|
|
858
|
+
stop: "end_turn",
|
|
859
|
+
length: "max_tokens",
|
|
860
|
+
tool_calls: "tool_use",
|
|
861
|
+
content_filter: "stop_sequence"
|
|
862
|
+
};
|
|
863
|
+
const usage = openaiResponse.usage;
|
|
864
|
+
const usageDetails = usage?.prompt_tokens_details;
|
|
865
|
+
return {
|
|
866
|
+
id: openaiResponse.id,
|
|
867
|
+
type: "message",
|
|
868
|
+
role: "assistant",
|
|
869
|
+
model: openaiResponse.model,
|
|
870
|
+
content,
|
|
871
|
+
stop_reason: stopReasonMapping[finishReason] || "end_turn",
|
|
872
|
+
stop_sequence: null,
|
|
873
|
+
usage: {
|
|
874
|
+
input_tokens: (usage?.prompt_tokens || 0) - (usageDetails?.cached_tokens || 0),
|
|
875
|
+
output_tokens: usage?.completion_tokens || 0,
|
|
876
|
+
cache_read_input_tokens: usageDetails?.cached_tokens || 0
|
|
877
|
+
}
|
|
878
|
+
};
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// src/transformer/transformers/AnthropicConversion.ts
|
|
882
|
+
function transformAnthropicRequestToUnified(request) {
|
|
883
|
+
const anthropicRequest = request;
|
|
884
|
+
const messages = [];
|
|
885
|
+
if (anthropicRequest.system) {
|
|
886
|
+
if (typeof anthropicRequest.system === "string") {
|
|
887
|
+
messages.push({
|
|
888
|
+
role: "system",
|
|
889
|
+
content: anthropicRequest.system
|
|
890
|
+
});
|
|
891
|
+
} else if (Array.isArray(anthropicRequest.system)) {
|
|
892
|
+
const textParts = anthropicRequest.system.filter((item) => item.type === "text" && item.text).map((item) => ({
|
|
893
|
+
type: "text",
|
|
894
|
+
text: item.text,
|
|
895
|
+
cache_control: item.cache_control
|
|
896
|
+
}));
|
|
897
|
+
if (textParts.length > 0) {
|
|
898
|
+
messages.push({
|
|
899
|
+
role: "system",
|
|
900
|
+
content: textParts
|
|
901
|
+
});
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
const requestMessages = JSON.parse(JSON.stringify(anthropicRequest.messages || []));
|
|
906
|
+
for (const msg of requestMessages) {
|
|
907
|
+
if (msg.role !== "user" && msg.role !== "assistant") continue;
|
|
908
|
+
if (typeof msg.content === "string") {
|
|
909
|
+
messages.push({
|
|
910
|
+
role: msg.role,
|
|
911
|
+
content: msg.content
|
|
912
|
+
});
|
|
913
|
+
continue;
|
|
914
|
+
}
|
|
915
|
+
if (Array.isArray(msg.content)) {
|
|
916
|
+
if (msg.role === "user") {
|
|
917
|
+
const toolParts = msg.content.filter(
|
|
918
|
+
(c) => c.type === "tool_result" && c.tool_use_id
|
|
919
|
+
);
|
|
920
|
+
for (const tool of toolParts) {
|
|
921
|
+
messages.push({
|
|
922
|
+
role: "tool",
|
|
923
|
+
content: typeof tool.content === "string" ? tool.content : JSON.stringify(tool.content),
|
|
924
|
+
tool_call_id: tool.tool_use_id,
|
|
925
|
+
cache_control: tool.cache_control
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
const textAndMediaParts = msg.content.filter(
|
|
929
|
+
(c) => c.type === "text" && c.text || c.type === "image" && c.source
|
|
930
|
+
);
|
|
931
|
+
if (textAndMediaParts.length > 0) {
|
|
932
|
+
messages.push({
|
|
933
|
+
role: "user",
|
|
934
|
+
content: textAndMediaParts.map((part) => {
|
|
935
|
+
if (part.type === "image") {
|
|
936
|
+
const imagePart = part;
|
|
937
|
+
return {
|
|
938
|
+
type: "image_url",
|
|
939
|
+
image_url: {
|
|
940
|
+
url: imagePart.source.type === "base64" ? formatBase64(imagePart.source.data || "", imagePart.source.media_type) : imagePart.source.url || ""
|
|
941
|
+
},
|
|
942
|
+
media_type: imagePart.source.media_type
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
return {
|
|
946
|
+
type: "text",
|
|
947
|
+
text: part.text
|
|
948
|
+
};
|
|
949
|
+
})
|
|
950
|
+
});
|
|
951
|
+
}
|
|
952
|
+
} else if (msg.role === "assistant") {
|
|
953
|
+
const assistantMessage = {
|
|
954
|
+
role: "assistant",
|
|
955
|
+
content: ""
|
|
956
|
+
};
|
|
957
|
+
const textParts = msg.content.filter(
|
|
958
|
+
(c) => c.type === "text" && c.text
|
|
959
|
+
);
|
|
960
|
+
if (textParts.length > 0) {
|
|
961
|
+
assistantMessage.content = textParts.map((t) => t.text).join("\n");
|
|
962
|
+
}
|
|
963
|
+
const toolCallParts = msg.content.filter(
|
|
964
|
+
(c) => c.type === "tool_use" && c.id
|
|
965
|
+
);
|
|
966
|
+
if (toolCallParts.length > 0) {
|
|
967
|
+
assistantMessage.tool_calls = toolCallParts.map((tool) => ({
|
|
968
|
+
id: tool.id,
|
|
969
|
+
type: "function",
|
|
970
|
+
function: {
|
|
971
|
+
name: tool.name,
|
|
972
|
+
arguments: JSON.stringify(tool.input || {})
|
|
973
|
+
}
|
|
974
|
+
}));
|
|
975
|
+
}
|
|
976
|
+
const thinkingPart = msg.content.find(
|
|
977
|
+
(c) => c.type === "thinking"
|
|
978
|
+
);
|
|
979
|
+
if (thinkingPart?.thinking) {
|
|
980
|
+
assistantMessage.thinking = {
|
|
981
|
+
content: thinkingPart.thinking,
|
|
982
|
+
signature: thinkingPart.signature
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
messages.push(assistantMessage);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
const rawTools = anthropicRequest.tools || [];
|
|
990
|
+
const serverSideTools = rawTools.filter((t) => isServerSideTool(t));
|
|
991
|
+
const functionTools = rawTools.length > 0 ? convertAnthropicToolsToOpenAI(rawTools) : void 0;
|
|
992
|
+
const result = {
|
|
993
|
+
messages,
|
|
994
|
+
model: anthropicRequest.model,
|
|
995
|
+
max_tokens: anthropicRequest.max_tokens,
|
|
996
|
+
temperature: anthropicRequest.temperature,
|
|
997
|
+
stream: anthropicRequest.stream,
|
|
998
|
+
tools: functionTools?.length ? functionTools : void 0
|
|
999
|
+
};
|
|
1000
|
+
if (serverSideTools.length > 0) {
|
|
1001
|
+
result._serverSideTools = serverSideTools;
|
|
1002
|
+
}
|
|
1003
|
+
if (anthropicRequest.thinking) {
|
|
1004
|
+
result.reasoning = {
|
|
1005
|
+
effort: getThinkLevel(anthropicRequest.thinking.budget_tokens),
|
|
1006
|
+
enabled: anthropicRequest.thinking.type === "enabled"
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
if (anthropicRequest.tool_choice) {
|
|
1010
|
+
if (anthropicRequest.tool_choice.type === "tool" && anthropicRequest.tool_choice.name) {
|
|
1011
|
+
result.tool_choice = {
|
|
1012
|
+
type: "function",
|
|
1013
|
+
function: { name: anthropicRequest.tool_choice.name }
|
|
1014
|
+
};
|
|
1015
|
+
} else {
|
|
1016
|
+
result.tool_choice = anthropicRequest.tool_choice.type;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return result;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// src/transformer/transformers/AnthropicAnthropicToOpenAIStream.ts
|
|
1023
|
+
function convertAnthropicStreamToOpenAI(anthropicStream, logger) {
|
|
1024
|
+
const decoder = new TextDecoder();
|
|
1025
|
+
const encoder = new TextEncoder();
|
|
1026
|
+
const activeToolCalls = /* @__PURE__ */ new Map();
|
|
1027
|
+
let toolCallCounter = 0;
|
|
1028
|
+
let model = "unknown";
|
|
1029
|
+
let messageId = `chatcmpl-${Date.now()}`;
|
|
1030
|
+
return new ReadableStream({
|
|
1031
|
+
start: async (controller) => {
|
|
1032
|
+
const reader = anthropicStream.getReader();
|
|
1033
|
+
let buffer = "";
|
|
1034
|
+
let isClosed = false;
|
|
1035
|
+
const safeEnqueue = (data) => {
|
|
1036
|
+
if (!isClosed) {
|
|
1037
|
+
try {
|
|
1038
|
+
controller.enqueue(encoder.encode(data));
|
|
1039
|
+
} catch {
|
|
1040
|
+
isClosed = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
try {
|
|
1045
|
+
while (true) {
|
|
1046
|
+
const { done, value } = await reader.read();
|
|
1047
|
+
if (done) break;
|
|
1048
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1049
|
+
const lines = buffer.split("\n");
|
|
1050
|
+
buffer = lines.pop() || "";
|
|
1051
|
+
for (const line of lines) {
|
|
1052
|
+
if (isClosed) break;
|
|
1053
|
+
if (!line.startsWith("data:")) continue;
|
|
1054
|
+
const data = line.slice(5).trim();
|
|
1055
|
+
if (!data || data === "[DONE]") continue;
|
|
1056
|
+
try {
|
|
1057
|
+
const event = JSON.parse(data);
|
|
1058
|
+
if (event.type === "message_start" && event.message) {
|
|
1059
|
+
model = event.message.model || model;
|
|
1060
|
+
messageId = event.message.id || messageId;
|
|
1061
|
+
}
|
|
1062
|
+
if (event.type === "content_block_delta" && event.delta) {
|
|
1063
|
+
const chunk = {
|
|
1064
|
+
id: messageId,
|
|
1065
|
+
object: "chat.completion.chunk",
|
|
1066
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1067
|
+
model,
|
|
1068
|
+
choices: [{ index: 0, delta: {}, finish_reason: null }]
|
|
1069
|
+
};
|
|
1070
|
+
const delta = chunk.choices[0].delta;
|
|
1071
|
+
if (event.delta.type === "text_delta") {
|
|
1072
|
+
delta.content = event.delta.text;
|
|
1073
|
+
} else if (event.delta.type === "input_json_delta") {
|
|
1074
|
+
const toolInfo = activeToolCalls.get(event.index);
|
|
1075
|
+
if (toolInfo) {
|
|
1076
|
+
delta.tool_calls = [{
|
|
1077
|
+
index: toolInfo.index,
|
|
1078
|
+
function: { arguments: event.delta.partial_json }
|
|
1079
|
+
}];
|
|
1080
|
+
}
|
|
1081
|
+
} else if (event.delta.type === "thinking_delta") {
|
|
1082
|
+
delta.thinking = { content: event.delta.thinking };
|
|
1083
|
+
} else if (event.delta.type === "signature_delta") {
|
|
1084
|
+
delta.thinking = { signature: event.delta.signature };
|
|
1085
|
+
} else {
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
safeEnqueue(`data: ${JSON.stringify(chunk)}
|
|
1089
|
+
|
|
1090
|
+
`);
|
|
1091
|
+
}
|
|
1092
|
+
if (event.type === "content_block_start" && event.content_block) {
|
|
1093
|
+
if (event.content_block.type === "tool_use" || event.content_block.type === "server_tool_use") {
|
|
1094
|
+
const tcIndex = toolCallCounter++;
|
|
1095
|
+
activeToolCalls.set(event.index, {
|
|
1096
|
+
id: event.content_block.id,
|
|
1097
|
+
name: event.content_block.name,
|
|
1098
|
+
index: tcIndex
|
|
1099
|
+
});
|
|
1100
|
+
const chunk = {
|
|
1101
|
+
id: messageId,
|
|
1102
|
+
object: "chat.completion.chunk",
|
|
1103
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1104
|
+
model,
|
|
1105
|
+
choices: [{
|
|
1106
|
+
index: 0,
|
|
1107
|
+
delta: {
|
|
1108
|
+
tool_calls: [{
|
|
1109
|
+
index: tcIndex,
|
|
1110
|
+
id: event.content_block.id,
|
|
1111
|
+
type: "function",
|
|
1112
|
+
function: { name: event.content_block.name, arguments: "" }
|
|
1113
|
+
}]
|
|
1114
|
+
},
|
|
1115
|
+
finish_reason: null
|
|
1116
|
+
}]
|
|
1117
|
+
};
|
|
1118
|
+
safeEnqueue(`data: ${JSON.stringify(chunk)}
|
|
1119
|
+
|
|
1120
|
+
`);
|
|
1121
|
+
} else if (event.content_block.type === "web_search_tool_result") {
|
|
1122
|
+
const searches = event.content_block.content;
|
|
1123
|
+
if (searches?.length) {
|
|
1124
|
+
const formatted = searches.map((s) => `[${s.title}](${s.url}): ${s.page_content || s.snippet || ""}`).join("\n");
|
|
1125
|
+
const chunk = {
|
|
1126
|
+
id: messageId,
|
|
1127
|
+
object: "chat.completion.chunk",
|
|
1128
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1129
|
+
model,
|
|
1130
|
+
choices: [{
|
|
1131
|
+
index: 0,
|
|
1132
|
+
delta: { content: `
|
|
1133
|
+
|
|
1134
|
+
**Search Results:**
|
|
1135
|
+
${formatted}` },
|
|
1136
|
+
finish_reason: null
|
|
1137
|
+
}]
|
|
1138
|
+
};
|
|
1139
|
+
safeEnqueue(`data: ${JSON.stringify(chunk)}
|
|
1140
|
+
|
|
1141
|
+
`);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
if (event.type === "message_delta" && event.delta) {
|
|
1146
|
+
const stopReasonMapping = {
|
|
1147
|
+
end_turn: "stop",
|
|
1148
|
+
max_tokens: "length",
|
|
1149
|
+
tool_use: "tool_calls",
|
|
1150
|
+
stop_sequence: "stop"
|
|
1151
|
+
};
|
|
1152
|
+
const chunk = {
|
|
1153
|
+
id: messageId,
|
|
1154
|
+
object: "chat.completion.chunk",
|
|
1155
|
+
created: Math.floor(Date.now() / 1e3),
|
|
1156
|
+
model,
|
|
1157
|
+
choices: [{
|
|
1158
|
+
index: 0,
|
|
1159
|
+
delta: {},
|
|
1160
|
+
finish_reason: stopReasonMapping[event.delta.stop_reason] || "stop"
|
|
1161
|
+
}]
|
|
1162
|
+
};
|
|
1163
|
+
if (event.usage) {
|
|
1164
|
+
chunk.usage = {
|
|
1165
|
+
prompt_tokens: event.usage.input_tokens || 0,
|
|
1166
|
+
completion_tokens: event.usage.output_tokens || 0,
|
|
1167
|
+
total_tokens: (event.usage.input_tokens || 0) + (event.usage.output_tokens || 0)
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
safeEnqueue(`data: ${JSON.stringify(chunk)}
|
|
1171
|
+
|
|
1172
|
+
`);
|
|
1173
|
+
}
|
|
1174
|
+
} catch (e) {
|
|
1175
|
+
logger?.error("Error parsing Anthropic stream event:", e);
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
if (!isClosed) {
|
|
1181
|
+
controller.error(e);
|
|
1182
|
+
}
|
|
1183
|
+
} finally {
|
|
1184
|
+
safeEnqueue("data: [DONE]\n\n");
|
|
1185
|
+
if (!isClosed) {
|
|
1186
|
+
try {
|
|
1187
|
+
controller.close();
|
|
1188
|
+
} catch {
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
reader.releaseLock();
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
// src/transformer/transformers/AnthropicOpenAIToAnthropicStream.ts
|
|
1198
|
+
function convertOpenAIStreamToAnthropic(openaiStream, _context, logger) {
|
|
1199
|
+
const decoder = new TextDecoder();
|
|
1200
|
+
const encoder = new TextEncoder();
|
|
1201
|
+
let hasStarted = false;
|
|
1202
|
+
let hasTextContentStarted = false;
|
|
1203
|
+
let isThinkingStarted = false;
|
|
1204
|
+
let contentIndex = 0;
|
|
1205
|
+
let currentContentBlockIndex = -1;
|
|
1206
|
+
const toolCallIndexToContentBlockIndex = /* @__PURE__ */ new Map();
|
|
1207
|
+
return new ReadableStream({
|
|
1208
|
+
start: async (controller) => {
|
|
1209
|
+
const reader = openaiStream.getReader();
|
|
1210
|
+
let buffer = "";
|
|
1211
|
+
const messageId = `msg_${Date.now()}`;
|
|
1212
|
+
let model = "unknown";
|
|
1213
|
+
let isClosed = false;
|
|
1214
|
+
let stopReasonDelta = null;
|
|
1215
|
+
const safeEnqueue = (data) => {
|
|
1216
|
+
if (!isClosed) {
|
|
1217
|
+
try {
|
|
1218
|
+
controller.enqueue(encoder.encode(data));
|
|
1219
|
+
} catch (_e) {
|
|
1220
|
+
isClosed = true;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
};
|
|
1224
|
+
const assignContentBlockIndex = () => {
|
|
1225
|
+
return contentIndex++;
|
|
1226
|
+
};
|
|
1227
|
+
const safeClose = () => {
|
|
1228
|
+
if (isClosed) return;
|
|
1229
|
+
if (currentContentBlockIndex >= 0) {
|
|
1230
|
+
safeEnqueue(`event: content_block_stop
|
|
1231
|
+
data: ${JSON.stringify({
|
|
1232
|
+
type: "content_block_stop",
|
|
1233
|
+
index: currentContentBlockIndex
|
|
1234
|
+
})}
|
|
1235
|
+
|
|
1236
|
+
`);
|
|
1237
|
+
}
|
|
1238
|
+
if (stopReasonDelta) {
|
|
1239
|
+
safeEnqueue(`event: message_delta
|
|
1240
|
+
data: ${JSON.stringify(stopReasonDelta)}
|
|
1241
|
+
|
|
1242
|
+
`);
|
|
1243
|
+
} else {
|
|
1244
|
+
safeEnqueue(`event: message_delta
|
|
1245
|
+
data: ${JSON.stringify({
|
|
1246
|
+
type: "message_delta",
|
|
1247
|
+
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
1248
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
1249
|
+
})}
|
|
1250
|
+
|
|
1251
|
+
`);
|
|
1252
|
+
}
|
|
1253
|
+
safeEnqueue(`event: message_stop
|
|
1254
|
+
data: ${JSON.stringify({ type: "message_stop" })}
|
|
1255
|
+
|
|
1256
|
+
`);
|
|
1257
|
+
try {
|
|
1258
|
+
controller.close();
|
|
1259
|
+
} catch (_e) {
|
|
1260
|
+
}
|
|
1261
|
+
isClosed = true;
|
|
1262
|
+
};
|
|
1263
|
+
try {
|
|
1264
|
+
while (true) {
|
|
1265
|
+
const { done, value } = await reader.read();
|
|
1266
|
+
if (done) break;
|
|
1267
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1268
|
+
const lines = buffer.split("\n");
|
|
1269
|
+
buffer = lines.pop() || "";
|
|
1270
|
+
for (const line of lines) {
|
|
1271
|
+
if (isClosed) break;
|
|
1272
|
+
if (!line.startsWith("data:")) continue;
|
|
1273
|
+
const data = line.slice(5).trim();
|
|
1274
|
+
if (data === "[DONE]") continue;
|
|
1275
|
+
try {
|
|
1276
|
+
const chunk = JSON.parse(data);
|
|
1277
|
+
if (chunk.error) {
|
|
1278
|
+
safeEnqueue(`event: error
|
|
1279
|
+
data: ${JSON.stringify({
|
|
1280
|
+
type: "error",
|
|
1281
|
+
message: { type: "api_error", message: JSON.stringify(chunk.error) }
|
|
1282
|
+
})}
|
|
1283
|
+
|
|
1284
|
+
`);
|
|
1285
|
+
continue;
|
|
1286
|
+
}
|
|
1287
|
+
model = chunk.model || model;
|
|
1288
|
+
if (!hasStarted) {
|
|
1289
|
+
hasStarted = true;
|
|
1290
|
+
safeEnqueue(`event: message_start
|
|
1291
|
+
data: ${JSON.stringify({
|
|
1292
|
+
type: "message_start",
|
|
1293
|
+
message: {
|
|
1294
|
+
id: messageId,
|
|
1295
|
+
type: "message",
|
|
1296
|
+
role: "assistant",
|
|
1297
|
+
content: [],
|
|
1298
|
+
model,
|
|
1299
|
+
stop_reason: null,
|
|
1300
|
+
stop_sequence: null,
|
|
1301
|
+
usage: { input_tokens: 0, output_tokens: 0 }
|
|
1302
|
+
}
|
|
1303
|
+
})}
|
|
1304
|
+
|
|
1305
|
+
`);
|
|
1306
|
+
}
|
|
1307
|
+
const choice = chunk.choices?.[0];
|
|
1308
|
+
if (!choice) continue;
|
|
1309
|
+
if (chunk.usage) {
|
|
1310
|
+
stopReasonDelta = {
|
|
1311
|
+
type: "message_delta",
|
|
1312
|
+
delta: { stop_reason: "end_turn", stop_sequence: null },
|
|
1313
|
+
usage: {
|
|
1314
|
+
input_tokens: (chunk.usage.prompt_tokens || 0) - (chunk.usage.prompt_tokens_details?.cached_tokens || 0),
|
|
1315
|
+
output_tokens: chunk.usage.completion_tokens || 0,
|
|
1316
|
+
cache_read_input_tokens: chunk.usage.prompt_tokens_details?.cached_tokens || 0
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
if (choice.delta?.thinking) {
|
|
1321
|
+
if (!isThinkingStarted) {
|
|
1322
|
+
const thinkingBlockIndex = assignContentBlockIndex();
|
|
1323
|
+
safeEnqueue(`event: content_block_start
|
|
1324
|
+
data: ${JSON.stringify({
|
|
1325
|
+
type: "content_block_start",
|
|
1326
|
+
index: thinkingBlockIndex,
|
|
1327
|
+
content_block: { type: "thinking", thinking: "" }
|
|
1328
|
+
})}
|
|
1329
|
+
|
|
1330
|
+
`);
|
|
1331
|
+
currentContentBlockIndex = thinkingBlockIndex;
|
|
1332
|
+
isThinkingStarted = true;
|
|
1333
|
+
}
|
|
1334
|
+
if (choice.delta.thinking.signature) {
|
|
1335
|
+
safeEnqueue(`event: content_block_delta
|
|
1336
|
+
data: ${JSON.stringify({
|
|
1337
|
+
type: "content_block_delta",
|
|
1338
|
+
index: currentContentBlockIndex,
|
|
1339
|
+
delta: { type: "signature_delta", signature: choice.delta.thinking.signature }
|
|
1340
|
+
})}
|
|
1341
|
+
|
|
1342
|
+
`);
|
|
1343
|
+
safeEnqueue(`event: content_block_stop
|
|
1344
|
+
data: ${JSON.stringify({
|
|
1345
|
+
type: "content_block_stop",
|
|
1346
|
+
index: currentContentBlockIndex
|
|
1347
|
+
})}
|
|
1348
|
+
|
|
1349
|
+
`);
|
|
1350
|
+
currentContentBlockIndex = -1;
|
|
1351
|
+
} else if (choice.delta.thinking.content) {
|
|
1352
|
+
safeEnqueue(`event: content_block_delta
|
|
1353
|
+
data: ${JSON.stringify({
|
|
1354
|
+
type: "content_block_delta",
|
|
1355
|
+
index: currentContentBlockIndex,
|
|
1356
|
+
delta: { type: "thinking_delta", thinking: choice.delta.thinking.content }
|
|
1357
|
+
})}
|
|
1358
|
+
|
|
1359
|
+
`);
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
if (choice.delta?.content) {
|
|
1363
|
+
if (!hasTextContentStarted) {
|
|
1364
|
+
if (currentContentBlockIndex >= 0 && isThinkingStarted) {
|
|
1365
|
+
safeEnqueue(`event: content_block_stop
|
|
1366
|
+
data: ${JSON.stringify({
|
|
1367
|
+
type: "content_block_stop",
|
|
1368
|
+
index: currentContentBlockIndex
|
|
1369
|
+
})}
|
|
1370
|
+
|
|
1371
|
+
`);
|
|
1372
|
+
}
|
|
1373
|
+
hasTextContentStarted = true;
|
|
1374
|
+
const textBlockIndex = assignContentBlockIndex();
|
|
1375
|
+
safeEnqueue(`event: content_block_start
|
|
1376
|
+
data: ${JSON.stringify({
|
|
1377
|
+
type: "content_block_start",
|
|
1378
|
+
index: textBlockIndex,
|
|
1379
|
+
content_block: { type: "text", text: "" }
|
|
1380
|
+
})}
|
|
1381
|
+
|
|
1382
|
+
`);
|
|
1383
|
+
currentContentBlockIndex = textBlockIndex;
|
|
1384
|
+
}
|
|
1385
|
+
safeEnqueue(`event: content_block_delta
|
|
1386
|
+
data: ${JSON.stringify({
|
|
1387
|
+
type: "content_block_delta",
|
|
1388
|
+
index: currentContentBlockIndex,
|
|
1389
|
+
delta: { type: "text_delta", text: choice.delta.content }
|
|
1390
|
+
})}
|
|
1391
|
+
|
|
1392
|
+
`);
|
|
1393
|
+
}
|
|
1394
|
+
if (choice.delta?.tool_calls) {
|
|
1395
|
+
for (const toolCall of choice.delta.tool_calls) {
|
|
1396
|
+
const toolCallIndex = toolCall.index ?? 0;
|
|
1397
|
+
if (!toolCallIndexToContentBlockIndex.has(toolCallIndex)) {
|
|
1398
|
+
if (currentContentBlockIndex >= 0) {
|
|
1399
|
+
safeEnqueue(`event: content_block_stop
|
|
1400
|
+
data: ${JSON.stringify({
|
|
1401
|
+
type: "content_block_stop",
|
|
1402
|
+
index: currentContentBlockIndex
|
|
1403
|
+
})}
|
|
1404
|
+
|
|
1405
|
+
`);
|
|
1406
|
+
hasTextContentStarted = false;
|
|
1407
|
+
}
|
|
1408
|
+
const newBlockIndex = assignContentBlockIndex();
|
|
1409
|
+
toolCallIndexToContentBlockIndex.set(toolCallIndex, newBlockIndex);
|
|
1410
|
+
safeEnqueue(`event: content_block_start
|
|
1411
|
+
data: ${JSON.stringify({
|
|
1412
|
+
type: "content_block_start",
|
|
1413
|
+
index: newBlockIndex,
|
|
1414
|
+
content_block: {
|
|
1415
|
+
type: "tool_use",
|
|
1416
|
+
id: toolCall.id || `call_${Date.now()}_${toolCallIndex}`,
|
|
1417
|
+
name: toolCall.function?.name || `tool_${toolCallIndex}`,
|
|
1418
|
+
input: {}
|
|
1419
|
+
}
|
|
1420
|
+
})}
|
|
1421
|
+
|
|
1422
|
+
`);
|
|
1423
|
+
currentContentBlockIndex = newBlockIndex;
|
|
1424
|
+
}
|
|
1425
|
+
if (toolCall.function?.arguments) {
|
|
1426
|
+
const blockIndex = toolCallIndexToContentBlockIndex.get(toolCallIndex);
|
|
1427
|
+
if (blockIndex !== void 0) {
|
|
1428
|
+
safeEnqueue(`event: content_block_delta
|
|
1429
|
+
data: ${JSON.stringify({
|
|
1430
|
+
type: "content_block_delta",
|
|
1431
|
+
index: blockIndex,
|
|
1432
|
+
delta: { type: "input_json_delta", partial_json: toolCall.function.arguments }
|
|
1433
|
+
})}
|
|
1434
|
+
|
|
1435
|
+
`);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
if (choice.finish_reason) {
|
|
1441
|
+
const stopReasonMapping = {
|
|
1442
|
+
stop: "end_turn",
|
|
1443
|
+
length: "max_tokens",
|
|
1444
|
+
tool_calls: "tool_use",
|
|
1445
|
+
content_filter: "stop_sequence"
|
|
1446
|
+
};
|
|
1447
|
+
stopReasonDelta = {
|
|
1448
|
+
type: "message_delta",
|
|
1449
|
+
delta: {
|
|
1450
|
+
stop_reason: stopReasonMapping[choice.finish_reason] || "end_turn",
|
|
1451
|
+
stop_sequence: null
|
|
1452
|
+
},
|
|
1453
|
+
usage: {
|
|
1454
|
+
input_tokens: (chunk.usage?.prompt_tokens || 0) - (chunk.usage?.prompt_tokens_details?.cached_tokens || 0),
|
|
1455
|
+
output_tokens: chunk.usage?.completion_tokens || 0,
|
|
1456
|
+
cache_read_input_tokens: chunk.usage?.prompt_tokens_details?.cached_tokens || 0
|
|
1457
|
+
}
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
} catch (e) {
|
|
1461
|
+
logger?.error("Error parsing stream chunk:", e);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1465
|
+
} catch (e) {
|
|
1466
|
+
if (!isClosed) {
|
|
1467
|
+
controller.error(e);
|
|
1468
|
+
}
|
|
1469
|
+
} finally {
|
|
1470
|
+
safeClose();
|
|
1471
|
+
reader.releaseLock();
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
});
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
// src/transformer/transformers/AnthropicTransformer.ts
|
|
1478
|
+
var AnthropicTransformer = class {
|
|
1479
|
+
static TransformerName = "anthropic";
|
|
1480
|
+
name = "anthropic";
|
|
1481
|
+
logger;
|
|
1482
|
+
endPoint = "/v1/messages";
|
|
1483
|
+
useBearer;
|
|
1484
|
+
constructor(options) {
|
|
1485
|
+
this.useBearer = options?.UseBearer ?? false;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Handle authentication - Anthropic uses x-api-key header
|
|
1489
|
+
*/
|
|
1490
|
+
async auth(request, provider, _context) {
|
|
1491
|
+
const headers = {};
|
|
1492
|
+
if (this.useBearer) {
|
|
1493
|
+
headers["authorization"] = `Bearer ${provider.apiKey}`;
|
|
1494
|
+
headers["x-api-key"] = void 0;
|
|
1495
|
+
} else {
|
|
1496
|
+
headers["x-api-key"] = provider.apiKey;
|
|
1497
|
+
headers["authorization"] = void 0;
|
|
1498
|
+
}
|
|
1499
|
+
return {
|
|
1500
|
+
body: request,
|
|
1501
|
+
config: { headers }
|
|
1502
|
+
};
|
|
1503
|
+
}
|
|
1504
|
+
/**
|
|
1505
|
+
* Transform Anthropic request to unified format.
|
|
1506
|
+
*/
|
|
1507
|
+
async transformRequestOut(request, _context) {
|
|
1508
|
+
return transformAnthropicRequestToUnified(request);
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* Transform OpenAI/unified response back to Anthropic format
|
|
1512
|
+
* (auto-detects stream vs JSON via Content-Type).
|
|
1513
|
+
*/
|
|
1514
|
+
async transformResponseIn(response, context) {
|
|
1515
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1516
|
+
if (contentType.includes("text/event-stream")) {
|
|
1517
|
+
if (!response.body) {
|
|
1518
|
+
throw new Error("Stream response body is null");
|
|
1519
|
+
}
|
|
1520
|
+
const convertedStream = convertOpenAIStreamToAnthropic(response.body, context, this.logger);
|
|
1521
|
+
return new Response(convertedStream, {
|
|
1522
|
+
headers: {
|
|
1523
|
+
"Content-Type": "text/event-stream",
|
|
1524
|
+
"Cache-Control": "no-cache",
|
|
1525
|
+
Connection: "keep-alive"
|
|
1526
|
+
}
|
|
1527
|
+
});
|
|
1528
|
+
} else {
|
|
1529
|
+
const data = await response.json();
|
|
1530
|
+
const anthropicResponse = convertOpenAIResponseToAnthropic(data);
|
|
1531
|
+
return new Response(JSON.stringify(anthropicResponse), {
|
|
1532
|
+
headers: { "Content-Type": "application/json" }
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
/**
|
|
1537
|
+
* Transform unified request to Anthropic Messages API format.
|
|
1538
|
+
* This is the reverse of transformRequestOut — converts OpenAI/unified format
|
|
1539
|
+
* to Anthropic's expected request body structure.
|
|
1540
|
+
*/
|
|
1541
|
+
async transformRequestIn(request, _provider, _context) {
|
|
1542
|
+
return buildAnthropicRequestBody(request);
|
|
1543
|
+
}
|
|
1544
|
+
/**
|
|
1545
|
+
* Transform Anthropic response to OpenAI/unified format
|
|
1546
|
+
* (auto-detects stream vs JSON via Content-Type).
|
|
1547
|
+
*/
|
|
1548
|
+
async transformResponseOut(response, _context) {
|
|
1549
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1550
|
+
if (contentType.includes("text/event-stream")) {
|
|
1551
|
+
if (!response.body) {
|
|
1552
|
+
throw new Error("Stream response body is null");
|
|
1553
|
+
}
|
|
1554
|
+
const convertedStream = convertAnthropicStreamToOpenAI(response.body, this.logger);
|
|
1555
|
+
return new Response(convertedStream, {
|
|
1556
|
+
headers: {
|
|
1557
|
+
"Content-Type": "text/event-stream",
|
|
1558
|
+
"Cache-Control": "no-cache",
|
|
1559
|
+
Connection: "keep-alive"
|
|
1560
|
+
}
|
|
1561
|
+
});
|
|
1562
|
+
} else {
|
|
1563
|
+
const data = await response.json();
|
|
1564
|
+
const openaiResponse = convertAnthropicResponseToOpenAI(data);
|
|
1565
|
+
return new Response(JSON.stringify(openaiResponse), {
|
|
1566
|
+
headers: { "Content-Type": "application/json" }
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
|
|
1572
|
+
// src/transformer/transformers/DeepseekTransformer.ts
|
|
1573
|
+
var DeepseekTransformer = class {
|
|
1574
|
+
static TransformerName = "deepseek";
|
|
1575
|
+
name = "deepseek";
|
|
1576
|
+
logger;
|
|
1577
|
+
/**
|
|
1578
|
+
* Transform request: limit max_tokens to 8192 for legacy models only
|
|
1579
|
+
* V4 models support up to 384K output tokens
|
|
1580
|
+
*/
|
|
1581
|
+
async transformRequestIn(request, _provider, _context) {
|
|
1582
|
+
const isV4 = request.model?.startsWith("deepseek-v4");
|
|
1583
|
+
if (!isV4 && request.max_tokens && request.max_tokens > 8192) {
|
|
1584
|
+
this.logger?.debug("DeepSeek: Limiting max_tokens from %d to 8192", request.max_tokens);
|
|
1585
|
+
request.max_tokens = 8192;
|
|
1586
|
+
}
|
|
1587
|
+
return request;
|
|
1588
|
+
}
|
|
1589
|
+
/**
|
|
1590
|
+
* Transform response: convert reasoning_content to thinking blocks
|
|
1591
|
+
*/
|
|
1592
|
+
async transformResponseOut(response, _context) {
|
|
1593
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1594
|
+
if (contentType.includes("application/json")) {
|
|
1595
|
+
return this.handleJsonResponse(response);
|
|
1596
|
+
} else if (contentType.includes("stream") || contentType.includes("text/event-stream")) {
|
|
1597
|
+
return this.handleStreamResponse(response);
|
|
1598
|
+
}
|
|
1599
|
+
return response;
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Handle JSON (non-streaming) response
|
|
1603
|
+
*/
|
|
1604
|
+
async handleJsonResponse(response) {
|
|
1605
|
+
const jsonResponse = await response.json();
|
|
1606
|
+
if (jsonResponse.choices?.[0]?.message?.reasoning_content) {
|
|
1607
|
+
jsonResponse.choices[0].message.thinking = {
|
|
1608
|
+
content: jsonResponse.choices[0].message.reasoning_content
|
|
1609
|
+
};
|
|
1610
|
+
delete jsonResponse.choices[0].message.reasoning_content;
|
|
1611
|
+
}
|
|
1612
|
+
return new Response(JSON.stringify(jsonResponse), {
|
|
1613
|
+
status: response.status,
|
|
1614
|
+
statusText: response.statusText,
|
|
1615
|
+
headers: response.headers
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Handle streaming response - convert reasoning_content to thinking blocks
|
|
1620
|
+
*/
|
|
1621
|
+
async handleStreamResponse(response) {
|
|
1622
|
+
if (!response.body) {
|
|
1623
|
+
return response;
|
|
1624
|
+
}
|
|
1625
|
+
const decoder = new TextDecoder();
|
|
1626
|
+
const encoder = new TextEncoder();
|
|
1627
|
+
let reasoningContent = "";
|
|
1628
|
+
let isReasoningComplete = false;
|
|
1629
|
+
let buffer = "";
|
|
1630
|
+
const stream = new ReadableStream({
|
|
1631
|
+
start: async (controller) => {
|
|
1632
|
+
const reader = response.body.getReader();
|
|
1633
|
+
const processLine = (line) => {
|
|
1634
|
+
if (line.startsWith("data: ") && line.trim() !== "data: [DONE]") {
|
|
1635
|
+
try {
|
|
1636
|
+
const data = JSON.parse(line.slice(6));
|
|
1637
|
+
if (data.choices?.[0]?.delta?.reasoning_content) {
|
|
1638
|
+
reasoningContent += data.choices[0].delta.reasoning_content;
|
|
1639
|
+
const thinkingChunk = {
|
|
1640
|
+
...data,
|
|
1641
|
+
choices: [
|
|
1642
|
+
{
|
|
1643
|
+
...data.choices[0],
|
|
1644
|
+
delta: {
|
|
1645
|
+
...data.choices[0].delta,
|
|
1646
|
+
thinking: {
|
|
1647
|
+
content: data.choices[0].delta.reasoning_content
|
|
1648
|
+
}
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
]
|
|
1652
|
+
};
|
|
1653
|
+
delete thinkingChunk.choices[0].delta.reasoning_content;
|
|
1654
|
+
const thinkingLine = `data: ${JSON.stringify(thinkingChunk)}
|
|
1655
|
+
|
|
1656
|
+
`;
|
|
1657
|
+
controller.enqueue(encoder.encode(thinkingLine));
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
if (data.choices?.[0]?.delta?.content && reasoningContent && !isReasoningComplete) {
|
|
1661
|
+
isReasoningComplete = true;
|
|
1662
|
+
const signature = Date.now().toString();
|
|
1663
|
+
const thinkingChunk = {
|
|
1664
|
+
...data,
|
|
1665
|
+
choices: [
|
|
1666
|
+
{
|
|
1667
|
+
...data.choices[0],
|
|
1668
|
+
delta: {
|
|
1669
|
+
...data.choices[0].delta,
|
|
1670
|
+
content: null,
|
|
1671
|
+
thinking: {
|
|
1672
|
+
signature
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
]
|
|
1677
|
+
};
|
|
1678
|
+
const thinkingLine = `data: ${JSON.stringify(thinkingChunk)}
|
|
1679
|
+
|
|
1680
|
+
`;
|
|
1681
|
+
controller.enqueue(encoder.encode(thinkingLine));
|
|
1682
|
+
}
|
|
1683
|
+
if (data.choices?.[0]?.delta?.reasoning_content) {
|
|
1684
|
+
delete data.choices[0].delta.reasoning_content;
|
|
1685
|
+
}
|
|
1686
|
+
if (data.choices?.[0]?.delta && Object.keys(data.choices[0].delta).length > 0) {
|
|
1687
|
+
if (isReasoningComplete) {
|
|
1688
|
+
data.choices[0].index++;
|
|
1689
|
+
}
|
|
1690
|
+
const modifiedLine = `data: ${JSON.stringify(data)}
|
|
1691
|
+
|
|
1692
|
+
`;
|
|
1693
|
+
controller.enqueue(encoder.encode(modifiedLine));
|
|
1694
|
+
}
|
|
1695
|
+
} catch (_e) {
|
|
1696
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
1697
|
+
}
|
|
1698
|
+
} else {
|
|
1699
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
try {
|
|
1703
|
+
while (true) {
|
|
1704
|
+
const { done, value } = await reader.read();
|
|
1705
|
+
if (done) {
|
|
1706
|
+
if (buffer.trim()) {
|
|
1707
|
+
const lines2 = buffer.split("\n");
|
|
1708
|
+
for (const line of lines2) {
|
|
1709
|
+
if (line.trim()) {
|
|
1710
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
}
|
|
1714
|
+
break;
|
|
1715
|
+
}
|
|
1716
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
1717
|
+
buffer += chunk;
|
|
1718
|
+
const lines = buffer.split("\n");
|
|
1719
|
+
buffer = lines.pop() || "";
|
|
1720
|
+
for (const line of lines) {
|
|
1721
|
+
if (!line.trim()) continue;
|
|
1722
|
+
try {
|
|
1723
|
+
processLine(line);
|
|
1724
|
+
} catch (error) {
|
|
1725
|
+
this.logger?.error("Error processing DeepSeek stream line:", error);
|
|
1726
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
} catch (error) {
|
|
1731
|
+
this.logger?.error("DeepSeek stream error:", error);
|
|
1732
|
+
controller.error(error);
|
|
1733
|
+
} finally {
|
|
1734
|
+
try {
|
|
1735
|
+
reader.releaseLock();
|
|
1736
|
+
} catch (e) {
|
|
1737
|
+
this.logger?.error("Error releasing reader lock:", e);
|
|
1738
|
+
}
|
|
1739
|
+
controller.close();
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
});
|
|
1743
|
+
return new Response(stream, {
|
|
1744
|
+
status: response.status,
|
|
1745
|
+
statusText: response.statusText,
|
|
1746
|
+
headers: new Headers({
|
|
1747
|
+
"Content-Type": "text/event-stream",
|
|
1748
|
+
"Cache-Control": "no-cache",
|
|
1749
|
+
Connection: "keep-alive"
|
|
1750
|
+
})
|
|
1751
|
+
});
|
|
1752
|
+
}
|
|
1753
|
+
};
|
|
1754
|
+
|
|
1755
|
+
// src/transformer/transformers/utils/gemini.response-in.ts
|
|
1756
|
+
var FINISH_REASON_TO_GEMINI = {
|
|
1757
|
+
stop: "STOP",
|
|
1758
|
+
length: "MAX_TOKENS",
|
|
1759
|
+
tool_calls: "STOP",
|
|
1760
|
+
content_filter: "SAFETY",
|
|
1761
|
+
// Already-lowercased Gemini reasons (pass-through from another transformer)
|
|
1762
|
+
max_tokens: "MAX_TOKENS",
|
|
1763
|
+
safety: "SAFETY"
|
|
1764
|
+
};
|
|
1765
|
+
function toGeminiFinishReason(openaiReason) {
|
|
1766
|
+
if (!openaiReason) return null;
|
|
1767
|
+
return FINISH_REASON_TO_GEMINI[openaiReason] || openaiReason.toUpperCase();
|
|
1768
|
+
}
|
|
1769
|
+
function convertOpenAIResponseToGemini(openaiData) {
|
|
1770
|
+
const choice = openaiData.choices?.[0];
|
|
1771
|
+
const message = choice?.message ?? {};
|
|
1772
|
+
const usage = openaiData.usage;
|
|
1773
|
+
const usagePromptDetails = usage?.prompt_tokens_details;
|
|
1774
|
+
const usageOutputDetails = usage?.output_tokens_details;
|
|
1775
|
+
const parts = [];
|
|
1776
|
+
const thinking = message.thinking;
|
|
1777
|
+
if (thinking?.content) {
|
|
1778
|
+
parts.push({ text: thinking.content, thought: true });
|
|
1779
|
+
}
|
|
1780
|
+
if (thinking?.signature) {
|
|
1781
|
+
parts.push({ thoughtSignature: thinking.signature });
|
|
1782
|
+
}
|
|
1783
|
+
if (message.content) {
|
|
1784
|
+
parts.push({ text: message.content });
|
|
1785
|
+
}
|
|
1786
|
+
const toolCalls = message.tool_calls;
|
|
1787
|
+
if (toolCalls?.length) {
|
|
1788
|
+
for (const tc of toolCalls) {
|
|
1789
|
+
const func = tc.function;
|
|
1790
|
+
let args = {};
|
|
1791
|
+
try {
|
|
1792
|
+
const raw = func.arguments;
|
|
1793
|
+
args = typeof raw === "string" ? JSON.parse(raw) : raw;
|
|
1794
|
+
} catch {
|
|
1795
|
+
args = {};
|
|
1796
|
+
}
|
|
1797
|
+
parts.push({
|
|
1798
|
+
functionCall: {
|
|
1799
|
+
id: tc.id,
|
|
1800
|
+
name: func.name,
|
|
1801
|
+
args
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
if (parts.length === 0) {
|
|
1807
|
+
parts.push({ text: "" });
|
|
1808
|
+
}
|
|
1809
|
+
return {
|
|
1810
|
+
responseId: openaiData.id || "",
|
|
1811
|
+
modelVersion: openaiData.model || "",
|
|
1812
|
+
candidates: [{
|
|
1813
|
+
content: { parts },
|
|
1814
|
+
finishReason: toGeminiFinishReason(choice?.finish_reason)
|
|
1815
|
+
}],
|
|
1816
|
+
usageMetadata: usage ? {
|
|
1817
|
+
promptTokenCount: usage.prompt_tokens || 0,
|
|
1818
|
+
candidatesTokenCount: usage.completion_tokens || 0,
|
|
1819
|
+
totalTokenCount: usage.total_tokens || 0,
|
|
1820
|
+
cachedContentTokenCount: usagePromptDetails?.cached_tokens || 0,
|
|
1821
|
+
thoughtsTokenCount: usageOutputDetails?.reasoning_tokens || 0
|
|
1822
|
+
} : void 0
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
function convertOpenAIStreamToGemini(openaiStream, logger) {
|
|
1826
|
+
const decoder = new TextDecoder();
|
|
1827
|
+
const encoder = new TextEncoder();
|
|
1828
|
+
const pendingToolCalls = /* @__PURE__ */ new Map();
|
|
1829
|
+
let model = "";
|
|
1830
|
+
let responseId = "";
|
|
1831
|
+
return new ReadableStream({
|
|
1832
|
+
start: async (controller) => {
|
|
1833
|
+
const reader = openaiStream.getReader();
|
|
1834
|
+
let buffer = "";
|
|
1835
|
+
let isClosed = false;
|
|
1836
|
+
const emit = (data) => {
|
|
1837
|
+
if (isClosed) return;
|
|
1838
|
+
try {
|
|
1839
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(data)}
|
|
1840
|
+
|
|
1841
|
+
`));
|
|
1842
|
+
} catch {
|
|
1843
|
+
isClosed = true;
|
|
1844
|
+
}
|
|
1845
|
+
};
|
|
1846
|
+
const flushToolCalls = () => {
|
|
1847
|
+
if (pendingToolCalls.size === 0) return;
|
|
1848
|
+
const parts = [];
|
|
1849
|
+
for (const tc of pendingToolCalls.values()) {
|
|
1850
|
+
let args = {};
|
|
1851
|
+
try {
|
|
1852
|
+
args = JSON.parse(tc.args || "{}");
|
|
1853
|
+
} catch {
|
|
1854
|
+
}
|
|
1855
|
+
parts.push({
|
|
1856
|
+
functionCall: { id: tc.id, name: tc.name, args }
|
|
1857
|
+
});
|
|
1858
|
+
}
|
|
1859
|
+
pendingToolCalls.clear();
|
|
1860
|
+
emit({
|
|
1861
|
+
responseId,
|
|
1862
|
+
modelVersion: model,
|
|
1863
|
+
candidates: [{ content: { parts }, finishReason: null }]
|
|
1864
|
+
});
|
|
1865
|
+
};
|
|
1866
|
+
try {
|
|
1867
|
+
while (true) {
|
|
1868
|
+
const { done, value } = await reader.read();
|
|
1869
|
+
if (done) break;
|
|
1870
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1871
|
+
const lines = buffer.split("\n");
|
|
1872
|
+
buffer = lines.pop() || "";
|
|
1873
|
+
for (const line of lines) {
|
|
1874
|
+
if (isClosed) break;
|
|
1875
|
+
if (!line.startsWith("data:")) continue;
|
|
1876
|
+
const data = line.slice(5).trim();
|
|
1877
|
+
if (!data || data === "[DONE]") continue;
|
|
1878
|
+
try {
|
|
1879
|
+
const chunk = JSON.parse(data);
|
|
1880
|
+
if (!responseId && chunk.id) responseId = chunk.id;
|
|
1881
|
+
if (!model && chunk.model) model = chunk.model;
|
|
1882
|
+
const choice = chunk.choices?.[0];
|
|
1883
|
+
if (!choice) continue;
|
|
1884
|
+
const delta = choice.delta ?? {};
|
|
1885
|
+
if (delta.thinking) {
|
|
1886
|
+
const parts = [];
|
|
1887
|
+
if (delta.thinking.content) {
|
|
1888
|
+
parts.push({ text: delta.thinking.content, thought: true });
|
|
1889
|
+
}
|
|
1890
|
+
if (delta.thinking.signature) {
|
|
1891
|
+
parts.push({ thoughtSignature: delta.thinking.signature });
|
|
1892
|
+
}
|
|
1893
|
+
if (parts.length > 0) {
|
|
1894
|
+
emit({
|
|
1895
|
+
responseId,
|
|
1896
|
+
modelVersion: model,
|
|
1897
|
+
candidates: [{ content: { parts }, finishReason: null }]
|
|
1898
|
+
});
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (delta.content) {
|
|
1902
|
+
emit({
|
|
1903
|
+
responseId,
|
|
1904
|
+
modelVersion: model,
|
|
1905
|
+
candidates: [{
|
|
1906
|
+
content: { parts: [{ text: delta.content }] },
|
|
1907
|
+
finishReason: null
|
|
1908
|
+
}]
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
if (delta.tool_calls) {
|
|
1912
|
+
for (const tc of delta.tool_calls) {
|
|
1913
|
+
const idx = tc.index ?? 0;
|
|
1914
|
+
const existing = pendingToolCalls.get(idx);
|
|
1915
|
+
const func = tc.function;
|
|
1916
|
+
if (existing) {
|
|
1917
|
+
if (func?.arguments) {
|
|
1918
|
+
existing.args += func.arguments;
|
|
1919
|
+
}
|
|
1920
|
+
} else {
|
|
1921
|
+
pendingToolCalls.set(idx, {
|
|
1922
|
+
id: tc.id || `tool_${Date.now()}_${idx}`,
|
|
1923
|
+
name: func?.name || "",
|
|
1924
|
+
args: func?.arguments || ""
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
if (choice.finish_reason) {
|
|
1930
|
+
flushToolCalls();
|
|
1931
|
+
const geminiUsage = chunk.usage ? {
|
|
1932
|
+
promptTokenCount: chunk.usage.prompt_tokens || 0,
|
|
1933
|
+
candidatesTokenCount: chunk.usage.completion_tokens || 0,
|
|
1934
|
+
totalTokenCount: chunk.usage.total_tokens || 0,
|
|
1935
|
+
cachedContentTokenCount: chunk.usage.prompt_tokens_details?.cached_tokens || 0,
|
|
1936
|
+
thoughtsTokenCount: chunk.usage.output_tokens_details?.reasoning_tokens || 0
|
|
1937
|
+
} : void 0;
|
|
1938
|
+
emit({
|
|
1939
|
+
responseId,
|
|
1940
|
+
modelVersion: model,
|
|
1941
|
+
candidates: [{
|
|
1942
|
+
content: { parts: [{ text: "" }] },
|
|
1943
|
+
finishReason: toGeminiFinishReason(choice.finish_reason) || "STOP"
|
|
1944
|
+
}],
|
|
1945
|
+
usageMetadata: geminiUsage
|
|
1946
|
+
});
|
|
1947
|
+
}
|
|
1948
|
+
} catch (e) {
|
|
1949
|
+
logger?.error(`Error parsing OpenAI stream chunk for Gemini conversion: ${e}`);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
flushToolCalls();
|
|
1954
|
+
} catch (e) {
|
|
1955
|
+
if (!isClosed) controller.error(e);
|
|
1956
|
+
} finally {
|
|
1957
|
+
if (!isClosed) {
|
|
1958
|
+
try {
|
|
1959
|
+
controller.close();
|
|
1960
|
+
} catch {
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
reader.releaseLock();
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
}
|
|
1968
|
+
async function transformResponseIn(response, logger) {
|
|
1969
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1970
|
+
if (contentType.includes("text/event-stream")) {
|
|
1971
|
+
if (!response.body) {
|
|
1972
|
+
throw new Error("Stream response body is null");
|
|
1973
|
+
}
|
|
1974
|
+
const geminiStream = convertOpenAIStreamToGemini(response.body, logger);
|
|
1975
|
+
return new Response(geminiStream, {
|
|
1976
|
+
headers: {
|
|
1977
|
+
"Content-Type": "text/event-stream",
|
|
1978
|
+
"Cache-Control": "no-cache",
|
|
1979
|
+
Connection: "keep-alive"
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
}
|
|
1983
|
+
const data = await response.json();
|
|
1984
|
+
const geminiResponse = convertOpenAIResponseToGemini(data);
|
|
1985
|
+
return new Response(JSON.stringify(geminiResponse), {
|
|
1986
|
+
status: response.status,
|
|
1987
|
+
statusText: response.statusText,
|
|
1988
|
+
headers: { "Content-Type": "application/json" }
|
|
1989
|
+
});
|
|
1990
|
+
}
|
|
1991
|
+
|
|
1992
|
+
// src/transformer/transformers/utils/gemini.stream.ts
|
|
1993
|
+
async function transformResponseOut(response, providerName, logger) {
|
|
1994
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1995
|
+
if (contentType.includes("application/json")) {
|
|
1996
|
+
return handleJsonResponse(response, providerName, logger);
|
|
1997
|
+
} else if (contentType.includes("stream") || contentType.includes("text/event-stream")) {
|
|
1998
|
+
return handleStreamResponse(response, providerName, logger);
|
|
1999
|
+
}
|
|
2000
|
+
return response;
|
|
2001
|
+
}
|
|
2002
|
+
async function handleJsonResponse(response, providerName, logger) {
|
|
2003
|
+
const jsonResponse = await response.json();
|
|
2004
|
+
logger?.debug(`${providerName} JSON response received`);
|
|
2005
|
+
const parts = jsonResponse.candidates?.[0]?.content?.parts || [];
|
|
2006
|
+
let thinkingContent = "";
|
|
2007
|
+
let thinkingSignature = "";
|
|
2008
|
+
const nonThinkingParts = [];
|
|
2009
|
+
for (const part of parts) {
|
|
2010
|
+
if (part.text && part.thought === true) {
|
|
2011
|
+
thinkingContent += part.text;
|
|
2012
|
+
} else {
|
|
2013
|
+
nonThinkingParts.push(part);
|
|
2014
|
+
}
|
|
2015
|
+
}
|
|
2016
|
+
thinkingSignature = parts.find((part) => part.thoughtSignature)?.thoughtSignature ?? "";
|
|
2017
|
+
const toolCalls = nonThinkingParts.filter((part) => part.functionCall).map((part) => ({
|
|
2018
|
+
id: part.functionCall?.id || `tool_${Math.random().toString(36).substring(2, 15)}`,
|
|
2019
|
+
type: "function",
|
|
2020
|
+
function: {
|
|
2021
|
+
name: part.functionCall?.name ?? "",
|
|
2022
|
+
arguments: JSON.stringify(part.functionCall?.args || {})
|
|
2023
|
+
}
|
|
2024
|
+
}));
|
|
2025
|
+
const textContent = nonThinkingParts.filter((part) => part.text).map((part) => part.text).join("\n");
|
|
2026
|
+
const openAIResponse = {
|
|
2027
|
+
id: jsonResponse.responseId ?? "",
|
|
2028
|
+
choices: [
|
|
2029
|
+
{
|
|
2030
|
+
finish_reason: (jsonResponse.candidates?.[0]?.finishReason ?? "").toLowerCase() || null,
|
|
2031
|
+
index: 0,
|
|
2032
|
+
message: {
|
|
2033
|
+
content: textContent,
|
|
2034
|
+
role: "assistant",
|
|
2035
|
+
tool_calls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
2036
|
+
...thinkingSignature && {
|
|
2037
|
+
thinking: {
|
|
2038
|
+
content: thinkingContent || "(no content)",
|
|
2039
|
+
signature: thinkingSignature
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
],
|
|
2045
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2046
|
+
model: jsonResponse.modelVersion ?? "",
|
|
2047
|
+
object: "chat.completion",
|
|
2048
|
+
usage: {
|
|
2049
|
+
completion_tokens: jsonResponse.usageMetadata?.candidatesTokenCount || 0,
|
|
2050
|
+
prompt_tokens: jsonResponse.usageMetadata?.promptTokenCount || 0,
|
|
2051
|
+
prompt_tokens_details: {
|
|
2052
|
+
cached_tokens: jsonResponse.usageMetadata?.cachedContentTokenCount || 0
|
|
2053
|
+
},
|
|
2054
|
+
total_tokens: jsonResponse.usageMetadata?.totalTokenCount || 0,
|
|
2055
|
+
output_tokens_details: {
|
|
2056
|
+
reasoning_tokens: jsonResponse.usageMetadata?.thoughtsTokenCount || 0
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
return new Response(JSON.stringify(openAIResponse), {
|
|
2061
|
+
status: response.status,
|
|
2062
|
+
statusText: response.statusText,
|
|
2063
|
+
headers: response.headers
|
|
2064
|
+
});
|
|
2065
|
+
}
|
|
2066
|
+
function handleStreamResponse(response, providerName, logger) {
|
|
2067
|
+
if (!response.body) {
|
|
2068
|
+
return response;
|
|
2069
|
+
}
|
|
2070
|
+
const decoder = new TextDecoder();
|
|
2071
|
+
const encoder = new TextEncoder();
|
|
2072
|
+
let signatureSent = false;
|
|
2073
|
+
let contentSent = false;
|
|
2074
|
+
let hasThinkingContent = false;
|
|
2075
|
+
let pendingContent = "";
|
|
2076
|
+
let contentIndex = 0;
|
|
2077
|
+
let toolCallIndex = -1;
|
|
2078
|
+
const stream = new ReadableStream({
|
|
2079
|
+
async start(controller) {
|
|
2080
|
+
const reader = response.body.getReader();
|
|
2081
|
+
let buffer = "";
|
|
2082
|
+
const processLine = async (line) => {
|
|
2083
|
+
if (!line.startsWith("data: ")) return;
|
|
2084
|
+
const chunkStr = line.slice(6).trim();
|
|
2085
|
+
if (!chunkStr) return;
|
|
2086
|
+
logger?.debug(`${providerName} chunk: ${chunkStr.substring(0, 100)}...`);
|
|
2087
|
+
try {
|
|
2088
|
+
const chunk = JSON.parse(chunkStr);
|
|
2089
|
+
if (!chunk.candidates?.[0]) {
|
|
2090
|
+
logger?.debug("Invalid chunk structure");
|
|
2091
|
+
return;
|
|
2092
|
+
}
|
|
2093
|
+
const candidate = chunk.candidates[0];
|
|
2094
|
+
const parts = candidate.content?.parts || [];
|
|
2095
|
+
parts.filter((part) => part.text && part.thought === true).forEach((part) => {
|
|
2096
|
+
hasThinkingContent = true;
|
|
2097
|
+
const thinkingChunk = createChunk({
|
|
2098
|
+
responseId: chunk.responseId,
|
|
2099
|
+
modelVersion: chunk.modelVersion,
|
|
2100
|
+
contentIndex,
|
|
2101
|
+
delta: {
|
|
2102
|
+
role: "assistant",
|
|
2103
|
+
content: null,
|
|
2104
|
+
thinking: { content: part.text }
|
|
2105
|
+
}
|
|
2106
|
+
});
|
|
2107
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(thinkingChunk)}
|
|
2108
|
+
|
|
2109
|
+
`));
|
|
2110
|
+
});
|
|
2111
|
+
const signature = parts.find((part) => part.thoughtSignature)?.thoughtSignature;
|
|
2112
|
+
if (signature && !signatureSent) {
|
|
2113
|
+
if (!hasThinkingContent) {
|
|
2114
|
+
const thinkingChunk = createChunk({
|
|
2115
|
+
responseId: chunk.responseId,
|
|
2116
|
+
modelVersion: chunk.modelVersion,
|
|
2117
|
+
contentIndex,
|
|
2118
|
+
delta: {
|
|
2119
|
+
role: "assistant",
|
|
2120
|
+
content: null,
|
|
2121
|
+
thinking: { content: "" }
|
|
2122
|
+
}
|
|
2123
|
+
});
|
|
2124
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(thinkingChunk)}
|
|
2125
|
+
|
|
2126
|
+
`));
|
|
2127
|
+
}
|
|
2128
|
+
const signatureChunk = createChunk({
|
|
2129
|
+
responseId: chunk.responseId,
|
|
2130
|
+
modelVersion: chunk.modelVersion,
|
|
2131
|
+
contentIndex,
|
|
2132
|
+
delta: {
|
|
2133
|
+
role: "assistant",
|
|
2134
|
+
content: null,
|
|
2135
|
+
thinking: { signature }
|
|
2136
|
+
}
|
|
2137
|
+
});
|
|
2138
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(signatureChunk)}
|
|
2139
|
+
|
|
2140
|
+
`));
|
|
2141
|
+
signatureSent = true;
|
|
2142
|
+
contentIndex++;
|
|
2143
|
+
if (pendingContent) {
|
|
2144
|
+
const pendingChunk = createChunk({
|
|
2145
|
+
responseId: chunk.responseId,
|
|
2146
|
+
modelVersion: chunk.modelVersion,
|
|
2147
|
+
contentIndex,
|
|
2148
|
+
delta: { role: "assistant", content: pendingContent }
|
|
2149
|
+
});
|
|
2150
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(pendingChunk)}
|
|
2151
|
+
|
|
2152
|
+
`));
|
|
2153
|
+
pendingContent = "";
|
|
2154
|
+
contentSent = true;
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
const toolCalls = parts.filter((part) => part.functionCall).map((part) => ({
|
|
2158
|
+
id: part.functionCall?.id || `ccr_tool_${Math.random().toString(36).substring(2, 15)}`,
|
|
2159
|
+
type: "function",
|
|
2160
|
+
function: {
|
|
2161
|
+
name: part.functionCall?.name ?? "",
|
|
2162
|
+
arguments: JSON.stringify(part.functionCall?.args || {})
|
|
2163
|
+
}
|
|
2164
|
+
}));
|
|
2165
|
+
const textContent = parts.filter((part) => part.text && part.thought !== true).map((part) => part.text).join("\n");
|
|
2166
|
+
if (!textContent && signatureSent && !contentSent) {
|
|
2167
|
+
contentSent = true;
|
|
2168
|
+
}
|
|
2169
|
+
if (hasThinkingContent && textContent && !signatureSent) {
|
|
2170
|
+
if (chunk.modelVersion?.includes("3")) {
|
|
2171
|
+
pendingContent += textContent;
|
|
2172
|
+
return;
|
|
2173
|
+
} else {
|
|
2174
|
+
const signatureChunk = createChunk({
|
|
2175
|
+
responseId: chunk.responseId,
|
|
2176
|
+
modelVersion: chunk.modelVersion,
|
|
2177
|
+
contentIndex,
|
|
2178
|
+
delta: {
|
|
2179
|
+
role: "assistant",
|
|
2180
|
+
content: null,
|
|
2181
|
+
thinking: { signature: `ccr_${Date.now()}` }
|
|
2182
|
+
}
|
|
2183
|
+
});
|
|
2184
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(signatureChunk)}
|
|
2185
|
+
|
|
2186
|
+
`));
|
|
2187
|
+
signatureSent = true;
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
if (textContent) {
|
|
2191
|
+
if (!pendingContent) contentIndex++;
|
|
2192
|
+
const contentChunk = createChunk({
|
|
2193
|
+
responseId: chunk.responseId,
|
|
2194
|
+
modelVersion: chunk.modelVersion,
|
|
2195
|
+
contentIndex,
|
|
2196
|
+
delta: { role: "assistant", content: textContent },
|
|
2197
|
+
finishReason: candidate.finishReason,
|
|
2198
|
+
usageMetadata: chunk.usageMetadata,
|
|
2199
|
+
groundingMetadata: candidate.groundingMetadata
|
|
2200
|
+
});
|
|
2201
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(contentChunk)}
|
|
2202
|
+
|
|
2203
|
+
`));
|
|
2204
|
+
contentSent = true;
|
|
2205
|
+
}
|
|
2206
|
+
if (toolCalls.length > 0) {
|
|
2207
|
+
for (const tool of toolCalls) {
|
|
2208
|
+
contentIndex++;
|
|
2209
|
+
toolCallIndex++;
|
|
2210
|
+
const toolChunk = createChunk({
|
|
2211
|
+
responseId: chunk.responseId,
|
|
2212
|
+
modelVersion: chunk.modelVersion,
|
|
2213
|
+
contentIndex,
|
|
2214
|
+
delta: {
|
|
2215
|
+
role: "assistant",
|
|
2216
|
+
tool_calls: [{ ...tool, index: toolCallIndex }]
|
|
2217
|
+
},
|
|
2218
|
+
finishReason: candidate.finishReason,
|
|
2219
|
+
usageMetadata: chunk.usageMetadata,
|
|
2220
|
+
groundingMetadata: candidate.groundingMetadata
|
|
2221
|
+
});
|
|
2222
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(toolChunk)}
|
|
2223
|
+
|
|
2224
|
+
`));
|
|
2225
|
+
}
|
|
2226
|
+
contentSent = true;
|
|
2227
|
+
}
|
|
2228
|
+
} catch (_error) {
|
|
2229
|
+
logger?.error(`Error parsing ${providerName} stream chunk: ${chunkStr}`);
|
|
2230
|
+
}
|
|
2231
|
+
};
|
|
2232
|
+
try {
|
|
2233
|
+
while (true) {
|
|
2234
|
+
const { done, value } = await reader.read();
|
|
2235
|
+
if (done) {
|
|
2236
|
+
if (buffer) await processLine(buffer);
|
|
2237
|
+
break;
|
|
2238
|
+
}
|
|
2239
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2240
|
+
const lines = buffer.split("\n");
|
|
2241
|
+
buffer = lines.pop() || "";
|
|
2242
|
+
for (const line of lines) {
|
|
2243
|
+
await processLine(line);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
} catch (error) {
|
|
2247
|
+
controller.error(error);
|
|
2248
|
+
} finally {
|
|
2249
|
+
controller.close();
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
});
|
|
2253
|
+
return new Response(stream, {
|
|
2254
|
+
status: response.status,
|
|
2255
|
+
statusText: response.statusText,
|
|
2256
|
+
headers: response.headers
|
|
2257
|
+
});
|
|
2258
|
+
}
|
|
2259
|
+
function createChunk(options) {
|
|
2260
|
+
const {
|
|
2261
|
+
responseId,
|
|
2262
|
+
modelVersion,
|
|
2263
|
+
contentIndex,
|
|
2264
|
+
delta,
|
|
2265
|
+
finishReason,
|
|
2266
|
+
usageMetadata,
|
|
2267
|
+
groundingMetadata
|
|
2268
|
+
} = options;
|
|
2269
|
+
const chunk = {
|
|
2270
|
+
choices: [
|
|
2271
|
+
{
|
|
2272
|
+
delta,
|
|
2273
|
+
finish_reason: finishReason?.toLowerCase() || null,
|
|
2274
|
+
index: contentIndex,
|
|
2275
|
+
logprobs: null
|
|
2276
|
+
}
|
|
2277
|
+
],
|
|
2278
|
+
created: Math.floor(Date.now() / 1e3),
|
|
2279
|
+
id: responseId || "",
|
|
2280
|
+
model: modelVersion || "",
|
|
2281
|
+
object: "chat.completion.chunk",
|
|
2282
|
+
system_fingerprint: "fp_a49d71b8a1"
|
|
2283
|
+
};
|
|
2284
|
+
if (usageMetadata) {
|
|
2285
|
+
chunk.usage = {
|
|
2286
|
+
completion_tokens: usageMetadata.candidatesTokenCount || 0,
|
|
2287
|
+
prompt_tokens: usageMetadata.promptTokenCount || 0,
|
|
2288
|
+
prompt_tokens_details: {
|
|
2289
|
+
cached_tokens: usageMetadata.cachedContentTokenCount || 0
|
|
2290
|
+
},
|
|
2291
|
+
total_tokens: usageMetadata.totalTokenCount || 0,
|
|
2292
|
+
output_tokens_details: {
|
|
2293
|
+
reasoning_tokens: usageMetadata.thoughtsTokenCount || 0
|
|
2294
|
+
}
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
if (groundingMetadata?.groundingChunks?.length) {
|
|
2298
|
+
const annotations = groundingMetadata.groundingChunks.map((groundingChunk, index) => {
|
|
2299
|
+
const support = groundingMetadata.groundingSupports?.find(
|
|
2300
|
+
(s) => s.groundingChunkIndices?.includes(index)
|
|
2301
|
+
);
|
|
2302
|
+
return {
|
|
2303
|
+
type: "url_citation",
|
|
2304
|
+
url_citation: {
|
|
2305
|
+
url: groundingChunk.web?.uri || "",
|
|
2306
|
+
title: groundingChunk.web?.title || "",
|
|
2307
|
+
content: support?.segment?.text || "",
|
|
2308
|
+
start_index: support?.segment?.startIndex || 0,
|
|
2309
|
+
end_index: support?.segment?.endIndex || 0
|
|
2310
|
+
}
|
|
2311
|
+
};
|
|
2312
|
+
});
|
|
2313
|
+
chunk.choices[0].delta.annotations = annotations;
|
|
2314
|
+
}
|
|
2315
|
+
return chunk;
|
|
2316
|
+
}
|
|
2317
|
+
|
|
2318
|
+
// src/transformer/transformers/utils/gemini.schema.ts
|
|
2319
|
+
var GeminiType = {
|
|
2320
|
+
TYPE_UNSPECIFIED: "TYPE_UNSPECIFIED",
|
|
2321
|
+
STRING: "STRING",
|
|
2322
|
+
NUMBER: "NUMBER",
|
|
2323
|
+
INTEGER: "INTEGER",
|
|
2324
|
+
BOOLEAN: "BOOLEAN",
|
|
2325
|
+
ARRAY: "ARRAY",
|
|
2326
|
+
OBJECT: "OBJECT",
|
|
2327
|
+
NULL: "NULL"
|
|
2328
|
+
};
|
|
2329
|
+
function flattenTypeArrayToAnyOf(typeList, resultingSchema) {
|
|
2330
|
+
if (typeList.includes("null")) {
|
|
2331
|
+
resultingSchema.nullable = true;
|
|
2332
|
+
}
|
|
2333
|
+
const listWithoutNull = typeList.filter((type) => type !== "null");
|
|
2334
|
+
if (listWithoutNull.length === 1) {
|
|
2335
|
+
const upperCaseType = listWithoutNull[0].toUpperCase();
|
|
2336
|
+
resultingSchema.type = Object.values(GeminiType).includes(upperCaseType) ? upperCaseType : GeminiType.TYPE_UNSPECIFIED;
|
|
2337
|
+
} else {
|
|
2338
|
+
resultingSchema.anyOf = listWithoutNull.map((typeName) => {
|
|
2339
|
+
const upperCaseType = typeName.toUpperCase();
|
|
2340
|
+
return {
|
|
2341
|
+
type: Object.values(GeminiType).includes(upperCaseType) ? upperCaseType : GeminiType.TYPE_UNSPECIFIED
|
|
2342
|
+
};
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
function processJsonSchema(jsonSchema) {
|
|
2347
|
+
const genAISchema = {};
|
|
2348
|
+
const schemaFieldNames = ["items"];
|
|
2349
|
+
const listSchemaFieldNames = ["anyOf"];
|
|
2350
|
+
const dictSchemaFieldNames = ["properties"];
|
|
2351
|
+
let workingSchema = jsonSchema;
|
|
2352
|
+
if (workingSchema.type && workingSchema.anyOf) {
|
|
2353
|
+
throw new Error("type and anyOf cannot be both populated.");
|
|
2354
|
+
}
|
|
2355
|
+
const incomingAnyOf = workingSchema.anyOf;
|
|
2356
|
+
if (incomingAnyOf && Array.isArray(incomingAnyOf) && incomingAnyOf.length === 2) {
|
|
2357
|
+
if (incomingAnyOf[0]?.type === "null") {
|
|
2358
|
+
genAISchema.nullable = true;
|
|
2359
|
+
workingSchema = incomingAnyOf[1];
|
|
2360
|
+
} else if (incomingAnyOf[1]?.type === "null") {
|
|
2361
|
+
genAISchema.nullable = true;
|
|
2362
|
+
workingSchema = incomingAnyOf[0];
|
|
2363
|
+
}
|
|
2364
|
+
}
|
|
2365
|
+
if (workingSchema.type && Array.isArray(workingSchema.type)) {
|
|
2366
|
+
flattenTypeArrayToAnyOf(workingSchema.type, genAISchema);
|
|
2367
|
+
}
|
|
2368
|
+
for (const [fieldName, fieldValue] of Object.entries(workingSchema)) {
|
|
2369
|
+
if (fieldValue == null) {
|
|
2370
|
+
continue;
|
|
2371
|
+
}
|
|
2372
|
+
if (fieldName === "type") {
|
|
2373
|
+
if (fieldValue === "null") {
|
|
2374
|
+
throw new Error("type: null cannot be the only possible type for the field.");
|
|
2375
|
+
}
|
|
2376
|
+
if (Array.isArray(fieldValue)) {
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
const upperCaseValue = fieldValue.toUpperCase();
|
|
2380
|
+
genAISchema.type = Object.values(GeminiType).includes(upperCaseValue) ? upperCaseValue : GeminiType.TYPE_UNSPECIFIED;
|
|
2381
|
+
} else if (schemaFieldNames.includes(fieldName)) {
|
|
2382
|
+
genAISchema[fieldName] = processJsonSchema(fieldValue);
|
|
2383
|
+
} else if (listSchemaFieldNames.includes(fieldName)) {
|
|
2384
|
+
const listValue = [];
|
|
2385
|
+
for (const item of fieldValue) {
|
|
2386
|
+
if (item.type === "null") {
|
|
2387
|
+
genAISchema.nullable = true;
|
|
2388
|
+
continue;
|
|
2389
|
+
}
|
|
2390
|
+
listValue.push(processJsonSchema(item));
|
|
2391
|
+
}
|
|
2392
|
+
genAISchema[fieldName] = listValue;
|
|
2393
|
+
} else if (dictSchemaFieldNames.includes(fieldName)) {
|
|
2394
|
+
const dictValue = {};
|
|
2395
|
+
for (const [key, value] of Object.entries(fieldValue)) {
|
|
2396
|
+
dictValue[key] = processJsonSchema(value);
|
|
2397
|
+
}
|
|
2398
|
+
genAISchema[fieldName] = dictValue;
|
|
2399
|
+
} else {
|
|
2400
|
+
if (fieldName === "additionalProperties") {
|
|
2401
|
+
continue;
|
|
2402
|
+
}
|
|
2403
|
+
genAISchema[fieldName] = fieldValue;
|
|
2404
|
+
}
|
|
2405
|
+
}
|
|
2406
|
+
return genAISchema;
|
|
2407
|
+
}
|
|
2408
|
+
function transformTool(tool) {
|
|
2409
|
+
const functionDeclarations = tool.functionDeclarations;
|
|
2410
|
+
if (functionDeclarations) {
|
|
2411
|
+
for (const functionDeclaration of functionDeclarations) {
|
|
2412
|
+
if (functionDeclaration.parameters) {
|
|
2413
|
+
const params = functionDeclaration.parameters;
|
|
2414
|
+
if (!Object.keys(params).includes("$schema")) {
|
|
2415
|
+
functionDeclaration.parameters = processJsonSchema(params);
|
|
2416
|
+
} else {
|
|
2417
|
+
if (!functionDeclaration.parametersJsonSchema) {
|
|
2418
|
+
functionDeclaration.parametersJsonSchema = functionDeclaration.parameters;
|
|
2419
|
+
delete functionDeclaration.parameters;
|
|
2420
|
+
}
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
if (functionDeclaration.response) {
|
|
2424
|
+
const response = functionDeclaration.response;
|
|
2425
|
+
if (!Object.keys(response).includes("$schema")) {
|
|
2426
|
+
functionDeclaration.response = processJsonSchema(response);
|
|
2427
|
+
} else {
|
|
2428
|
+
if (!functionDeclaration.responseJsonSchema) {
|
|
2429
|
+
functionDeclaration.responseJsonSchema = functionDeclaration.response;
|
|
2430
|
+
delete functionDeclaration.response;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
}
|
|
2436
|
+
return tool;
|
|
2437
|
+
}
|
|
2438
|
+
|
|
2439
|
+
// src/transformer/transformers/utils/gemini.util.ts
|
|
2440
|
+
function buildRequestBody(request) {
|
|
2441
|
+
const tools = [];
|
|
2442
|
+
const functionDeclarations = request.tools?.filter((tool) => tool.function.name !== "web_search")?.map((tool) => ({
|
|
2443
|
+
name: tool.function.name,
|
|
2444
|
+
description: tool.function.description,
|
|
2445
|
+
parametersJsonSchema: tool.function.parameters
|
|
2446
|
+
}));
|
|
2447
|
+
if (functionDeclarations?.length) {
|
|
2448
|
+
tools.push(
|
|
2449
|
+
transformTool({
|
|
2450
|
+
functionDeclarations
|
|
2451
|
+
})
|
|
2452
|
+
);
|
|
2453
|
+
}
|
|
2454
|
+
const webSearch = request.tools?.find((tool) => tool.function.name === "web_search");
|
|
2455
|
+
if (webSearch) {
|
|
2456
|
+
tools.push({ googleSearch: {} });
|
|
2457
|
+
}
|
|
2458
|
+
const contents = [];
|
|
2459
|
+
const toolResponses = request.messages.filter((item) => item.role === "tool");
|
|
2460
|
+
request.messages.filter((item) => item.role !== "tool").forEach((message) => {
|
|
2461
|
+
let role;
|
|
2462
|
+
if (message.role === "assistant") {
|
|
2463
|
+
role = "model";
|
|
2464
|
+
} else if (["user", "system"].includes(message.role)) {
|
|
2465
|
+
role = "user";
|
|
2466
|
+
} else {
|
|
2467
|
+
role = "user";
|
|
2468
|
+
}
|
|
2469
|
+
const parts = [];
|
|
2470
|
+
if (typeof message.content === "string") {
|
|
2471
|
+
const part = { text: message.content };
|
|
2472
|
+
if (message.thinking?.signature) {
|
|
2473
|
+
part.thoughtSignature = message.thinking.signature;
|
|
2474
|
+
}
|
|
2475
|
+
parts.push(part);
|
|
2476
|
+
} else if (Array.isArray(message.content)) {
|
|
2477
|
+
for (const content of message.content) {
|
|
2478
|
+
if (content.type === "text") {
|
|
2479
|
+
parts.push({ text: content.text || "" });
|
|
2480
|
+
} else if (content.type === "image_url") {
|
|
2481
|
+
const imageUrl = content.image_url?.url ?? "";
|
|
2482
|
+
if (imageUrl.startsWith("http")) {
|
|
2483
|
+
parts.push({
|
|
2484
|
+
file_data: {
|
|
2485
|
+
mime_type: content.media_type,
|
|
2486
|
+
file_uri: imageUrl
|
|
2487
|
+
}
|
|
2488
|
+
});
|
|
2489
|
+
} else {
|
|
2490
|
+
const data = imageUrl.split(",").pop() || imageUrl;
|
|
2491
|
+
parts.push({
|
|
2492
|
+
inlineData: {
|
|
2493
|
+
mime_type: content.media_type || "image/png",
|
|
2494
|
+
data
|
|
2495
|
+
}
|
|
2496
|
+
});
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
2500
|
+
} else if (message.content && typeof message.content === "object") {
|
|
2501
|
+
const contentObj = message.content;
|
|
2502
|
+
if (contentObj.text) {
|
|
2503
|
+
parts.push({ text: contentObj.text });
|
|
2504
|
+
} else {
|
|
2505
|
+
parts.push({ text: JSON.stringify(message.content) });
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
if (Array.isArray(message.tool_calls)) {
|
|
2509
|
+
for (let index = 0; index < message.tool_calls.length; index++) {
|
|
2510
|
+
const toolCall = message.tool_calls[index];
|
|
2511
|
+
const functionCallPart = {
|
|
2512
|
+
functionCall: {
|
|
2513
|
+
id: toolCall.id || `tool_${Math.random().toString(36).substring(2, 15)}`,
|
|
2514
|
+
name: toolCall.function.name,
|
|
2515
|
+
args: JSON.parse(toolCall.function.arguments || "{}")
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
if (index === 0 && message.thinking?.signature) {
|
|
2519
|
+
functionCallPart.thoughtSignature = message.thinking.signature;
|
|
2520
|
+
}
|
|
2521
|
+
parts.push(functionCallPart);
|
|
2522
|
+
}
|
|
2523
|
+
}
|
|
2524
|
+
if (parts.length === 0) {
|
|
2525
|
+
parts.push({ text: "" });
|
|
2526
|
+
}
|
|
2527
|
+
contents.push({ role, parts });
|
|
2528
|
+
if (role === "model" && message.tool_calls) {
|
|
2529
|
+
const functionResponses = message.tool_calls.map(
|
|
2530
|
+
(tool) => {
|
|
2531
|
+
const response = toolResponses.find((item) => item.tool_call_id === tool.id);
|
|
2532
|
+
return {
|
|
2533
|
+
functionResponse: {
|
|
2534
|
+
name: tool.function?.name ?? "",
|
|
2535
|
+
response: { result: response?.content }
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
}
|
|
2539
|
+
);
|
|
2540
|
+
contents.push({
|
|
2541
|
+
role: "user",
|
|
2542
|
+
parts: functionResponses
|
|
2543
|
+
});
|
|
2544
|
+
}
|
|
2545
|
+
});
|
|
2546
|
+
const generationConfig = {};
|
|
2547
|
+
if (request.reasoning?.effort && request.reasoning.effort !== "none") {
|
|
2548
|
+
generationConfig.thinkingConfig = {
|
|
2549
|
+
includeThoughts: true
|
|
2550
|
+
};
|
|
2551
|
+
if (request.model.includes("gemini-3")) {
|
|
2552
|
+
generationConfig.thinkingConfig.thinkingLevel = request.reasoning.effort;
|
|
2553
|
+
} else {
|
|
2554
|
+
const thinkingBudgets = request.model.includes("pro") ? [128, 32768] : [0, 24576];
|
|
2555
|
+
const maxTokens = request.reasoning.max_tokens;
|
|
2556
|
+
if (typeof maxTokens !== "undefined") {
|
|
2557
|
+
let thinkingBudget;
|
|
2558
|
+
if (maxTokens >= thinkingBudgets[0] && maxTokens <= thinkingBudgets[1]) {
|
|
2559
|
+
thinkingBudget = maxTokens;
|
|
2560
|
+
} else if (maxTokens < thinkingBudgets[0]) {
|
|
2561
|
+
thinkingBudget = thinkingBudgets[0];
|
|
2562
|
+
} else {
|
|
2563
|
+
thinkingBudget = thinkingBudgets[1];
|
|
2564
|
+
}
|
|
2565
|
+
generationConfig.thinkingConfig.thinkingBudget = thinkingBudget;
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
}
|
|
2569
|
+
const body = {
|
|
2570
|
+
contents,
|
|
2571
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
2572
|
+
generationConfig: Object.keys(generationConfig).length > 0 ? generationConfig : void 0
|
|
2573
|
+
};
|
|
2574
|
+
if (request.tool_choice) {
|
|
2575
|
+
const toolConfig = {
|
|
2576
|
+
functionCallingConfig: {}
|
|
2577
|
+
};
|
|
2578
|
+
if (request.tool_choice === "auto") {
|
|
2579
|
+
toolConfig.functionCallingConfig.mode = "auto";
|
|
2580
|
+
} else if (request.tool_choice === "none") {
|
|
2581
|
+
toolConfig.functionCallingConfig.mode = "none";
|
|
2582
|
+
} else if (request.tool_choice === "required") {
|
|
2583
|
+
toolConfig.functionCallingConfig.mode = "any";
|
|
2584
|
+
} else if (typeof request.tool_choice === "object" && request.tool_choice.function?.name) {
|
|
2585
|
+
toolConfig.functionCallingConfig.mode = "any";
|
|
2586
|
+
toolConfig.functionCallingConfig.allowedFunctionNames = [
|
|
2587
|
+
request.tool_choice.function.name
|
|
2588
|
+
];
|
|
2589
|
+
}
|
|
2590
|
+
body.toolConfig = toolConfig;
|
|
2591
|
+
}
|
|
2592
|
+
return body;
|
|
2593
|
+
}
|
|
2594
|
+
function transformRequestOut(request) {
|
|
2595
|
+
const contents = request.contents;
|
|
2596
|
+
const tools = request.tools;
|
|
2597
|
+
const model = request.model;
|
|
2598
|
+
const maxTokens = request.max_tokens;
|
|
2599
|
+
const temperature = request.temperature;
|
|
2600
|
+
const stream = request.stream;
|
|
2601
|
+
const toolChoice = request.tool_choice;
|
|
2602
|
+
const unifiedRequest = {
|
|
2603
|
+
messages: [],
|
|
2604
|
+
model,
|
|
2605
|
+
max_tokens: maxTokens,
|
|
2606
|
+
temperature,
|
|
2607
|
+
stream,
|
|
2608
|
+
tool_choice: toolChoice
|
|
2609
|
+
};
|
|
2610
|
+
if (Array.isArray(contents)) {
|
|
2611
|
+
for (const content of contents) {
|
|
2612
|
+
if (typeof content === "string") {
|
|
2613
|
+
unifiedRequest.messages.push({
|
|
2614
|
+
role: "user",
|
|
2615
|
+
content
|
|
2616
|
+
});
|
|
2617
|
+
} else if ("text" in content && typeof content.text === "string") {
|
|
2618
|
+
unifiedRequest.messages.push({
|
|
2619
|
+
role: "user",
|
|
2620
|
+
content: content.text || null
|
|
2621
|
+
});
|
|
2622
|
+
} else if ("role" in content && content.role === "user") {
|
|
2623
|
+
const geminiContent = content;
|
|
2624
|
+
unifiedRequest.messages.push({
|
|
2625
|
+
role: "user",
|
|
2626
|
+
content: geminiContent.parts?.map((part) => ({
|
|
2627
|
+
type: "text",
|
|
2628
|
+
text: part.text || ""
|
|
2629
|
+
})) || []
|
|
2630
|
+
});
|
|
2631
|
+
} else if (content.role === "model") {
|
|
2632
|
+
unifiedRequest.messages.push({
|
|
2633
|
+
role: "assistant",
|
|
2634
|
+
content: content.parts?.map((part) => ({
|
|
2635
|
+
type: "text",
|
|
2636
|
+
text: part.text || ""
|
|
2637
|
+
})) || []
|
|
2638
|
+
});
|
|
2639
|
+
}
|
|
2640
|
+
}
|
|
2641
|
+
}
|
|
2642
|
+
if (Array.isArray(tools)) {
|
|
2643
|
+
unifiedRequest.tools = [];
|
|
2644
|
+
for (const tool of tools) {
|
|
2645
|
+
if (Array.isArray(tool.functionDeclarations)) {
|
|
2646
|
+
for (const funcDecl of tool.functionDeclarations) {
|
|
2647
|
+
unifiedRequest.tools.push({
|
|
2648
|
+
type: "function",
|
|
2649
|
+
function: {
|
|
2650
|
+
name: funcDecl.name,
|
|
2651
|
+
description: funcDecl.description ?? "",
|
|
2652
|
+
parameters: funcDecl.parameters ?? {}
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
}
|
|
2659
|
+
return unifiedRequest;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
// src/transformer/transformers/GeminiCodeAssistTransformer.ts
|
|
2663
|
+
var DEFAULT_CODE_ASSIST_ENDPOINT = "https://cloudcode-pa.googleapis.com";
|
|
2664
|
+
var DEFAULT_CODE_ASSIST_API_VERSION = "v1internal";
|
|
2665
|
+
function resolveCodeAssistEndpoint() {
|
|
2666
|
+
return (process.env.CODE_ASSIST_ENDPOINT || DEFAULT_CODE_ASSIST_ENDPOINT).replace(/\/+$/, "");
|
|
2667
|
+
}
|
|
2668
|
+
function resolveCodeAssistApiVersion() {
|
|
2669
|
+
return process.env.CODE_ASSIST_API_VERSION || DEFAULT_CODE_ASSIST_API_VERSION;
|
|
2670
|
+
}
|
|
2671
|
+
function buildCodeAssistUrl(stream) {
|
|
2672
|
+
const base = resolveCodeAssistEndpoint();
|
|
2673
|
+
const version = resolveCodeAssistApiVersion();
|
|
2674
|
+
const method = stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
2675
|
+
return `${base}/${version}:${method}`;
|
|
2676
|
+
}
|
|
2677
|
+
function generateUserPromptId() {
|
|
2678
|
+
const c = globalThis.crypto;
|
|
2679
|
+
if (c?.randomUUID) return c.randomUUID();
|
|
2680
|
+
return `omnicross-${Date.now()}-${Math.random().toString(36).slice(2, 10)}`;
|
|
2681
|
+
}
|
|
2682
|
+
function peelResponseEnvelope(parsed) {
|
|
2683
|
+
if (parsed && typeof parsed === "object" && "response" in parsed) {
|
|
2684
|
+
return parsed.response;
|
|
2685
|
+
}
|
|
2686
|
+
return parsed;
|
|
2687
|
+
}
|
|
2688
|
+
async function unwrapCodeAssistResponse(response) {
|
|
2689
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
2690
|
+
if (contentType.includes("stream") || contentType.includes("text/event-stream")) {
|
|
2691
|
+
const sourceBody = response.body;
|
|
2692
|
+
if (!sourceBody) return response;
|
|
2693
|
+
const decoder = new TextDecoder();
|
|
2694
|
+
const encoder = new TextEncoder();
|
|
2695
|
+
const peeled = new ReadableStream({
|
|
2696
|
+
async start(controller) {
|
|
2697
|
+
const reader = sourceBody.getReader();
|
|
2698
|
+
let buffer = "";
|
|
2699
|
+
const processLine = (line) => {
|
|
2700
|
+
if (!line.startsWith("data:")) {
|
|
2701
|
+
if (line.length > 0) controller.enqueue(encoder.encode(`${line}
|
|
2702
|
+
`));
|
|
2703
|
+
return;
|
|
2704
|
+
}
|
|
2705
|
+
const payload = line.slice(line.indexOf(":") + 1).trim();
|
|
2706
|
+
if (!payload || payload === "[DONE]") {
|
|
2707
|
+
controller.enqueue(encoder.encode(`${line}
|
|
2708
|
+
`));
|
|
2709
|
+
return;
|
|
2710
|
+
}
|
|
2711
|
+
try {
|
|
2712
|
+
const parsed = JSON.parse(payload);
|
|
2713
|
+
const inner2 = peelResponseEnvelope(parsed);
|
|
2714
|
+
controller.enqueue(encoder.encode(`data: ${JSON.stringify(inner2)}
|
|
2715
|
+
`));
|
|
2716
|
+
} catch {
|
|
2717
|
+
controller.enqueue(encoder.encode(`${line}
|
|
2718
|
+
`));
|
|
2719
|
+
}
|
|
2720
|
+
};
|
|
2721
|
+
try {
|
|
2722
|
+
while (true) {
|
|
2723
|
+
const { done, value } = await reader.read();
|
|
2724
|
+
if (done) {
|
|
2725
|
+
if (buffer) processLine(buffer);
|
|
2726
|
+
break;
|
|
2727
|
+
}
|
|
2728
|
+
buffer += decoder.decode(value, { stream: true });
|
|
2729
|
+
const lines = buffer.split("\n");
|
|
2730
|
+
buffer = lines.pop() || "";
|
|
2731
|
+
for (const line of lines) processLine(line);
|
|
2732
|
+
}
|
|
2733
|
+
} catch (err) {
|
|
2734
|
+
controller.error(err);
|
|
2735
|
+
} finally {
|
|
2736
|
+
controller.close();
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
});
|
|
2740
|
+
return new Response(peeled, {
|
|
2741
|
+
status: response.status,
|
|
2742
|
+
statusText: response.statusText,
|
|
2743
|
+
headers: response.headers
|
|
2744
|
+
});
|
|
2745
|
+
}
|
|
2746
|
+
const raw = await response.json().catch(() => null);
|
|
2747
|
+
const inner = peelResponseEnvelope(raw);
|
|
2748
|
+
return new Response(JSON.stringify(inner), {
|
|
2749
|
+
status: response.status,
|
|
2750
|
+
statusText: response.statusText,
|
|
2751
|
+
headers: response.headers
|
|
2752
|
+
});
|
|
2753
|
+
}
|
|
2754
|
+
var GeminiCodeAssistTransformer = class {
|
|
2755
|
+
static TransformerName = "gemini-code-assist";
|
|
2756
|
+
name = "gemini-code-assist";
|
|
2757
|
+
logger;
|
|
2758
|
+
/** Code Assist has no fixed `/models/:modelAndAction` endpoint pattern — the
|
|
2759
|
+
* URL is built per-request in `transformRequestIn`. Left undefined so the
|
|
2760
|
+
* TransformerService does NOT treat this as an endpoint (reverse) transformer. */
|
|
2761
|
+
endPoint = void 0;
|
|
2762
|
+
/**
|
|
2763
|
+
* unified → Code Assist envelope.
|
|
2764
|
+
*
|
|
2765
|
+
* Builds the inner public-Gemini body via the shared `buildRequestBody`, then
|
|
2766
|
+
* wraps it as `{ model, project, user_prompt_id, request: <inner> }` and sets
|
|
2767
|
+
* the Code Assist URL + Bearer-only headers.
|
|
2768
|
+
*
|
|
2769
|
+
* The resolved Code Assist `project` is threaded in via `provider.geminiProject`
|
|
2770
|
+
* (stashed by the subscription dispatch seam — see SubscriptionDispatcher /
|
|
2771
|
+
* openaiResponsesIngress). `undefined` is the valid fresh free-tier value.
|
|
2772
|
+
*/
|
|
2773
|
+
async transformRequestIn(request, provider, _context) {
|
|
2774
|
+
const inner = buildRequestBody(request);
|
|
2775
|
+
const envelope = {
|
|
2776
|
+
model: request.model,
|
|
2777
|
+
project: provider.geminiProject,
|
|
2778
|
+
user_prompt_id: generateUserPromptId(),
|
|
2779
|
+
request: inner
|
|
2780
|
+
};
|
|
2781
|
+
const url = buildCodeAssistUrl(Boolean(request.stream));
|
|
2782
|
+
const headers = {
|
|
2783
|
+
"x-goog-api-key": void 0,
|
|
2784
|
+
"X-Goog-Api-Key": void 0
|
|
2785
|
+
};
|
|
2786
|
+
return {
|
|
2787
|
+
body: envelope,
|
|
2788
|
+
config: { url, headers }
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
/**
|
|
2792
|
+
* Code Assist request → unified (endpoint-decode parity with GeminiTransformer).
|
|
2793
|
+
* Peels the top-level `request` envelope first, then delegates to the shared
|
|
2794
|
+
* gemini request decoder. Not used on the subscription dispatch path (the
|
|
2795
|
+
* endpoint transformer there is Anthropic/OpenAI-Response), but provided for
|
|
2796
|
+
* completeness so this transformer is a drop-in for the gemini one.
|
|
2797
|
+
*/
|
|
2798
|
+
async transformRequestOut(request, _context) {
|
|
2799
|
+
const r = request;
|
|
2800
|
+
const inner = r && typeof r === "object" && "request" in r ? r.request : r;
|
|
2801
|
+
if (inner && typeof inner === "object" && !("model" in inner) && "model" in r) {
|
|
2802
|
+
inner.model = r.model;
|
|
2803
|
+
}
|
|
2804
|
+
return transformRequestOut(inner);
|
|
2805
|
+
}
|
|
2806
|
+
/**
|
|
2807
|
+
* Code Assist response → OpenAI-compatible. PEEL the `.response` envelope from
|
|
2808
|
+
* the body / each SSE chunk, then DELEGATE to the existing gemini parser.
|
|
2809
|
+
*/
|
|
2810
|
+
async transformResponseOut(response, _context) {
|
|
2811
|
+
const unwrapped = await unwrapCodeAssistResponse(response);
|
|
2812
|
+
return transformResponseOut(unwrapped, this.name, this.logger);
|
|
2813
|
+
}
|
|
2814
|
+
/**
|
|
2815
|
+
* OpenAI-compatible response → Code Assist (endpoint-encode parity). Re-wraps
|
|
2816
|
+
* the standard gemini response under the top-level `response` key after the
|
|
2817
|
+
* shared gemini encoder produces a public-Gemini body. Symmetric with
|
|
2818
|
+
* `transformResponseOut`'s peel.
|
|
2819
|
+
*/
|
|
2820
|
+
async transformResponseIn(response, _context) {
|
|
2821
|
+
const geminiResponse = await transformResponseIn(response, this.logger);
|
|
2822
|
+
const contentType = geminiResponse.headers.get("Content-Type") ?? "";
|
|
2823
|
+
if (contentType.includes("text/event-stream")) {
|
|
2824
|
+
return geminiResponse;
|
|
2825
|
+
}
|
|
2826
|
+
const data = await geminiResponse.json().catch(() => null);
|
|
2827
|
+
return new Response(JSON.stringify({ response: data }), {
|
|
2828
|
+
status: geminiResponse.status,
|
|
2829
|
+
statusText: geminiResponse.statusText,
|
|
2830
|
+
headers: { "Content-Type": "application/json" }
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
};
|
|
2834
|
+
|
|
2835
|
+
// src/transformer/transformers/GeminiTransformer.ts
|
|
2836
|
+
var GeminiTransformer = class {
|
|
2837
|
+
static TransformerName = "gemini";
|
|
2838
|
+
name = "gemini";
|
|
2839
|
+
logger;
|
|
2840
|
+
/**
|
|
2841
|
+
* API endpoint pattern for Gemini
|
|
2842
|
+
* :modelAndAction will be replaced with actual model and action
|
|
2843
|
+
*/
|
|
2844
|
+
endPoint = "/v1beta/models/:modelAndAction";
|
|
2845
|
+
/** Use Bearer token instead of x-goog-api-key (for relay providers) */
|
|
2846
|
+
useBearer;
|
|
2847
|
+
constructor(options) {
|
|
2848
|
+
this.useBearer = options?.UseBearer ?? false;
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Handle authentication
|
|
2852
|
+
* - Official Gemini: x-goog-api-key header
|
|
2853
|
+
* - Relay providers: Authorization: Bearer header
|
|
2854
|
+
*/
|
|
2855
|
+
async auth(request, provider, _context) {
|
|
2856
|
+
const headers = {};
|
|
2857
|
+
if (this.useBearer) {
|
|
2858
|
+
headers["authorization"] = `Bearer ${provider.apiKey}`;
|
|
2859
|
+
headers["x-goog-api-key"] = void 0;
|
|
2860
|
+
} else {
|
|
2861
|
+
headers["x-goog-api-key"] = provider.apiKey;
|
|
2862
|
+
headers["authorization"] = void 0;
|
|
2863
|
+
}
|
|
2864
|
+
return {
|
|
2865
|
+
body: request,
|
|
2866
|
+
config: { headers }
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2869
|
+
/**
|
|
2870
|
+
* Transform request from unified format to Gemini format
|
|
2871
|
+
* Also builds the correct URL for the Gemini API
|
|
2872
|
+
*/
|
|
2873
|
+
async transformRequestIn(request, provider, _context) {
|
|
2874
|
+
const body = buildRequestBody(request);
|
|
2875
|
+
const action = request.stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
2876
|
+
const url = new URL(`./${request.model}:${action}`, provider.baseUrl);
|
|
2877
|
+
const headers = {};
|
|
2878
|
+
if (this.useBearer) {
|
|
2879
|
+
headers["authorization"] = `Bearer ${provider.apiKey}`;
|
|
2880
|
+
headers["x-goog-api-key"] = void 0;
|
|
2881
|
+
} else {
|
|
2882
|
+
headers["x-goog-api-key"] = provider.apiKey;
|
|
2883
|
+
headers["Authorization"] = void 0;
|
|
2884
|
+
}
|
|
2885
|
+
return {
|
|
2886
|
+
body,
|
|
2887
|
+
config: { url, headers }
|
|
2888
|
+
};
|
|
2889
|
+
}
|
|
2890
|
+
/**
|
|
2891
|
+
* Transform incoming request to unified format
|
|
2892
|
+
* (For requests coming into the Gemini endpoint)
|
|
2893
|
+
*/
|
|
2894
|
+
async transformRequestOut(request, _context) {
|
|
2895
|
+
return transformRequestOut(request);
|
|
2896
|
+
}
|
|
2897
|
+
/**
|
|
2898
|
+
* Transform Gemini response to OpenAI-compatible format
|
|
2899
|
+
*/
|
|
2900
|
+
async transformResponseOut(response, _context) {
|
|
2901
|
+
return transformResponseOut(response, this.name, this.logger);
|
|
2902
|
+
}
|
|
2903
|
+
/**
|
|
2904
|
+
* Transform OpenAI-compatible response back to Gemini format
|
|
2905
|
+
* (For endpoint mode — returning Gemini-format responses to the client)
|
|
2906
|
+
*/
|
|
2907
|
+
async transformResponseIn(response, _context) {
|
|
2908
|
+
return transformResponseIn(response, this.logger);
|
|
2909
|
+
}
|
|
2910
|
+
};
|
|
2911
|
+
|
|
2912
|
+
// src/transformer/transformers/OpenAIResponseTransformer.ts
|
|
2913
|
+
var OpenAIResponseTransformer = class {
|
|
2914
|
+
static TransformerName = "openai-response";
|
|
2915
|
+
name = "openai-response";
|
|
2916
|
+
endPoint = "/v1/responses";
|
|
2917
|
+
logger;
|
|
2918
|
+
/**
|
|
2919
|
+
* Handle authentication - Bearer token
|
|
2920
|
+
*/
|
|
2921
|
+
async auth(request, provider, _context) {
|
|
2922
|
+
return {
|
|
2923
|
+
body: request,
|
|
2924
|
+
config: {
|
|
2925
|
+
headers: {
|
|
2926
|
+
Authorization: `Bearer ${provider.apiKey}`,
|
|
2927
|
+
"Content-Type": "application/json"
|
|
2928
|
+
}
|
|
2929
|
+
}
|
|
2930
|
+
};
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Transform unified request → Response API format
|
|
2934
|
+
*/
|
|
2935
|
+
async transformRequestIn(request, provider, _context) {
|
|
2936
|
+
const input = [];
|
|
2937
|
+
for (const msg of request.messages) {
|
|
2938
|
+
if (msg.role === "system") {
|
|
2939
|
+
input.push({
|
|
2940
|
+
role: "developer",
|
|
2941
|
+
content: typeof msg.content === "string" ? msg.content : flattenContent(msg.content)
|
|
2942
|
+
});
|
|
2943
|
+
} else if (msg.role === "tool") {
|
|
2944
|
+
input.push({
|
|
2945
|
+
type: "function_call_output",
|
|
2946
|
+
call_id: msg.tool_call_id,
|
|
2947
|
+
output: typeof msg.content === "string" ? msg.content : ""
|
|
2948
|
+
});
|
|
2949
|
+
} else {
|
|
2950
|
+
const entry = {
|
|
2951
|
+
role: msg.role,
|
|
2952
|
+
content: typeof msg.content === "string" ? msg.content : flattenContent(msg.content)
|
|
2953
|
+
};
|
|
2954
|
+
if (msg.role === "assistant" && msg.tool_calls?.length) {
|
|
2955
|
+
input.push(entry);
|
|
2956
|
+
for (const tc of msg.tool_calls) {
|
|
2957
|
+
input.push({
|
|
2958
|
+
type: "function_call",
|
|
2959
|
+
id: tc.id,
|
|
2960
|
+
call_id: tc.id,
|
|
2961
|
+
name: tc.function.name,
|
|
2962
|
+
arguments: tc.function.arguments
|
|
2963
|
+
});
|
|
2964
|
+
}
|
|
2965
|
+
continue;
|
|
2966
|
+
}
|
|
2967
|
+
input.push(entry);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
const body = {
|
|
2971
|
+
model: request.model,
|
|
2972
|
+
input,
|
|
2973
|
+
stream: request.stream ?? false,
|
|
2974
|
+
...request.max_tokens ? { max_output_tokens: request.max_tokens } : {},
|
|
2975
|
+
...request.temperature !== void 0 ? { temperature: request.temperature } : {}
|
|
2976
|
+
};
|
|
2977
|
+
if (request.reasoning?.effort && request.reasoning.effort !== "none") {
|
|
2978
|
+
body.reasoning = { effort: request.reasoning.effort, summary: "auto" };
|
|
2979
|
+
}
|
|
2980
|
+
if (request.tools?.length) {
|
|
2981
|
+
body.tools = request.tools.map((tool) => ({
|
|
2982
|
+
type: "function",
|
|
2983
|
+
name: tool.function.name,
|
|
2984
|
+
description: tool.function.description,
|
|
2985
|
+
parameters: tool.function.parameters
|
|
2986
|
+
}));
|
|
2987
|
+
}
|
|
2988
|
+
if (request.tool_choice) {
|
|
2989
|
+
if (typeof request.tool_choice === "string") {
|
|
2990
|
+
body.tool_choice = request.tool_choice;
|
|
2991
|
+
} else if (typeof request.tool_choice === "object" && "function" in request.tool_choice) {
|
|
2992
|
+
body.tool_choice = { type: "function", name: request.tool_choice.function.name };
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
const url = new URL("/v1/responses", provider.baseUrl);
|
|
2996
|
+
return { body, config: { url } };
|
|
2997
|
+
}
|
|
2998
|
+
/**
|
|
2999
|
+
* Transform Response API request → unified format
|
|
3000
|
+
*/
|
|
3001
|
+
async transformRequestOut(request, _context) {
|
|
3002
|
+
const req = request;
|
|
3003
|
+
const messages = [];
|
|
3004
|
+
if (req.input) {
|
|
3005
|
+
for (const item of req.input) {
|
|
3006
|
+
const entry = item;
|
|
3007
|
+
if (entry.type === "function_call_output") {
|
|
3008
|
+
messages.push({
|
|
3009
|
+
role: "tool",
|
|
3010
|
+
content: entry.output || "",
|
|
3011
|
+
tool_call_id: entry.call_id || void 0
|
|
3012
|
+
});
|
|
3013
|
+
continue;
|
|
3014
|
+
}
|
|
3015
|
+
if (entry.type === "function_call") {
|
|
3016
|
+
const toolCall = {
|
|
3017
|
+
id: (entry.call_id ?? entry.id) || "",
|
|
3018
|
+
type: "function",
|
|
3019
|
+
function: {
|
|
3020
|
+
name: entry.name || "",
|
|
3021
|
+
arguments: typeof entry.arguments === "string" ? entry.arguments : ""
|
|
3022
|
+
}
|
|
3023
|
+
};
|
|
3024
|
+
const last = messages[messages.length - 1];
|
|
3025
|
+
if (last && last.role === "assistant") {
|
|
3026
|
+
(last.tool_calls ??= []).push(toolCall);
|
|
3027
|
+
} else {
|
|
3028
|
+
messages.push({ role: "assistant", content: null, tool_calls: [toolCall] });
|
|
3029
|
+
}
|
|
3030
|
+
continue;
|
|
3031
|
+
}
|
|
3032
|
+
const role = entry.role;
|
|
3033
|
+
if (role === "developer") {
|
|
3034
|
+
messages.push({
|
|
3035
|
+
role: "system",
|
|
3036
|
+
content: typeof entry.content === "string" ? entry.content : JSON.stringify(entry.content)
|
|
3037
|
+
});
|
|
3038
|
+
} else if (role === "user" || role === "assistant") {
|
|
3039
|
+
messages.push({
|
|
3040
|
+
role,
|
|
3041
|
+
content: typeof entry.content === "string" ? entry.content : JSON.stringify(entry.content)
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
const result = {
|
|
3047
|
+
messages,
|
|
3048
|
+
model: req.model,
|
|
3049
|
+
max_tokens: req.max_output_tokens,
|
|
3050
|
+
temperature: req.temperature,
|
|
3051
|
+
stream: req.stream
|
|
3052
|
+
};
|
|
3053
|
+
if (req.reasoning?.effort) {
|
|
3054
|
+
result.reasoning = {
|
|
3055
|
+
effort: req.reasoning.effort,
|
|
3056
|
+
enabled: true
|
|
3057
|
+
};
|
|
3058
|
+
}
|
|
3059
|
+
if (req.tools?.length) {
|
|
3060
|
+
result.tools = req.tools.filter((t) => t.type === "function").map((t) => ({
|
|
3061
|
+
type: "function",
|
|
3062
|
+
function: {
|
|
3063
|
+
name: t.name || "",
|
|
3064
|
+
description: t.description || "",
|
|
3065
|
+
parameters: t.parameters || {}
|
|
3066
|
+
}
|
|
3067
|
+
}));
|
|
3068
|
+
}
|
|
3069
|
+
return result;
|
|
3070
|
+
}
|
|
3071
|
+
/**
|
|
3072
|
+
* Transform Response API response → unified (OpenAI CC) format
|
|
3073
|
+
*/
|
|
3074
|
+
async transformResponseOut(response, _context) {
|
|
3075
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
3076
|
+
if (contentType.includes("text/event-stream")) {
|
|
3077
|
+
if (!response.body) {
|
|
3078
|
+
throw new Error("Stream response body is null");
|
|
3079
|
+
}
|
|
3080
|
+
return new Response(convertResponseApiStreamToOpenAI(response.body), {
|
|
3081
|
+
headers: {
|
|
3082
|
+
"Content-Type": "text/event-stream",
|
|
3083
|
+
"Cache-Control": "no-cache",
|
|
3084
|
+
Connection: "keep-alive"
|
|
3085
|
+
}
|
|
3086
|
+
});
|
|
3087
|
+
}
|
|
3088
|
+
const data = await response.json();
|
|
3089
|
+
return new Response(JSON.stringify(convertResponseApiJsonToOpenAI(data)), {
|
|
3090
|
+
headers: { "Content-Type": "application/json" }
|
|
3091
|
+
});
|
|
3092
|
+
}
|
|
3093
|
+
/**
|
|
3094
|
+
* Transform OpenAI CC response → Response API format
|
|
3095
|
+
*/
|
|
3096
|
+
async transformResponseIn(response, _context) {
|
|
3097
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
3098
|
+
if (contentType.includes("text/event-stream")) {
|
|
3099
|
+
if (!response.body) {
|
|
3100
|
+
throw new Error("Stream response body is null");
|
|
3101
|
+
}
|
|
3102
|
+
return new Response(convertOpenAIStreamToResponseApi(response.body), {
|
|
3103
|
+
headers: {
|
|
3104
|
+
"Content-Type": "text/event-stream",
|
|
3105
|
+
"Cache-Control": "no-cache",
|
|
3106
|
+
Connection: "keep-alive"
|
|
3107
|
+
}
|
|
3108
|
+
});
|
|
3109
|
+
}
|
|
3110
|
+
const data = await response.json();
|
|
3111
|
+
return new Response(JSON.stringify(convertOpenAIJsonToResponseApi(data)), {
|
|
3112
|
+
headers: { "Content-Type": "application/json" }
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3115
|
+
};
|
|
3116
|
+
function flattenContent(content) {
|
|
3117
|
+
if (typeof content === "string") return content;
|
|
3118
|
+
if (!content) return "";
|
|
3119
|
+
if (Array.isArray(content)) {
|
|
3120
|
+
return content.filter((c) => c.type === "text").map((c) => c.text || "").join("\n");
|
|
3121
|
+
}
|
|
3122
|
+
return "";
|
|
3123
|
+
}
|
|
3124
|
+
function convertResponseApiJsonToOpenAI(data) {
|
|
3125
|
+
let textContent = "";
|
|
3126
|
+
const toolCalls = [];
|
|
3127
|
+
const output = data.output;
|
|
3128
|
+
if (output) {
|
|
3129
|
+
for (const item of output) {
|
|
3130
|
+
if (item.type === "message") {
|
|
3131
|
+
const content = item.content;
|
|
3132
|
+
if (content) {
|
|
3133
|
+
for (const part of content) {
|
|
3134
|
+
if (part.type === "output_text" && typeof part.text === "string") {
|
|
3135
|
+
textContent += part.text;
|
|
3136
|
+
}
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
} else if (item.type === "function_call") {
|
|
3140
|
+
toolCalls.push({
|
|
3141
|
+
id: item.call_id || item.id || `call_${Date.now()}`,
|
|
3142
|
+
type: "function",
|
|
3143
|
+
function: {
|
|
3144
|
+
name: item.name,
|
|
3145
|
+
arguments: typeof item.arguments === "string" ? item.arguments : JSON.stringify(item.arguments || {})
|
|
3146
|
+
}
|
|
3147
|
+
});
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
const usage = data.usage;
|
|
3152
|
+
const message = {
|
|
3153
|
+
role: "assistant",
|
|
3154
|
+
content: textContent || null
|
|
3155
|
+
};
|
|
3156
|
+
if (toolCalls.length > 0) {
|
|
3157
|
+
message.tool_calls = toolCalls;
|
|
3158
|
+
}
|
|
3159
|
+
return {
|
|
3160
|
+
id: data.id || `chatcmpl-${Date.now()}`,
|
|
3161
|
+
object: "chat.completion",
|
|
3162
|
+
created: Math.floor(Date.now() / 1e3),
|
|
3163
|
+
model: data.model || "unknown",
|
|
3164
|
+
choices: [
|
|
3165
|
+
{
|
|
3166
|
+
index: 0,
|
|
3167
|
+
message,
|
|
3168
|
+
finish_reason: toolCalls.length > 0 ? "tool_calls" : "stop"
|
|
3169
|
+
}
|
|
3170
|
+
],
|
|
3171
|
+
usage: usage ? {
|
|
3172
|
+
prompt_tokens: usage.input_tokens || 0,
|
|
3173
|
+
completion_tokens: usage.output_tokens || 0,
|
|
3174
|
+
total_tokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
|
3175
|
+
} : void 0
|
|
3176
|
+
};
|
|
3177
|
+
}
|
|
3178
|
+
function convertOpenAIJsonToResponseApi(data) {
|
|
3179
|
+
const choices = data.choices;
|
|
3180
|
+
const message = choices?.[0]?.message;
|
|
3181
|
+
const output = [];
|
|
3182
|
+
if (message) {
|
|
3183
|
+
const contentParts = [];
|
|
3184
|
+
if (message.content) {
|
|
3185
|
+
contentParts.push({ type: "output_text", text: message.content });
|
|
3186
|
+
}
|
|
3187
|
+
if (contentParts.length > 0) {
|
|
3188
|
+
output.push({ type: "message", role: "assistant", content: contentParts });
|
|
3189
|
+
}
|
|
3190
|
+
const toolCalls = message.tool_calls;
|
|
3191
|
+
if (toolCalls?.length) {
|
|
3192
|
+
for (const tc of toolCalls) {
|
|
3193
|
+
const func = tc.function;
|
|
3194
|
+
output.push({
|
|
3195
|
+
type: "function_call",
|
|
3196
|
+
id: tc.id,
|
|
3197
|
+
call_id: tc.id,
|
|
3198
|
+
name: func?.name,
|
|
3199
|
+
arguments: func?.arguments
|
|
3200
|
+
});
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
}
|
|
3204
|
+
const usage = data.usage;
|
|
3205
|
+
return {
|
|
3206
|
+
id: data.id || `resp_${Date.now()}`,
|
|
3207
|
+
object: "response",
|
|
3208
|
+
status: "completed",
|
|
3209
|
+
model: data.model || "unknown",
|
|
3210
|
+
output,
|
|
3211
|
+
usage: usage ? {
|
|
3212
|
+
input_tokens: usage.prompt_tokens || 0,
|
|
3213
|
+
output_tokens: usage.completion_tokens || 0,
|
|
3214
|
+
total_tokens: usage.total_tokens || (usage.prompt_tokens || 0) + (usage.completion_tokens || 0)
|
|
3215
|
+
} : void 0
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
function convertResponseApiStreamToOpenAI(responseApiStream) {
|
|
3219
|
+
const decoder = new TextDecoder();
|
|
3220
|
+
const encoder = new TextEncoder();
|
|
3221
|
+
return new ReadableStream({
|
|
3222
|
+
start: async (controller) => {
|
|
3223
|
+
const reader = responseApiStream.getReader();
|
|
3224
|
+
let buffer = "";
|
|
3225
|
+
let isClosed = false;
|
|
3226
|
+
const messageId = `chatcmpl-${Date.now()}`;
|
|
3227
|
+
let model = "unknown";
|
|
3228
|
+
let hasEmittedRole = false;
|
|
3229
|
+
const safeEnqueue = (str) => {
|
|
3230
|
+
if (!isClosed) {
|
|
3231
|
+
try {
|
|
3232
|
+
controller.enqueue(encoder.encode(str));
|
|
3233
|
+
} catch {
|
|
3234
|
+
isClosed = true;
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
};
|
|
3238
|
+
const emitChunk = (choices, usage) => {
|
|
3239
|
+
const chunk = {
|
|
3240
|
+
id: messageId,
|
|
3241
|
+
object: "chat.completion.chunk",
|
|
3242
|
+
created: Math.floor(Date.now() / 1e3),
|
|
3243
|
+
model,
|
|
3244
|
+
choices
|
|
3245
|
+
};
|
|
3246
|
+
if (usage) chunk.usage = usage;
|
|
3247
|
+
safeEnqueue(`data: ${JSON.stringify(chunk)}
|
|
3248
|
+
|
|
3249
|
+
`);
|
|
3250
|
+
};
|
|
3251
|
+
try {
|
|
3252
|
+
while (true) {
|
|
3253
|
+
const { done, value } = await reader.read();
|
|
3254
|
+
if (done) break;
|
|
3255
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3256
|
+
const lines = buffer.split("\n");
|
|
3257
|
+
buffer = lines.pop() || "";
|
|
3258
|
+
for (const line of lines) {
|
|
3259
|
+
if (isClosed) break;
|
|
3260
|
+
if (!line.startsWith("data:")) continue;
|
|
3261
|
+
const data = line.slice(5).trim();
|
|
3262
|
+
if (data === "[DONE]") continue;
|
|
3263
|
+
try {
|
|
3264
|
+
const event = JSON.parse(data);
|
|
3265
|
+
model = event.model || event.response?.model || model;
|
|
3266
|
+
switch (event.type) {
|
|
3267
|
+
case "response.output_text.delta":
|
|
3268
|
+
if (event.delta) {
|
|
3269
|
+
if (!hasEmittedRole) {
|
|
3270
|
+
emitChunk([{ index: 0, delta: { role: "assistant", content: "" }, finish_reason: null }]);
|
|
3271
|
+
hasEmittedRole = true;
|
|
3272
|
+
}
|
|
3273
|
+
emitChunk([{ index: 0, delta: { content: event.delta }, finish_reason: null }]);
|
|
3274
|
+
}
|
|
3275
|
+
break;
|
|
3276
|
+
case "response.reasoning_summary_text.delta":
|
|
3277
|
+
if (event.delta) {
|
|
3278
|
+
emitChunk([{
|
|
3279
|
+
index: 0,
|
|
3280
|
+
delta: { thinking: { content: event.delta } },
|
|
3281
|
+
finish_reason: null
|
|
3282
|
+
}]);
|
|
3283
|
+
}
|
|
3284
|
+
break;
|
|
3285
|
+
case "response.completed": {
|
|
3286
|
+
const resp = event.response;
|
|
3287
|
+
const respUsage = resp?.usage;
|
|
3288
|
+
const usage = respUsage ? {
|
|
3289
|
+
prompt_tokens: respUsage.input_tokens || 0,
|
|
3290
|
+
completion_tokens: respUsage.output_tokens || 0,
|
|
3291
|
+
total_tokens: (respUsage.input_tokens || 0) + (respUsage.output_tokens || 0)
|
|
3292
|
+
} : void 0;
|
|
3293
|
+
emitChunk([{ index: 0, delta: {}, finish_reason: "stop" }], usage);
|
|
3294
|
+
safeEnqueue("data: [DONE]\n\n");
|
|
3295
|
+
break;
|
|
3296
|
+
}
|
|
3297
|
+
case "error":
|
|
3298
|
+
emitChunk([{
|
|
3299
|
+
index: 0,
|
|
3300
|
+
delta: { content: `[Error: ${event.error?.message || "Unknown error"}]` },
|
|
3301
|
+
finish_reason: "stop"
|
|
3302
|
+
}]);
|
|
3303
|
+
break;
|
|
3304
|
+
default:
|
|
3305
|
+
break;
|
|
3306
|
+
}
|
|
3307
|
+
} catch {
|
|
3308
|
+
}
|
|
3309
|
+
}
|
|
3310
|
+
}
|
|
3311
|
+
} catch (e) {
|
|
3312
|
+
if (!isClosed) controller.error(e);
|
|
3313
|
+
} finally {
|
|
3314
|
+
if (!isClosed) {
|
|
3315
|
+
try {
|
|
3316
|
+
controller.close();
|
|
3317
|
+
} catch {
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
reader.releaseLock();
|
|
3321
|
+
}
|
|
3322
|
+
}
|
|
3323
|
+
});
|
|
3324
|
+
}
|
|
3325
|
+
function convertOpenAIStreamToResponseApi(openaiStream) {
|
|
3326
|
+
const decoder = new TextDecoder();
|
|
3327
|
+
const encoder = new TextEncoder();
|
|
3328
|
+
return new ReadableStream({
|
|
3329
|
+
start: async (controller) => {
|
|
3330
|
+
const reader = openaiStream.getReader();
|
|
3331
|
+
let buffer = "";
|
|
3332
|
+
let isClosed = false;
|
|
3333
|
+
let accumulatedContent = "";
|
|
3334
|
+
let model = "unknown";
|
|
3335
|
+
const responseId = `resp_${Date.now()}`;
|
|
3336
|
+
const safeEnqueue = (str) => {
|
|
3337
|
+
if (!isClosed) {
|
|
3338
|
+
try {
|
|
3339
|
+
controller.enqueue(encoder.encode(str));
|
|
3340
|
+
} catch {
|
|
3341
|
+
isClosed = true;
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
};
|
|
3345
|
+
const emitEvent = (event) => {
|
|
3346
|
+
safeEnqueue(`data: ${JSON.stringify(event)}
|
|
3347
|
+
|
|
3348
|
+
`);
|
|
3349
|
+
};
|
|
3350
|
+
emitEvent({
|
|
3351
|
+
type: "response.created",
|
|
3352
|
+
response: { id: responseId, status: "in_progress" }
|
|
3353
|
+
});
|
|
3354
|
+
try {
|
|
3355
|
+
while (true) {
|
|
3356
|
+
const { done, value } = await reader.read();
|
|
3357
|
+
if (done) break;
|
|
3358
|
+
buffer += decoder.decode(value, { stream: true });
|
|
3359
|
+
const lines = buffer.split("\n");
|
|
3360
|
+
buffer = lines.pop() || "";
|
|
3361
|
+
for (const line of lines) {
|
|
3362
|
+
if (isClosed) break;
|
|
3363
|
+
if (!line.startsWith("data:")) continue;
|
|
3364
|
+
const data = line.slice(5).trim();
|
|
3365
|
+
if (data === "[DONE]") continue;
|
|
3366
|
+
try {
|
|
3367
|
+
const chunk = JSON.parse(data);
|
|
3368
|
+
const choice = chunk.choices?.[0];
|
|
3369
|
+
model = chunk.model || model;
|
|
3370
|
+
if (!choice) continue;
|
|
3371
|
+
if (choice.delta?.content) {
|
|
3372
|
+
accumulatedContent += choice.delta.content;
|
|
3373
|
+
emitEvent({ type: "response.output_text.delta", delta: choice.delta.content });
|
|
3374
|
+
}
|
|
3375
|
+
if (choice.delta?.thinking?.content) {
|
|
3376
|
+
emitEvent({
|
|
3377
|
+
type: "response.reasoning_summary_text.delta",
|
|
3378
|
+
delta: choice.delta.thinking.content
|
|
3379
|
+
});
|
|
3380
|
+
}
|
|
3381
|
+
if (choice.finish_reason) {
|
|
3382
|
+
emitEvent({ type: "response.output_text.done", text: accumulatedContent });
|
|
3383
|
+
emitEvent({
|
|
3384
|
+
type: "response.completed",
|
|
3385
|
+
response: {
|
|
3386
|
+
id: responseId,
|
|
3387
|
+
status: "completed",
|
|
3388
|
+
model,
|
|
3389
|
+
output: [
|
|
3390
|
+
{
|
|
3391
|
+
type: "message",
|
|
3392
|
+
role: "assistant",
|
|
3393
|
+
content: [{ type: "output_text", text: accumulatedContent }]
|
|
3394
|
+
}
|
|
3395
|
+
],
|
|
3396
|
+
usage: chunk.usage ? {
|
|
3397
|
+
input_tokens: chunk.usage.prompt_tokens || 0,
|
|
3398
|
+
output_tokens: chunk.usage.completion_tokens || 0,
|
|
3399
|
+
total_tokens: chunk.usage.total_tokens || 0
|
|
3400
|
+
} : void 0
|
|
3401
|
+
}
|
|
3402
|
+
});
|
|
3403
|
+
}
|
|
3404
|
+
} catch {
|
|
3405
|
+
}
|
|
3406
|
+
}
|
|
3407
|
+
}
|
|
3408
|
+
} catch (e) {
|
|
3409
|
+
if (!isClosed) controller.error(e);
|
|
3410
|
+
} finally {
|
|
3411
|
+
if (!isClosed) {
|
|
3412
|
+
try {
|
|
3413
|
+
controller.close();
|
|
3414
|
+
} catch {
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
reader.releaseLock();
|
|
3418
|
+
}
|
|
3419
|
+
}
|
|
3420
|
+
});
|
|
3421
|
+
}
|
|
3422
|
+
|
|
3423
|
+
// src/transformer/transformers/OpenCodeGoTransformer.ts
|
|
3424
|
+
var OpenCodeGoTransformer = class {
|
|
3425
|
+
static TransformerName = "opencodego";
|
|
3426
|
+
name = "opencodego";
|
|
3427
|
+
endPoint = "/v1/chat/completions";
|
|
3428
|
+
logger;
|
|
3429
|
+
async auth(request, _provider, _context) {
|
|
3430
|
+
return { body: request, config: { headers: {} } };
|
|
3431
|
+
}
|
|
3432
|
+
/**
|
|
3433
|
+
* Unified → OpenAI Chat Completions.
|
|
3434
|
+
* Unified IS chat completions shape; this is mostly stripping `meta` and
|
|
3435
|
+
* normalizing string-content single-block messages.
|
|
3436
|
+
*/
|
|
3437
|
+
async transformRequestIn(request, _provider, _context) {
|
|
3438
|
+
const messages = request.messages.map((m) => {
|
|
3439
|
+
const content = m.content;
|
|
3440
|
+
if (Array.isArray(content) && content.length === 1 && content[0]?.type === "text") {
|
|
3441
|
+
return { ...m, content: content[0].text };
|
|
3442
|
+
}
|
|
3443
|
+
return m;
|
|
3444
|
+
});
|
|
3445
|
+
const out = {
|
|
3446
|
+
model: request.model,
|
|
3447
|
+
messages,
|
|
3448
|
+
stream: request.stream
|
|
3449
|
+
};
|
|
3450
|
+
if (request.temperature !== void 0) out.temperature = request.temperature;
|
|
3451
|
+
if (request.max_tokens !== void 0) out.max_tokens = request.max_tokens;
|
|
3452
|
+
if (request.tools && request.tools.length > 0) out.tools = request.tools;
|
|
3453
|
+
if (request.tool_choice !== void 0) out.tool_choice = request.tool_choice;
|
|
3454
|
+
if (request.reasoning?.effort && request.reasoning.effort !== "none") {
|
|
3455
|
+
out.reasoning_effort = request.reasoning.effort;
|
|
3456
|
+
}
|
|
3457
|
+
return out;
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* OpenAI Chat Completions response → Unified.
|
|
3461
|
+
* The upstream's response shape already matches Unified — pass through.
|
|
3462
|
+
* The endpoint AnthropicTransformer re-encodes to Anthropic for the SDK.
|
|
3463
|
+
*/
|
|
3464
|
+
async transformResponseOut(response, _context) {
|
|
3465
|
+
return response;
|
|
3466
|
+
}
|
|
3467
|
+
};
|
|
3468
|
+
|
|
3469
|
+
// src/transformer/transformers/ReasoningTransformer.ts
|
|
3470
|
+
var import_thinking_config = require("@omnicross/contracts/thinking-config");
|
|
3471
|
+
var ReasoningTransformer = class {
|
|
3472
|
+
static TransformerName = "reasoning";
|
|
3473
|
+
name = "reasoning";
|
|
3474
|
+
logger;
|
|
3475
|
+
enabled;
|
|
3476
|
+
constructor(options) {
|
|
3477
|
+
this.enabled = options?.enable !== false;
|
|
3478
|
+
}
|
|
3479
|
+
/**
|
|
3480
|
+
* Transform request: convert reasoning config to thinking parameters
|
|
3481
|
+
* Uses model-specific budget calculation based on effort level
|
|
3482
|
+
*/
|
|
3483
|
+
async transformRequestIn(request, provider, _context) {
|
|
3484
|
+
const extendedRequest = request;
|
|
3485
|
+
const modelId = request.model;
|
|
3486
|
+
if (!this.enabled) {
|
|
3487
|
+
extendedRequest.thinking = {
|
|
3488
|
+
type: "disabled",
|
|
3489
|
+
budget_tokens: -1
|
|
3490
|
+
};
|
|
3491
|
+
extendedRequest.enable_thinking = false;
|
|
3492
|
+
return extendedRequest;
|
|
3493
|
+
}
|
|
3494
|
+
if (request.reasoning) {
|
|
3495
|
+
const effortLevel = request.reasoning.effort || "none";
|
|
3496
|
+
const userMaxTokens = request.reasoning.max_tokens || request.max_tokens;
|
|
3497
|
+
const calculatedBudget = (0, import_thinking_config.calculateThinkingBudget)(modelId, effortLevel, userMaxTokens);
|
|
3498
|
+
const providerName = provider.name?.toLowerCase() || "";
|
|
3499
|
+
if (providerName === "anthropic" || providerName.includes("claude")) {
|
|
3500
|
+
const thinkingConfig = (0, import_thinking_config.buildAnthropicThinking)(modelId, effortLevel, userMaxTokens);
|
|
3501
|
+
if (thinkingConfig) {
|
|
3502
|
+
extendedRequest.thinking = thinkingConfig;
|
|
3503
|
+
} else {
|
|
3504
|
+
extendedRequest.thinking = {
|
|
3505
|
+
type: "disabled"
|
|
3506
|
+
};
|
|
3507
|
+
}
|
|
3508
|
+
} else if (providerName === "openai" || modelId.match(/^o[134]/i)) {
|
|
3509
|
+
const reasoningEffort = (0, import_thinking_config.getOpenAIReasoningEffort)(effortLevel);
|
|
3510
|
+
if (reasoningEffort) {
|
|
3511
|
+
extendedRequest.reasoning_effort = reasoningEffort;
|
|
3512
|
+
}
|
|
3513
|
+
extendedRequest.thinking = {
|
|
3514
|
+
type: effortLevel === "none" ? "disabled" : "enabled",
|
|
3515
|
+
budget_tokens: calculatedBudget
|
|
3516
|
+
};
|
|
3517
|
+
} else if (providerName === "deepseek" && modelId?.startsWith("deepseek-v4")) {
|
|
3518
|
+
extendedRequest.thinking = {
|
|
3519
|
+
type: effortLevel === "none" ? "disabled" : "enabled",
|
|
3520
|
+
budget_tokens: calculatedBudget
|
|
3521
|
+
};
|
|
3522
|
+
if (effortLevel !== "none") {
|
|
3523
|
+
extendedRequest.reasoning_effort = effortLevel === "high" ? "max" : "high";
|
|
3524
|
+
}
|
|
3525
|
+
} else if (providerName === "deepseek" || providerName === "qwen" || providerName === "alibaba") {
|
|
3526
|
+
const qwenConfig = (0, import_thinking_config.buildQwenThinkingConfig)(effortLevel, userMaxTokens);
|
|
3527
|
+
extendedRequest.enable_thinking = qwenConfig.enable_thinking;
|
|
3528
|
+
if (qwenConfig.thinking_budget) {
|
|
3529
|
+
extendedRequest.thinking_budget = qwenConfig.thinking_budget;
|
|
3530
|
+
}
|
|
3531
|
+
extendedRequest.thinking = {
|
|
3532
|
+
type: qwenConfig.enable_thinking ? "enabled" : "disabled",
|
|
3533
|
+
budget_tokens: calculatedBudget
|
|
3534
|
+
};
|
|
3535
|
+
} else {
|
|
3536
|
+
extendedRequest.thinking = {
|
|
3537
|
+
type: effortLevel === "none" ? "disabled" : "enabled",
|
|
3538
|
+
budget_tokens: calculatedBudget
|
|
3539
|
+
};
|
|
3540
|
+
extendedRequest.enable_thinking = effortLevel !== "none";
|
|
3541
|
+
}
|
|
3542
|
+
this.logger?.debug(`[ReasoningTransformer] Model: ${modelId}, Effort: ${effortLevel}, Budget: ${calculatedBudget}`);
|
|
3543
|
+
}
|
|
3544
|
+
return extendedRequest;
|
|
3545
|
+
}
|
|
3546
|
+
/**
|
|
3547
|
+
* Transform response: convert reasoning_content to thinking blocks
|
|
3548
|
+
*/
|
|
3549
|
+
async transformResponseOut(response, _context) {
|
|
3550
|
+
if (!this.enabled) return response;
|
|
3551
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
3552
|
+
if (contentType.includes("application/json")) {
|
|
3553
|
+
return this.handleJsonResponse(response);
|
|
3554
|
+
} else if (contentType.includes("stream") || contentType.includes("text/event-stream")) {
|
|
3555
|
+
return this.handleStreamResponse(response);
|
|
3556
|
+
}
|
|
3557
|
+
return response;
|
|
3558
|
+
}
|
|
3559
|
+
/**
|
|
3560
|
+
* Handle JSON (non-streaming) response
|
|
3561
|
+
*/
|
|
3562
|
+
async handleJsonResponse(response) {
|
|
3563
|
+
const jsonResponse = await response.json();
|
|
3564
|
+
if (jsonResponse.choices?.[0]?.message?.reasoning_content) {
|
|
3565
|
+
jsonResponse.thinking = {
|
|
3566
|
+
content: jsonResponse.choices[0].message.reasoning_content
|
|
3567
|
+
};
|
|
3568
|
+
delete jsonResponse.choices[0].message.reasoning_content;
|
|
3569
|
+
}
|
|
3570
|
+
return new Response(JSON.stringify(jsonResponse), {
|
|
3571
|
+
status: response.status,
|
|
3572
|
+
statusText: response.statusText,
|
|
3573
|
+
headers: response.headers
|
|
3574
|
+
});
|
|
3575
|
+
}
|
|
3576
|
+
/**
|
|
3577
|
+
* Handle streaming response - convert reasoning_content to thinking blocks
|
|
3578
|
+
*/
|
|
3579
|
+
handleStreamResponse(response) {
|
|
3580
|
+
if (!response.body) {
|
|
3581
|
+
return response;
|
|
3582
|
+
}
|
|
3583
|
+
const decoder = new TextDecoder();
|
|
3584
|
+
const encoder = new TextEncoder();
|
|
3585
|
+
let reasoningContent = "";
|
|
3586
|
+
let isReasoningComplete = false;
|
|
3587
|
+
let buffer = "";
|
|
3588
|
+
const stream = new ReadableStream({
|
|
3589
|
+
start: async (controller) => {
|
|
3590
|
+
const reader = response.body.getReader();
|
|
3591
|
+
const processLine = (line) => {
|
|
3592
|
+
this.logger?.debug("Processing reasoning line:", line);
|
|
3593
|
+
if (line.startsWith("data: ") && line.trim() !== "data: [DONE]") {
|
|
3594
|
+
try {
|
|
3595
|
+
const data = JSON.parse(line.slice(6));
|
|
3596
|
+
if (data.choices?.[0]?.delta?.reasoning_content) {
|
|
3597
|
+
reasoningContent += data.choices[0].delta.reasoning_content;
|
|
3598
|
+
const thinkingChunk = {
|
|
3599
|
+
...data,
|
|
3600
|
+
choices: [
|
|
3601
|
+
{
|
|
3602
|
+
...data.choices[0],
|
|
3603
|
+
delta: {
|
|
3604
|
+
...data.choices[0].delta,
|
|
3605
|
+
thinking: {
|
|
3606
|
+
content: data.choices[0].delta.reasoning_content
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
]
|
|
3611
|
+
};
|
|
3612
|
+
delete thinkingChunk.choices[0].delta.reasoning_content;
|
|
3613
|
+
const thinkingLine = `data: ${JSON.stringify(thinkingChunk)}
|
|
3614
|
+
|
|
3615
|
+
`;
|
|
3616
|
+
controller.enqueue(encoder.encode(thinkingLine));
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
if ((data.choices?.[0]?.delta?.content || data.choices?.[0]?.delta?.tool_calls) && reasoningContent && !isReasoningComplete) {
|
|
3620
|
+
isReasoningComplete = true;
|
|
3621
|
+
const signature = Date.now().toString();
|
|
3622
|
+
const thinkingChunk = {
|
|
3623
|
+
...data,
|
|
3624
|
+
choices: [
|
|
3625
|
+
{
|
|
3626
|
+
...data.choices[0],
|
|
3627
|
+
delta: {
|
|
3628
|
+
...data.choices[0].delta,
|
|
3629
|
+
content: null,
|
|
3630
|
+
thinking: {
|
|
3631
|
+
signature
|
|
3632
|
+
}
|
|
3633
|
+
}
|
|
3634
|
+
}
|
|
3635
|
+
]
|
|
3636
|
+
};
|
|
3637
|
+
delete thinkingChunk.choices[0].delta.reasoning_content;
|
|
3638
|
+
const thinkingLine = `data: ${JSON.stringify(thinkingChunk)}
|
|
3639
|
+
|
|
3640
|
+
`;
|
|
3641
|
+
controller.enqueue(encoder.encode(thinkingLine));
|
|
3642
|
+
}
|
|
3643
|
+
if (data.choices?.[0]?.delta?.reasoning_content) {
|
|
3644
|
+
delete data.choices[0].delta.reasoning_content;
|
|
3645
|
+
}
|
|
3646
|
+
if (data.choices?.[0]?.delta && Object.keys(data.choices[0].delta).length > 0) {
|
|
3647
|
+
if (isReasoningComplete) {
|
|
3648
|
+
data.choices[0].index++;
|
|
3649
|
+
}
|
|
3650
|
+
const modifiedLine = `data: ${JSON.stringify(data)}
|
|
3651
|
+
|
|
3652
|
+
`;
|
|
3653
|
+
controller.enqueue(encoder.encode(modifiedLine));
|
|
3654
|
+
}
|
|
3655
|
+
} catch (_e) {
|
|
3656
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
3657
|
+
}
|
|
3658
|
+
} else {
|
|
3659
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
3660
|
+
}
|
|
3661
|
+
};
|
|
3662
|
+
try {
|
|
3663
|
+
while (true) {
|
|
3664
|
+
const { done, value } = await reader.read();
|
|
3665
|
+
if (done) {
|
|
3666
|
+
if (buffer.trim()) {
|
|
3667
|
+
const lines2 = buffer.split("\n");
|
|
3668
|
+
for (const line of lines2) {
|
|
3669
|
+
if (line.trim()) {
|
|
3670
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
3671
|
+
}
|
|
3672
|
+
}
|
|
3673
|
+
}
|
|
3674
|
+
break;
|
|
3675
|
+
}
|
|
3676
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
3677
|
+
buffer += chunk;
|
|
3678
|
+
const lines = buffer.split("\n");
|
|
3679
|
+
buffer = lines.pop() || "";
|
|
3680
|
+
for (const line of lines) {
|
|
3681
|
+
if (!line.trim()) continue;
|
|
3682
|
+
try {
|
|
3683
|
+
processLine(line);
|
|
3684
|
+
} catch (error) {
|
|
3685
|
+
this.logger?.error("Error processing reasoning stream line:", error);
|
|
3686
|
+
controller.enqueue(encoder.encode(line + "\n"));
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
}
|
|
3690
|
+
} catch (error) {
|
|
3691
|
+
this.logger?.error("Reasoning stream error:", error);
|
|
3692
|
+
controller.error(error);
|
|
3693
|
+
} finally {
|
|
3694
|
+
try {
|
|
3695
|
+
reader.releaseLock();
|
|
3696
|
+
} catch (e) {
|
|
3697
|
+
this.logger?.error("Error releasing reader lock:", e);
|
|
3698
|
+
}
|
|
3699
|
+
controller.close();
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
});
|
|
3703
|
+
return new Response(stream, {
|
|
3704
|
+
status: response.status,
|
|
3705
|
+
statusText: response.statusText,
|
|
3706
|
+
headers: new Headers({
|
|
3707
|
+
"Content-Type": "text/event-stream",
|
|
3708
|
+
"Cache-Control": "no-cache",
|
|
3709
|
+
Connection: "keep-alive"
|
|
3710
|
+
})
|
|
3711
|
+
});
|
|
3712
|
+
}
|
|
3713
|
+
};
|
|
3714
|
+
|
|
3715
|
+
// src/transformer/transformers/index.ts
|
|
3716
|
+
var BuiltinTransformers = {
|
|
3717
|
+
DeepseekTransformer,
|
|
3718
|
+
ReasoningTransformer,
|
|
3719
|
+
GeminiTransformer,
|
|
3720
|
+
GeminiCodeAssistTransformer,
|
|
3721
|
+
AnthropicTransformer,
|
|
3722
|
+
OpenAIResponseTransformer,
|
|
3723
|
+
OpenCodeGoTransformer
|
|
3724
|
+
};
|
|
3725
|
+
function getBuiltinTransformers() {
|
|
3726
|
+
return BuiltinTransformers;
|
|
3727
|
+
}
|
|
3728
|
+
async function registerBuiltinTransformers(service) {
|
|
3729
|
+
await service.initialize(getBuiltinTransformers());
|
|
3730
|
+
}
|
|
3731
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
3732
|
+
0 && (module.exports = {
|
|
3733
|
+
TransformerChainExecutor,
|
|
3734
|
+
TransformerService,
|
|
3735
|
+
registerBuiltinTransformers
|
|
3736
|
+
});
|