@omnicross/core 0.1.4 → 0.1.6
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/dist/ApiConverter.cjs +2 -2
- package/dist/ApiConverter.js +1 -1
- package/dist/{CompletionService-DdWaAxq0.d.ts → CompletionService-C3BX0Ovy.d.ts} +4 -3
- package/dist/{CompletionService-D3a47vdU.d.cts → CompletionService-kNA17cfI.d.cts} +4 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +7 -5
- package/dist/auth/GeminiCodeAssistProjectResolver.js +5 -3
- package/dist/{chunk-HRRYG2AX.js → chunk-23S5OPIO.js} +10 -1
- package/dist/chunk-25GXJCEZ.cjs +40 -0
- package/dist/chunk-2JYRIADS.js +40 -0
- package/dist/chunk-3Z5BQZFM.cjs +52 -0
- package/dist/{chunk-4VAVYUZS.cjs → chunk-4VLRHHFE.cjs} +82 -40
- package/dist/{chunk-VACWOIRE.cjs → chunk-4VZX5F4T.cjs} +39 -15
- package/dist/{chunk-FTQ7IQUC.js → chunk-6CL5HTPQ.js} +52 -10
- package/dist/chunk-6QQG7YG3.cjs +153 -0
- package/dist/{chunk-SU2GCLAC.cjs → chunk-6VUQPOV3.cjs} +359 -60
- package/dist/chunk-7AK7B36B.cjs +44 -0
- package/dist/{chunk-5ZQBEOMD.js → chunk-AT7B7SOZ.js} +25 -2
- package/dist/{chunk-IK3KE3AU.js → chunk-DE2SRBFZ.js} +1 -1
- package/dist/chunk-DLXZEZWO.cjs +285 -0
- package/dist/{chunk-UHGVBFU3.js → chunk-EBLFCNPY.js} +38 -14
- package/dist/chunk-EJLH6NFT.cjs +166 -0
- package/dist/chunk-HVBBDBS3.cjs +350 -0
- package/dist/{chunk-7Y47UCF2.js → chunk-IFXJ2BG7.js} +5 -38
- package/dist/{chunk-46ZIFWYG.cjs → chunk-IIRPUVPH.cjs} +7 -1
- package/dist/{chunk-YGXJGRFI.cjs → chunk-KBM2W6UY.cjs} +111 -24
- package/dist/{chunk-OCEUVQHW.cjs → chunk-KCVJWJST.cjs} +73 -3
- package/dist/chunk-LB6EBS6Y.js +69 -0
- package/dist/{chunk-C7PSERMH.js → chunk-LVUCAWMK.js} +1 -1
- package/dist/chunk-MKF46GNZ.js +350 -0
- package/dist/{chunk-HYN75H6G.cjs → chunk-MUHWHQVM.cjs} +1 -1
- package/dist/{chunk-Z7Y2NPHY.js → chunk-N5T4M4GU.js} +1 -1
- package/dist/{chunk-TIER4SUC.js → chunk-OEWZUX4G.js} +346 -47
- package/dist/chunk-OF3BECU2.cjs +69 -0
- package/dist/{chunk-RSJ3TWJQ.cjs → chunk-OKMJLEXX.cjs} +5 -5
- package/dist/chunk-OPRS4IQZ.js +153 -0
- package/dist/{chunk-XK5CIRMQ.js → chunk-SIZQQD6T.js} +72 -2
- package/dist/{chunk-DKPXN34V.cjs → chunk-SQYVORNP.cjs} +1334 -510
- package/dist/chunk-SYQF7OIR.js +52 -0
- package/dist/{chunk-S4WBV25P.js → chunk-TOVPMMHM.js} +111 -24
- package/dist/{chunk-QYTT6GE3.js → chunk-UDCC7EMR.js} +1093 -269
- package/dist/{chunk-E3UCVHLP.cjs → chunk-UNWKG6EI.cjs} +5 -38
- package/dist/{chunk-ZLX3KGXX.js → chunk-UX7VWA46.js} +135 -23
- package/dist/{chunk-7YN22L2H.cjs → chunk-V4TXMEJS.cjs} +5 -5
- package/dist/chunk-VRFBB5DB.js +44 -0
- package/dist/chunk-WB6PH56J.js +166 -0
- package/dist/{chunk-G2UTNSGU.js → chunk-WNKWAEUR.js} +7 -1
- package/dist/{chunk-SUEGJ75D.cjs → chunk-WYTEQ4Q5.cjs} +25 -2
- package/dist/{chunk-5ENKBSWO.js → chunk-YJVHCHQX.js} +42 -6
- package/dist/{chunk-VQY5W4YW.cjs → chunk-YK57NMIF.cjs} +44 -8
- package/dist/{chunk-KFI44N2R.cjs → chunk-YNJNTCSQ.cjs} +10 -1
- package/dist/completion/ApiKeyPoolService.cjs +12 -0
- package/dist/completion/ApiKeyPoolService.d.cts +6 -0
- package/dist/completion/ApiKeyPoolService.d.ts +6 -0
- package/dist/completion/ApiKeyPoolService.js +12 -0
- package/dist/completion/CompletionService.cjs +27 -24
- package/dist/completion/CompletionService.d.cts +2 -1
- package/dist/completion/CompletionService.d.ts +2 -1
- package/dist/completion/CompletionService.js +26 -23
- package/dist/completion.cjs +27 -24
- package/dist/completion.d.cts +2 -1
- package/dist/completion.d.ts +2 -1
- package/dist/completion.js +26 -23
- package/dist/index.cjs +53 -29
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +56 -32
- package/dist/{keyPolicy-Cp0FntZl.d.cts → keyPolicy-zdWTu3CQ.d.cts} +1 -1
- package/dist/{keyPolicy-Cp0FntZl.d.ts → keyPolicy-zdWTu3CQ.d.ts} +1 -1
- package/dist/outbound-api/auditCapture.cjs +2 -2
- package/dist/outbound-api/auditCapture.d.cts +5 -3
- package/dist/outbound-api/auditCapture.d.ts +5 -3
- package/dist/outbound-api/auditCapture.js +1 -1
- package/dist/outbound-api/quotaWarn.d.cts +1 -1
- package/dist/outbound-api/quotaWarn.d.ts +1 -1
- package/dist/outbound-api/routeResolver.cjs +2 -2
- package/dist/outbound-api/routeResolver.d.cts +4 -3
- package/dist/outbound-api/routeResolver.d.ts +4 -3
- package/dist/outbound-api/routeResolver.js +1 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +1 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +1 -0
- package/dist/outbound-api/types.d.cts +3 -2
- package/dist/outbound-api/types.d.ts +3 -2
- package/dist/outbound-api.cjs +49 -25
- package/dist/outbound-api.d.cts +127 -85
- package/dist/outbound-api.d.ts +127 -85
- package/dist/outbound-api.js +52 -28
- package/dist/pipeline/AccountAllowanceScheduling.cjs +15 -0
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +90 -0
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +90 -0
- package/dist/pipeline/AccountAllowanceScheduling.js +15 -0
- package/dist/pipeline/AccountAllowanceStore.cjs +20 -0
- package/dist/pipeline/AccountAllowanceStore.d.cts +77 -0
- package/dist/pipeline/AccountAllowanceStore.d.ts +77 -0
- package/dist/pipeline/AccountAllowanceStore.js +20 -0
- package/dist/pipeline/AuthSource.d.cts +11 -2
- package/dist/pipeline/AuthSource.d.ts +11 -2
- package/dist/pipeline/BoundAccountSelectionError.cjs +10 -0
- package/dist/pipeline/BoundAccountSelectionError.d.cts +28 -0
- package/dist/pipeline/BoundAccountSelectionError.d.ts +28 -0
- package/dist/pipeline/BoundAccountSelectionError.js +10 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.cts +1 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.ts +1 -0
- package/dist/pipeline/SubscriptionAccountHealth.cjs +4 -2
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +34 -6
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +34 -6
- package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
- package/dist/pipeline/SubscriptionAuthSource.cjs +5 -2
- package/dist/pipeline/SubscriptionAuthSource.d.cts +1 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +1 -0
- package/dist/pipeline/SubscriptionAuthSource.js +4 -1
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +8 -3
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +8 -3
- package/dist/pipeline/executeProviderCall.cjs +2 -2
- package/dist/pipeline/executeProviderCall.d.cts +8 -3
- package/dist/pipeline/executeProviderCall.d.ts +8 -3
- package/dist/pipeline/executeProviderCall.js +1 -1
- package/dist/pipeline/resolveProviderChain.d.cts +2 -3
- package/dist/pipeline/resolveProviderChain.d.ts +2 -3
- package/dist/pipeline/resolveSubscriptionChain.d.cts +1 -0
- package/dist/pipeline/resolveSubscriptionChain.d.ts +1 -0
- package/dist/pipeline/upstreamFetch.cjs +4 -2
- package/dist/pipeline/upstreamFetch.d.cts +6 -0
- package/dist/pipeline/upstreamFetch.d.ts +6 -0
- package/dist/pipeline/upstreamFetch.js +3 -1
- package/dist/pipeline/upstreamTrace.cjs +14 -0
- package/dist/pipeline/upstreamTrace.d.cts +85 -0
- package/dist/pipeline/upstreamTrace.d.ts +85 -0
- package/dist/pipeline/upstreamTrace.js +14 -0
- package/dist/ports/pricing-store.d.cts +4 -3
- package/dist/ports/pricing-store.d.ts +4 -3
- package/dist/ports.d.cts +4 -3
- package/dist/ports.d.ts +4 -3
- package/dist/provider-proxy/ProviderProxy.cjs +27 -24
- package/dist/provider-proxy/ProviderProxy.d.cts +1 -0
- package/dist/provider-proxy/ProviderProxy.d.ts +1 -0
- package/dist/provider-proxy/ProviderProxy.js +26 -23
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +31 -24
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +50 -5
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +50 -5
- package/dist/provider-proxy/ingress/providerProxyShared.js +30 -23
- package/dist/provider-proxy/matchText.cjs +4 -2
- package/dist/provider-proxy/matchText.d.cts +41 -6
- package/dist/provider-proxy/matchText.d.ts +41 -6
- package/dist/provider-proxy/matchText.js +3 -1
- package/dist/provider-proxy/types.d.cts +30 -2
- package/dist/provider-proxy/types.d.ts +30 -2
- package/dist/provider-proxy.cjs +27 -24
- package/dist/provider-proxy.d.cts +1 -0
- package/dist/provider-proxy.d.ts +1 -0
- package/dist/provider-proxy.js +26 -23
- package/dist/{routeResolver-C4T7yClx.d.ts → routeResolver-CHTv_Bs5.d.ts} +1 -1
- package/dist/{routeResolver-CB3tvJKy.d.cts → routeResolver-kepxnfcC.d.cts} +1 -1
- package/dist/transformer/TransformerChainExecutor.cjs +2 -2
- package/dist/transformer/TransformerChainExecutor.js +1 -1
- package/dist/transformer/transformers/AnthropicTransformer.cjs +2 -2
- package/dist/transformer/transformers/AnthropicTransformer.js +1 -1
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +3 -3
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +2 -2
- package/dist/transformer/transformers/GeminiTransformer.cjs +3 -3
- package/dist/transformer/transformers/GeminiTransformer.js +2 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +2 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +1 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +1 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +1 -1
- package/dist/transformer/transformers/OpenAITransformer.cjs +6 -0
- package/dist/transformer/transformers/OpenAITransformer.d.cts +68 -0
- package/dist/transformer/transformers/OpenAITransformer.d.ts +68 -0
- package/dist/transformer/transformers/OpenAITransformer.js +6 -0
- package/dist/transformer/transformers.cjs +8 -14
- package/dist/transformer/transformers.d.cts +12 -48
- package/dist/transformer/transformers.d.ts +12 -48
- package/dist/transformer/transformers.js +11 -17
- package/dist/transformer/types.d.cts +17 -0
- package/dist/transformer/types.d.ts +17 -0
- package/dist/transformer.cjs +11 -12
- package/dist/transformer.d.cts +1 -2
- package/dist/transformer.d.ts +1 -2
- package/dist/transformer.js +11 -12
- package/dist/{types-B8Arpxn0.d.cts → types-H3wGZSOo.d.cts} +134 -7
- package/dist/{types-lgPYQuZS.d.ts → types-hsoKrVQS.d.ts} +134 -7
- package/dist/usage/pricing-engine.cjs +2 -2
- package/dist/usage/pricing-engine.d.cts +9 -1
- package/dist/usage/pricing-engine.d.ts +9 -1
- package/dist/usage/pricing-engine.js +1 -1
- package/dist/usage.cjs +2 -2
- package/dist/usage.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-C72S6TTV.cjs +0 -49
- package/dist/chunk-DWKQZ2SK.cjs +0 -254
- package/dist/chunk-IAXWICZU.js +0 -69
- package/dist/chunk-QWBWGODS.cjs +0 -69
- package/dist/chunk-RGTR7CIO.cjs +0 -235
- package/dist/chunk-VPBAUQ2M.js +0 -235
- package/dist/chunk-YMFAJFQT.cjs +0 -173
- package/dist/chunk-Z7LUNECE.js +0 -49
- package/dist/chunk-ZJZAOKI7.js +0 -254
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +0 -6
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +0 -51
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +0 -51
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +0 -6
- package/dist/transformer/transformers/ReasoningTransformer.cjs +0 -6
- package/dist/transformer/transformers/ReasoningTransformer.d.cts +0 -47
- package/dist/transformer/transformers/ReasoningTransformer.d.ts +0 -47
- package/dist/transformer/transformers/ReasoningTransformer.js +0 -6
|
@@ -11,11 +11,6 @@ var _chunkXSVNC2UDcjs = require('./chunk-XSVNC2UD.cjs');
|
|
|
11
11
|
|
|
12
12
|
// src/outbound-api/auditCapture.ts
|
|
13
13
|
var _crypto = require('crypto');
|
|
14
|
-
function isStreamingContentType(res) {
|
|
15
|
-
const ct = res.getHeader("content-type");
|
|
16
|
-
const value = Array.isArray(ct) ? ct.join(";") : String(_nullishCoalesce(ct, () => ( "")));
|
|
17
|
-
return value.toLowerCase().includes("text/event-stream");
|
|
18
|
-
}
|
|
19
14
|
function resolveClientIp(req, trustForwardedFor) {
|
|
20
15
|
if (trustForwardedFor) {
|
|
21
16
|
const xff = req.headers["x-forwarded-for"];
|
|
@@ -27,18 +22,11 @@ function resolveClientIp(req, trustForwardedFor) {
|
|
|
27
22
|
}
|
|
28
23
|
return _nullishCoalesce(_optionalChain([req, 'access', _6 => _6.socket, 'optionalAccess', _7 => _7.remoteAddress]), () => ( void 0));
|
|
29
24
|
}
|
|
30
|
-
function truncateToBytes(text, maxBytes) {
|
|
31
|
-
const buf = Buffer.from(text, "utf8");
|
|
32
|
-
if (buf.length <= maxBytes) return text;
|
|
33
|
-
return buf.subarray(0, maxBytes).toString("utf8");
|
|
34
|
-
}
|
|
35
25
|
function beginAuditCapture(req, res, now) {
|
|
36
26
|
const config = _chunkAEOZIDEBcjs.getAuditCaptureConfig.call(void 0, );
|
|
37
27
|
if (!config) return null;
|
|
38
28
|
let rawRequestBody;
|
|
39
29
|
const responseChunks = [];
|
|
40
|
-
let responseBytes = 0;
|
|
41
|
-
let streamDetected = false;
|
|
42
30
|
let finished = false;
|
|
43
31
|
const ctx = {
|
|
44
32
|
setRequestBody(raw) {
|
|
@@ -46,19 +34,9 @@ function beginAuditCapture(req, res, now) {
|
|
|
46
34
|
}
|
|
47
35
|
};
|
|
48
36
|
if (config.captureBodies) {
|
|
49
|
-
installResponseCapture(res,
|
|
37
|
+
installResponseCapture(res, {
|
|
50
38
|
push(chunk) {
|
|
51
|
-
responseBytes += chunk.length;
|
|
52
39
|
responseChunks.push(chunk);
|
|
53
|
-
},
|
|
54
|
-
get bytes() {
|
|
55
|
-
return responseBytes;
|
|
56
|
-
},
|
|
57
|
-
markStream() {
|
|
58
|
-
streamDetected = true;
|
|
59
|
-
},
|
|
60
|
-
get isStream() {
|
|
61
|
-
return streamDetected;
|
|
62
40
|
}
|
|
63
41
|
});
|
|
64
42
|
}
|
|
@@ -92,9 +70,9 @@ function beginAuditCapture(req, res, now) {
|
|
|
92
70
|
if (ctx.error) record.error = _chunkFCR77GYMcjs.redactAuditText.call(void 0, ctx.error);
|
|
93
71
|
if (config.captureBodies) {
|
|
94
72
|
if (rawRequestBody != null && rawRequestBody.length > 0) {
|
|
95
|
-
record.requestBody = _chunkFCR77GYMcjs.redactAuditText.call(void 0,
|
|
73
|
+
record.requestBody = _chunkFCR77GYMcjs.redactAuditText.call(void 0, rawRequestBody);
|
|
96
74
|
}
|
|
97
|
-
if (
|
|
75
|
+
if (responseChunks.length > 0) {
|
|
98
76
|
const body = Buffer.concat(responseChunks).toString("utf8");
|
|
99
77
|
record.responseBody = _chunkFCR77GYMcjs.redactAuditText.call(void 0, body);
|
|
100
78
|
}
|
|
@@ -106,24 +84,13 @@ function beginAuditCapture(req, res, now) {
|
|
|
106
84
|
res.once("close", finalize);
|
|
107
85
|
return ctx;
|
|
108
86
|
}
|
|
109
|
-
function installResponseCapture(res,
|
|
110
|
-
let contentTypeChecked = false;
|
|
87
|
+
function installResponseCapture(res, sink) {
|
|
111
88
|
const capture = (chunk) => {
|
|
112
|
-
if (sink.isStream) return;
|
|
113
|
-
if (!contentTypeChecked) {
|
|
114
|
-
contentTypeChecked = true;
|
|
115
|
-
if (isStreamingContentType(res)) {
|
|
116
|
-
sink.markStream();
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
if (sink.bytes >= maxBodyBytes) return;
|
|
121
89
|
let buf = null;
|
|
122
90
|
if (Buffer.isBuffer(chunk)) buf = chunk;
|
|
123
91
|
else if (typeof chunk === "string") buf = Buffer.from(chunk, "utf8");
|
|
124
92
|
if (!buf || buf.length === 0) return;
|
|
125
|
-
|
|
126
|
-
sink.push(buf.length > remaining ? buf.subarray(0, remaining) : buf);
|
|
93
|
+
sink.push(buf);
|
|
127
94
|
};
|
|
128
95
|
const originalWrite = res.write.bind(res);
|
|
129
96
|
const originalEnd = res.end.bind(res);
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// src/usage/pricing-engine.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_LITELLM_PRICING_URL,
|
|
4
|
+
DEFAULT_OPENROUTER_PRICING_URL
|
|
5
|
+
} from "@omnicross/contracts/pricing-types";
|
|
3
6
|
var PricingEngine = class {
|
|
4
7
|
constructor(store, logger, options = {}) {
|
|
5
8
|
this.store = store;
|
|
@@ -19,6 +22,8 @@ var PricingEngine = class {
|
|
|
19
22
|
*/
|
|
20
23
|
cacheByModel = /* @__PURE__ */ new Map();
|
|
21
24
|
cacheLoaded = false;
|
|
25
|
+
/** Coalesce manual and background refreshes to prevent a catalog stampede. */
|
|
26
|
+
refreshInFlight = null;
|
|
22
27
|
// ===== Cache management =====
|
|
23
28
|
/** Lazy-load and cache the full pricing table. */
|
|
24
29
|
async ensureCache() {
|
|
@@ -27,7 +32,7 @@ var PricingEngine = class {
|
|
|
27
32
|
this.cache = new Map(rows.map((r) => [keyOf(r.providerId, r.modelId), r]));
|
|
28
33
|
this.cacheByModel = /* @__PURE__ */ new Map();
|
|
29
34
|
for (const r of rows) {
|
|
30
|
-
if (r.providerId === "*") continue;
|
|
35
|
+
if (r.providerId === "*" || r.providerId === "openrouter" || r.source === "openrouter") continue;
|
|
31
36
|
if (!this.cacheByModel.has(r.modelId)) this.cacheByModel.set(r.modelId, r);
|
|
32
37
|
}
|
|
33
38
|
this.cacheLoaded = true;
|
|
@@ -90,40 +95,82 @@ var PricingEngine = class {
|
|
|
90
95
|
true
|
|
91
96
|
);
|
|
92
97
|
this.cache.set(keyOf(updated.providerId, updated.modelId), updated);
|
|
93
|
-
if (updated.providerId !== "*"
|
|
98
|
+
if (updated.providerId !== "*" && updated.providerId !== "openrouter" && updated.source !== "openrouter") {
|
|
99
|
+
this.cacheByModel.set(updated.modelId, updated);
|
|
100
|
+
}
|
|
94
101
|
return updated;
|
|
95
102
|
}
|
|
96
103
|
// ===== Source refresh =====
|
|
97
104
|
async fetchLatestFromSource() {
|
|
105
|
+
if (this.refreshInFlight) return this.refreshInFlight;
|
|
106
|
+
const refresh = this.refreshSources();
|
|
107
|
+
this.refreshInFlight = refresh;
|
|
108
|
+
try {
|
|
109
|
+
return await refresh;
|
|
110
|
+
} finally {
|
|
111
|
+
if (this.refreshInFlight === refresh) this.refreshInFlight = null;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
async refreshSources() {
|
|
98
115
|
const sourceUrl = this.options.sourceUrl ?? DEFAULT_LITELLM_PRICING_URL;
|
|
116
|
+
const openRouterSourceUrl = this.options.openRouterSourceUrl ?? DEFAULT_OPENROUTER_PRICING_URL;
|
|
99
117
|
const fetchImpl = this.options.fetchImpl ?? fetch;
|
|
118
|
+
const timeoutMs = this.options.fetchTimeoutMs ?? 1e4;
|
|
100
119
|
const fetchedAt = Date.now();
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
120
|
+
const [litellm, openrouter] = await Promise.all([
|
|
121
|
+
fetchCatalog(fetchImpl, sourceUrl, timeoutMs, parseLitellmJson),
|
|
122
|
+
fetchCatalog(fetchImpl, openRouterSourceUrl, timeoutMs, parseOpenRouterJson)
|
|
123
|
+
]);
|
|
124
|
+
const sources = [
|
|
125
|
+
sourceResult("litellm", sourceUrl, litellm),
|
|
126
|
+
sourceResult("openrouter", openRouterSourceUrl, openrouter)
|
|
127
|
+
];
|
|
128
|
+
for (const source of sources) {
|
|
129
|
+
if (source.status === "failed") {
|
|
130
|
+
this.logger.warn(`[PricingEngine] ${source.source} pricing-source fetch failed`, {
|
|
131
|
+
error: source.error
|
|
132
|
+
});
|
|
106
133
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
134
|
+
}
|
|
135
|
+
if (!litellm.ok && !openrouter.ok) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Pricing-source fetch failed: LiteLLM (${litellm.error}); OpenRouter (${openrouter.error})`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
const applied = [];
|
|
141
|
+
const conflicts = [];
|
|
142
|
+
if (litellm.ok) {
|
|
143
|
+
const result = await this.store.bulkApplyFromSource(litellm.entries, "litellm");
|
|
144
|
+
applied.push(...result.applied);
|
|
145
|
+
conflicts.push(
|
|
146
|
+
...result.conflicts.map((c) => ({
|
|
147
|
+
providerId: c.current.providerId,
|
|
148
|
+
modelId: c.current.modelId,
|
|
149
|
+
current: c.current,
|
|
150
|
+
incoming: c.incoming
|
|
151
|
+
}))
|
|
152
|
+
);
|
|
153
|
+
sources[0].appliedCount = result.applied.length;
|
|
154
|
+
}
|
|
155
|
+
if (openrouter.ok) {
|
|
156
|
+
const current = new Map(
|
|
157
|
+
(await this.store.getAll()).map((row) => [keyOf(row.providerId, row.modelId), row])
|
|
158
|
+
);
|
|
159
|
+
const supplemental = openrouter.entries.filter((incoming) => {
|
|
160
|
+
const row = current.get(keyOf(incoming.providerId, incoming.modelId));
|
|
161
|
+
return !row || row.source === "openrouter" || row.source === "builtin";
|
|
111
162
|
});
|
|
112
|
-
|
|
163
|
+
const result = await this.store.bulkApplyFromSource(supplemental, "openrouter");
|
|
164
|
+
applied.push(...result.applied);
|
|
165
|
+
sources[1].appliedCount = result.applied.length;
|
|
113
166
|
}
|
|
114
|
-
|
|
115
|
-
const { applied, conflicts } = await this.store.bulkApplyFromSource(incoming);
|
|
116
|
-
await this.invalidateCache();
|
|
167
|
+
if (applied.length > 0) await this.invalidateCache();
|
|
117
168
|
return {
|
|
118
169
|
applied,
|
|
119
|
-
conflicts
|
|
120
|
-
providerId: c.current.providerId,
|
|
121
|
-
modelId: c.current.modelId,
|
|
122
|
-
current: c.current,
|
|
123
|
-
incoming: c.incoming
|
|
124
|
-
})),
|
|
170
|
+
conflicts,
|
|
125
171
|
fetchedAt,
|
|
126
|
-
sourceUrl
|
|
172
|
+
sourceUrl,
|
|
173
|
+
sources
|
|
127
174
|
};
|
|
128
175
|
}
|
|
129
176
|
// ===== Conflict resolution =====
|
|
@@ -147,8 +194,12 @@ var PROVIDER_ALIASES = {
|
|
|
147
194
|
azure: "openai"
|
|
148
195
|
};
|
|
149
196
|
var parseLitellmJson = (json) => {
|
|
197
|
+
if (!json || typeof json !== "object" || Array.isArray(json)) {
|
|
198
|
+
throw new Error("invalid LiteLLM catalog shape");
|
|
199
|
+
}
|
|
150
200
|
const out = [];
|
|
151
201
|
for (const [modelId, entry] of Object.entries(json)) {
|
|
202
|
+
if (modelId === "sample_spec") continue;
|
|
152
203
|
if (!entry || typeof entry !== "object") continue;
|
|
153
204
|
if (entry.input_cost_per_token == null || entry.output_cost_per_token == null) {
|
|
154
205
|
continue;
|
|
@@ -167,6 +218,67 @@ var parseLitellmJson = (json) => {
|
|
|
167
218
|
}
|
|
168
219
|
return out;
|
|
169
220
|
};
|
|
221
|
+
var parseOpenRouterJson = (json) => {
|
|
222
|
+
if (!json || !Array.isArray(json.data)) {
|
|
223
|
+
throw new Error("invalid OpenRouter catalog shape");
|
|
224
|
+
}
|
|
225
|
+
const out = [];
|
|
226
|
+
for (const model of json.data) {
|
|
227
|
+
const modelId = typeof model?.id === "string" ? model.id.trim() : "";
|
|
228
|
+
const input = parseUnitPrice(model?.pricing?.prompt);
|
|
229
|
+
const output = parseUnitPrice(model?.pricing?.completion);
|
|
230
|
+
if (!modelId || input === null || output === null) continue;
|
|
231
|
+
const cacheRead = parseUnitPrice(model.pricing?.input_cache_read);
|
|
232
|
+
const cacheWrite = parseUnitPrice(model.pricing?.input_cache_write);
|
|
233
|
+
out.push({
|
|
234
|
+
providerId: "openrouter",
|
|
235
|
+
modelId,
|
|
236
|
+
inputPricePer1m: input * 1e6,
|
|
237
|
+
outputPricePer1m: output * 1e6,
|
|
238
|
+
cacheReadPricePer1m: cacheRead === null ? null : cacheRead * 1e6,
|
|
239
|
+
cacheWritePricePer1m: cacheWrite === null ? null : cacheWrite * 1e6
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
return out;
|
|
243
|
+
};
|
|
244
|
+
function parseUnitPrice(value) {
|
|
245
|
+
if (value === "" || value === null || value === void 0) return null;
|
|
246
|
+
const parsed = typeof value === "number" ? value : Number(value);
|
|
247
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : null;
|
|
248
|
+
}
|
|
249
|
+
async function fetchCatalog(fetchImpl, url, timeoutMs, parse) {
|
|
250
|
+
const controller = new AbortController();
|
|
251
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
252
|
+
timer.unref?.();
|
|
253
|
+
try {
|
|
254
|
+
const response = await fetchImpl(url, { signal: controller.signal });
|
|
255
|
+
if (!response.ok) throw new Error(`status ${response.status}`);
|
|
256
|
+
const entries = parse(await response.json());
|
|
257
|
+
if (entries.length === 0) throw new Error("pricing catalog contained no usable entries");
|
|
258
|
+
return { ok: true, entries };
|
|
259
|
+
} catch (error) {
|
|
260
|
+
const message = controller.signal.aborted ? `timeout after ${timeoutMs}ms` : error instanceof Error ? error.message : String(error);
|
|
261
|
+
return { ok: false, error: message };
|
|
262
|
+
} finally {
|
|
263
|
+
clearTimeout(timer);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function sourceResult(source, sourceUrl, attempt) {
|
|
267
|
+
return attempt.ok ? {
|
|
268
|
+
source,
|
|
269
|
+
sourceUrl,
|
|
270
|
+
status: "applied",
|
|
271
|
+
parsedCount: attempt.entries.length,
|
|
272
|
+
appliedCount: 0
|
|
273
|
+
} : {
|
|
274
|
+
source,
|
|
275
|
+
sourceUrl,
|
|
276
|
+
status: "failed",
|
|
277
|
+
parsedCount: 0,
|
|
278
|
+
appliedCount: 0,
|
|
279
|
+
error: attempt.error
|
|
280
|
+
};
|
|
281
|
+
}
|
|
170
282
|
|
|
171
283
|
export {
|
|
172
284
|
PricingEngine
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
var
|
|
6
|
+
var _chunkKBM2W6UYcjs = require('./chunk-KBM2W6UY.cjs');
|
|
7
7
|
|
|
8
8
|
// src/transformer/transformers/GeminiTransformer.ts
|
|
9
9
|
var GeminiTransformer = (_class = class {
|
|
@@ -44,7 +44,7 @@ var GeminiTransformer = (_class = class {
|
|
|
44
44
|
* Also builds the correct URL for the Gemini API
|
|
45
45
|
*/
|
|
46
46
|
async transformRequestIn(request, provider, _context) {
|
|
47
|
-
const body =
|
|
47
|
+
const body = _chunkKBM2W6UYcjs.buildRequestBody.call(void 0, request);
|
|
48
48
|
const action = request.stream ? "streamGenerateContent?alt=sse" : "generateContent";
|
|
49
49
|
const url = new URL(`./${request.model}:${action}`, provider.baseUrl);
|
|
50
50
|
const headers = {};
|
|
@@ -65,20 +65,20 @@ var GeminiTransformer = (_class = class {
|
|
|
65
65
|
* (For requests coming into the Gemini endpoint)
|
|
66
66
|
*/
|
|
67
67
|
async transformRequestOut(request, _context) {
|
|
68
|
-
return
|
|
68
|
+
return _chunkKBM2W6UYcjs.transformRequestOut.call(void 0, request);
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
71
|
* Transform Gemini response to OpenAI-compatible format
|
|
72
72
|
*/
|
|
73
73
|
async transformResponseOut(response, _context) {
|
|
74
|
-
return
|
|
74
|
+
return _chunkKBM2W6UYcjs.transformResponseOut.call(void 0, response, this.name, this.logger);
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Transform OpenAI-compatible response back to Gemini format
|
|
78
78
|
* (For endpoint mode — returning Gemini-format responses to the client)
|
|
79
79
|
*/
|
|
80
80
|
async transformResponseIn(response, _context) {
|
|
81
|
-
return
|
|
81
|
+
return _chunkKBM2W6UYcjs.transformResponseIn.call(void 0, response, this.logger);
|
|
82
82
|
}
|
|
83
83
|
}, _class.__initStatic(), _class);
|
|
84
84
|
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {
|
|
2
|
+
GeminiTransformer
|
|
3
|
+
} from "./chunk-LVUCAWMK.js";
|
|
4
|
+
import {
|
|
5
|
+
OpenAIResponseTransformer
|
|
6
|
+
} from "./chunk-OEWZUX4G.js";
|
|
7
|
+
import {
|
|
8
|
+
OpenAITransformer
|
|
9
|
+
} from "./chunk-LB6EBS6Y.js";
|
|
10
|
+
import {
|
|
11
|
+
AnthropicTransformer
|
|
12
|
+
} from "./chunk-6CL5HTPQ.js";
|
|
13
|
+
import {
|
|
14
|
+
GeminiCodeAssistTransformer
|
|
15
|
+
} from "./chunk-N5T4M4GU.js";
|
|
16
|
+
|
|
17
|
+
// src/transformer/transformers/index.ts
|
|
18
|
+
var BuiltinTransformers = {
|
|
19
|
+
OpenAITransformer,
|
|
20
|
+
GeminiTransformer,
|
|
21
|
+
GeminiCodeAssistTransformer,
|
|
22
|
+
AnthropicTransformer,
|
|
23
|
+
OpenAIResponseTransformer
|
|
24
|
+
};
|
|
25
|
+
function getBuiltinTransformers() {
|
|
26
|
+
return BuiltinTransformers;
|
|
27
|
+
}
|
|
28
|
+
var BUILTIN_TRANSFORMER_NAMES = [
|
|
29
|
+
"openai",
|
|
30
|
+
"gemini",
|
|
31
|
+
"gemini-code-assist",
|
|
32
|
+
"anthropic",
|
|
33
|
+
"openai-response"
|
|
34
|
+
];
|
|
35
|
+
async function registerBuiltinTransformers(service) {
|
|
36
|
+
await service.initialize(getBuiltinTransformers());
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
BuiltinTransformers,
|
|
41
|
+
getBuiltinTransformers,
|
|
42
|
+
BUILTIN_TRANSFORMER_NAMES,
|
|
43
|
+
registerBuiltinTransformers
|
|
44
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
// src/provider-proxy/matchText.ts
|
|
2
|
+
var MATCH_TEXT_PER_MESSAGE_CAP = 8192;
|
|
3
|
+
var MATCH_TEXT_RECENT_MESSAGES = 6;
|
|
4
|
+
function flattenMatchText(value) {
|
|
5
|
+
if (typeof value === "string") return value;
|
|
6
|
+
if (Array.isArray(value)) {
|
|
7
|
+
const parts = [];
|
|
8
|
+
for (const item of value) {
|
|
9
|
+
const text = flattenMatchText(item);
|
|
10
|
+
if (text) parts.push(text);
|
|
11
|
+
}
|
|
12
|
+
return parts.join("\n");
|
|
13
|
+
}
|
|
14
|
+
if (value && typeof value === "object") {
|
|
15
|
+
const obj = value;
|
|
16
|
+
if (obj.type === "tool_result" && obj.content !== void 0) {
|
|
17
|
+
return flattenMatchText(obj.content);
|
|
18
|
+
}
|
|
19
|
+
if (typeof obj.text === "string") return obj.text;
|
|
20
|
+
}
|
|
21
|
+
return "";
|
|
22
|
+
}
|
|
23
|
+
function collectMatchText(anthropicBody) {
|
|
24
|
+
const messages = Array.isArray(anthropicBody.messages) ? anthropicBody.messages : [];
|
|
25
|
+
const slices = [];
|
|
26
|
+
const sys = flattenMatchText(anthropicBody.system).trim();
|
|
27
|
+
if (sys) slices.push(sys.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
28
|
+
const recent = [];
|
|
29
|
+
for (let i = messages.length - 1; i >= 0 && recent.length < MATCH_TEXT_RECENT_MESSAGES; i--) {
|
|
30
|
+
const message = messages[i];
|
|
31
|
+
if (!message || typeof message !== "object") continue;
|
|
32
|
+
const role = message.role;
|
|
33
|
+
if (role !== "user" && role !== "system") continue;
|
|
34
|
+
const text = flattenMatchText(message.content).trim();
|
|
35
|
+
if (text) recent.push(text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
36
|
+
}
|
|
37
|
+
for (let i = recent.length - 1; i >= 0; i--) slices.push(recent[i]);
|
|
38
|
+
return slices;
|
|
39
|
+
}
|
|
40
|
+
function deriveSubscriptionSessionKey(anthropicBody) {
|
|
41
|
+
const system = flattenMatchText(anthropicBody.system).trim().slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
42
|
+
const messages = Array.isArray(anthropicBody.messages) ? anthropicBody.messages : [];
|
|
43
|
+
let firstUser = "";
|
|
44
|
+
for (const message of messages) {
|
|
45
|
+
if (message && typeof message === "object" && message.role === "user") {
|
|
46
|
+
firstUser = flattenMatchText(message.content).trim().slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const anchor = `${system}\0${firstUser}`;
|
|
51
|
+
if (!system && !firstUser) return void 0;
|
|
52
|
+
return fnv1a(anchor);
|
|
53
|
+
}
|
|
54
|
+
var SESSION_ID_CAP = 1024;
|
|
55
|
+
function deriveGatewaySessionKey(body, headers = {}, options = {}) {
|
|
56
|
+
const sessionHeader = firstHeaderValue(headers, ["session-id", "session_id", "x-session-id"]);
|
|
57
|
+
if (sessionHeader) {
|
|
58
|
+
return { key: fnv1a(sessionHeader), source: "session-header" };
|
|
59
|
+
}
|
|
60
|
+
const threadHeader = firstHeaderValue(headers, ["thread-id", "thread_id", "x-thread-id"]);
|
|
61
|
+
if (threadHeader) {
|
|
62
|
+
return { key: fnv1a(threadHeader), source: "thread-header" };
|
|
63
|
+
}
|
|
64
|
+
const bodySessionId = scalarSessionValue(body.session_id);
|
|
65
|
+
if (bodySessionId) {
|
|
66
|
+
return { key: fnv1a(bodySessionId), source: "body-session-id" };
|
|
67
|
+
}
|
|
68
|
+
const bodyThreadId = scalarSessionValue(body.thread_id ?? body.threadId);
|
|
69
|
+
if (bodyThreadId) {
|
|
70
|
+
return { key: fnv1a(bodyThreadId), source: "body-thread-id" };
|
|
71
|
+
}
|
|
72
|
+
const conversationId = scalarSessionValue(body.conversation_id);
|
|
73
|
+
if (conversationId) {
|
|
74
|
+
return { key: fnv1a(conversationId), source: "body-session-id" };
|
|
75
|
+
}
|
|
76
|
+
const promptCacheKey = scalarSessionValue(body.prompt_cache_key);
|
|
77
|
+
if (promptCacheKey) {
|
|
78
|
+
return { key: fnv1a(promptCacheKey), source: "prompt-cache-key" };
|
|
79
|
+
}
|
|
80
|
+
const fingerprint = deriveResponsesContentFingerprint(body);
|
|
81
|
+
if (fingerprint) {
|
|
82
|
+
return { key: fnv1a(fingerprint), source: "content-fingerprint" };
|
|
83
|
+
}
|
|
84
|
+
const endpoint = options.endpoint?.trim() || "responses";
|
|
85
|
+
const fallback = options.fallbackKey?.trim() || "anonymous";
|
|
86
|
+
return {
|
|
87
|
+
key: fnv1a(`${endpoint}\0${fallback}`),
|
|
88
|
+
source: "api-key-fallback"
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function firstHeaderValue(headers, names) {
|
|
92
|
+
const byLower = /* @__PURE__ */ new Map();
|
|
93
|
+
for (const [name, value] of Object.entries(headers)) {
|
|
94
|
+
if (typeof value === "string" || Array.isArray(value)) {
|
|
95
|
+
const lower = name.toLowerCase();
|
|
96
|
+
if (!byLower.has(lower)) byLower.set(lower, value);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const name of names) {
|
|
100
|
+
const value = byLower.get(name);
|
|
101
|
+
const values = Array.isArray(value) ? value : [value];
|
|
102
|
+
for (const item of values) {
|
|
103
|
+
if (typeof item !== "string") continue;
|
|
104
|
+
const trimmed = item.trim();
|
|
105
|
+
if (trimmed) return trimmed.slice(0, SESSION_ID_CAP);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return void 0;
|
|
109
|
+
}
|
|
110
|
+
function scalarSessionValue(value) {
|
|
111
|
+
if (typeof value !== "string" && typeof value !== "number") return void 0;
|
|
112
|
+
const trimmed = String(value).trim();
|
|
113
|
+
return trimmed ? trimmed.slice(0, SESSION_ID_CAP) : void 0;
|
|
114
|
+
}
|
|
115
|
+
function deriveResponsesContentFingerprint(body) {
|
|
116
|
+
const stable = [];
|
|
117
|
+
const instructions = flattenMatchText(body.instructions).trim();
|
|
118
|
+
if (instructions) stable.push(instructions.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
119
|
+
const input = body.input;
|
|
120
|
+
let firstUser = "";
|
|
121
|
+
const visitItem = (item) => {
|
|
122
|
+
if (typeof item === "string") {
|
|
123
|
+
if (!firstUser) firstUser = item.trim().slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!item || typeof item !== "object") return;
|
|
127
|
+
const record = item;
|
|
128
|
+
const role = typeof record.role === "string" ? record.role.toLowerCase() : "";
|
|
129
|
+
const content = record.content ?? record.text ?? record.input;
|
|
130
|
+
const text = flattenMatchText(content !== void 0 ? content : record).trim();
|
|
131
|
+
if (!text) return;
|
|
132
|
+
if (role === "developer" || role === "system") {
|
|
133
|
+
stable.push(text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
134
|
+
} else if (role === "user" || !role) {
|
|
135
|
+
if (!firstUser) firstUser = text.slice(0, MATCH_TEXT_PER_MESSAGE_CAP);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
if (Array.isArray(input)) {
|
|
139
|
+
for (const item of input) visitItem(item);
|
|
140
|
+
} else if (input !== void 0) {
|
|
141
|
+
visitItem(input);
|
|
142
|
+
}
|
|
143
|
+
if (stable.length === 0) {
|
|
144
|
+
const legacySystem = flattenMatchText(body.system).trim();
|
|
145
|
+
if (legacySystem) stable.push(legacySystem.slice(0, MATCH_TEXT_PER_MESSAGE_CAP));
|
|
146
|
+
}
|
|
147
|
+
if (stable.length === 0 && !firstUser) return void 0;
|
|
148
|
+
return `${stable.join("\0")}\0${firstUser}`;
|
|
149
|
+
}
|
|
150
|
+
function fnv1a(str) {
|
|
151
|
+
let h = 2166136261;
|
|
152
|
+
for (let i = 0; i < str.length; i++) {
|
|
153
|
+
h ^= str.charCodeAt(i);
|
|
154
|
+
h = Math.imul(h, 16777619);
|
|
155
|
+
}
|
|
156
|
+
return (h >>> 0).toString(16).padStart(8, "0");
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export {
|
|
160
|
+
MATCH_TEXT_PER_MESSAGE_CAP,
|
|
161
|
+
MATCH_TEXT_RECENT_MESSAGES,
|
|
162
|
+
flattenMatchText,
|
|
163
|
+
collectMatchText,
|
|
164
|
+
deriveSubscriptionSessionKey,
|
|
165
|
+
deriveGatewaySessionKey
|
|
166
|
+
};
|
|
@@ -134,7 +134,13 @@ var TransformerChainExecutor = class {
|
|
|
134
134
|
const { endpointTransformer } = options;
|
|
135
135
|
const context = {
|
|
136
136
|
logger: this.logger,
|
|
137
|
-
providerName: provider.name
|
|
137
|
+
providerName: provider.name,
|
|
138
|
+
// The unified REQUEST this response answers. Response encoders need it to
|
|
139
|
+
// restore request-scoped protocol state that the chat wire cannot carry —
|
|
140
|
+
// e.g. which codex tools were declared `custom` (see
|
|
141
|
+
// `UnifiedChatRequestMeta.codexTools`), which decides whether a tool call
|
|
142
|
+
// is encoded back as a `function_call` or a `custom_tool_call` item.
|
|
143
|
+
req: request
|
|
138
144
|
};
|
|
139
145
|
let finalResponse = response;
|
|
140
146
|
const bypass = this.shouldBypassTransformers(chain, endpointTransformer, request);
|
|
@@ -59,6 +59,26 @@ function hasThinkingEnabled(request) {
|
|
|
59
59
|
}
|
|
60
60
|
return false;
|
|
61
61
|
}
|
|
62
|
+
function flattenToolResultContent(content) {
|
|
63
|
+
if (typeof content === "string") return content;
|
|
64
|
+
if (content == null) return "";
|
|
65
|
+
if (!Array.isArray(content)) return JSON.stringify(content);
|
|
66
|
+
const parts = [];
|
|
67
|
+
for (const block of content) {
|
|
68
|
+
if (typeof block === "string") {
|
|
69
|
+
parts.push(block);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (!block || typeof block !== "object") continue;
|
|
73
|
+
const typed = block;
|
|
74
|
+
if (typed.type === "image" || typed.type === "image_url") {
|
|
75
|
+
parts.push("[image omitted]");
|
|
76
|
+
} else if (typeof typed.text === "string") {
|
|
77
|
+
parts.push(typed.text);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return parts.filter((text) => text !== "").join("\n");
|
|
81
|
+
}
|
|
62
82
|
|
|
63
83
|
// src/api-converter/anthropic-to-openai.ts
|
|
64
84
|
function convertAnthropicToOpenAI(response) {
|
|
@@ -158,7 +178,7 @@ function convertAnthropicRequestToOpenAI(request) {
|
|
|
158
178
|
});
|
|
159
179
|
} else if (part.type === "tool_result") {
|
|
160
180
|
toolResultId = part.tool_use_id;
|
|
161
|
-
toolResultContent =
|
|
181
|
+
toolResultContent = flattenToolResultContent(part.content);
|
|
162
182
|
}
|
|
163
183
|
}
|
|
164
184
|
if (toolResultId && toolResultContent !== void 0) {
|
|
@@ -368,7 +388,10 @@ function convertOpenAIToAnthropic(request, config) {
|
|
|
368
388
|
const toolResult = {
|
|
369
389
|
type: "tool_result",
|
|
370
390
|
tool_use_id: msg.tool_call_id,
|
|
371
|
-
|
|
391
|
+
// An OpenAI tool message is normally a plain string, but a parts array
|
|
392
|
+
// must be flattened to text rather than stringified into a literal
|
|
393
|
+
// `[{"type":"text",…}]` envelope the model then has to read.
|
|
394
|
+
content: flattenToolResultContent(msg.content)
|
|
372
395
|
};
|
|
373
396
|
const lastMsg = messages[messages.length - 1];
|
|
374
397
|
if (lastMsg && lastMsg.role === "user" && Array.isArray(lastMsg.content)) {
|