@omnicross/core 0.1.0 → 0.1.2
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 +24 -0
- package/dist/ApiConverter.d.cts +82 -0
- package/dist/ApiConverter.d.ts +82 -0
- package/dist/ApiConverter.js +24 -0
- package/dist/BuiltinToolExecutor-BluWyeob.d.ts +81 -0
- package/dist/BuiltinToolExecutor-CS2WpXhM.d.cts +81 -0
- package/dist/CompletionService-D3a47vdU.d.cts +213 -0
- package/dist/CompletionService-DdWaAxq0.d.ts +213 -0
- package/dist/{ProviderProxy-f_8ziIhW.d.cts → ProviderProxy-C-xqrkKi.d.ts} +7 -2
- package/dist/{ProviderProxy-vjt8sQQk.d.ts → ProviderProxy-CnMQYN59.d.cts} +7 -2
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +27 -60
- package/dist/auth/GeminiCodeAssistProjectResolver.js +5 -9
- package/dist/builtin-web-fetch-77JZUZGM.cjs +100 -0
- package/dist/builtin-web-fetch-IGLBX2XY.js +100 -0
- package/dist/chunk-2DCNB7DF.cjs +514 -0
- package/dist/chunk-2FEVTJWG.js +4400 -0
- package/dist/chunk-3MEACFK3.js +193 -0
- package/dist/chunk-3OPYJG76.cjs +266 -0
- package/dist/chunk-46RLHI7D.js +0 -0
- package/dist/chunk-46ZIFWYG.cjs +277 -0
- package/dist/chunk-4NBS6KPV.cjs +43 -0
- package/dist/chunk-4VAVYUZS.cjs +991 -0
- package/dist/chunk-5ERIZW3D.js +44 -0
- package/dist/chunk-5HTVET6E.js +514 -0
- package/dist/chunk-5ZQBEOMD.js +764 -0
- package/dist/chunk-6VIXXLMX.cjs +14 -0
- package/dist/chunk-745DV5FL.js +147 -0
- package/dist/chunk-74TMJA7Z.js +53 -0
- package/dist/chunk-7VU7V2E4.js +0 -0
- package/dist/chunk-7YN22L2H.cjs +87 -0
- package/dist/chunk-A2YMUCBT.cjs +105 -0
- package/dist/chunk-AQ5TNQB7.js +13 -0
- package/dist/chunk-AVYJ4YBN.js +58 -0
- package/dist/chunk-C72S6TTV.cjs +49 -0
- package/dist/chunk-C7PSERMH.js +87 -0
- package/dist/chunk-CDHS2QDC.js +179 -0
- package/dist/chunk-DK4A7DLE.js +266 -0
- package/dist/chunk-DRWHTIIZ.js +13 -0
- package/dist/chunk-DWKQZ2SK.cjs +254 -0
- package/dist/chunk-E3WHL7CO.js +14 -0
- package/dist/chunk-EYZYXJTJ.cjs +1 -0
- package/dist/chunk-FMOBD2OQ.js +39 -0
- package/dist/chunk-FTQ7IQUC.js +991 -0
- package/dist/chunk-FZ46M34D.cjs +1 -0
- package/dist/chunk-G2FUJNA2.cjs +193 -0
- package/dist/chunk-G2UTNSGU.js +277 -0
- package/dist/chunk-GBHYAGX3.cjs +26 -0
- package/dist/chunk-GMMT7RVN.cjs +39 -0
- package/dist/chunk-H5JUT3KV.js +105 -0
- package/dist/chunk-HQP3K7PU.cjs +431 -0
- package/dist/chunk-JO5NLTLY.js +46 -0
- package/dist/chunk-JWC6APFQ.js +23 -0
- package/dist/chunk-KUU2RNG6.cjs +46 -0
- package/dist/chunk-LKZJEL6E.cjs +124 -0
- package/dist/chunk-MNYKI4CI.js +94 -0
- package/dist/chunk-MZNPGW5Q.js +26 -0
- package/dist/chunk-N3V2J5ZO.cjs +53 -0
- package/dist/chunk-NYUNU75A.cjs +44 -0
- package/dist/chunk-NZYN7C3P.cjs +179 -0
- package/dist/chunk-O466Y272.js +235 -0
- package/dist/chunk-PXUJF5HS.js +124 -0
- package/dist/chunk-QBZJ7P2T.cjs +13 -0
- package/dist/chunk-QOCNX236.js +46 -0
- package/dist/chunk-QXD4MV2X.cjs +23 -0
- package/dist/chunk-R3HK6R4B.cjs +13 -0
- package/dist/chunk-RSJ3TWJQ.cjs +186 -0
- package/dist/chunk-S4WBV25P.js +913 -0
- package/dist/chunk-SN3YWBX7.cjs +4400 -0
- package/dist/chunk-SPVWWUHX.cjs +46 -0
- package/dist/chunk-SUEGJ75D.cjs +764 -0
- package/dist/chunk-SVNDB62D.cjs +58 -0
- package/dist/chunk-UYPEN5XE.cjs +235 -0
- package/dist/chunk-V5KPWNYX.cjs +147 -0
- package/dist/chunk-VVEHS2LI.js +431 -0
- package/dist/chunk-XBSYYZIY.cjs +94 -0
- package/dist/chunk-YGXJGRFI.cjs +913 -0
- package/dist/chunk-YMFAJFQT.cjs +173 -0
- package/dist/chunk-Z7LUNECE.js +49 -0
- package/dist/chunk-Z7Y2NPHY.js +186 -0
- package/dist/chunk-ZJZAOKI7.js +254 -0
- package/dist/chunk-ZLX3KGXX.js +173 -0
- package/dist/chunk-ZSVQT3PW.js +43 -0
- package/dist/completion/ApiKeyPoolService.cjs +23 -48
- package/dist/completion/ApiKeyPoolService.d.cts +150 -2
- package/dist/completion/ApiKeyPoolService.d.ts +150 -2
- package/dist/completion/BuiltinToolExecutor.cjs +12 -0
- package/dist/completion/BuiltinToolExecutor.d.cts +4 -0
- package/dist/completion/BuiltinToolExecutor.d.ts +4 -0
- package/dist/completion/BuiltinToolExecutor.js +12 -0
- package/dist/completion/CompletionService.cjs +38 -0
- package/dist/completion/CompletionService.d.cts +22 -0
- package/dist/completion/CompletionService.d.ts +22 -0
- package/dist/completion/CompletionService.js +38 -0
- package/dist/completion/NativeSearchInjector.cjs +12 -0
- package/dist/completion/NativeSearchInjector.d.cts +42 -0
- package/dist/completion/NativeSearchInjector.d.ts +42 -0
- package/dist/completion/NativeSearchInjector.js +12 -0
- package/dist/completion/ProviderSearchInjector.cjs +60 -0
- package/dist/completion/ProviderSearchInjector.d.cts +47 -0
- package/dist/completion/ProviderSearchInjector.d.ts +47 -0
- package/dist/completion/ProviderSearchInjector.js +60 -0
- package/dist/completion/native-search-types.cjs +14 -0
- package/dist/completion/native-search-types.d.cts +3 -0
- package/dist/completion/native-search-types.d.ts +3 -0
- package/dist/completion/native-search-types.js +14 -0
- package/dist/completion/openrouter-headers.cjs +16 -0
- package/dist/completion/openrouter-headers.d.cts +44 -0
- package/dist/completion/openrouter-headers.d.ts +44 -0
- package/dist/completion/openrouter-headers.js +16 -0
- package/dist/completion/openrouter-models.cjs +29 -0
- package/dist/completion/openrouter-models.d.cts +27 -0
- package/dist/completion/openrouter-models.d.ts +27 -0
- package/dist/completion/openrouter-models.js +29 -0
- package/dist/completion/types.cjs +1 -0
- package/dist/completion/types.d.cts +3 -0
- package/dist/completion/types.d.ts +3 -0
- package/dist/completion/types.js +0 -0
- package/dist/completion/url-builder.cjs +24 -0
- package/dist/completion/url-builder.d.cts +87 -0
- package/dist/completion/url-builder.d.ts +87 -0
- package/dist/completion/url-builder.js +24 -0
- package/dist/completion.cjs +55 -3989
- package/dist/completion.d.cts +150 -8
- package/dist/completion.d.ts +150 -8
- package/dist/completion.js +63 -3953
- package/dist/index.cjs +197 -10428
- package/dist/index.d.cts +35 -91
- package/dist/index.d.ts +35 -91
- package/dist/index.js +125 -10267
- package/dist/logger-4GvQNzhE.d.cts +22 -0
- package/dist/logger-4GvQNzhE.d.ts +22 -0
- package/dist/outbound-api/routeResolver.cjs +11 -0
- package/dist/outbound-api/routeResolver.d.cts +19 -0
- package/dist/outbound-api/routeResolver.d.ts +19 -0
- package/dist/outbound-api/routeResolver.js +11 -0
- package/dist/outbound-api/subscriptionRegistryPort.cjs +7 -37
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +7 -3
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +7 -3
- package/dist/outbound-api/subscriptionRegistryPort.js +4 -8
- package/dist/outbound-api/types.cjs +1 -0
- package/dist/{types-CbCN2NQP.d.ts → outbound-api/types.d.cts} +18 -3
- package/dist/{types-CGGrKqC_.d.cts → outbound-api/types.d.ts} +18 -3
- package/dist/outbound-api/types.js +0 -0
- package/dist/outbound-api.cjs +35 -5220
- package/dist/outbound-api.d.cts +16 -88
- package/dist/outbound-api.d.ts +16 -88
- package/dist/outbound-api.js +54 -5193
- package/dist/pipeline/AuthSource.cjs +1 -0
- package/dist/pipeline/AuthSource.d.cts +101 -0
- package/dist/pipeline/AuthSource.d.ts +101 -0
- package/dist/pipeline/AuthSource.js +0 -0
- package/dist/pipeline/LlmConfigProviderAuth.cjs +8 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.cts +87 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.ts +87 -0
- package/dist/pipeline/LlmConfigProviderAuth.js +8 -0
- package/dist/pipeline/SubscriptionAuthSource.cjs +6 -128
- package/dist/pipeline/SubscriptionAuthSource.d.cts +165 -3
- package/dist/pipeline/SubscriptionAuthSource.d.ts +165 -3
- package/dist/pipeline/SubscriptionAuthSource.js +5 -99
- package/dist/pipeline/SubscriptionAuthStrategy.cjs +1 -18
- package/dist/pipeline/executeProviderCall.cjs +6 -0
- package/dist/pipeline/executeProviderCall.d.cts +149 -0
- package/dist/pipeline/executeProviderCall.d.ts +149 -0
- package/dist/pipeline/executeProviderCall.js +6 -0
- package/dist/pipeline/resolveProviderChain.cjs +6 -0
- package/dist/pipeline/resolveProviderChain.d.cts +58 -0
- package/dist/pipeline/resolveProviderChain.d.ts +58 -0
- package/dist/pipeline/resolveProviderChain.js +6 -0
- package/dist/pipeline/resolveSubscriptionChain.cjs +6 -0
- package/dist/pipeline/resolveSubscriptionChain.d.cts +68 -0
- package/dist/pipeline/resolveSubscriptionChain.d.ts +68 -0
- package/dist/pipeline/resolveSubscriptionChain.js +6 -0
- package/dist/ports/gemini-code-assist-resolver.cjs +7 -37
- package/dist/ports/gemini-code-assist-resolver.js +4 -8
- package/dist/ports/pricing-store.cjs +1 -0
- package/dist/ports/pricing-store.d.cts +50 -0
- package/dist/ports/pricing-store.d.ts +50 -0
- package/dist/ports/pricing-store.js +0 -0
- package/dist/ports/provider-config-source.cjs +1 -0
- package/dist/ports/provider-config-source.d.cts +51 -0
- package/dist/ports/provider-config-source.d.ts +51 -0
- package/dist/ports/provider-config-source.js +0 -0
- package/dist/ports/usage-event-store.cjs +1 -0
- package/dist/ports/usage-event-store.d.cts +29 -0
- package/dist/ports/usage-event-store.d.ts +29 -0
- package/dist/ports/usage-event-store.js +0 -0
- package/dist/ports/web-search-backend.cjs +1 -0
- package/dist/ports/web-search-backend.d.cts +29 -0
- package/dist/ports/web-search-backend.d.ts +29 -0
- package/dist/ports/web-search-backend.js +0 -0
- package/dist/ports.cjs +1 -18
- package/dist/ports.d.cts +16 -8
- package/dist/ports.d.ts +16 -8
- package/dist/provider-proxy/ProviderProxy.cjs +40 -0
- package/dist/provider-proxy/ProviderProxy.d.cts +17 -0
- package/dist/provider-proxy/ProviderProxy.d.ts +17 -0
- package/dist/provider-proxy/ProviderProxy.js +40 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +35 -2941
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +7 -3
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +7 -3
- package/dist/provider-proxy/ingress/providerProxyShared.js +41 -2914
- package/dist/provider-proxy/matchText.cjs +11 -72
- package/dist/provider-proxy/matchText.js +6 -39
- package/dist/provider-proxy/types.cjs +1 -18
- package/dist/provider-proxy/types.d.cts +407 -8
- package/dist/provider-proxy/types.d.ts +407 -8
- package/dist/provider-proxy.cjs +35 -4654
- package/dist/provider-proxy.d.cts +10 -6
- package/dist/provider-proxy.d.ts +10 -6
- package/dist/provider-proxy.js +39 -4627
- package/dist/routeResolver-BrbK6ja9.d.cts +88 -0
- package/dist/routeResolver-HE-ZO0fO.d.ts +88 -0
- package/dist/serializeError.cjs +5 -81
- package/dist/serializeError.js +3 -54
- package/dist/sse-parser.cjs +7 -455
- package/dist/sse-parser.js +4 -426
- package/dist/transformer/TransformerChainExecutor.cjs +4 -318
- package/dist/transformer/TransformerChainExecutor.js +4 -291
- package/dist/transformer/TransformerService.cjs +5 -289
- package/dist/transformer/TransformerService.js +3 -262
- package/dist/transformer/anthropicBetaInject.cjs +8 -0
- package/dist/transformer/anthropicBetaInject.d.cts +20 -0
- package/dist/transformer/anthropicBetaInject.d.ts +20 -0
- package/dist/transformer/anthropicBetaInject.js +8 -0
- package/dist/transformer/transformers/AnthropicTransformer.cjs +6 -0
- package/dist/transformer/transformers/AnthropicTransformer.d.cts +148 -0
- package/dist/transformer/transformers/AnthropicTransformer.d.ts +148 -0
- package/dist/transformer/transformers/AnthropicTransformer.js +6 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +4 -1106
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +7 -1079
- package/dist/transformer/transformers/GeminiTransformer.cjs +4 -1010
- package/dist/transformer/transformers/GeminiTransformer.js +4 -983
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -535
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +3 -510
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +5 -72
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +3 -45
- package/dist/transformer/transformers/ReasoningTransformer.cjs +6 -0
- package/dist/transformer/transformers/ReasoningTransformer.d.cts +47 -0
- package/dist/transformer/transformers/ReasoningTransformer.d.ts +47 -0
- package/dist/transformer/transformers/ReasoningTransformer.js +6 -0
- package/dist/transformer/transformers.cjs +39 -0
- package/dist/transformer/transformers.d.cts +100 -0
- package/dist/transformer/transformers.d.ts +100 -0
- package/dist/transformer/transformers.js +39 -0
- package/dist/transformer/types.cjs +1 -18
- package/dist/transformer.cjs +13 -3726
- package/dist/transformer.d.cts +8 -31
- package/dist/transformer.d.ts +8 -31
- package/dist/transformer.js +17 -3706
- package/dist/types-BScIHmPr.d.cts +153 -0
- package/dist/types-BScIHmPr.d.ts +153 -0
- package/dist/usage/pricing-engine.cjs +6 -0
- package/dist/usage/pricing-engine.d.cts +83 -0
- package/dist/usage/pricing-engine.d.ts +83 -0
- package/dist/usage/pricing-engine.js +6 -0
- package/dist/usage/usage-recorder.cjs +6 -0
- package/dist/usage/usage-recorder.d.cts +73 -0
- package/dist/usage/usage-recorder.d.ts +73 -0
- package/dist/usage/usage-recorder.js +6 -0
- package/dist/usage.cjs +11 -0
- package/dist/usage.d.cts +8 -0
- package/dist/usage.d.ts +8 -0
- package/dist/usage.js +11 -0
- package/package.json +3 -3
- package/dist/ApiKeyPoolService-BmMkau07.d.cts +0 -170
- package/dist/ApiKeyPoolService-BmMkau07.d.ts +0 -170
- package/dist/SubscriptionAuthSource-Cr4fVEYY.d.cts +0 -264
- package/dist/SubscriptionAuthSource-D89zmiSS.d.ts +0 -264
- package/dist/index-BTSmc9Sm.d.ts +0 -645
- package/dist/index-DXazdTzZ.d.cts +0 -645
- package/dist/types-DCzHkhJt.d.ts +0 -467
- package/dist/types-DZIQbgp0.d.cts +0 -467
|
@@ -0,0 +1,4400 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5;
|
|
2
|
+
|
|
3
|
+
var _chunkSPVWWUHXcjs = require('./chunk-SPVWWUHX.cjs');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
var _chunkHQP3K7PUcjs = require('./chunk-HQP3K7PU.cjs');
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
var _chunk46ZIFWYGcjs = require('./chunk-46ZIFWYG.cjs');
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
var _chunkGBHYAGX3cjs = require('./chunk-GBHYAGX3.cjs');
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
var _chunk7YN22L2Hcjs = require('./chunk-7YN22L2H.cjs');
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
var _chunk2DCNB7DFcjs = require('./chunk-2DCNB7DF.cjs');
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
var _chunk4VAVYUZScjs = require('./chunk-4VAVYUZS.cjs');
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
var _chunkR3HK6R4Bcjs = require('./chunk-R3HK6R4B.cjs');
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
var _chunkKUU2RNG6cjs = require('./chunk-KUU2RNG6.cjs');
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
var _chunkLKZJEL6Ecjs = require('./chunk-LKZJEL6E.cjs');
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
var _chunkQXD4MV2Xcjs = require('./chunk-QXD4MV2X.cjs');
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
var _chunkNYUNU75Acjs = require('./chunk-NYUNU75A.cjs');
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
var _chunkN3V2J5ZOcjs = require('./chunk-N3V2J5ZO.cjs');
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
var _chunkSVNDB62Dcjs = require('./chunk-SVNDB62D.cjs');
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
var _chunkA2YMUCBTcjs = require('./chunk-A2YMUCBT.cjs');
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
var _chunkG2FUJNA2cjs = require('./chunk-G2FUJNA2.cjs');
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
var _chunkSUEGJ75Dcjs = require('./chunk-SUEGJ75D.cjs');
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
var _chunkV5KPWNYXcjs = require('./chunk-V5KPWNYX.cjs');
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
var _chunk6VIXXLMXcjs = require('./chunk-6VIXXLMX.cjs');
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
var _chunkGMMT7RVNcjs = require('./chunk-GMMT7RVN.cjs');
|
|
66
|
+
|
|
67
|
+
// src/completion/StreamEventBuffer.ts
|
|
68
|
+
var TERMINAL_TYPES = /* @__PURE__ */ new Set(["done", "error", "abort"]);
|
|
69
|
+
var QUEUE_CAP = 200;
|
|
70
|
+
var entries = /* @__PURE__ */ new Map();
|
|
71
|
+
function channelOf(streamId) {
|
|
72
|
+
return `completion:stream:${streamId}`;
|
|
73
|
+
}
|
|
74
|
+
function safeIsDestroyed(sender) {
|
|
75
|
+
try {
|
|
76
|
+
return sender.isDestroyed();
|
|
77
|
+
} catch (e2) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function safeSend(sender, channel, payload) {
|
|
82
|
+
if (safeIsDestroyed(sender)) return false;
|
|
83
|
+
try {
|
|
84
|
+
sender.send(channel, payload);
|
|
85
|
+
return true;
|
|
86
|
+
} catch (e3) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function register(streamId, sender) {
|
|
91
|
+
entries.set(streamId, { sender, queue: [], attached: false, closed: false });
|
|
92
|
+
}
|
|
93
|
+
function emit(streamId, event) {
|
|
94
|
+
const entry = entries.get(streamId);
|
|
95
|
+
if (!entry) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (safeIsDestroyed(entry.sender)) {
|
|
99
|
+
entries.delete(streamId);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (entry.attached) {
|
|
103
|
+
safeSend(entry.sender, channelOf(streamId), event);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (entry.queue.length >= QUEUE_CAP) {
|
|
107
|
+
const evictAt = entry.queue.findIndex((e) => !TERMINAL_TYPES.has(e.type));
|
|
108
|
+
if (evictAt >= 0) {
|
|
109
|
+
entry.queue.splice(evictAt, 1);
|
|
110
|
+
} else {
|
|
111
|
+
entry.queue.shift();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
entry.queue.push(event);
|
|
115
|
+
}
|
|
116
|
+
function attach(streamId) {
|
|
117
|
+
const entry = entries.get(streamId);
|
|
118
|
+
if (!entry) return { ok: false, drained: 0 };
|
|
119
|
+
if (entry.attached) return { ok: true, drained: 0 };
|
|
120
|
+
const channel = channelOf(streamId);
|
|
121
|
+
let drained = 0;
|
|
122
|
+
for (const event of entry.queue) {
|
|
123
|
+
if (safeSend(entry.sender, channel, event)) {
|
|
124
|
+
drained++;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
entry.queue = [];
|
|
128
|
+
entry.attached = true;
|
|
129
|
+
return { ok: true, drained };
|
|
130
|
+
}
|
|
131
|
+
function release(streamId) {
|
|
132
|
+
const entry = entries.get(streamId);
|
|
133
|
+
if (!entry) return;
|
|
134
|
+
if (entry.closed) return;
|
|
135
|
+
entry.closed = true;
|
|
136
|
+
if (entry.attached) {
|
|
137
|
+
entries.delete(streamId);
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
setImmediate(() => {
|
|
141
|
+
entries.delete(streamId);
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/completion/message-converter.ts
|
|
146
|
+
var OPENROUTER_AUDIO_FORMAT_MAP = {
|
|
147
|
+
"audio/wav": "wav",
|
|
148
|
+
"audio/x-wav": "wav",
|
|
149
|
+
"audio/wave": "wav",
|
|
150
|
+
"audio/mpeg": "mp3",
|
|
151
|
+
"audio/mp3": "mp3",
|
|
152
|
+
"audio/aiff": "aiff",
|
|
153
|
+
"audio/x-aiff": "aiff",
|
|
154
|
+
"audio/aac": "aac",
|
|
155
|
+
"audio/ogg": "ogg",
|
|
156
|
+
"audio/flac": "flac",
|
|
157
|
+
"audio/x-flac": "flac",
|
|
158
|
+
"audio/mp4": "m4a",
|
|
159
|
+
"audio/m4a": "m4a",
|
|
160
|
+
"audio/x-m4a": "m4a",
|
|
161
|
+
"audio/L16": "pcm16",
|
|
162
|
+
"audio/L24": "pcm24"
|
|
163
|
+
};
|
|
164
|
+
var MAX_INLINE_VIDEO_BYTES = 25 * 1024 * 1024;
|
|
165
|
+
function decodeDataUrl(url) {
|
|
166
|
+
const match = url.match(/^data:([^;]+);base64,(.+)$/);
|
|
167
|
+
if (!match) return null;
|
|
168
|
+
return { mimeType: match[1], data: match[2] };
|
|
169
|
+
}
|
|
170
|
+
function looksLikeRemoteUrl(url) {
|
|
171
|
+
return /^https?:\/\//i.test(url);
|
|
172
|
+
}
|
|
173
|
+
function resolveAudioFormat(mimeType, sourceUrl) {
|
|
174
|
+
const candidate = (mimeType || "").toLowerCase();
|
|
175
|
+
const mapped = OPENROUTER_AUDIO_FORMAT_MAP[candidate];
|
|
176
|
+
if (mapped) return mapped;
|
|
177
|
+
const decoded = decodeDataUrl(sourceUrl);
|
|
178
|
+
if (decoded) {
|
|
179
|
+
const mappedFromUrl = OPENROUTER_AUDIO_FORMAT_MAP[decoded.mimeType.toLowerCase()];
|
|
180
|
+
if (mappedFromUrl) return mappedFromUrl;
|
|
181
|
+
}
|
|
182
|
+
throw new Error(
|
|
183
|
+
`Unsupported audio format for OpenAI/OpenRouter chat input: ${mimeType || "unknown"}. Supported: wav, mp3, aiff, aac, ogg, flac, m4a, pcm16, pcm24.`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
function convertMessageToOpenAI(msg) {
|
|
187
|
+
const role = msg.role;
|
|
188
|
+
const hasImages = msg.images && msg.images.length > 0;
|
|
189
|
+
const hasAudios = msg.audios && msg.audios.length > 0;
|
|
190
|
+
const hasVideos = msg.videos && msg.videos.length > 0;
|
|
191
|
+
if (!hasImages && !hasAudios && !hasVideos) {
|
|
192
|
+
return { role, content: msg.content };
|
|
193
|
+
}
|
|
194
|
+
const content = [];
|
|
195
|
+
if (msg.content) {
|
|
196
|
+
content.push({ type: "text", text: msg.content });
|
|
197
|
+
}
|
|
198
|
+
if (msg.images) {
|
|
199
|
+
for (const img of msg.images) {
|
|
200
|
+
content.push({
|
|
201
|
+
type: "image_url",
|
|
202
|
+
image_url: { url: img.url }
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
if (msg.audios) {
|
|
207
|
+
for (const audio of msg.audios) {
|
|
208
|
+
const decoded = decodeDataUrl(audio.url);
|
|
209
|
+
if (decoded) {
|
|
210
|
+
const format = resolveAudioFormat(decoded.mimeType, audio.url);
|
|
211
|
+
content.push({ type: "input_audio", input_audio: { data: decoded.data, format } });
|
|
212
|
+
} else if (audio.mimeType) {
|
|
213
|
+
content.push({ type: "audio_url", audio_url: { url: audio.url, format: audio.mimeType } });
|
|
214
|
+
} else {
|
|
215
|
+
content.push({ type: "audio_url", audio_url: { url: audio.url } });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (msg.videos) {
|
|
220
|
+
for (const video of msg.videos) {
|
|
221
|
+
if (looksLikeRemoteUrl(video.url)) {
|
|
222
|
+
content.push({ type: "video_url", video_url: { url: video.url } });
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
const decoded = decodeDataUrl(video.url);
|
|
226
|
+
if (decoded) {
|
|
227
|
+
const sizeBytes = Math.floor(decoded.data.length * 3 / 4);
|
|
228
|
+
if (sizeBytes > MAX_INLINE_VIDEO_BYTES) {
|
|
229
|
+
const sizeMb = (sizeBytes / 1024 / 1024).toFixed(1);
|
|
230
|
+
throw new Error(
|
|
231
|
+
`Video too large for inline upload: ${sizeMb} MB exceeds the 25 MB cap. Use a publicly accessible HTTPS URL instead.`
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
content.push({ type: "video_url", video_url: { url: video.url } });
|
|
235
|
+
} else {
|
|
236
|
+
content.push({ type: "video_url", video_url: { url: video.url } });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return { role, content };
|
|
241
|
+
}
|
|
242
|
+
function convertMessageToAnthropic(msg) {
|
|
243
|
+
const role = msg.role === "system" ? "user" : msg.role;
|
|
244
|
+
const hasImages = msg.images && msg.images.length > 0;
|
|
245
|
+
const hasAudios = msg.audios && msg.audios.length > 0;
|
|
246
|
+
if (!hasImages && !hasAudios) {
|
|
247
|
+
return { role, content: msg.content };
|
|
248
|
+
}
|
|
249
|
+
const content = [];
|
|
250
|
+
if (msg.content) {
|
|
251
|
+
content.push({ type: "text", text: msg.content });
|
|
252
|
+
}
|
|
253
|
+
if (msg.images) {
|
|
254
|
+
for (const img of msg.images) {
|
|
255
|
+
let base64Data = img.url;
|
|
256
|
+
let mediaType = img.mimeType || "image/jpeg";
|
|
257
|
+
if (img.url.startsWith("data:")) {
|
|
258
|
+
const match = img.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
259
|
+
if (match) {
|
|
260
|
+
mediaType = match[1];
|
|
261
|
+
base64Data = match[2];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
content.push({
|
|
265
|
+
type: "image",
|
|
266
|
+
source: { type: "base64", media_type: mediaType, data: base64Data }
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (msg.audios) {
|
|
271
|
+
for (const audio of msg.audios) {
|
|
272
|
+
let base64Data = audio.url;
|
|
273
|
+
let mediaType = audio.mimeType || "audio/wav";
|
|
274
|
+
if (audio.url.startsWith("data:")) {
|
|
275
|
+
const match = audio.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
276
|
+
if (match) {
|
|
277
|
+
mediaType = match[1];
|
|
278
|
+
base64Data = match[2];
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
content.push({
|
|
282
|
+
type: "audio",
|
|
283
|
+
source: { type: "base64", media_type: mediaType, data: base64Data }
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { role, content };
|
|
288
|
+
}
|
|
289
|
+
function convertMessageToGemini(msg) {
|
|
290
|
+
const role = msg.role === "assistant" ? "model" : msg.role;
|
|
291
|
+
const parts = [];
|
|
292
|
+
if (msg.content) {
|
|
293
|
+
parts.push({ text: msg.content });
|
|
294
|
+
}
|
|
295
|
+
if (msg.images && msg.images.length > 0) {
|
|
296
|
+
for (const img of msg.images) {
|
|
297
|
+
let base64Data = img.url;
|
|
298
|
+
let mimeType = img.mimeType || "image/jpeg";
|
|
299
|
+
if (img.url.startsWith("data:")) {
|
|
300
|
+
const match = img.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
301
|
+
if (match) {
|
|
302
|
+
mimeType = match[1];
|
|
303
|
+
base64Data = match[2];
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
parts.push({ inlineData: { mimeType, data: base64Data } });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (msg.audios && msg.audios.length > 0) {
|
|
310
|
+
for (const audio of msg.audios) {
|
|
311
|
+
let base64Data = audio.url;
|
|
312
|
+
let mimeType = audio.mimeType || "audio/wav";
|
|
313
|
+
if (audio.url.startsWith("data:")) {
|
|
314
|
+
const match = audio.url.match(/^data:([^;]+);base64,(.+)$/);
|
|
315
|
+
if (match) {
|
|
316
|
+
mimeType = match[1];
|
|
317
|
+
base64Data = match[2];
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
parts.push({ inlineData: { mimeType, data: base64Data } });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return { role, parts };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/completion/openrouter-utils.ts
|
|
327
|
+
function getOpenRouterProviderConfig(provider, modelId) {
|
|
328
|
+
if (!_chunk6VIXXLMXcjs.isOpenRouterProvider.call(void 0, provider)) {
|
|
329
|
+
return void 0;
|
|
330
|
+
}
|
|
331
|
+
const modelConfig = _optionalChain([provider, 'access', _ => _.modelConfigs, 'optionalAccess', _2 => _2.find, 'call', _3 => _3((m) => m.id === modelId)]);
|
|
332
|
+
return _optionalChain([modelConfig, 'optionalAccess', _4 => _4.openRouterProvider]);
|
|
333
|
+
}
|
|
334
|
+
function addOpenRouterProviderToRequest(requestBody, provider, modelId) {
|
|
335
|
+
const providerRouting = getOpenRouterProviderConfig(provider, modelId);
|
|
336
|
+
if (!providerRouting) {
|
|
337
|
+
return requestBody;
|
|
338
|
+
}
|
|
339
|
+
const hasConfig = Object.values(providerRouting).some(
|
|
340
|
+
(v) => v !== void 0 && v !== null && (Array.isArray(v) ? v.length > 0 : true)
|
|
341
|
+
);
|
|
342
|
+
if (!hasConfig) {
|
|
343
|
+
return requestBody;
|
|
344
|
+
}
|
|
345
|
+
return {
|
|
346
|
+
...requestBody,
|
|
347
|
+
provider: providerRouting
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/completion/DirectApiHandler.ts
|
|
352
|
+
var _thinkingconfig = require('@omnicross/contracts/thinking-config');
|
|
353
|
+
|
|
354
|
+
// src/provider-proxy/ProviderProxy.ts
|
|
355
|
+
var _http = require('http'); var _http2 = _interopRequireDefault(_http);
|
|
356
|
+
|
|
357
|
+
// src/provider-proxy/providerProxyRouteMap.ts
|
|
358
|
+
var _crypto = require('crypto');
|
|
359
|
+
var DEFAULT_ROUTE_IDLE_MS = 10 * 60 * 1e3;
|
|
360
|
+
var TOKEN_BYTES = 32;
|
|
361
|
+
var ProviderProxyRouteMap = (_class = class {
|
|
362
|
+
constructor(defaultIdleMs = DEFAULT_ROUTE_IDLE_MS) {;_class.prototype.__init.call(this);
|
|
363
|
+
this.defaultIdleMs = defaultIdleMs;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
__init() {this.routes = /* @__PURE__ */ new Map()}
|
|
367
|
+
/**
|
|
368
|
+
* Register a route for one run and return its crypto-random token. The
|
|
369
|
+
* caller (next batch) injects the token as the forwarded auth-header sentinel
|
|
370
|
+
* (`ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY`). Optionally override the idle
|
|
371
|
+
* timeout (tests use a short one).
|
|
372
|
+
*/
|
|
373
|
+
addRoute(context, idleMs) {
|
|
374
|
+
const token = _crypto.randomBytes.call(void 0, TOKEN_BYTES).toString("hex");
|
|
375
|
+
const entry = {
|
|
376
|
+
context,
|
|
377
|
+
idleTimer: null,
|
|
378
|
+
idleMs: _nullishCoalesce(idleMs, () => ( this.defaultIdleMs))
|
|
379
|
+
};
|
|
380
|
+
this.routes.set(token, entry);
|
|
381
|
+
this.armIdleTimer(token, entry);
|
|
382
|
+
return token;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Look up a route by its token, touching the idle timer so an active run's
|
|
386
|
+
* context survives. Returns `undefined` on a miss / reaped entry — the caller
|
|
387
|
+
* rejects (no fallback).
|
|
388
|
+
*/
|
|
389
|
+
lookup(token) {
|
|
390
|
+
if (!token) return void 0;
|
|
391
|
+
const entry = this.routes.get(token);
|
|
392
|
+
if (!entry) return void 0;
|
|
393
|
+
this.armIdleTimer(token, entry);
|
|
394
|
+
return entry.context;
|
|
395
|
+
}
|
|
396
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
397
|
+
removeRoute(token) {
|
|
398
|
+
const entry = this.routes.get(token);
|
|
399
|
+
if (!entry) return false;
|
|
400
|
+
this.clearIdleTimer(entry);
|
|
401
|
+
this.routes.delete(token);
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
/** Current live-route count (tests / diagnostics). */
|
|
405
|
+
size() {
|
|
406
|
+
return this.routes.size;
|
|
407
|
+
}
|
|
408
|
+
/** Whether a token currently resolves (does NOT touch the idle timer). */
|
|
409
|
+
has(token) {
|
|
410
|
+
return this.routes.has(token);
|
|
411
|
+
}
|
|
412
|
+
/** Tear down every route (proxy stop / app teardown). */
|
|
413
|
+
clear() {
|
|
414
|
+
for (const entry of this.routes.values()) {
|
|
415
|
+
this.clearIdleTimer(entry);
|
|
416
|
+
}
|
|
417
|
+
this.routes.clear();
|
|
418
|
+
}
|
|
419
|
+
// ---------------------------------------------------------------------------
|
|
420
|
+
// Idle reaping (modeled on AcpSessionManager.armIdleTimer / clearIdleTimer)
|
|
421
|
+
// ---------------------------------------------------------------------------
|
|
422
|
+
armIdleTimer(token, entry) {
|
|
423
|
+
this.clearIdleTimer(entry);
|
|
424
|
+
entry.idleTimer = setTimeout(() => {
|
|
425
|
+
this.routes.delete(token);
|
|
426
|
+
}, entry.idleMs);
|
|
427
|
+
_optionalChain([entry, 'access', _5 => _5.idleTimer, 'access', _6 => _6.unref, 'optionalCall', _7 => _7()]);
|
|
428
|
+
}
|
|
429
|
+
clearIdleTimer(entry) {
|
|
430
|
+
if (entry.idleTimer) {
|
|
431
|
+
clearTimeout(entry.idleTimer);
|
|
432
|
+
entry.idleTimer = null;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}, _class);
|
|
436
|
+
|
|
437
|
+
// src/provider-proxy/usage/recordAnthropicUsage.ts
|
|
438
|
+
function readAnthropicUsage(usage) {
|
|
439
|
+
if (!usage) return null;
|
|
440
|
+
const inputTokens = Number(usage.input_tokens) || 0;
|
|
441
|
+
const outputTokens = Number(usage.output_tokens) || 0;
|
|
442
|
+
const cacheReadTokens = Number(usage.cache_read_input_tokens) || 0;
|
|
443
|
+
const cacheCreationTokens = Number(usage.cache_creation_input_tokens) || 0;
|
|
444
|
+
return {
|
|
445
|
+
inputTokens,
|
|
446
|
+
outputTokens,
|
|
447
|
+
cacheReadTokens,
|
|
448
|
+
cacheCreationTokens,
|
|
449
|
+
// The Anthropic wire carries no separate reasoning-token field; thinking
|
|
450
|
+
// tokens are folded into `output_tokens`.
|
|
451
|
+
reasoningTokens: 0
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function recordAnthropicNonStreamUsage(recorder, bodyText, attribution) {
|
|
455
|
+
try {
|
|
456
|
+
const parsed = JSON.parse(bodyText);
|
|
457
|
+
const tapped = readAnthropicUsage(parsed.usage);
|
|
458
|
+
if (!tapped) return;
|
|
459
|
+
recorder.record({
|
|
460
|
+
messageId: null,
|
|
461
|
+
parentMessageId: null,
|
|
462
|
+
sessionId: attribution.sessionId,
|
|
463
|
+
providerId: attribution.providerId,
|
|
464
|
+
model: attribution.model,
|
|
465
|
+
apiKeyId: null,
|
|
466
|
+
engineOrigin: "codex-ingress",
|
|
467
|
+
usage: tapped,
|
|
468
|
+
rawUsage: parsed.usage
|
|
469
|
+
});
|
|
470
|
+
} catch (e4) {
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// src/provider-proxy/ingress/providerProxyShared.ts
|
|
475
|
+
var sharedExecutor = null;
|
|
476
|
+
function getSharedExecutor() {
|
|
477
|
+
if (!sharedExecutor) {
|
|
478
|
+
sharedExecutor = new (0, _chunk46ZIFWYGcjs.TransformerChainExecutor)();
|
|
479
|
+
}
|
|
480
|
+
return sharedExecutor;
|
|
481
|
+
}
|
|
482
|
+
var sharedResponses = null;
|
|
483
|
+
function getResponsesEndpointTransformer() {
|
|
484
|
+
if (!sharedResponses) sharedResponses = new (0, _chunk2DCNB7DFcjs.OpenAIResponseTransformer)();
|
|
485
|
+
return sharedResponses;
|
|
486
|
+
}
|
|
487
|
+
var sharedAnthropic = null;
|
|
488
|
+
function getAnthropicEndpointTransformer() {
|
|
489
|
+
if (!sharedAnthropic) sharedAnthropic = new (0, _chunk4VAVYUZScjs.AnthropicTransformer)();
|
|
490
|
+
return sharedAnthropic;
|
|
491
|
+
}
|
|
492
|
+
var sharedGemini = null;
|
|
493
|
+
function getGeminiEndpointTransformer() {
|
|
494
|
+
if (!sharedGemini) sharedGemini = new (0, _chunk7YN22L2Hcjs.GeminiTransformer)();
|
|
495
|
+
return sharedGemini;
|
|
496
|
+
}
|
|
497
|
+
function readBody(req) {
|
|
498
|
+
return new Promise((resolve, reject) => {
|
|
499
|
+
const chunks = [];
|
|
500
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
501
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
|
502
|
+
req.on("error", reject);
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
async function relayResponse(res, providerResponse, isStream) {
|
|
506
|
+
const contentType = _nullishCoalesce(providerResponse.headers.get("Content-Type"), () => ( ""));
|
|
507
|
+
const status = providerResponse.status && providerResponse.status >= 100 ? providerResponse.status : 200;
|
|
508
|
+
if (isStream || contentType.includes("text/event-stream")) {
|
|
509
|
+
res.writeHead(status, {
|
|
510
|
+
"Content-Type": "text/event-stream",
|
|
511
|
+
"Cache-Control": "no-cache",
|
|
512
|
+
Connection: "keep-alive"
|
|
513
|
+
});
|
|
514
|
+
if (!providerResponse.body) {
|
|
515
|
+
res.end();
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
const reader = providerResponse.body.getReader();
|
|
519
|
+
try {
|
|
520
|
+
for (; ; ) {
|
|
521
|
+
const { done, value } = await reader.read();
|
|
522
|
+
if (done) break;
|
|
523
|
+
res.write(value);
|
|
524
|
+
}
|
|
525
|
+
} finally {
|
|
526
|
+
reader.releaseLock();
|
|
527
|
+
res.end();
|
|
528
|
+
}
|
|
529
|
+
return null;
|
|
530
|
+
}
|
|
531
|
+
const bodyText = await providerResponse.text();
|
|
532
|
+
res.writeHead(status, { "Content-Type": contentType.includes("json") ? contentType : "application/json" });
|
|
533
|
+
res.end(bodyText);
|
|
534
|
+
return bodyText;
|
|
535
|
+
}
|
|
536
|
+
function writeError(res, status, message) {
|
|
537
|
+
if (res.headersSent) return;
|
|
538
|
+
res.writeHead(status, { "Content-Type": "application/json" });
|
|
539
|
+
res.end(JSON.stringify({ error: { type: "provider_proxy_error", message } }));
|
|
540
|
+
}
|
|
541
|
+
function resolveApiKey(apiKey) {
|
|
542
|
+
if (!apiKey) return "";
|
|
543
|
+
if (apiKey.startsWith("$")) {
|
|
544
|
+
return process.env[apiKey.slice(1)] || "";
|
|
545
|
+
}
|
|
546
|
+
return apiKey;
|
|
547
|
+
}
|
|
548
|
+
async function resolvePoolBoundKey(deps, providerId, provider, sessionId) {
|
|
549
|
+
if (deps.apiKeyPool && sessionId) {
|
|
550
|
+
const poolKey = await deps.apiKeyPool.getKeyForSession(providerId, sessionId);
|
|
551
|
+
if (poolKey) return poolKey;
|
|
552
|
+
}
|
|
553
|
+
return resolveApiKey(_chunkA2YMUCBTcjs.resolveProviderEndpoint.call(void 0, provider).apiKey);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/provider-proxy/ingress/anthropicSubscriptionPlan.ts
|
|
557
|
+
async function buildSubscriptionPlan(res, route, deps, anthropicBody, routeModel, isStream) {
|
|
558
|
+
const profile = route.subscriptionProfile;
|
|
559
|
+
if (!profile) {
|
|
560
|
+
writeError(res, 502, "Subscription mode requires an Anthropic subscription profile");
|
|
561
|
+
return null;
|
|
562
|
+
}
|
|
563
|
+
let resolvedModel = routeModel;
|
|
564
|
+
let scenario;
|
|
565
|
+
if (profile.modelMapper) {
|
|
566
|
+
const summary = buildSubscriptionRequestSummary(anthropicBody);
|
|
567
|
+
const mapped = profile.modelMapper(routeModel, summary, route.subscriptionConfig);
|
|
568
|
+
resolvedModel = mapped.resolvedModel;
|
|
569
|
+
scenario = mapped.scenario;
|
|
570
|
+
}
|
|
571
|
+
const plan = buildSubscriptionIterationPlan(profile, route, deps, resolvedModel, isStream, scenario);
|
|
572
|
+
if (!plan) {
|
|
573
|
+
writeError(res, 502, "Subscription profile is missing resolveUpstreamUrl");
|
|
574
|
+
return null;
|
|
575
|
+
}
|
|
576
|
+
return plan;
|
|
577
|
+
}
|
|
578
|
+
function buildSubscriptionIterationPlan(profile, route, deps, resolvedModel, isStream, scenario) {
|
|
579
|
+
const upstreamUrl = _optionalChain([profile, 'access', _8 => _8.resolveUpstreamUrl, 'optionalCall', _9 => _9(resolvedModel, route.subscriptionConfig)]);
|
|
580
|
+
if (!upstreamUrl) return null;
|
|
581
|
+
const auth = _nullishCoalesce(route.auth, () => ( new (0, _chunkN3V2J5ZOcjs.SubscriptionAuthSource)(profile)));
|
|
582
|
+
const sameFormat = profile.mode === "pass-through" || upstreamUrl.endsWith("/v1/messages");
|
|
583
|
+
const overrideNames = _optionalChain([profile, 'access', _10 => _10.resolveProviderTransformerNames, 'optionalCall', _11 => _11(
|
|
584
|
+
resolvedModel,
|
|
585
|
+
route.subscriptionConfig
|
|
586
|
+
)]);
|
|
587
|
+
const chain = sameFormat ? { providerTransformers: [], modelTransformers: [] } : _chunkNYUNU75Acjs.resolveSubscriptionChain.call(void 0,
|
|
588
|
+
profile,
|
|
589
|
+
deps.llmConfig.getTransformerService(),
|
|
590
|
+
getAnthropicEndpointTransformer(),
|
|
591
|
+
overrideNames
|
|
592
|
+
);
|
|
593
|
+
const transformerProvider = {
|
|
594
|
+
name: profile.authStrategy.providerId,
|
|
595
|
+
baseUrl: upstreamUrl,
|
|
596
|
+
apiKey: "",
|
|
597
|
+
models: [resolvedModel]
|
|
598
|
+
};
|
|
599
|
+
return {
|
|
600
|
+
auth,
|
|
601
|
+
chain,
|
|
602
|
+
transformerProvider,
|
|
603
|
+
resolvedModel,
|
|
604
|
+
isStream,
|
|
605
|
+
// EXCEPTION (the Responses chain) (Phase 3 task 3.3): the `openai-response`
|
|
606
|
+
// provider transformer emits an ABSOLUTE-path `config.url`
|
|
607
|
+
// (`new URL('/v1/responses', baseUrl)`) that DISCARDS any base PATH prefix — so
|
|
608
|
+
// for a path-prefixed base (zen `https://opencode.ai/zen/v1/responses` → drops
|
|
609
|
+
// `/zen/`; codex `https://chatgpt.com/backend-api/codex/responses` → drops
|
|
610
|
+
// `/backend-api/codex/`). On the core `/v1/messages` plan the endpoint
|
|
611
|
+
// transformer is `anthropic` and the codex/zen-responses chain is
|
|
612
|
+
// `['openai-response']` (endpoint != provider), so `transformRequestIn` RUNS
|
|
613
|
+
// and emits the lossy URL. The guard makes this safe REGARDLESS of whether it
|
|
614
|
+
// ran: `usesResponsesChain(['openai-response'])` captures BOTH codex and zen
|
|
615
|
+
// responses, so the profile's complete `upstreamUrl` always wins (codex → its
|
|
616
|
+
// backend-api/codex/responses; zen → its `/zen/...` endpoint). The gemini
|
|
617
|
+
// chain's `config.url` is RELATIVE (`./{model}:{action}`) and PRESERVES the
|
|
618
|
+
// base path, so it correctly wins. The bypass-by-name optimization only applies
|
|
619
|
+
// on the core OpenAI-Responses ingress (endpoint == `openai-response`), NOT
|
|
620
|
+
// here. `// UNVERIFIED (no live zen key)`.
|
|
621
|
+
resolveUrl: usesResponsesChain(_nullishCoalesce(overrideNames, () => ( profile.providerTransformerNames))) ? () => upstreamUrl : (config) => config.url instanceof URL ? config.url.toString() : typeof config.url === "string" ? config.url : upstreamUrl,
|
|
622
|
+
upstreamUrl,
|
|
623
|
+
sameFormat,
|
|
624
|
+
isSubscription: true,
|
|
625
|
+
scenario
|
|
626
|
+
};
|
|
627
|
+
}
|
|
628
|
+
function usesResponsesChain(names) {
|
|
629
|
+
return !!names && names.includes("openai-response");
|
|
630
|
+
}
|
|
631
|
+
function buildSubscriptionRequestSummary(anthropicBody) {
|
|
632
|
+
const messages = Array.isArray(anthropicBody.messages) ? anthropicBody.messages : [];
|
|
633
|
+
let totalChars = collectText(anthropicBody.system);
|
|
634
|
+
for (const message of messages) {
|
|
635
|
+
if (message && typeof message === "object") {
|
|
636
|
+
totalChars += collectText(message.content);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
return {
|
|
640
|
+
messageCount: messages.length,
|
|
641
|
+
estimatedInputTokens: Math.ceil(totalChars / 4),
|
|
642
|
+
// Single source of truth (shared with the dispatcher builder) so both
|
|
643
|
+
// ingress paths yield identical `matchText` for the same body.
|
|
644
|
+
matchText: _chunkSPVWWUHXcjs.collectMatchText.call(void 0, anthropicBody)
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function collectText(value) {
|
|
648
|
+
if (typeof value === "string") return value.length;
|
|
649
|
+
if (Array.isArray(value)) {
|
|
650
|
+
let sum = 0;
|
|
651
|
+
for (const item of value) sum += collectText(item);
|
|
652
|
+
return sum;
|
|
653
|
+
}
|
|
654
|
+
if (value && typeof value === "object") {
|
|
655
|
+
const text = value.text;
|
|
656
|
+
if (typeof text === "string") return text.length;
|
|
657
|
+
}
|
|
658
|
+
return 0;
|
|
659
|
+
}
|
|
660
|
+
async function runPipeline(anthropicBody, plan) {
|
|
661
|
+
const executor = getSharedExecutor();
|
|
662
|
+
const endpointTransformer = getAnthropicEndpointTransformer();
|
|
663
|
+
const { auth, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
664
|
+
const authHeaders = {};
|
|
665
|
+
await auth.applyHeaders(authHeaders, { upstreamUrl, model: resolvedModel });
|
|
666
|
+
let rawStatus = null;
|
|
667
|
+
const { response } = await _chunkKUU2RNG6cjs.executeProviderCall.call(void 0, {
|
|
668
|
+
executor,
|
|
669
|
+
request: anthropicBody,
|
|
670
|
+
provider: transformerProvider,
|
|
671
|
+
chain,
|
|
672
|
+
endpointTransformer,
|
|
673
|
+
resolveUrl,
|
|
674
|
+
buildHeaders: (config) => {
|
|
675
|
+
const headers = { ...authHeaders };
|
|
676
|
+
if (config.headers) {
|
|
677
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
678
|
+
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
return headers;
|
|
682
|
+
},
|
|
683
|
+
fetchFn: (url, headers, body) => {
|
|
684
|
+
console.info(`[ProviderProxy:anthropic] -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
685
|
+
return fetch(url, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => {
|
|
686
|
+
rawStatus = r.status;
|
|
687
|
+
return r;
|
|
688
|
+
});
|
|
689
|
+
},
|
|
690
|
+
runResponseChain: true
|
|
691
|
+
});
|
|
692
|
+
return { response, rawStatus };
|
|
693
|
+
}
|
|
694
|
+
async function runSubscriptionSameFormatFetch(rawBody, plan) {
|
|
695
|
+
const headers = { "content-type": "application/json" };
|
|
696
|
+
await plan.auth.applyHeaders(headers, {
|
|
697
|
+
upstreamUrl: plan.upstreamUrl,
|
|
698
|
+
model: plan.resolvedModel
|
|
699
|
+
});
|
|
700
|
+
console.info(
|
|
701
|
+
`[ProviderProxy:anthropic] (subscription same-format) -> ${plan.upstreamUrl} model=${plan.resolvedModel} stream=${plan.isStream}`
|
|
702
|
+
);
|
|
703
|
+
const response = await fetch(plan.upstreamUrl, { method: "POST", headers, body: rawBody });
|
|
704
|
+
return { response, rawStatus: response.status };
|
|
705
|
+
}
|
|
706
|
+
async function runSubscriptionAttemptWith401Retry(anthropicBody, relayBody, plan) {
|
|
707
|
+
const runOnce = () => plan.sameFormat ? runSubscriptionSameFormatFetch(relayBody, plan) : runPipeline(anthropicBody, plan);
|
|
708
|
+
const first = await runOnce();
|
|
709
|
+
if (first.rawStatus !== 401) return first;
|
|
710
|
+
const refreshed = await _optionalChain([plan, 'access', _12 => _12.auth, 'access', _13 => _13.onUnauthorized, 'optionalCall', _14 => _14()]);
|
|
711
|
+
if (!refreshed) {
|
|
712
|
+
console.warn("[ProviderProxy:anthropic] 401 not recoverable (onUnauthorized returned false)");
|
|
713
|
+
return first;
|
|
714
|
+
}
|
|
715
|
+
console.info("[ProviderProxy:anthropic] 401 \u2192 token refreshed; retrying once");
|
|
716
|
+
return runOnce();
|
|
717
|
+
}
|
|
718
|
+
function isFallbackEligibleStatus(rawStatus) {
|
|
719
|
+
return rawStatus === null || rawStatus >= 500 || rawStatus === 429;
|
|
720
|
+
}
|
|
721
|
+
function breakerOutcome(rawStatus) {
|
|
722
|
+
if (isFallbackEligibleStatus(rawStatus)) return "failure";
|
|
723
|
+
if (rawStatus !== null && rawStatus >= 200 && rawStatus < 300) return "success";
|
|
724
|
+
return "neutral";
|
|
725
|
+
}
|
|
726
|
+
function recordBreakerOutcome(profile, modelId, rawStatus) {
|
|
727
|
+
const outcome = breakerOutcome(rawStatus);
|
|
728
|
+
if (outcome === "neutral") return;
|
|
729
|
+
_optionalChain([profile, 'optionalAccess', _15 => _15.recordModelOutcome, 'optionalCall', _16 => _16(modelId, outcome === "success")]);
|
|
730
|
+
}
|
|
731
|
+
async function runSubscriptionAttemptOutcome(anthropicBody, relayBody, plan) {
|
|
732
|
+
try {
|
|
733
|
+
return { kind: "result", result: await runSubscriptionAttemptWith401Retry(anthropicBody, relayBody, plan) };
|
|
734
|
+
} catch (error) {
|
|
735
|
+
return { kind: "thrown", error };
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
function outcomeStatus(outcome) {
|
|
739
|
+
return outcome.kind === "thrown" ? null : outcome.result.rawStatus;
|
|
740
|
+
}
|
|
741
|
+
function settleOutcome(outcome) {
|
|
742
|
+
if (outcome.kind === "thrown") throw outcome.error;
|
|
743
|
+
return outcome.result;
|
|
744
|
+
}
|
|
745
|
+
async function runPipelineWithSubscriptionRetry(anthropicBody, rawBody, initialPlan, route, deps) {
|
|
746
|
+
const profile = route.subscriptionProfile;
|
|
747
|
+
const scenario = initialPlan.scenario;
|
|
748
|
+
if (!_optionalChain([profile, 'optionalAccess', _17 => _17.nextFallback]) || scenario === void 0) {
|
|
749
|
+
const loneOutcome = await runSubscriptionAttemptOutcome(anthropicBody, rawBody, initialPlan);
|
|
750
|
+
recordBreakerOutcome(profile, initialPlan.resolvedModel, outcomeStatus(loneOutcome));
|
|
751
|
+
return settleOutcome(loneOutcome);
|
|
752
|
+
}
|
|
753
|
+
const attempted = [];
|
|
754
|
+
let plan = initialPlan;
|
|
755
|
+
let firstRelayBody = rawBody;
|
|
756
|
+
let firstBodyObj = anthropicBody;
|
|
757
|
+
if (profile.allowModel && !profile.allowModel(initialPlan.resolvedModel)) {
|
|
758
|
+
attempted.push(initialPlan.resolvedModel);
|
|
759
|
+
const firstAdmitting = profile.nextFallback(scenario, attempted, route.subscriptionConfig);
|
|
760
|
+
const gatedPlan = firstAdmitting ? buildSubscriptionIterationPlan(profile, route, deps, firstAdmitting.modelId, initialPlan.isStream, scenario) : null;
|
|
761
|
+
if (firstAdmitting && gatedPlan) {
|
|
762
|
+
console.warn(
|
|
763
|
+
`[ProviderProxy:anthropic] subscription primary ${initialPlan.resolvedModel} circuit open -> first admitting fallback ${firstAdmitting.modelId}`
|
|
764
|
+
);
|
|
765
|
+
plan = gatedPlan;
|
|
766
|
+
firstBodyObj = { ...anthropicBody, model: firstAdmitting.modelId };
|
|
767
|
+
firstRelayBody = gatedPlan.sameFormat ? JSON.stringify(firstBodyObj) : rawBody;
|
|
768
|
+
} else {
|
|
769
|
+
attempted.length = 0;
|
|
770
|
+
console.warn(
|
|
771
|
+
`[ProviderProxy:anthropic] subscription all opencodego circuits open -> fail open to primary ${initialPlan.resolvedModel}`
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
let outcome = await runSubscriptionAttemptOutcome(firstBodyObj, firstRelayBody, plan);
|
|
776
|
+
recordBreakerOutcome(profile, plan.resolvedModel, outcomeStatus(outcome));
|
|
777
|
+
if (!attempted.includes(plan.resolvedModel)) attempted.push(plan.resolvedModel);
|
|
778
|
+
while (attempted.length < MAX_FALLBACK_ATTEMPTS) {
|
|
779
|
+
if (!isFallbackEligibleStatus(outcomeStatus(outcome))) return settleOutcome(outcome);
|
|
780
|
+
const next = profile.nextFallback(scenario, attempted, route.subscriptionConfig);
|
|
781
|
+
if (!next) return settleOutcome(outcome);
|
|
782
|
+
const nextPlan = buildSubscriptionIterationPlan(
|
|
783
|
+
profile,
|
|
784
|
+
route,
|
|
785
|
+
deps,
|
|
786
|
+
next.modelId,
|
|
787
|
+
plan.isStream,
|
|
788
|
+
scenario
|
|
789
|
+
);
|
|
790
|
+
if (!nextPlan) return settleOutcome(outcome);
|
|
791
|
+
const sinceLabel = outcome.kind === "thrown" ? "thrown error" : `status ${String(outcome.result.rawStatus)}`;
|
|
792
|
+
console.warn(
|
|
793
|
+
`[ProviderProxy:anthropic] subscription fallback ${plan.resolvedModel} -> ${next.modelId} after ${sinceLabel}`
|
|
794
|
+
);
|
|
795
|
+
const fallbackBodyObj = { ...anthropicBody, model: next.modelId };
|
|
796
|
+
const fallbackRelayBody = nextPlan.sameFormat ? JSON.stringify(fallbackBodyObj) : rawBody;
|
|
797
|
+
plan = nextPlan;
|
|
798
|
+
attempted.push(next.modelId);
|
|
799
|
+
outcome = await runSubscriptionAttemptOutcome(fallbackBodyObj, fallbackRelayBody, plan);
|
|
800
|
+
recordBreakerOutcome(profile, plan.resolvedModel, outcomeStatus(outcome));
|
|
801
|
+
}
|
|
802
|
+
return settleOutcome(outcome);
|
|
803
|
+
}
|
|
804
|
+
var MAX_FALLBACK_ATTEMPTS = 3;
|
|
805
|
+
|
|
806
|
+
// src/provider-proxy/ingress/anthropicMessagesByo.ts
|
|
807
|
+
async function handleAnthropicMessagesByo(res, rawBody, route, deps, options = {}) {
|
|
808
|
+
let anthropicBody;
|
|
809
|
+
try {
|
|
810
|
+
anthropicBody = JSON.parse(rawBody);
|
|
811
|
+
} catch (e5) {
|
|
812
|
+
writeError(res, 400, "Invalid JSON in request body");
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
const isStream = anthropicBody.stream === true;
|
|
816
|
+
const resolvedModel = route.model;
|
|
817
|
+
anthropicBody.model = resolvedModel;
|
|
818
|
+
try {
|
|
819
|
+
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan(res, route, deps, anthropicBody, resolvedModel, isStream) : await buildByoPlan(res, route, deps, resolvedModel, isStream);
|
|
820
|
+
if (!plan) return;
|
|
821
|
+
anthropicBody.model = plan.resolvedModel;
|
|
822
|
+
const providerResponse = route.authMode === "subscription" ? await runPipelineWithSubscriptionRetry(anthropicBody, rawBody, plan, route, deps) : await runPipelineWithPoolReporting(anthropicBody, rawBody, plan, options);
|
|
823
|
+
const bodyText = await relayResponse(res, providerResponse.response, isStream);
|
|
824
|
+
if (bodyText && deps.usageRecorder) {
|
|
825
|
+
recordAnthropicNonStreamUsage(deps.usageRecorder, bodyText, {
|
|
826
|
+
sessionId: route.sessionId,
|
|
827
|
+
providerId: _nullishCoalesce(route.providerId, () => ( "anthropic")),
|
|
828
|
+
model: plan.resolvedModel
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
} catch (err) {
|
|
832
|
+
const errMsg = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
833
|
+
console.error("[ProviderProxy:anthropic] Pipeline error:", errMsg);
|
|
834
|
+
writeError(res, 502, errMsg);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
async function buildByoPlan(res, route, deps, resolvedModel, isStream) {
|
|
838
|
+
const providerId = route.providerId;
|
|
839
|
+
if (!providerId) {
|
|
840
|
+
writeError(res, 502, "BYO route is missing a providerId");
|
|
841
|
+
return null;
|
|
842
|
+
}
|
|
843
|
+
const provider = await deps.llmConfig.getProvider(providerId);
|
|
844
|
+
if (!provider) {
|
|
845
|
+
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
846
|
+
return null;
|
|
847
|
+
}
|
|
848
|
+
const apiKey = await resolvePoolBoundKey(deps, providerId, provider, route.sessionId);
|
|
849
|
+
if (!apiKey) {
|
|
850
|
+
writeError(res, 502, "API key not configured");
|
|
851
|
+
return null;
|
|
852
|
+
}
|
|
853
|
+
const auth = _nullishCoalesce(route.auth, () => ( new (0, _chunkLKZJEL6Ecjs.LlmConfigProviderAuth)({
|
|
854
|
+
provider,
|
|
855
|
+
apiKey,
|
|
856
|
+
apiKeyPool: _nullishCoalesce(deps.apiKeyPool, () => ( null)),
|
|
857
|
+
providerId,
|
|
858
|
+
sessionId: route.sessionId
|
|
859
|
+
})));
|
|
860
|
+
const { chain } = await _chunkQXD4MV2Xcjs.resolveProviderChain.call(void 0, deps.llmConfig, providerId, resolvedModel);
|
|
861
|
+
const transformerProvider = {
|
|
862
|
+
name: provider.name,
|
|
863
|
+
baseUrl: provider.api_base_url,
|
|
864
|
+
apiKey,
|
|
865
|
+
models: provider.models || []
|
|
866
|
+
};
|
|
867
|
+
const byoUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: resolvedModel, stream: isStream });
|
|
868
|
+
const sameFormat = route.targetProviderFormat === "anthropic" || _chunkA2YMUCBTcjs.resolveApiFormat.call(void 0, provider) === "anthropic";
|
|
869
|
+
return {
|
|
870
|
+
auth,
|
|
871
|
+
chain,
|
|
872
|
+
transformerProvider,
|
|
873
|
+
resolvedModel,
|
|
874
|
+
isStream,
|
|
875
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : byoUrl,
|
|
876
|
+
upstreamUrl: byoUrl,
|
|
877
|
+
sameFormat,
|
|
878
|
+
isSubscription: false,
|
|
879
|
+
provider,
|
|
880
|
+
apiKey,
|
|
881
|
+
extendedContextEnabled: _nullishCoalesce(_optionalChain([route, 'access', _18 => _18.anthropicSdkHints, 'optionalAccess', _19 => _19.extendedContext, 'optionalAccess', _20 => _20.enabled]), () => ( false))
|
|
882
|
+
};
|
|
883
|
+
}
|
|
884
|
+
async function runSameFormatFetch(rawBody, plan, options, keyOverride) {
|
|
885
|
+
const { provider, apiKey, resolvedModel, isStream, extendedContextEnabled } = plan;
|
|
886
|
+
if (!provider) {
|
|
887
|
+
throw new Error("[ProviderProxy:anthropic] same-format BYO path requires a provider row");
|
|
888
|
+
}
|
|
889
|
+
const effectiveKey = _nullishCoalesce(_nullishCoalesce(keyOverride, () => ( apiKey)), () => ( ""));
|
|
890
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, effectiveKey);
|
|
891
|
+
const callerBeta = _optionalChain([options, 'access', _21 => _21.callerAnthropicBeta, 'optionalAccess', _22 => _22.trim, 'call', _23 => _23()]);
|
|
892
|
+
if (callerBeta) {
|
|
893
|
+
const existing = _nullishCoalesce(headers["anthropic-beta"], () => ( headers["Anthropic-Beta"]));
|
|
894
|
+
const parts = new Set(
|
|
895
|
+
[existing, callerBeta].filter((v) => typeof v === "string" && v.length > 0).flatMap((v) => v.split(",").map((s) => s.trim()).filter((s) => s.length > 0))
|
|
896
|
+
);
|
|
897
|
+
if (parts.size > 0) {
|
|
898
|
+
delete headers["Anthropic-Beta"];
|
|
899
|
+
headers["anthropic-beta"] = [...parts].join(",");
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
_chunkGBHYAGX3cjs.injectExtendedContextBeta.call(void 0, headers, resolvedModel, _nullishCoalesce(extendedContextEnabled, () => ( false)));
|
|
903
|
+
const url = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: resolvedModel, stream: isStream });
|
|
904
|
+
console.info(`[ProviderProxy:anthropic] (same-format) -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
905
|
+
const response = await fetch(url, { method: "POST", headers, body: rawBody });
|
|
906
|
+
return { response, rawStatus: response.status };
|
|
907
|
+
}
|
|
908
|
+
async function runPipelineWithPoolReporting(anthropicBody, rawBody, plan, options) {
|
|
909
|
+
const runOnce = (keyOverride) => plan.sameFormat ? runSameFormatFetch(rawBody, plan, options, keyOverride) : runPipeline(anthropicBody, plan);
|
|
910
|
+
const first = await runOnce();
|
|
911
|
+
const outcome = await _optionalChain([plan, 'access', _24 => _24.auth, 'access', _25 => _25.onResult, 'optionalCall', _26 => _26(first.rawStatus)]);
|
|
912
|
+
if (_optionalChain([outcome, 'optionalAccess', _27 => _27.rebound])) {
|
|
913
|
+
console.info(
|
|
914
|
+
"[ProviderProxy:anthropic] pool re-bound key after status",
|
|
915
|
+
first.rawStatus,
|
|
916
|
+
"\u2192 retrying once"
|
|
917
|
+
);
|
|
918
|
+
return runOnce(outcome.newKey);
|
|
919
|
+
}
|
|
920
|
+
return first;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// src/provider-proxy/ingress/anthropicMessagesIngress.ts
|
|
924
|
+
function isAnthropicMessagesRequest(method, url) {
|
|
925
|
+
return method === "POST" && !!url && url.includes("/v1/messages");
|
|
926
|
+
}
|
|
927
|
+
async function handleAnthropicMessagesRequest(req, res, route, deps) {
|
|
928
|
+
const handlerFactory = deps.anthropicIngressHandlerFactory;
|
|
929
|
+
if (!handlerFactory) {
|
|
930
|
+
const rawBody = await readBody(req);
|
|
931
|
+
const callerBetaRaw = req.headers["anthropic-beta"];
|
|
932
|
+
const callerAnthropicBeta = Array.isArray(callerBetaRaw) ? callerBetaRaw.join(",") : callerBetaRaw;
|
|
933
|
+
await handleAnthropicMessagesByo(res, rawBody, route, deps, { callerAnthropicBeta });
|
|
934
|
+
return;
|
|
935
|
+
}
|
|
936
|
+
const hints = route.anthropicSdkHints;
|
|
937
|
+
if (!hints) {
|
|
938
|
+
writeError(res, 502, "Anthropic route is missing anthropicSdkHints");
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (!hints.passThrough && !route.providerId) {
|
|
942
|
+
writeError(res, 502, "Anthropic route is missing a providerId");
|
|
943
|
+
return;
|
|
944
|
+
}
|
|
945
|
+
const handler = handlerFactory({
|
|
946
|
+
llmConfig: deps.llmConfig,
|
|
947
|
+
providerId: _nullishCoalesce(route.providerId, () => ( "")),
|
|
948
|
+
model: route.model,
|
|
949
|
+
apiKey: hints.apiKey,
|
|
950
|
+
backgroundTaskModel: route.backgroundTaskModel,
|
|
951
|
+
isOfficialProvider: hints.isOfficialProvider,
|
|
952
|
+
thinkingLevel: hints.thinkingLevel,
|
|
953
|
+
extendedContext: _nullishCoalesce(hints.extendedContext, () => ( null)),
|
|
954
|
+
passThrough: hints.passThrough,
|
|
955
|
+
passThroughAuthToken: _nullishCoalesce(hints.passThroughAuthToken, () => ( null)),
|
|
956
|
+
resolvePassThroughAuthToken: _nullishCoalesce(hints.resolvePassThroughAuthToken, () => ( null)),
|
|
957
|
+
subscriptionProfile: _nullishCoalesce(hints.subscriptionProfile, () => ( null)),
|
|
958
|
+
maxConcurrency: hints.maxConcurrency,
|
|
959
|
+
webSearchService: _nullishCoalesce(hints.webSearchService, () => ( null)),
|
|
960
|
+
onRetry: hints.onRetry,
|
|
961
|
+
onStreamEvent: hints.onStreamEvent,
|
|
962
|
+
usageRecorder: _nullishCoalesce(hints.usageRecorder, () => ( null)),
|
|
963
|
+
attribution: _nullishCoalesce(hints.attribution, () => ( null))
|
|
964
|
+
});
|
|
965
|
+
await handler.handle(req, res);
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// src/provider-proxy/usage/recordGeminiUsage.ts
|
|
969
|
+
function readGeminiUsage(usage) {
|
|
970
|
+
if (!usage) return null;
|
|
971
|
+
const promptTokens = Number(usage.promptTokenCount) || 0;
|
|
972
|
+
const candidatesTokens = Number(usage.candidatesTokenCount) || 0;
|
|
973
|
+
const cacheReadTokens = Number(usage.cachedContentTokenCount) || 0;
|
|
974
|
+
const reasoningTokens = Number(usage.thoughtsTokenCount) || 0;
|
|
975
|
+
const billableInput = Math.max(0, promptTokens - cacheReadTokens);
|
|
976
|
+
return {
|
|
977
|
+
inputTokens: billableInput,
|
|
978
|
+
outputTokens: candidatesTokens,
|
|
979
|
+
cacheReadTokens,
|
|
980
|
+
cacheCreationTokens: 0,
|
|
981
|
+
reasoningTokens
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function recordGeminiNonStreamUsage(recorder, bodyText, attribution) {
|
|
985
|
+
try {
|
|
986
|
+
const parsed = JSON.parse(bodyText);
|
|
987
|
+
const tapped = readGeminiUsage(parsed.usageMetadata);
|
|
988
|
+
if (!tapped) return;
|
|
989
|
+
recorder.record({
|
|
990
|
+
messageId: null,
|
|
991
|
+
parentMessageId: null,
|
|
992
|
+
sessionId: attribution.sessionId,
|
|
993
|
+
providerId: attribution.providerId,
|
|
994
|
+
model: attribution.model,
|
|
995
|
+
apiKeyId: null,
|
|
996
|
+
engineOrigin: "codex-ingress",
|
|
997
|
+
usage: tapped,
|
|
998
|
+
rawUsage: parsed.usageMetadata
|
|
999
|
+
});
|
|
1000
|
+
} catch (e6) {
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
// src/provider-proxy/ingress/geminiGenerateContentIngress.ts
|
|
1005
|
+
var GENERATE_CONTENT_ACTION = ":generateContent";
|
|
1006
|
+
var STREAM_GENERATE_CONTENT_ACTION = ":streamGenerateContent";
|
|
1007
|
+
function isGeminiGenerateContentRequest(method, url) {
|
|
1008
|
+
if (method !== "POST" || !url) return false;
|
|
1009
|
+
const path = _nullishCoalesce(_optionalChain([url, 'access', _28 => _28.split, 'call', _29 => _29("?"), 'access', _30 => _30[0], 'optionalAccess', _31 => _31.replace, 'call', _32 => _32(/\/+$/, "")]), () => ( ""));
|
|
1010
|
+
const lastSegment = _nullishCoalesce(path.split("/").pop(), () => ( ""));
|
|
1011
|
+
return lastSegment.includes(STREAM_GENERATE_CONTENT_ACTION) || lastSegment.includes(GENERATE_CONTENT_ACTION);
|
|
1012
|
+
}
|
|
1013
|
+
function isGeminiStreamRequest(url) {
|
|
1014
|
+
if (!url) return false;
|
|
1015
|
+
const [path, query = ""] = url.split("?");
|
|
1016
|
+
const lastSegment = _nullishCoalesce((_nullishCoalesce(_optionalChain([path, 'optionalAccess', _33 => _33.replace, 'call', _34 => _34(/\/+$/, "")]), () => ( ""))).split("/").pop(), () => ( ""));
|
|
1017
|
+
if (lastSegment.includes(STREAM_GENERATE_CONTENT_ACTION)) return true;
|
|
1018
|
+
return /(^|&)alt=sse(&|$)/.test(query);
|
|
1019
|
+
}
|
|
1020
|
+
async function handleGeminiGenerateContentRequest(res, rawBody, url, route, deps) {
|
|
1021
|
+
let geminiBody;
|
|
1022
|
+
try {
|
|
1023
|
+
geminiBody = JSON.parse(rawBody);
|
|
1024
|
+
} catch (e7) {
|
|
1025
|
+
writeError(res, 400, "Invalid JSON in request body");
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
if (route.authMode === "subscription") {
|
|
1029
|
+
writeError(res, 502, "Gemini generateContent ingress does not support subscription auth");
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
const isStream = isGeminiStreamRequest(url);
|
|
1033
|
+
geminiBody.stream = isStream;
|
|
1034
|
+
const resolvedModel = route.model;
|
|
1035
|
+
geminiBody.model = resolvedModel;
|
|
1036
|
+
try {
|
|
1037
|
+
const plan = await buildByoPlan2(res, route, deps, resolvedModel, isStream);
|
|
1038
|
+
if (!plan) return;
|
|
1039
|
+
const providerResponse = await runPipelineWithPoolReporting2(geminiBody, plan);
|
|
1040
|
+
const bodyText = await relayResponse(res, providerResponse.response, isStream);
|
|
1041
|
+
if (bodyText && deps.usageRecorder) {
|
|
1042
|
+
recordGeminiNonStreamUsage(deps.usageRecorder, bodyText, {
|
|
1043
|
+
sessionId: route.sessionId,
|
|
1044
|
+
providerId: _nullishCoalesce(route.providerId, () => ( "gemini")),
|
|
1045
|
+
model: resolvedModel
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
1048
|
+
} catch (err) {
|
|
1049
|
+
const errMsg = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
1050
|
+
console.error("[ProviderProxy:gemini] Pipeline error:", errMsg);
|
|
1051
|
+
writeError(res, 502, errMsg);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
async function buildByoPlan2(res, route, deps, resolvedModel, isStream) {
|
|
1055
|
+
const providerId = route.providerId;
|
|
1056
|
+
if (!providerId) {
|
|
1057
|
+
writeError(res, 502, "BYO route is missing a providerId");
|
|
1058
|
+
return null;
|
|
1059
|
+
}
|
|
1060
|
+
const provider = await deps.llmConfig.getProvider(providerId);
|
|
1061
|
+
if (!provider) {
|
|
1062
|
+
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1063
|
+
return null;
|
|
1064
|
+
}
|
|
1065
|
+
const apiKey = await resolvePoolBoundKey(deps, providerId, provider, route.sessionId);
|
|
1066
|
+
if (!apiKey) {
|
|
1067
|
+
writeError(res, 502, "API key not configured");
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
const auth = _nullishCoalesce(route.auth, () => ( new (0, _chunkLKZJEL6Ecjs.LlmConfigProviderAuth)({
|
|
1071
|
+
provider,
|
|
1072
|
+
apiKey,
|
|
1073
|
+
apiKeyPool: _nullishCoalesce(deps.apiKeyPool, () => ( null)),
|
|
1074
|
+
providerId,
|
|
1075
|
+
sessionId: route.sessionId
|
|
1076
|
+
})));
|
|
1077
|
+
const { chain } = await _chunkQXD4MV2Xcjs.resolveProviderChain.call(void 0, deps.llmConfig, providerId, resolvedModel);
|
|
1078
|
+
const transformerProvider = {
|
|
1079
|
+
name: provider.name,
|
|
1080
|
+
baseUrl: provider.api_base_url,
|
|
1081
|
+
apiKey,
|
|
1082
|
+
models: provider.models || []
|
|
1083
|
+
};
|
|
1084
|
+
const byoUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: resolvedModel, stream: isStream });
|
|
1085
|
+
return {
|
|
1086
|
+
auth,
|
|
1087
|
+
chain,
|
|
1088
|
+
transformerProvider,
|
|
1089
|
+
resolvedModel,
|
|
1090
|
+
isStream,
|
|
1091
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : byoUrl,
|
|
1092
|
+
upstreamUrl: byoUrl
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
async function runPipeline2(geminiBody, plan) {
|
|
1096
|
+
const executor = getSharedExecutor();
|
|
1097
|
+
const endpointTransformer = getGeminiEndpointTransformer();
|
|
1098
|
+
const { auth, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
1099
|
+
const authHeaders = {};
|
|
1100
|
+
await auth.applyHeaders(authHeaders, { upstreamUrl, model: resolvedModel });
|
|
1101
|
+
let rawStatus = null;
|
|
1102
|
+
const { response } = await _chunkKUU2RNG6cjs.executeProviderCall.call(void 0, {
|
|
1103
|
+
executor,
|
|
1104
|
+
request: geminiBody,
|
|
1105
|
+
provider: transformerProvider,
|
|
1106
|
+
chain,
|
|
1107
|
+
endpointTransformer,
|
|
1108
|
+
resolveUrl,
|
|
1109
|
+
buildHeaders: (config) => {
|
|
1110
|
+
const headers = { ...authHeaders };
|
|
1111
|
+
if (config.headers) {
|
|
1112
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
1113
|
+
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
return headers;
|
|
1117
|
+
},
|
|
1118
|
+
fetchFn: (url, headers, body) => {
|
|
1119
|
+
console.log(`[ProviderProxy:gemini] -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
1120
|
+
return fetch(url, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => {
|
|
1121
|
+
rawStatus = r.status;
|
|
1122
|
+
return r;
|
|
1123
|
+
});
|
|
1124
|
+
},
|
|
1125
|
+
runResponseChain: true
|
|
1126
|
+
});
|
|
1127
|
+
return { response, rawStatus };
|
|
1128
|
+
}
|
|
1129
|
+
async function runPipelineWithPoolReporting2(geminiBody, plan) {
|
|
1130
|
+
const first = await runPipeline2(geminiBody, plan);
|
|
1131
|
+
const outcome = await _optionalChain([plan, 'access', _35 => _35.auth, 'access', _36 => _36.onResult, 'optionalCall', _37 => _37(first.rawStatus)]);
|
|
1132
|
+
if (_optionalChain([outcome, 'optionalAccess', _38 => _38.rebound])) {
|
|
1133
|
+
console.log(
|
|
1134
|
+
"[ProviderProxy:gemini] pool re-bound key after status",
|
|
1135
|
+
first.rawStatus,
|
|
1136
|
+
"\u2192 retrying once"
|
|
1137
|
+
);
|
|
1138
|
+
return runPipeline2(geminiBody, plan);
|
|
1139
|
+
}
|
|
1140
|
+
return first;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
// src/provider-proxy/usage/recordChatCompletionsUsage.ts
|
|
1144
|
+
function readChatCompletionsUsage(usage) {
|
|
1145
|
+
if (!usage) return null;
|
|
1146
|
+
const promptTokens = Number(usage.prompt_tokens) || 0;
|
|
1147
|
+
const completionTokens = Number(usage.completion_tokens) || 0;
|
|
1148
|
+
const promptDetails = _nullishCoalesce(usage.prompt_tokens_details, () => ( {}));
|
|
1149
|
+
const cacheReadTokens = Number(promptDetails.cached_tokens) || 0;
|
|
1150
|
+
const completionDetails = _nullishCoalesce(usage.completion_tokens_details, () => ( {}));
|
|
1151
|
+
const reasoningTokens = Number(completionDetails.reasoning_tokens) || 0;
|
|
1152
|
+
const billableInput = Math.max(0, promptTokens - cacheReadTokens);
|
|
1153
|
+
return {
|
|
1154
|
+
inputTokens: billableInput,
|
|
1155
|
+
outputTokens: completionTokens,
|
|
1156
|
+
cacheReadTokens,
|
|
1157
|
+
cacheCreationTokens: 0,
|
|
1158
|
+
reasoningTokens
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
function recordChatCompletionsNonStreamUsage(recorder, bodyText, attribution) {
|
|
1162
|
+
try {
|
|
1163
|
+
const parsed = JSON.parse(bodyText);
|
|
1164
|
+
const tapped = readChatCompletionsUsage(parsed.usage);
|
|
1165
|
+
if (!tapped) return;
|
|
1166
|
+
recorder.record({
|
|
1167
|
+
messageId: null,
|
|
1168
|
+
parentMessageId: null,
|
|
1169
|
+
sessionId: attribution.sessionId,
|
|
1170
|
+
providerId: attribution.providerId,
|
|
1171
|
+
model: attribution.model,
|
|
1172
|
+
apiKeyId: null,
|
|
1173
|
+
engineOrigin: "codex-ingress",
|
|
1174
|
+
usage: tapped,
|
|
1175
|
+
rawUsage: parsed.usage
|
|
1176
|
+
});
|
|
1177
|
+
} catch (e8) {
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// src/provider-proxy/ingress/openaiChatIngress.ts
|
|
1182
|
+
function isOpenAIChatRequest(method, url) {
|
|
1183
|
+
if (method !== "POST" || !url) return false;
|
|
1184
|
+
const path = _nullishCoalesce(_optionalChain([url, 'access', _39 => _39.split, 'call', _40 => _40("?"), 'access', _41 => _41[0], 'optionalAccess', _42 => _42.replace, 'call', _43 => _43(/\/+$/, "")]), () => ( ""));
|
|
1185
|
+
return path.endsWith("/chat/completions");
|
|
1186
|
+
}
|
|
1187
|
+
async function handleOpenAIChatRequest(res, rawBody, route, deps) {
|
|
1188
|
+
let chatBody;
|
|
1189
|
+
try {
|
|
1190
|
+
chatBody = JSON.parse(rawBody);
|
|
1191
|
+
} catch (e9) {
|
|
1192
|
+
writeError(res, 400, "Invalid JSON in request body");
|
|
1193
|
+
return;
|
|
1194
|
+
}
|
|
1195
|
+
if (route.authMode === "subscription") {
|
|
1196
|
+
writeError(res, 502, "OpenAI Chat Completions ingress does not support subscription auth");
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
const isStream = chatBody.stream === true;
|
|
1200
|
+
const resolvedModel = route.model;
|
|
1201
|
+
chatBody.model = resolvedModel;
|
|
1202
|
+
try {
|
|
1203
|
+
const plan = await buildByoPlan3(res, route, deps, resolvedModel, isStream);
|
|
1204
|
+
if (!plan) return;
|
|
1205
|
+
const providerResponse = await runPipelineWithPoolReporting3(chatBody, plan);
|
|
1206
|
+
const bodyText = await relayResponse(res, providerResponse.response, isStream);
|
|
1207
|
+
if (bodyText && deps.usageRecorder) {
|
|
1208
|
+
recordChatCompletionsNonStreamUsage(deps.usageRecorder, bodyText, {
|
|
1209
|
+
sessionId: route.sessionId,
|
|
1210
|
+
providerId: _nullishCoalesce(route.providerId, () => ( "openai")),
|
|
1211
|
+
model: resolvedModel
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
} catch (err) {
|
|
1215
|
+
const errMsg = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
1216
|
+
console.error("[ProviderProxy:chat] Pipeline error:", errMsg);
|
|
1217
|
+
writeError(res, 502, errMsg);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
async function buildByoPlan3(res, route, deps, resolvedModel, isStream) {
|
|
1221
|
+
const providerId = route.providerId;
|
|
1222
|
+
if (!providerId) {
|
|
1223
|
+
writeError(res, 502, "BYO route is missing a providerId");
|
|
1224
|
+
return null;
|
|
1225
|
+
}
|
|
1226
|
+
const provider = await deps.llmConfig.getProvider(providerId);
|
|
1227
|
+
if (!provider) {
|
|
1228
|
+
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
const apiKey = await resolvePoolBoundKey(deps, providerId, provider, route.sessionId);
|
|
1232
|
+
if (!apiKey) {
|
|
1233
|
+
writeError(res, 502, "API key not configured");
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
const auth = _nullishCoalesce(route.auth, () => ( new (0, _chunkLKZJEL6Ecjs.LlmConfigProviderAuth)({
|
|
1237
|
+
provider,
|
|
1238
|
+
apiKey,
|
|
1239
|
+
apiKeyPool: _nullishCoalesce(deps.apiKeyPool, () => ( null)),
|
|
1240
|
+
providerId,
|
|
1241
|
+
sessionId: route.sessionId
|
|
1242
|
+
})));
|
|
1243
|
+
const { chain } = await _chunkQXD4MV2Xcjs.resolveProviderChain.call(void 0, deps.llmConfig, providerId, resolvedModel);
|
|
1244
|
+
const transformerProvider = {
|
|
1245
|
+
name: provider.name,
|
|
1246
|
+
baseUrl: provider.api_base_url,
|
|
1247
|
+
apiKey,
|
|
1248
|
+
models: provider.models || []
|
|
1249
|
+
};
|
|
1250
|
+
const byoUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: resolvedModel, stream: isStream });
|
|
1251
|
+
return {
|
|
1252
|
+
auth,
|
|
1253
|
+
chain,
|
|
1254
|
+
transformerProvider,
|
|
1255
|
+
resolvedModel,
|
|
1256
|
+
isStream,
|
|
1257
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : byoUrl,
|
|
1258
|
+
upstreamUrl: byoUrl
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
async function runPipeline3(chatBody, plan) {
|
|
1262
|
+
const executor = getSharedExecutor();
|
|
1263
|
+
const { auth, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
1264
|
+
const authHeaders = {};
|
|
1265
|
+
await auth.applyHeaders(authHeaders, { upstreamUrl, model: resolvedModel });
|
|
1266
|
+
let rawStatus = null;
|
|
1267
|
+
const { response } = await _chunkKUU2RNG6cjs.executeProviderCall.call(void 0, {
|
|
1268
|
+
executor,
|
|
1269
|
+
request: chatBody,
|
|
1270
|
+
provider: transformerProvider,
|
|
1271
|
+
chain,
|
|
1272
|
+
resolveUrl,
|
|
1273
|
+
buildHeaders: (config) => {
|
|
1274
|
+
const headers = { ...authHeaders };
|
|
1275
|
+
if (config.headers) {
|
|
1276
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
1277
|
+
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
return headers;
|
|
1281
|
+
},
|
|
1282
|
+
fetchFn: (url, headers, body) => {
|
|
1283
|
+
console.log(`[ProviderProxy:chat] -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
1284
|
+
return fetch(url, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => {
|
|
1285
|
+
rawStatus = r.status;
|
|
1286
|
+
return r;
|
|
1287
|
+
});
|
|
1288
|
+
},
|
|
1289
|
+
runResponseChain: true
|
|
1290
|
+
});
|
|
1291
|
+
return { response, rawStatus };
|
|
1292
|
+
}
|
|
1293
|
+
async function runPipelineWithPoolReporting3(chatBody, plan) {
|
|
1294
|
+
const first = await runPipeline3(chatBody, plan);
|
|
1295
|
+
const outcome = await _optionalChain([plan, 'access', _44 => _44.auth, 'access', _45 => _45.onResult, 'optionalCall', _46 => _46(first.rawStatus)]);
|
|
1296
|
+
if (_optionalChain([outcome, 'optionalAccess', _47 => _47.rebound])) {
|
|
1297
|
+
console.log(
|
|
1298
|
+
"[ProviderProxy:chat] pool re-bound key after status",
|
|
1299
|
+
first.rawStatus,
|
|
1300
|
+
"\u2192 retrying once"
|
|
1301
|
+
);
|
|
1302
|
+
return runPipeline3(chatBody, plan);
|
|
1303
|
+
}
|
|
1304
|
+
return first;
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
// src/provider-proxy/usage/recordResponsesUsage.ts
|
|
1308
|
+
function readResponsesUsage(usage) {
|
|
1309
|
+
if (!usage) return null;
|
|
1310
|
+
const inputTokens = Number(usage.input_tokens) || 0;
|
|
1311
|
+
const outputTokens = Number(usage.output_tokens) || 0;
|
|
1312
|
+
const inputDetails = _nullishCoalesce(usage.input_tokens_details, () => ( {}));
|
|
1313
|
+
const cacheReadTokens = Number(inputDetails.cached_tokens) || 0;
|
|
1314
|
+
const outputDetails = _nullishCoalesce(usage.output_tokens_details, () => ( {}));
|
|
1315
|
+
const reasoningTokens = Number(outputDetails.reasoning_tokens) || 0;
|
|
1316
|
+
const billableInput = Math.max(0, inputTokens - cacheReadTokens);
|
|
1317
|
+
return {
|
|
1318
|
+
inputTokens: billableInput,
|
|
1319
|
+
outputTokens,
|
|
1320
|
+
cacheReadTokens,
|
|
1321
|
+
cacheCreationTokens: 0,
|
|
1322
|
+
reasoningTokens
|
|
1323
|
+
};
|
|
1324
|
+
}
|
|
1325
|
+
function recordResponsesNonStreamUsage(recorder, bodyText, attribution) {
|
|
1326
|
+
try {
|
|
1327
|
+
const parsed = JSON.parse(bodyText);
|
|
1328
|
+
const tapped = readResponsesUsage(parsed.usage);
|
|
1329
|
+
if (!tapped) return;
|
|
1330
|
+
recorder.record({
|
|
1331
|
+
messageId: null,
|
|
1332
|
+
parentMessageId: null,
|
|
1333
|
+
sessionId: attribution.sessionId,
|
|
1334
|
+
providerId: attribution.providerId,
|
|
1335
|
+
model: attribution.model,
|
|
1336
|
+
apiKeyId: null,
|
|
1337
|
+
engineOrigin: "codex-ingress",
|
|
1338
|
+
usage: tapped,
|
|
1339
|
+
rawUsage: parsed.usage
|
|
1340
|
+
});
|
|
1341
|
+
} catch (e10) {
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
// src/provider-proxy/ingress/openaiResponsesIngress.ts
|
|
1346
|
+
function isOpenAIResponsesRequest(method, url) {
|
|
1347
|
+
if (method !== "POST" || !url) return false;
|
|
1348
|
+
const path = _nullishCoalesce(_optionalChain([url, 'access', _48 => _48.split, 'call', _49 => _49("?"), 'access', _50 => _50[0], 'optionalAccess', _51 => _51.replace, 'call', _52 => _52(/\/+$/, "")]), () => ( ""));
|
|
1349
|
+
return path.endsWith("/responses");
|
|
1350
|
+
}
|
|
1351
|
+
async function handleOpenAIResponsesRequest(res, rawBody, route, deps) {
|
|
1352
|
+
let responsesBody;
|
|
1353
|
+
try {
|
|
1354
|
+
responsesBody = JSON.parse(rawBody);
|
|
1355
|
+
} catch (e11) {
|
|
1356
|
+
writeError(res, 400, "Invalid JSON in request body");
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1359
|
+
const isStream = responsesBody.stream === true;
|
|
1360
|
+
const resolvedModel = route.model;
|
|
1361
|
+
responsesBody.model = resolvedModel;
|
|
1362
|
+
try {
|
|
1363
|
+
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan2(res, route, deps, resolvedModel, isStream) : await buildByoPlan4(res, route, deps, resolvedModel, isStream);
|
|
1364
|
+
if (!plan) return;
|
|
1365
|
+
const providerResponse = route.authMode === "subscription" ? await runPipelineWithSubscriptionRetry2(responsesBody, plan) : await runPipelineWithPoolReporting4(responsesBody, plan);
|
|
1366
|
+
const bodyText = await relayResponse(res, providerResponse.response, isStream);
|
|
1367
|
+
if (bodyText && deps.usageRecorder) {
|
|
1368
|
+
recordResponsesNonStreamUsage(deps.usageRecorder, bodyText, {
|
|
1369
|
+
sessionId: route.sessionId,
|
|
1370
|
+
providerId: _nullishCoalesce(route.providerId, () => ( "codex")),
|
|
1371
|
+
model: resolvedModel
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
} catch (err) {
|
|
1375
|
+
const errMsg = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
1376
|
+
console.error("[ProviderProxy:responses] Pipeline error:", errMsg);
|
|
1377
|
+
writeError(res, 502, errMsg);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
|
|
1381
|
+
const providerId = route.providerId;
|
|
1382
|
+
if (!providerId) {
|
|
1383
|
+
writeError(res, 502, "BYO route is missing a providerId");
|
|
1384
|
+
return null;
|
|
1385
|
+
}
|
|
1386
|
+
const provider = await deps.llmConfig.getProvider(providerId);
|
|
1387
|
+
if (!provider) {
|
|
1388
|
+
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1389
|
+
return null;
|
|
1390
|
+
}
|
|
1391
|
+
const apiKey = await resolvePoolBoundKey(deps, providerId, provider, route.sessionId);
|
|
1392
|
+
if (!apiKey) {
|
|
1393
|
+
writeError(res, 502, "API key not configured");
|
|
1394
|
+
return null;
|
|
1395
|
+
}
|
|
1396
|
+
const auth = _nullishCoalesce(route.auth, () => ( new (0, _chunkLKZJEL6Ecjs.LlmConfigProviderAuth)({
|
|
1397
|
+
provider,
|
|
1398
|
+
apiKey,
|
|
1399
|
+
apiKeyPool: _nullishCoalesce(deps.apiKeyPool, () => ( null)),
|
|
1400
|
+
providerId,
|
|
1401
|
+
sessionId: route.sessionId
|
|
1402
|
+
})));
|
|
1403
|
+
const { chain } = await _chunkQXD4MV2Xcjs.resolveProviderChain.call(void 0, deps.llmConfig, providerId, resolvedModel);
|
|
1404
|
+
const transformerProvider = {
|
|
1405
|
+
name: provider.name,
|
|
1406
|
+
baseUrl: provider.api_base_url,
|
|
1407
|
+
apiKey,
|
|
1408
|
+
models: provider.models || []
|
|
1409
|
+
};
|
|
1410
|
+
const byoUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: resolvedModel, stream: isStream });
|
|
1411
|
+
return {
|
|
1412
|
+
auth,
|
|
1413
|
+
chain,
|
|
1414
|
+
transformerProvider,
|
|
1415
|
+
resolvedModel,
|
|
1416
|
+
isStream,
|
|
1417
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : byoUrl,
|
|
1418
|
+
upstreamUrl: byoUrl
|
|
1419
|
+
};
|
|
1420
|
+
}
|
|
1421
|
+
async function buildSubscriptionPlan2(res, route, deps, resolvedModel, isStream) {
|
|
1422
|
+
const profile = route.subscriptionProfile;
|
|
1423
|
+
if (!profile) {
|
|
1424
|
+
writeError(res, 502, "Subscription mode requires a codex subscription profile");
|
|
1425
|
+
return null;
|
|
1426
|
+
}
|
|
1427
|
+
const upstreamUrl = _optionalChain([profile, 'access', _53 => _53.resolveUpstreamUrl, 'optionalCall', _54 => _54(resolvedModel)]);
|
|
1428
|
+
if (!upstreamUrl) {
|
|
1429
|
+
writeError(res, 502, "Subscription profile is missing resolveUpstreamUrl");
|
|
1430
|
+
return null;
|
|
1431
|
+
}
|
|
1432
|
+
const auth = _nullishCoalesce(route.auth, () => ( new (0, _chunkN3V2J5ZOcjs.SubscriptionAuthSource)(profile)));
|
|
1433
|
+
const chain = _chunkNYUNU75Acjs.resolveSubscriptionChain.call(void 0,
|
|
1434
|
+
profile,
|
|
1435
|
+
deps.llmConfig.getTransformerService(),
|
|
1436
|
+
getResponsesEndpointTransformer()
|
|
1437
|
+
);
|
|
1438
|
+
const transformerProvider = {
|
|
1439
|
+
name: profile.authStrategy.providerId,
|
|
1440
|
+
baseUrl: upstreamUrl,
|
|
1441
|
+
apiKey: "",
|
|
1442
|
+
models: [resolvedModel]
|
|
1443
|
+
};
|
|
1444
|
+
if (profile.authStrategy.providerId === "gemini") {
|
|
1445
|
+
try {
|
|
1446
|
+
transformerProvider.geminiProject = await resolveGeminiCodeAssistProject(profile);
|
|
1447
|
+
} catch (err) {
|
|
1448
|
+
writeError(res, 502, _chunkSVNDB62Dcjs.serializeError.call(void 0, err));
|
|
1449
|
+
return null;
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
return {
|
|
1453
|
+
auth,
|
|
1454
|
+
chain,
|
|
1455
|
+
transformerProvider,
|
|
1456
|
+
resolvedModel,
|
|
1457
|
+
isStream,
|
|
1458
|
+
// Prefer the transformer-supplied URL (Code Assist carries the correct
|
|
1459
|
+
// stream vs non-stream colon-method URL in `config.url`); fall back to the
|
|
1460
|
+
// profile's `resolveUpstreamUrl` for non-Code-Assist subscriptions.
|
|
1461
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : typeof config.url === "string" ? config.url : upstreamUrl,
|
|
1462
|
+
upstreamUrl
|
|
1463
|
+
};
|
|
1464
|
+
}
|
|
1465
|
+
async function resolveGeminiCodeAssistProject(profile) {
|
|
1466
|
+
const probe = {};
|
|
1467
|
+
await profile.authStrategy.applyHeaders(probe);
|
|
1468
|
+
const bearer = _nullishCoalesce(_nullishCoalesce(probe.Authorization, () => ( probe.authorization)), () => ( ""));
|
|
1469
|
+
const accessToken = bearer.replace(/^Bearer\s+/i, "").trim();
|
|
1470
|
+
if (!accessToken) return void 0;
|
|
1471
|
+
const resolver = _chunkR3HK6R4Bcjs.getGeminiCodeAssistResolver.call(void 0, );
|
|
1472
|
+
if (!resolver) return void 0;
|
|
1473
|
+
return resolver.resolveProject(accessToken);
|
|
1474
|
+
}
|
|
1475
|
+
async function runPipeline4(responsesBody, plan) {
|
|
1476
|
+
const executor = getSharedExecutor();
|
|
1477
|
+
const endpointTransformer = getResponsesEndpointTransformer();
|
|
1478
|
+
const { auth, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
1479
|
+
const authHeaders = {};
|
|
1480
|
+
await auth.applyHeaders(authHeaders, { upstreamUrl, model: resolvedModel });
|
|
1481
|
+
let rawStatus = null;
|
|
1482
|
+
const { response } = await _chunkKUU2RNG6cjs.executeProviderCall.call(void 0, {
|
|
1483
|
+
executor,
|
|
1484
|
+
request: responsesBody,
|
|
1485
|
+
provider: transformerProvider,
|
|
1486
|
+
chain,
|
|
1487
|
+
endpointTransformer,
|
|
1488
|
+
resolveUrl,
|
|
1489
|
+
buildHeaders: (config) => {
|
|
1490
|
+
const headers = { ...authHeaders };
|
|
1491
|
+
if (config.headers) {
|
|
1492
|
+
for (const [key, value] of Object.entries(config.headers)) {
|
|
1493
|
+
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return headers;
|
|
1497
|
+
},
|
|
1498
|
+
fetchFn: (url, headers, body) => {
|
|
1499
|
+
console.log(`[ProviderProxy:responses] -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
1500
|
+
return fetch(url, { method: "POST", headers, body: JSON.stringify(body) }).then((r) => {
|
|
1501
|
+
rawStatus = r.status;
|
|
1502
|
+
return r;
|
|
1503
|
+
});
|
|
1504
|
+
},
|
|
1505
|
+
runResponseChain: true
|
|
1506
|
+
});
|
|
1507
|
+
return { response, rawStatus };
|
|
1508
|
+
}
|
|
1509
|
+
async function runPipelineWithPoolReporting4(responsesBody, plan) {
|
|
1510
|
+
const first = await runPipeline4(responsesBody, plan);
|
|
1511
|
+
const outcome = await _optionalChain([plan, 'access', _55 => _55.auth, 'access', _56 => _56.onResult, 'optionalCall', _57 => _57(first.rawStatus)]);
|
|
1512
|
+
if (_optionalChain([outcome, 'optionalAccess', _58 => _58.rebound])) {
|
|
1513
|
+
console.log(
|
|
1514
|
+
"[ProviderProxy:responses] pool re-bound key after status",
|
|
1515
|
+
first.rawStatus,
|
|
1516
|
+
"\u2192 retrying once"
|
|
1517
|
+
);
|
|
1518
|
+
return runPipeline4(responsesBody, plan);
|
|
1519
|
+
}
|
|
1520
|
+
return first;
|
|
1521
|
+
}
|
|
1522
|
+
async function runPipelineWithSubscriptionRetry2(responsesBody, plan) {
|
|
1523
|
+
const first = await runPipeline4(responsesBody, plan);
|
|
1524
|
+
if (first.rawStatus !== 401) return first;
|
|
1525
|
+
const refreshed = await _optionalChain([plan, 'access', _59 => _59.auth, 'access', _60 => _60.onUnauthorized, 'optionalCall', _61 => _61()]);
|
|
1526
|
+
if (!refreshed) {
|
|
1527
|
+
console.warn("[ProviderProxy:responses] 401 not recoverable (onUnauthorized returned false)");
|
|
1528
|
+
return first;
|
|
1529
|
+
}
|
|
1530
|
+
console.log("[ProviderProxy:responses] 401 \u2192 token refreshed; retrying once");
|
|
1531
|
+
return runPipeline4(responsesBody, plan);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// src/provider-proxy/providerProxyRouter.ts
|
|
1535
|
+
function extractRouteToken(authHeader) {
|
|
1536
|
+
if (!authHeader) return void 0;
|
|
1537
|
+
const trimmed = authHeader.trim();
|
|
1538
|
+
if (!trimmed) return void 0;
|
|
1539
|
+
const m = /^Bearer\s+(.+)$/i.exec(trimmed);
|
|
1540
|
+
return (m ? m[1] : trimmed).trim() || void 0;
|
|
1541
|
+
}
|
|
1542
|
+
function resolveRouteToken(req) {
|
|
1543
|
+
const fromAuth = extractRouteToken(req.headers["authorization"]);
|
|
1544
|
+
if (fromAuth) return fromAuth;
|
|
1545
|
+
const googKey = req.headers["x-goog-api-key"];
|
|
1546
|
+
const value = Array.isArray(googKey) ? googKey[0] : googKey;
|
|
1547
|
+
return _optionalChain([value, 'optionalAccess', _62 => _62.trim, 'call', _63 => _63()]) || void 0;
|
|
1548
|
+
}
|
|
1549
|
+
async function routeRequest(req, res, routes, deps) {
|
|
1550
|
+
const token = resolveRouteToken(req);
|
|
1551
|
+
const route = routes.lookup(token);
|
|
1552
|
+
if (!route) {
|
|
1553
|
+
writeError(res, 401, "Invalid or expired route token");
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
const method = req.method;
|
|
1557
|
+
const url = req.url;
|
|
1558
|
+
if (isAnthropicMessagesRequest(method, url)) {
|
|
1559
|
+
await handleAnthropicMessagesRequest(req, res, route, deps);
|
|
1560
|
+
return;
|
|
1561
|
+
}
|
|
1562
|
+
if (isOpenAIResponsesRequest(method, url)) {
|
|
1563
|
+
const rawBody = await readBody(req);
|
|
1564
|
+
await handleOpenAIResponsesRequest(res, rawBody, route, deps);
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (isOpenAIChatRequest(method, url)) {
|
|
1568
|
+
const rawBody = await readBody(req);
|
|
1569
|
+
await handleOpenAIChatRequest(res, rawBody, route, deps);
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (isGeminiGenerateContentRequest(method, url)) {
|
|
1573
|
+
const rawBody = await readBody(req);
|
|
1574
|
+
await handleGeminiGenerateContentRequest(res, rawBody, url, route, deps);
|
|
1575
|
+
return;
|
|
1576
|
+
}
|
|
1577
|
+
writeError(res, 404, `Unsupported: ${method} ${url}`);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
// src/provider-proxy/ProviderProxy.ts
|
|
1581
|
+
function isLoopbackAddress(addr) {
|
|
1582
|
+
if (!addr) return false;
|
|
1583
|
+
const normalized = addr.startsWith("::ffff:") ? addr.slice("::ffff:".length) : addr;
|
|
1584
|
+
return normalized === "127.0.0.1" || normalized.startsWith("127.") || normalized === "::1" || addr === "::1";
|
|
1585
|
+
}
|
|
1586
|
+
var ProviderProxy = (_class2 = class {
|
|
1587
|
+
constructor(deps, routes) {;_class2.prototype.__init2.call(this);_class2.prototype.__init3.call(this);
|
|
1588
|
+
this.deps = deps;
|
|
1589
|
+
this.routes = _nullishCoalesce(routes, () => ( new ProviderProxyRouteMap()));
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
__init2() {this.server = null}
|
|
1593
|
+
__init3() {this.port = 0}
|
|
1594
|
+
|
|
1595
|
+
/**
|
|
1596
|
+
* Start the resident listener on a stable port on 127.0.0.1. Idempotent —
|
|
1597
|
+
* a second `start()` returns the already-bound port.
|
|
1598
|
+
*/
|
|
1599
|
+
async start() {
|
|
1600
|
+
if (this.server) return this.port;
|
|
1601
|
+
return new Promise((resolve, reject) => {
|
|
1602
|
+
const server = _http2.default.createServer((req, res) => {
|
|
1603
|
+
const peer = req.socket.remoteAddress;
|
|
1604
|
+
if (!isLoopbackAddress(peer)) {
|
|
1605
|
+
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1606
|
+
res.end(JSON.stringify({ error: { type: "provider_proxy_error", message: "Loopback only" } }));
|
|
1607
|
+
req.socket.destroy();
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
routeRequest(req, res, this.routes, this.deps).catch((err) => {
|
|
1611
|
+
const errMsg = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
1612
|
+
console.error("[ProviderProxy] Unhandled error:", errMsg);
|
|
1613
|
+
if (!res.headersSent) {
|
|
1614
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
1615
|
+
res.end(JSON.stringify({ error: { type: "provider_proxy_error", message: errMsg } }));
|
|
1616
|
+
}
|
|
1617
|
+
});
|
|
1618
|
+
});
|
|
1619
|
+
server.listen(0, "127.0.0.1", () => {
|
|
1620
|
+
const addr = server.address();
|
|
1621
|
+
if (addr && typeof addr === "object") {
|
|
1622
|
+
this.port = addr.port;
|
|
1623
|
+
this.server = server;
|
|
1624
|
+
console.log(`[ProviderProxy] Listening on 127.0.0.1:${this.port}`);
|
|
1625
|
+
resolve(this.port);
|
|
1626
|
+
} else {
|
|
1627
|
+
reject(new Error("Failed to get server address"));
|
|
1628
|
+
}
|
|
1629
|
+
});
|
|
1630
|
+
server.on("error", reject);
|
|
1631
|
+
});
|
|
1632
|
+
}
|
|
1633
|
+
/** Stop the listener, clear all routes, and release the port. */
|
|
1634
|
+
async stop() {
|
|
1635
|
+
this.routes.clear();
|
|
1636
|
+
const server = this.server;
|
|
1637
|
+
if (!server) return;
|
|
1638
|
+
return new Promise((resolve) => {
|
|
1639
|
+
server.close(() => {
|
|
1640
|
+
console.log(`[ProviderProxy] Stopped (port=${this.port})`);
|
|
1641
|
+
this.server = null;
|
|
1642
|
+
this.port = 0;
|
|
1643
|
+
resolve();
|
|
1644
|
+
});
|
|
1645
|
+
});
|
|
1646
|
+
}
|
|
1647
|
+
/** Base URL for injector wiring (`ANTHROPIC_BASE_URL` / codex `base_url`). */
|
|
1648
|
+
getBaseUrl() {
|
|
1649
|
+
return `http://127.0.0.1:${this.port}`;
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* The SHARED route map. Exposed so the outbound API server
|
|
1653
|
+
* (`outbound-api-server`) can mint per-request routes on the SAME map and
|
|
1654
|
+
* delegate to the existing `routeRequest()` dispatch — guaranteeing a single
|
|
1655
|
+
* conversion stack. Not used by the resident per-run flow.
|
|
1656
|
+
*/
|
|
1657
|
+
getRouteMap() {
|
|
1658
|
+
return this.routes;
|
|
1659
|
+
}
|
|
1660
|
+
/**
|
|
1661
|
+
* The app-session deps the proxy services all routes with. Exposed so the
|
|
1662
|
+
* outbound server can pass them verbatim into `routeRequest()`.
|
|
1663
|
+
*/
|
|
1664
|
+
getDeps() {
|
|
1665
|
+
return this.deps;
|
|
1666
|
+
}
|
|
1667
|
+
/** Register a route for one run; returns the crypto route token (task 2.2). */
|
|
1668
|
+
addRoute(context, idleMs) {
|
|
1669
|
+
return this.routes.addRoute(context, idleMs);
|
|
1670
|
+
}
|
|
1671
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
1672
|
+
removeRoute(token) {
|
|
1673
|
+
return this.routes.removeRoute(token);
|
|
1674
|
+
}
|
|
1675
|
+
/** Live-route count (diagnostics / tests). */
|
|
1676
|
+
routeCount() {
|
|
1677
|
+
return this.routes.size();
|
|
1678
|
+
}
|
|
1679
|
+
}, _class2);
|
|
1680
|
+
|
|
1681
|
+
// src/provider-proxy/index.ts
|
|
1682
|
+
var instance = null;
|
|
1683
|
+
function getProviderProxy(deps) {
|
|
1684
|
+
if (!instance) {
|
|
1685
|
+
if (!deps) {
|
|
1686
|
+
throw new Error("getProviderProxy: deps are required on first construction");
|
|
1687
|
+
}
|
|
1688
|
+
instance = new ProviderProxy(deps);
|
|
1689
|
+
}
|
|
1690
|
+
return instance;
|
|
1691
|
+
}
|
|
1692
|
+
function __resetProviderProxyForTests() {
|
|
1693
|
+
instance = null;
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// src/outbound-api/OutboundApiServer.ts
|
|
1697
|
+
|
|
1698
|
+
var _os = require('os');
|
|
1699
|
+
|
|
1700
|
+
// src/outbound-api/outboundApiRouter.ts
|
|
1701
|
+
var _stream = require('stream');
|
|
1702
|
+
|
|
1703
|
+
// src/outbound-api/outboundApiKeyAuth.ts
|
|
1704
|
+
|
|
1705
|
+
var SECRET_BYTES = 32;
|
|
1706
|
+
var PREFIX_LEN = 14;
|
|
1707
|
+
var KEY_PREFIX = "sk-omnicross-";
|
|
1708
|
+
var BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
1709
|
+
var BASE62_REJECT_THRESHOLD = 248;
|
|
1710
|
+
function randomBase62(count) {
|
|
1711
|
+
let out = "";
|
|
1712
|
+
while (out.length < count) {
|
|
1713
|
+
const buf = _crypto.randomBytes.call(void 0, count - out.length + 8);
|
|
1714
|
+
for (const byte of buf) {
|
|
1715
|
+
if (out.length >= count) break;
|
|
1716
|
+
if (byte >= BASE62_REJECT_THRESHOLD) continue;
|
|
1717
|
+
out += BASE62[byte % 62];
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
return out;
|
|
1721
|
+
}
|
|
1722
|
+
function generateSecret() {
|
|
1723
|
+
return KEY_PREFIX + randomBase62(SECRET_BYTES);
|
|
1724
|
+
}
|
|
1725
|
+
function hashKey(secret) {
|
|
1726
|
+
return _crypto.createHash.call(void 0, "sha256").update(secret, "utf8").digest("hex");
|
|
1727
|
+
}
|
|
1728
|
+
function keyPrefix(secret) {
|
|
1729
|
+
return secret.slice(0, PREFIX_LEN);
|
|
1730
|
+
}
|
|
1731
|
+
function newKeyId() {
|
|
1732
|
+
return `oak_${Date.now()}_${_crypto.randomBytes.call(void 0, 6).toString("hex")}`;
|
|
1733
|
+
}
|
|
1734
|
+
async function createNamedKey(db, name) {
|
|
1735
|
+
const secret = generateSecret();
|
|
1736
|
+
const id = newKeyId();
|
|
1737
|
+
const prefix = keyPrefix(secret);
|
|
1738
|
+
const row = await db.outboundApiKeysCreate({
|
|
1739
|
+
id,
|
|
1740
|
+
name,
|
|
1741
|
+
keyHash: hashKey(secret),
|
|
1742
|
+
keyPrefix: prefix
|
|
1743
|
+
});
|
|
1744
|
+
return {
|
|
1745
|
+
id: row.id,
|
|
1746
|
+
name: row.name,
|
|
1747
|
+
keyPrefix: row.keyPrefix,
|
|
1748
|
+
createdAt: row.createdAt,
|
|
1749
|
+
plaintextOnce: secret
|
|
1750
|
+
};
|
|
1751
|
+
}
|
|
1752
|
+
async function verifyPresentedKey(db, presentedKey) {
|
|
1753
|
+
if (!presentedKey) return null;
|
|
1754
|
+
const trimmed = presentedKey.trim();
|
|
1755
|
+
if (!trimmed) return null;
|
|
1756
|
+
const row = await db.outboundApiKeysGetByHash(hashKey(trimmed));
|
|
1757
|
+
if (!row) return null;
|
|
1758
|
+
if (!row.enabled || row.revokedAt !== null) return null;
|
|
1759
|
+
void db.outboundApiKeysTouchLastUsed(row.id).catch(() => {
|
|
1760
|
+
});
|
|
1761
|
+
return { id: row.id };
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
// src/outbound-api/roleDetection.ts
|
|
1765
|
+
var _canonicalmodels = require('@omnicross/contracts/canonical-models');
|
|
1766
|
+
var BACKGROUND_TIER_TOKENS = /* @__PURE__ */ new Set([
|
|
1767
|
+
"haiku",
|
|
1768
|
+
"mini",
|
|
1769
|
+
"flash",
|
|
1770
|
+
"small",
|
|
1771
|
+
"lite",
|
|
1772
|
+
"nano",
|
|
1773
|
+
"8b"
|
|
1774
|
+
]);
|
|
1775
|
+
function modelTokens(normalizedId) {
|
|
1776
|
+
return normalizedId.split(/[-._:/\s]+/).filter(Boolean);
|
|
1777
|
+
}
|
|
1778
|
+
function isBackgroundTierModel(rawModelId) {
|
|
1779
|
+
if (!rawModelId) return false;
|
|
1780
|
+
const tokens = modelTokens(_canonicalmodels.normalizeModelId.call(void 0, rawModelId));
|
|
1781
|
+
return tokens.some((tok) => BACKGROUND_TIER_TOKENS.has(tok));
|
|
1782
|
+
}
|
|
1783
|
+
function extractRequestedModel(ingressFormat, body) {
|
|
1784
|
+
const model = body["model"];
|
|
1785
|
+
return typeof model === "string" && model ? model : void 0;
|
|
1786
|
+
}
|
|
1787
|
+
function anthropicHasVision(body) {
|
|
1788
|
+
const messages = body["messages"];
|
|
1789
|
+
if (!Array.isArray(messages)) return false;
|
|
1790
|
+
for (const msg of messages) {
|
|
1791
|
+
const content = _optionalChain([msg, 'optionalAccess', _64 => _64.content]);
|
|
1792
|
+
if (!Array.isArray(content)) continue;
|
|
1793
|
+
for (const part of content) {
|
|
1794
|
+
if (_optionalChain([part, 'optionalAccess', _65 => _65.type]) === "image") return true;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
return false;
|
|
1798
|
+
}
|
|
1799
|
+
function openaiChatHasVision(body) {
|
|
1800
|
+
const messages = body["messages"];
|
|
1801
|
+
if (!Array.isArray(messages)) return false;
|
|
1802
|
+
for (const msg of messages) {
|
|
1803
|
+
const content = _optionalChain([msg, 'optionalAccess', _66 => _66.content]);
|
|
1804
|
+
if (!Array.isArray(content)) continue;
|
|
1805
|
+
for (const part of content) {
|
|
1806
|
+
if (_optionalChain([part, 'optionalAccess', _67 => _67.type]) === "image_url") return true;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return false;
|
|
1810
|
+
}
|
|
1811
|
+
function openaiResponsesHasVision(body) {
|
|
1812
|
+
const input = body["input"];
|
|
1813
|
+
if (!Array.isArray(input)) return false;
|
|
1814
|
+
for (const item of input) {
|
|
1815
|
+
const content = _optionalChain([item, 'optionalAccess', _68 => _68.content]);
|
|
1816
|
+
if (Array.isArray(content)) {
|
|
1817
|
+
for (const part of content) {
|
|
1818
|
+
if (_optionalChain([part, 'optionalAccess', _69 => _69.type]) === "input_image") return true;
|
|
1819
|
+
}
|
|
1820
|
+
}
|
|
1821
|
+
if (_optionalChain([item, 'optionalAccess', _70 => _70.type]) === "input_image") return true;
|
|
1822
|
+
}
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
function geminiHasVision(body) {
|
|
1826
|
+
const contents = body["contents"];
|
|
1827
|
+
if (!Array.isArray(contents)) return false;
|
|
1828
|
+
for (const content of contents) {
|
|
1829
|
+
const parts = _optionalChain([content, 'optionalAccess', _71 => _71.parts]);
|
|
1830
|
+
if (!Array.isArray(parts)) continue;
|
|
1831
|
+
for (const part of parts) {
|
|
1832
|
+
const p = part;
|
|
1833
|
+
if (_optionalChain([p, 'optionalAccess', _72 => _72.inline_data]) || _optionalChain([p, 'optionalAccess', _73 => _73.inlineData]) || _optionalChain([p, 'optionalAccess', _74 => _74.file_data]) || _optionalChain([p, 'optionalAccess', _75 => _75.fileData])) return true;
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
return false;
|
|
1837
|
+
}
|
|
1838
|
+
function hasVisionContent(ingressFormat, body) {
|
|
1839
|
+
switch (ingressFormat) {
|
|
1840
|
+
case "anthropic-messages":
|
|
1841
|
+
return anthropicHasVision(body);
|
|
1842
|
+
case "openai-chat":
|
|
1843
|
+
return openaiChatHasVision(body);
|
|
1844
|
+
case "openai-responses":
|
|
1845
|
+
return openaiResponsesHasVision(body);
|
|
1846
|
+
case "gemini-generatecontent":
|
|
1847
|
+
return geminiHasVision(body);
|
|
1848
|
+
default:
|
|
1849
|
+
return false;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
function detectRequestRole(ingressFormat, body, options) {
|
|
1853
|
+
if (hasVisionContent(ingressFormat, body)) return "vision";
|
|
1854
|
+
const requestedModel = extractRequestedModel(ingressFormat, body);
|
|
1855
|
+
if (requestedModel) {
|
|
1856
|
+
const overrides = _optionalChain([options, 'optionalAccess', _76 => _76.backgroundModelIds]);
|
|
1857
|
+
if (overrides && overrides.length > 0) {
|
|
1858
|
+
const reqNorm = _canonicalmodels.normalizeModelId.call(void 0, requestedModel);
|
|
1859
|
+
const inOverride = overrides.some((id) => {
|
|
1860
|
+
if (!id) return false;
|
|
1861
|
+
const candidate = id.includes(",") ? id.slice(id.indexOf(",") + 1) : id;
|
|
1862
|
+
return _canonicalmodels.normalizeModelId.call(void 0, candidate) === reqNorm || candidate === requestedModel;
|
|
1863
|
+
});
|
|
1864
|
+
if (inOverride) return "background";
|
|
1865
|
+
}
|
|
1866
|
+
if (isBackgroundTierModel(requestedModel)) return "background";
|
|
1867
|
+
}
|
|
1868
|
+
return "default";
|
|
1869
|
+
}
|
|
1870
|
+
function endpointToIngressFormat(endpoint) {
|
|
1871
|
+
switch (endpoint) {
|
|
1872
|
+
case "chat":
|
|
1873
|
+
return "openai-chat";
|
|
1874
|
+
case "responses":
|
|
1875
|
+
return "openai-responses";
|
|
1876
|
+
case "messages":
|
|
1877
|
+
return "anthropic-messages";
|
|
1878
|
+
case "gemini":
|
|
1879
|
+
return "gemini-generatecontent";
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// src/outbound-api/outboundApiRouter.ts
|
|
1884
|
+
function writeJsonError(res, status, message, headers = {}) {
|
|
1885
|
+
if (res.headersSent) return;
|
|
1886
|
+
res.writeHead(status, { "Content-Type": "application/json", ...headers });
|
|
1887
|
+
res.end(JSON.stringify({ error: { type: "outbound_api_error", message } }));
|
|
1888
|
+
}
|
|
1889
|
+
function extractPresentedKey(req) {
|
|
1890
|
+
const auth = req.headers["authorization"];
|
|
1891
|
+
if (typeof auth === "string" && auth.trim()) {
|
|
1892
|
+
const m = /^Bearer\s+(.+)$/i.exec(auth.trim());
|
|
1893
|
+
return (m ? m[1] : auth).trim() || void 0;
|
|
1894
|
+
}
|
|
1895
|
+
const xApiKey = req.headers["x-api-key"];
|
|
1896
|
+
const xApi = Array.isArray(xApiKey) ? xApiKey[0] : xApiKey;
|
|
1897
|
+
if (_optionalChain([xApi, 'optionalAccess', _77 => _77.trim, 'call', _78 => _78()])) return xApi.trim();
|
|
1898
|
+
const goog = req.headers["x-goog-api-key"];
|
|
1899
|
+
const g = Array.isArray(goog) ? goog[0] : goog;
|
|
1900
|
+
return _optionalChain([g, 'optionalAccess', _79 => _79.trim, 'call', _80 => _80()]) || void 0;
|
|
1901
|
+
}
|
|
1902
|
+
function selectEndpoint(method, url) {
|
|
1903
|
+
if (method !== "POST" || !url) return null;
|
|
1904
|
+
const path = _nullishCoalesce(_optionalChain([url, 'access', _81 => _81.split, 'call', _82 => _82("?"), 'access', _83 => _83[0], 'optionalAccess', _84 => _84.replace, 'call', _85 => _85(/\/+$/, "")]), () => ( ""));
|
|
1905
|
+
if (path.endsWith("/chat/completions")) return "chat";
|
|
1906
|
+
if (path.endsWith("/responses")) return "responses";
|
|
1907
|
+
if (path.includes("/v1/messages")) return "messages";
|
|
1908
|
+
const lastSeg = _nullishCoalesce(path.split("/").pop(), () => ( ""));
|
|
1909
|
+
if (lastSeg.endsWith(":generateContent") || lastSeg.endsWith(":streamGenerateContent")) {
|
|
1910
|
+
return "gemini";
|
|
1911
|
+
}
|
|
1912
|
+
return null;
|
|
1913
|
+
}
|
|
1914
|
+
function extractGeminiModelFromUrl(url) {
|
|
1915
|
+
if (!url) return void 0;
|
|
1916
|
+
const path = _nullishCoalesce(url.split("?")[0], () => ( ""));
|
|
1917
|
+
const m = /\/models\/([^/:]+):(?:stream)?[gG]enerateContent$/.exec(path.replace(/\/+$/, ""));
|
|
1918
|
+
return m ? decodeURIComponent(m[1]) : void 0;
|
|
1919
|
+
}
|
|
1920
|
+
function readBody2(req) {
|
|
1921
|
+
return new Promise((resolve, reject) => {
|
|
1922
|
+
const chunks = [];
|
|
1923
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
1924
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
1925
|
+
req.on("error", reject);
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
function shimAuthHeader(req, routeToken) {
|
|
1929
|
+
req.headers["authorization"] = `Bearer ${routeToken}`;
|
|
1930
|
+
delete req.headers["x-goog-api-key"];
|
|
1931
|
+
delete req.headers["x-api-key"];
|
|
1932
|
+
}
|
|
1933
|
+
async function handleOutboundRequest(req, res, deps, config, rateLimiter) {
|
|
1934
|
+
const presented = extractPresentedKey(req);
|
|
1935
|
+
const verified = await verifyPresentedKey(deps.db, presented);
|
|
1936
|
+
if (!verified) {
|
|
1937
|
+
writeJsonError(res, 401, "Invalid or missing API key");
|
|
1938
|
+
return;
|
|
1939
|
+
}
|
|
1940
|
+
const decision = rateLimiter.check(verified.id);
|
|
1941
|
+
if (!decision.allowed) {
|
|
1942
|
+
writeJsonError(res, 429, "Rate limit exceeded", {
|
|
1943
|
+
"Retry-After": String(decision.retryAfterSeconds)
|
|
1944
|
+
});
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
const endpoint = selectEndpoint(req.method, req.url);
|
|
1948
|
+
if (!endpoint) {
|
|
1949
|
+
writeJsonError(res, 404, `Unsupported: ${req.method} ${req.url}`);
|
|
1950
|
+
return;
|
|
1951
|
+
}
|
|
1952
|
+
const endpointConfig = config.endpoints.find((e) => e.endpoint === endpoint);
|
|
1953
|
+
if (!endpointConfig) {
|
|
1954
|
+
writeJsonError(res, 503, `endpoint '${endpoint}' is not configured`);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
const ingressFormat = endpointToIngressFormat(endpoint);
|
|
1958
|
+
const rawBody = await readBody2(req);
|
|
1959
|
+
let parsedBody = {};
|
|
1960
|
+
try {
|
|
1961
|
+
parsedBody = rawBody ? JSON.parse(rawBody) : {};
|
|
1962
|
+
} catch (e12) {
|
|
1963
|
+
writeJsonError(res, 400, "Invalid JSON in request body");
|
|
1964
|
+
return;
|
|
1965
|
+
}
|
|
1966
|
+
if (endpoint === "gemini" && typeof parsedBody["model"] !== "string") {
|
|
1967
|
+
const urlModel = extractGeminiModelFromUrl(req.url);
|
|
1968
|
+
if (urlModel) parsedBody["model"] = urlModel;
|
|
1969
|
+
}
|
|
1970
|
+
const role = detectRequestRole(ingressFormat, parsedBody, {
|
|
1971
|
+
backgroundModelIds: endpointConfig.backgroundModelIds
|
|
1972
|
+
});
|
|
1973
|
+
const resolved = await _chunkG2FUJNA2cjs.resolveRoute.call(void 0, {
|
|
1974
|
+
config: endpointConfig,
|
|
1975
|
+
role,
|
|
1976
|
+
ingressFormat,
|
|
1977
|
+
llmConfig: deps.llmConfig,
|
|
1978
|
+
// pool-seam (omnicross-daemon-parity-poolseam, design D1/D2(a)): synthesize a
|
|
1979
|
+
// STABLE per-verified-key sessionId so the BYO ingress can seed a pool session
|
|
1980
|
+
// binding and 429/529/401/403 failover actually fires. Synthesize ONLY when
|
|
1981
|
+
// the pool is wired — pool-null embedders/tests keep `route.sessionId === null`
|
|
1982
|
+
// (byte-identical to pre-seam). The `outbound:` prefix is namespace-isolated
|
|
1983
|
+
// from real chat-session ids; `verified.id` is a small operator-controlled set,
|
|
1984
|
+
// so `sessionBindings` stays bounded (one binding per named key, not per
|
|
1985
|
+
// request — `sessionBindings` has no TTL).
|
|
1986
|
+
sessionId: deps.proxyDeps.apiKeyPool ? `outbound:${verified.id}` : null
|
|
1987
|
+
});
|
|
1988
|
+
if (!resolved.ok) {
|
|
1989
|
+
writeJsonError(res, resolved.error.status, resolved.error.message);
|
|
1990
|
+
return;
|
|
1991
|
+
}
|
|
1992
|
+
const routeMap = deps.providerProxy.getRouteMap();
|
|
1993
|
+
const token = routeMap.addRoute(resolved.route);
|
|
1994
|
+
try {
|
|
1995
|
+
shimAuthHeader(req, token);
|
|
1996
|
+
const replay = makeReplayRequest(req, rawBody);
|
|
1997
|
+
await routeRequest(replay, res, routeMap, deps.proxyDeps);
|
|
1998
|
+
} catch (err) {
|
|
1999
|
+
const message = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
2000
|
+
console.error("[OutboundApi] dispatch error:", message);
|
|
2001
|
+
writeJsonError(res, 502, message);
|
|
2002
|
+
} finally {
|
|
2003
|
+
routeMap.removeRoute(token);
|
|
2004
|
+
}
|
|
2005
|
+
}
|
|
2006
|
+
function makeReplayRequest(req, rawBody) {
|
|
2007
|
+
const buf = Buffer.from(rawBody, "utf8");
|
|
2008
|
+
const readable = _stream.Readable.from(buf.length > 0 ? [buf] : []);
|
|
2009
|
+
readable.method = req.method;
|
|
2010
|
+
readable.url = req.url;
|
|
2011
|
+
readable.headers = req.headers;
|
|
2012
|
+
readable.httpVersion = req.httpVersion;
|
|
2013
|
+
readable.socket = req.socket;
|
|
2014
|
+
return readable;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
// src/outbound-api/outboundRateLimiter.ts
|
|
2018
|
+
var DEFAULT_WINDOW_MS = 6e4;
|
|
2019
|
+
var DEFAULT_MAX_REQUESTS = 60;
|
|
2020
|
+
var OutboundRateLimiter = (_class3 = class {
|
|
2021
|
+
|
|
2022
|
+
|
|
2023
|
+
/** apiKeyId → ascending request timestamps within the current window. */
|
|
2024
|
+
__init4() {this.hits = /* @__PURE__ */ new Map()}
|
|
2025
|
+
constructor(options = {}) {;_class3.prototype.__init4.call(this);
|
|
2026
|
+
this.windowMs = _nullishCoalesce(options.windowMs, () => ( DEFAULT_WINDOW_MS));
|
|
2027
|
+
this.maxRequests = _nullishCoalesce(options.maxRequests, () => ( DEFAULT_MAX_REQUESTS));
|
|
2028
|
+
}
|
|
2029
|
+
/**
|
|
2030
|
+
* Record a request for `apiKeyId` and decide whether it is allowed. Prunes
|
|
2031
|
+
* timestamps older than the window first; when allowed, the request's
|
|
2032
|
+
* timestamp is appended.
|
|
2033
|
+
*/
|
|
2034
|
+
check(apiKeyId, now = Date.now()) {
|
|
2035
|
+
const cutoff = now - this.windowMs;
|
|
2036
|
+
const timestamps = (_nullishCoalesce(this.hits.get(apiKeyId), () => ( []))).filter((ts) => ts > cutoff);
|
|
2037
|
+
if (timestamps.length >= this.maxRequests) {
|
|
2038
|
+
if (timestamps.length === 0) {
|
|
2039
|
+
this.hits.delete(apiKeyId);
|
|
2040
|
+
return { allowed: false, retryAfterSeconds: Math.max(1, Math.ceil(this.windowMs / 1e3)) };
|
|
2041
|
+
}
|
|
2042
|
+
const oldest = timestamps[0];
|
|
2043
|
+
const retryAfterMs = Math.max(0, oldest + this.windowMs - now);
|
|
2044
|
+
this.hits.set(apiKeyId, timestamps);
|
|
2045
|
+
return {
|
|
2046
|
+
allowed: false,
|
|
2047
|
+
retryAfterSeconds: Math.max(1, Math.ceil(retryAfterMs / 1e3))
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
timestamps.push(now);
|
|
2051
|
+
this.hits.set(apiKeyId, timestamps);
|
|
2052
|
+
return { allowed: true, retryAfterSeconds: 0 };
|
|
2053
|
+
}
|
|
2054
|
+
/** Drop all recorded state (tests / teardown). */
|
|
2055
|
+
reset() {
|
|
2056
|
+
this.hits.clear();
|
|
2057
|
+
}
|
|
2058
|
+
}, _class3);
|
|
2059
|
+
|
|
2060
|
+
// src/outbound-api/OutboundApiServer.ts
|
|
2061
|
+
var DEFAULT_OUTBOUND_PORT = 8765;
|
|
2062
|
+
var LOOPBACK_ADDR = "127.0.0.1";
|
|
2063
|
+
var LAN_ADDR = "0.0.0.0";
|
|
2064
|
+
var OutboundApiServer = (_class4 = class {
|
|
2065
|
+
constructor(deps, onPortChange) {;_class4.prototype.__init5.call(this);_class4.prototype.__init6.call(this);_class4.prototype.__init7.call(this);_class4.prototype.__init8.call(this);_class4.prototype.__init9.call(this);
|
|
2066
|
+
this.deps = deps;
|
|
2067
|
+
this.onPortChange = onPortChange;
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
|
|
2071
|
+
__init5() {this.server = null}
|
|
2072
|
+
__init6() {this.boundPort = 0}
|
|
2073
|
+
__init7() {this.boundAddr = LOOPBACK_ADDR}
|
|
2074
|
+
__init8() {this.endpoints = []}
|
|
2075
|
+
__init9() {this.rateLimiter = new OutboundRateLimiter()}
|
|
2076
|
+
/**
|
|
2077
|
+
* Apply a config. Restarts the listener ONLY when the bind address or port
|
|
2078
|
+
* changes (or when toggling enabled); per-endpoint routing config is updated
|
|
2079
|
+
* in place (read live per request — no restart).
|
|
2080
|
+
*/
|
|
2081
|
+
async applyConfig(input) {
|
|
2082
|
+
this.endpoints = input.endpoints;
|
|
2083
|
+
const wantAddr = input.networkBinding ? LAN_ADDR : LOOPBACK_ADDR;
|
|
2084
|
+
const wantPort = _nullishCoalesce(input.port, () => ( DEFAULT_OUTBOUND_PORT));
|
|
2085
|
+
if (!input.enabled) {
|
|
2086
|
+
await this.stop();
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
const running = this.server !== null;
|
|
2090
|
+
const bindChanged = running && (this.boundAddr !== wantAddr || this.boundPort !== wantPort);
|
|
2091
|
+
if (running && !bindChanged) {
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
if (running) await this.stop();
|
|
2095
|
+
await this.start(wantAddr, wantPort);
|
|
2096
|
+
}
|
|
2097
|
+
/** Start the listener on `bindAddr:port`, falling back on EADDRINUSE. */
|
|
2098
|
+
async start(bindAddr, port) {
|
|
2099
|
+
if (this.server) return this.boundPort;
|
|
2100
|
+
const actualPort = await this.listen(bindAddr, port);
|
|
2101
|
+
this.boundAddr = bindAddr;
|
|
2102
|
+
this.boundPort = actualPort;
|
|
2103
|
+
if (actualPort !== port) _optionalChain([this, 'access', _86 => _86.onPortChange, 'optionalCall', _87 => _87(actualPort)]);
|
|
2104
|
+
console.log(`[OutboundApiServer] Listening on ${bindAddr}:${actualPort}`);
|
|
2105
|
+
return actualPort;
|
|
2106
|
+
}
|
|
2107
|
+
/** Bind once; on EADDRINUSE retry with an ephemeral port (port 0). */
|
|
2108
|
+
listen(bindAddr, port) {
|
|
2109
|
+
return new Promise((resolve, reject) => {
|
|
2110
|
+
const server = _http2.default.createServer((req, res) => {
|
|
2111
|
+
this.onRequest(req, res);
|
|
2112
|
+
});
|
|
2113
|
+
const onError = (err) => {
|
|
2114
|
+
if (err.code === "EADDRINUSE" && port !== 0) {
|
|
2115
|
+
server.removeListener("error", onError);
|
|
2116
|
+
this.listen(bindAddr, 0).then(resolve, reject);
|
|
2117
|
+
return;
|
|
2118
|
+
}
|
|
2119
|
+
reject(err);
|
|
2120
|
+
};
|
|
2121
|
+
server.on("error", onError);
|
|
2122
|
+
server.listen(port, bindAddr, () => {
|
|
2123
|
+
const addr = server.address();
|
|
2124
|
+
if (addr && typeof addr === "object") {
|
|
2125
|
+
server.removeListener("error", onError);
|
|
2126
|
+
server.on("error", (e) => console.error("[OutboundApiServer] server error", _chunkSVNDB62Dcjs.serializeError.call(void 0, e)));
|
|
2127
|
+
this.server = server;
|
|
2128
|
+
resolve(addr.port);
|
|
2129
|
+
} else {
|
|
2130
|
+
reject(new Error("Failed to get outbound server address"));
|
|
2131
|
+
}
|
|
2132
|
+
});
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
/** Per-request handler. Auth is enforced on EVERY request (incl. loopback). */
|
|
2136
|
+
onRequest(req, res) {
|
|
2137
|
+
handleOutboundRequest(
|
|
2138
|
+
req,
|
|
2139
|
+
res,
|
|
2140
|
+
this.deps,
|
|
2141
|
+
{ endpoints: this.endpoints },
|
|
2142
|
+
this.rateLimiter
|
|
2143
|
+
).catch((err) => {
|
|
2144
|
+
const message = _chunkSVNDB62Dcjs.serializeError.call(void 0, err);
|
|
2145
|
+
console.error("[OutboundApiServer] unhandled error:", message);
|
|
2146
|
+
if (!res.headersSent) {
|
|
2147
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
2148
|
+
res.end(JSON.stringify({ error: { type: "outbound_api_error", message } }));
|
|
2149
|
+
}
|
|
2150
|
+
});
|
|
2151
|
+
}
|
|
2152
|
+
/** Stop the listener and release the port. */
|
|
2153
|
+
async stop() {
|
|
2154
|
+
const server = this.server;
|
|
2155
|
+
if (!server) return;
|
|
2156
|
+
this.server = null;
|
|
2157
|
+
this.boundPort = 0;
|
|
2158
|
+
return new Promise((resolve) => {
|
|
2159
|
+
server.close(() => {
|
|
2160
|
+
console.log("[OutboundApiServer] Stopped");
|
|
2161
|
+
resolve();
|
|
2162
|
+
});
|
|
2163
|
+
});
|
|
2164
|
+
}
|
|
2165
|
+
/** A live status snapshot for the Settings tab. */
|
|
2166
|
+
getStatus() {
|
|
2167
|
+
const running = this.server !== null;
|
|
2168
|
+
if (!running) {
|
|
2169
|
+
return {
|
|
2170
|
+
running: false,
|
|
2171
|
+
port: 0,
|
|
2172
|
+
loopbackUrl: null,
|
|
2173
|
+
lanUrl: null,
|
|
2174
|
+
formats: null,
|
|
2175
|
+
lanFormats: null
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
const port = this.boundPort;
|
|
2179
|
+
const loopbackBase = `http://${LOOPBACK_ADDR}:${port}`;
|
|
2180
|
+
const isLan = this.boundAddr === LAN_ADDR;
|
|
2181
|
+
const lanIp = isLan ? firstLanIPv4() : null;
|
|
2182
|
+
const lanBase = lanIp ? `http://${lanIp}:${port}` : null;
|
|
2183
|
+
return {
|
|
2184
|
+
running: true,
|
|
2185
|
+
port,
|
|
2186
|
+
loopbackUrl: loopbackBase,
|
|
2187
|
+
lanUrl: lanBase,
|
|
2188
|
+
formats: formatUrls(loopbackBase),
|
|
2189
|
+
lanFormats: lanBase ? formatUrls(lanBase) : null
|
|
2190
|
+
};
|
|
2191
|
+
}
|
|
2192
|
+
}, _class4);
|
|
2193
|
+
function formatUrls(base) {
|
|
2194
|
+
return {
|
|
2195
|
+
chat: `${base}/v1/chat/completions`,
|
|
2196
|
+
responses: `${base}/v1/responses`,
|
|
2197
|
+
messages: `${base}/v1/messages`,
|
|
2198
|
+
gemini: `${base}/v1beta/models/{model}:generateContent`
|
|
2199
|
+
};
|
|
2200
|
+
}
|
|
2201
|
+
function firstLanIPv4() {
|
|
2202
|
+
const ifaces = _os.networkInterfaces.call(void 0, );
|
|
2203
|
+
for (const addrs of Object.values(ifaces)) {
|
|
2204
|
+
if (!addrs) continue;
|
|
2205
|
+
for (const addr of addrs) {
|
|
2206
|
+
if (addr.family === "IPv4" && !addr.internal) return addr.address;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return null;
|
|
2210
|
+
}
|
|
2211
|
+
|
|
2212
|
+
// src/outbound-api/apiServerConfig.ts
|
|
2213
|
+
var OUTBOUND_API_SERVER_CONFIG_KEY = "outboundApiServer.config";
|
|
2214
|
+
var ALL_ENDPOINTS = ["chat", "responses", "messages", "gemini"];
|
|
2215
|
+
function defaultEndpointConfig(endpoint) {
|
|
2216
|
+
return {
|
|
2217
|
+
endpoint,
|
|
2218
|
+
defaultModel: "",
|
|
2219
|
+
backgroundModel: "",
|
|
2220
|
+
useSubscription: false
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
function defaultServerConfig() {
|
|
2224
|
+
return {
|
|
2225
|
+
enabled: false,
|
|
2226
|
+
networkBinding: false,
|
|
2227
|
+
endpoints: ALL_ENDPOINTS.map(defaultEndpointConfig),
|
|
2228
|
+
port: DEFAULT_OUTBOUND_PORT
|
|
2229
|
+
};
|
|
2230
|
+
}
|
|
2231
|
+
function normalizeServerConfig(raw) {
|
|
2232
|
+
const base = defaultServerConfig();
|
|
2233
|
+
if (!raw) return base;
|
|
2234
|
+
const byEndpoint = /* @__PURE__ */ new Map();
|
|
2235
|
+
for (const e of _nullishCoalesce(raw.endpoints, () => ( []))) {
|
|
2236
|
+
if (e && ALL_ENDPOINTS.includes(e.endpoint)) {
|
|
2237
|
+
byEndpoint.set(e.endpoint, {
|
|
2238
|
+
endpoint: e.endpoint,
|
|
2239
|
+
defaultModel: _nullishCoalesce(e.defaultModel, () => ( "")),
|
|
2240
|
+
backgroundModel: _nullishCoalesce(e.backgroundModel, () => ( "")),
|
|
2241
|
+
visionModel: e.visionModel,
|
|
2242
|
+
useSubscription: e.useSubscription === true,
|
|
2243
|
+
backgroundModelIds: e.backgroundModelIds
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
return {
|
|
2248
|
+
enabled: raw.enabled === true,
|
|
2249
|
+
networkBinding: raw.networkBinding === true,
|
|
2250
|
+
endpoints: ALL_ENDPOINTS.map(
|
|
2251
|
+
(ep) => _nullishCoalesce(byEndpoint.get(ep), () => ( defaultEndpointConfig(ep)))
|
|
2252
|
+
),
|
|
2253
|
+
port: _nullishCoalesce(raw.port, () => ( base.port))
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
async function loadServerConfig(store) {
|
|
2257
|
+
const raw = await store.get(
|
|
2258
|
+
OUTBOUND_API_SERVER_CONFIG_KEY
|
|
2259
|
+
);
|
|
2260
|
+
return normalizeServerConfig(raw);
|
|
2261
|
+
}
|
|
2262
|
+
async function saveServerConfig(store, config) {
|
|
2263
|
+
await store.set(OUTBOUND_API_SERVER_CONFIG_KEY, config);
|
|
2264
|
+
}
|
|
2265
|
+
function mergeServerConfig(current, patch) {
|
|
2266
|
+
return normalizeServerConfig({
|
|
2267
|
+
enabled: _nullishCoalesce(patch.enabled, () => ( current.enabled)),
|
|
2268
|
+
networkBinding: _nullishCoalesce(patch.networkBinding, () => ( current.networkBinding)),
|
|
2269
|
+
endpoints: _nullishCoalesce(patch.endpoints, () => ( current.endpoints)),
|
|
2270
|
+
port: _nullishCoalesce(patch.port, () => ( current.port))
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
// src/outbound-api/index.ts
|
|
2275
|
+
var instance2 = null;
|
|
2276
|
+
function getOutboundApiServer(deps, onPortChange) {
|
|
2277
|
+
if (!instance2) {
|
|
2278
|
+
if (!deps) {
|
|
2279
|
+
throw new Error("getOutboundApiServer: deps are required on first construction");
|
|
2280
|
+
}
|
|
2281
|
+
instance2 = new OutboundApiServer(deps, onPortChange);
|
|
2282
|
+
}
|
|
2283
|
+
return instance2;
|
|
2284
|
+
}
|
|
2285
|
+
function __resetOutboundApiServerForTests() {
|
|
2286
|
+
instance2 = null;
|
|
2287
|
+
}
|
|
2288
|
+
|
|
2289
|
+
// src/completion/DirectApiHandler.ts
|
|
2290
|
+
async function callOpenAICompletion(provider, apiKey, options, logger) {
|
|
2291
|
+
const request = {
|
|
2292
|
+
model: options.model,
|
|
2293
|
+
messages: options.messages.map((m) => convertMessageToOpenAI(m)),
|
|
2294
|
+
// Only set max_tokens if explicitly provided, otherwise let API use its default
|
|
2295
|
+
...options.maxTokens ? { max_tokens: options.maxTokens } : {},
|
|
2296
|
+
temperature: options.temperature,
|
|
2297
|
+
stream: false
|
|
2298
|
+
// For now, non-streaming only
|
|
2299
|
+
};
|
|
2300
|
+
if (options.thinkLevel && options.thinkLevel !== "none") {
|
|
2301
|
+
const effort = _thinkingconfig.getOpenAIReasoningEffort.call(void 0, options.thinkLevel);
|
|
2302
|
+
if (effort) {
|
|
2303
|
+
request.reasoning_effort = effort;
|
|
2304
|
+
}
|
|
2305
|
+
}
|
|
2306
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: false });
|
|
2307
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2308
|
+
logger.info("Calling OpenAI completion API", { url: apiUrl, model: options.model });
|
|
2309
|
+
const response = await fetch(apiUrl, {
|
|
2310
|
+
method: "POST",
|
|
2311
|
+
headers,
|
|
2312
|
+
body: JSON.stringify(request)
|
|
2313
|
+
});
|
|
2314
|
+
if (!response.ok) {
|
|
2315
|
+
const errorText = await response.text();
|
|
2316
|
+
return {
|
|
2317
|
+
success: false,
|
|
2318
|
+
error: `API error (${response.status}): ${errorText}`
|
|
2319
|
+
};
|
|
2320
|
+
}
|
|
2321
|
+
const data = await response.json();
|
|
2322
|
+
const choice = data.choices[0];
|
|
2323
|
+
return {
|
|
2324
|
+
success: true,
|
|
2325
|
+
message: {
|
|
2326
|
+
id: `msg_${Date.now()}`,
|
|
2327
|
+
role: "assistant",
|
|
2328
|
+
content: choice.message.content || "",
|
|
2329
|
+
timestamp: Date.now(),
|
|
2330
|
+
thinking: data.reasoning_content ? { content: data.reasoning_content } : void 0
|
|
2331
|
+
},
|
|
2332
|
+
usage: {
|
|
2333
|
+
promptTokens: data.usage.prompt_tokens,
|
|
2334
|
+
completionTokens: data.usage.completion_tokens,
|
|
2335
|
+
totalTokens: data.usage.total_tokens
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
}
|
|
2339
|
+
async function callAnthropicCompletion(provider, apiKey, options, logger) {
|
|
2340
|
+
const hasImages = options.messages.some((m) => m.images && m.images.length > 0);
|
|
2341
|
+
if (hasImages) {
|
|
2342
|
+
const systemMessages = options.messages.filter((m) => m.role === "system");
|
|
2343
|
+
const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
|
|
2344
|
+
const anthropicRequest2 = {
|
|
2345
|
+
model: options.model,
|
|
2346
|
+
max_tokens: options.maxTokens || 16384,
|
|
2347
|
+
temperature: options.temperature,
|
|
2348
|
+
...systemMessages.length > 0 ? { system: systemMessages.map((m) => m.content).join("\n\n") } : {},
|
|
2349
|
+
messages: nonSystemMessages.map((m) => convertMessageToAnthropic(m)),
|
|
2350
|
+
stream: false
|
|
2351
|
+
};
|
|
2352
|
+
const apiUrl2 = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: false });
|
|
2353
|
+
const headers2 = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2354
|
+
logger.info("Calling Anthropic completion API with images", { url: apiUrl2, model: options.model });
|
|
2355
|
+
const response2 = await fetch(apiUrl2, {
|
|
2356
|
+
method: "POST",
|
|
2357
|
+
headers: headers2,
|
|
2358
|
+
body: JSON.stringify(anthropicRequest2)
|
|
2359
|
+
});
|
|
2360
|
+
if (!response2.ok) {
|
|
2361
|
+
const errorText = await response2.text();
|
|
2362
|
+
return {
|
|
2363
|
+
success: false,
|
|
2364
|
+
error: `API error (${response2.status}): ${errorText}`
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
const anthropicResponse2 = await response2.json();
|
|
2368
|
+
const openaiResponse2 = _chunkSUEGJ75Dcjs.convertAnthropicToOpenAI.call(void 0, anthropicResponse2);
|
|
2369
|
+
const choice2 = openaiResponse2.choices[0];
|
|
2370
|
+
return {
|
|
2371
|
+
success: true,
|
|
2372
|
+
message: {
|
|
2373
|
+
id: `assistant-${Date.now()}`,
|
|
2374
|
+
role: "assistant",
|
|
2375
|
+
content: _optionalChain([choice2, 'access', _88 => _88.message, 'optionalAccess', _89 => _89.content]) || "",
|
|
2376
|
+
timestamp: Date.now()
|
|
2377
|
+
},
|
|
2378
|
+
usage: openaiResponse2.usage ? {
|
|
2379
|
+
promptTokens: openaiResponse2.usage.prompt_tokens,
|
|
2380
|
+
completionTokens: openaiResponse2.usage.completion_tokens,
|
|
2381
|
+
totalTokens: openaiResponse2.usage.total_tokens
|
|
2382
|
+
} : void 0
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
const openaiRequest = {
|
|
2386
|
+
model: options.model,
|
|
2387
|
+
messages: options.messages.map((m) => ({
|
|
2388
|
+
role: m.role,
|
|
2389
|
+
content: m.content
|
|
2390
|
+
})),
|
|
2391
|
+
// Anthropic requires max_tokens; use 16384 default if not explicitly set
|
|
2392
|
+
max_tokens: options.maxTokens || 16384,
|
|
2393
|
+
temperature: options.temperature,
|
|
2394
|
+
stream: false
|
|
2395
|
+
};
|
|
2396
|
+
const config = {
|
|
2397
|
+
defaultModel: options.model
|
|
2398
|
+
};
|
|
2399
|
+
const anthropicRequest = _chunkSUEGJ75Dcjs.convertOpenAIToAnthropic.call(void 0, openaiRequest, config);
|
|
2400
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: false });
|
|
2401
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2402
|
+
logger.info("Calling Anthropic completion API", { url: apiUrl, model: options.model });
|
|
2403
|
+
const response = await fetch(apiUrl, {
|
|
2404
|
+
method: "POST",
|
|
2405
|
+
headers,
|
|
2406
|
+
body: JSON.stringify(anthropicRequest)
|
|
2407
|
+
});
|
|
2408
|
+
if (!response.ok) {
|
|
2409
|
+
const errorText = await response.text();
|
|
2410
|
+
return {
|
|
2411
|
+
success: false,
|
|
2412
|
+
error: `API error (${response.status}): ${errorText}`
|
|
2413
|
+
};
|
|
2414
|
+
}
|
|
2415
|
+
const anthropicResponse = await response.json();
|
|
2416
|
+
const openaiResponse = _chunkSUEGJ75Dcjs.convertAnthropicToOpenAI.call(void 0, anthropicResponse);
|
|
2417
|
+
const choice = openaiResponse.choices[0];
|
|
2418
|
+
return {
|
|
2419
|
+
success: true,
|
|
2420
|
+
message: {
|
|
2421
|
+
id: `msg_${Date.now()}`,
|
|
2422
|
+
role: "assistant",
|
|
2423
|
+
content: choice.message.content || "",
|
|
2424
|
+
timestamp: Date.now(),
|
|
2425
|
+
thinking: openaiResponse.reasoning_content ? { content: openaiResponse.reasoning_content } : void 0
|
|
2426
|
+
},
|
|
2427
|
+
usage: {
|
|
2428
|
+
promptTokens: openaiResponse.usage.prompt_tokens,
|
|
2429
|
+
completionTokens: openaiResponse.usage.completion_tokens,
|
|
2430
|
+
totalTokens: openaiResponse.usage.total_tokens
|
|
2431
|
+
}
|
|
2432
|
+
};
|
|
2433
|
+
}
|
|
2434
|
+
async function callGeminiCompletion(provider, apiKey, options, logger) {
|
|
2435
|
+
const contents = [];
|
|
2436
|
+
let systemInstruction;
|
|
2437
|
+
for (const msg of options.messages) {
|
|
2438
|
+
if (msg.role === "system") {
|
|
2439
|
+
systemInstruction = { parts: [{ text: msg.content }] };
|
|
2440
|
+
} else {
|
|
2441
|
+
contents.push(convertMessageToGemini(msg));
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
const request = {
|
|
2445
|
+
contents,
|
|
2446
|
+
generationConfig: {
|
|
2447
|
+
...options.maxTokens ? { maxOutputTokens: options.maxTokens } : {},
|
|
2448
|
+
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
if (systemInstruction) {
|
|
2452
|
+
request.systemInstruction = systemInstruction;
|
|
2453
|
+
}
|
|
2454
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: false });
|
|
2455
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2456
|
+
logger.info("Calling Gemini completion API", { url: apiUrl, model: options.model });
|
|
2457
|
+
const response = await fetch(apiUrl, {
|
|
2458
|
+
method: "POST",
|
|
2459
|
+
headers,
|
|
2460
|
+
body: JSON.stringify(request)
|
|
2461
|
+
});
|
|
2462
|
+
if (!response.ok) {
|
|
2463
|
+
const errorText = await response.text();
|
|
2464
|
+
return {
|
|
2465
|
+
success: false,
|
|
2466
|
+
error: `API error (${response.status}): ${errorText}`
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
const data = await response.json();
|
|
2470
|
+
const candidates = data.candidates;
|
|
2471
|
+
if (!candidates || candidates.length === 0) {
|
|
2472
|
+
return {
|
|
2473
|
+
success: false,
|
|
2474
|
+
error: "No candidates in response"
|
|
2475
|
+
};
|
|
2476
|
+
}
|
|
2477
|
+
const candidate = candidates[0];
|
|
2478
|
+
const content = candidate.content;
|
|
2479
|
+
let textContent = "";
|
|
2480
|
+
if (_optionalChain([content, 'optionalAccess', _90 => _90.parts])) {
|
|
2481
|
+
for (const part of content.parts) {
|
|
2482
|
+
if (part.text) {
|
|
2483
|
+
textContent += part.text;
|
|
2484
|
+
}
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
const usage = data.usageMetadata;
|
|
2488
|
+
return {
|
|
2489
|
+
success: true,
|
|
2490
|
+
message: {
|
|
2491
|
+
id: `msg_${Date.now()}`,
|
|
2492
|
+
role: "assistant",
|
|
2493
|
+
content: textContent,
|
|
2494
|
+
timestamp: Date.now()
|
|
2495
|
+
},
|
|
2496
|
+
usage: usage ? {
|
|
2497
|
+
promptTokens: usage.promptTokenCount || 0,
|
|
2498
|
+
completionTokens: usage.candidatesTokenCount || 0,
|
|
2499
|
+
totalTokens: usage.totalTokenCount || 0
|
|
2500
|
+
} : void 0
|
|
2501
|
+
};
|
|
2502
|
+
}
|
|
2503
|
+
async function callOpenAIResponseCompletion(provider, apiKey, options, logger) {
|
|
2504
|
+
const input = [];
|
|
2505
|
+
for (const msg of options.messages) {
|
|
2506
|
+
if (msg.role === "system") {
|
|
2507
|
+
input.push({ role: "developer", content: msg.content });
|
|
2508
|
+
} else {
|
|
2509
|
+
input.push({ role: msg.role, content: msg.content });
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const request = {
|
|
2513
|
+
model: options.model,
|
|
2514
|
+
input,
|
|
2515
|
+
stream: false,
|
|
2516
|
+
...options.maxTokens ? { max_output_tokens: options.maxTokens } : {},
|
|
2517
|
+
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
2518
|
+
};
|
|
2519
|
+
if (options.thinkLevel && options.thinkLevel !== "none") {
|
|
2520
|
+
const effort = _thinkingconfig.getOpenAIReasoningEffort.call(void 0, options.thinkLevel);
|
|
2521
|
+
if (effort) {
|
|
2522
|
+
request.reasoning = { effort, summary: "auto" };
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: false });
|
|
2526
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2527
|
+
logger.info("Calling OpenAI Response API", { url: apiUrl, model: options.model });
|
|
2528
|
+
const response = await fetch(apiUrl, {
|
|
2529
|
+
method: "POST",
|
|
2530
|
+
headers,
|
|
2531
|
+
body: JSON.stringify(request)
|
|
2532
|
+
});
|
|
2533
|
+
if (!response.ok) {
|
|
2534
|
+
const errorText = await response.text();
|
|
2535
|
+
return {
|
|
2536
|
+
success: false,
|
|
2537
|
+
error: `API error (${response.status}): ${errorText}`
|
|
2538
|
+
};
|
|
2539
|
+
}
|
|
2540
|
+
const data = await response.json();
|
|
2541
|
+
let textContent = "";
|
|
2542
|
+
const output = data.output;
|
|
2543
|
+
if (output) {
|
|
2544
|
+
for (const item of output) {
|
|
2545
|
+
if (item.type === "message") {
|
|
2546
|
+
const content = item.content;
|
|
2547
|
+
if (content) {
|
|
2548
|
+
for (const part of content) {
|
|
2549
|
+
if (part.type === "output_text" && typeof part.text === "string") {
|
|
2550
|
+
textContent += part.text;
|
|
2551
|
+
}
|
|
2552
|
+
}
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
const usage = data.usage;
|
|
2558
|
+
return {
|
|
2559
|
+
success: true,
|
|
2560
|
+
message: {
|
|
2561
|
+
id: `msg_${Date.now()}`,
|
|
2562
|
+
role: "assistant",
|
|
2563
|
+
content: textContent,
|
|
2564
|
+
timestamp: Date.now()
|
|
2565
|
+
},
|
|
2566
|
+
usage: usage ? {
|
|
2567
|
+
promptTokens: usage.input_tokens || 0,
|
|
2568
|
+
completionTokens: usage.output_tokens || 0,
|
|
2569
|
+
totalTokens: (usage.input_tokens || 0) + (usage.output_tokens || 0)
|
|
2570
|
+
} : void 0
|
|
2571
|
+
};
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
// src/completion/StreamHandler.ts
|
|
2575
|
+
|
|
2576
|
+
async function streamOpenAICompletion(provider, apiKey, options, messageId, callbacks, logger) {
|
|
2577
|
+
const request = {
|
|
2578
|
+
model: options.model,
|
|
2579
|
+
messages: options.messages.map((m) => convertMessageToOpenAI(m)),
|
|
2580
|
+
// Only set max_tokens if explicitly provided, otherwise let API use its default
|
|
2581
|
+
...options.maxTokens ? { max_tokens: options.maxTokens } : {},
|
|
2582
|
+
temperature: options.temperature,
|
|
2583
|
+
stream: true
|
|
2584
|
+
};
|
|
2585
|
+
if (options.thinkLevel && options.thinkLevel !== "none") {
|
|
2586
|
+
const effort = _thinkingconfig.getOpenAIReasoningEffort.call(void 0, options.thinkLevel);
|
|
2587
|
+
if (effort) {
|
|
2588
|
+
request.reasoning_effort = effort;
|
|
2589
|
+
}
|
|
2590
|
+
}
|
|
2591
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: true });
|
|
2592
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2593
|
+
if (options.nativeSearchAugmentation) {
|
|
2594
|
+
_chunkV5KPWNYXcjs.applyAugmentation.call(void 0, request, options.nativeSearchAugmentation);
|
|
2595
|
+
}
|
|
2596
|
+
logger.info("Streaming OpenAI completion request", {
|
|
2597
|
+
url: apiUrl,
|
|
2598
|
+
providerId: options.providerId,
|
|
2599
|
+
model: options.model,
|
|
2600
|
+
maxTokens: options.maxTokens,
|
|
2601
|
+
max_tokens_in_request: request.max_tokens,
|
|
2602
|
+
temperature: options.temperature,
|
|
2603
|
+
messagesCount: options.messages.length,
|
|
2604
|
+
hasNativeSearchAugmentation: !!options.nativeSearchAugmentation
|
|
2605
|
+
});
|
|
2606
|
+
const response = await fetch(apiUrl, {
|
|
2607
|
+
method: "POST",
|
|
2608
|
+
headers,
|
|
2609
|
+
body: JSON.stringify(request)
|
|
2610
|
+
});
|
|
2611
|
+
if (!response.ok) {
|
|
2612
|
+
const errorText = await response.text();
|
|
2613
|
+
_optionalChain([callbacks, 'access', _91 => _91.onError, 'optionalCall', _92 => _92(`API error (${response.status}): ${errorText}`)]);
|
|
2614
|
+
return;
|
|
2615
|
+
}
|
|
2616
|
+
const result = await _chunkHQP3K7PUcjs.streamSSEResponse.call(void 0, response, "openai", {
|
|
2617
|
+
onDelta: callbacks.onDelta,
|
|
2618
|
+
onReasoning: callbacks.onReasoning,
|
|
2619
|
+
onAudio: callbacks.onAudio,
|
|
2620
|
+
onVideo: callbacks.onVideo,
|
|
2621
|
+
onError: callbacks.onError
|
|
2622
|
+
});
|
|
2623
|
+
_optionalChain([callbacks, 'access', _93 => _93.onDone, 'optionalCall', _94 => _94(
|
|
2624
|
+
{
|
|
2625
|
+
id: messageId,
|
|
2626
|
+
role: "assistant",
|
|
2627
|
+
content: result.content,
|
|
2628
|
+
timestamp: Date.now(),
|
|
2629
|
+
thinking: result.reasoning ? { content: result.reasoning } : void 0,
|
|
2630
|
+
audios: result.audios.length > 0 ? result.audios : void 0,
|
|
2631
|
+
videos: result.videos.length > 0 ? result.videos : void 0
|
|
2632
|
+
},
|
|
2633
|
+
result.usage,
|
|
2634
|
+
result.metrics
|
|
2635
|
+
)]);
|
|
2636
|
+
}
|
|
2637
|
+
async function streamAnthropicCompletion(provider, apiKey, options, messageId, callbacks, logger) {
|
|
2638
|
+
const hasImages = options.messages.some((m) => m.images && m.images.length > 0);
|
|
2639
|
+
const MAX_TOKENS_FOR_THINKING = 16384;
|
|
2640
|
+
let effectiveMaxTokens = options.maxTokens || 16384;
|
|
2641
|
+
const thinkingMaxTokens = options.thinkLevel && options.thinkLevel !== "none" ? Math.min(effectiveMaxTokens, MAX_TOKENS_FOR_THINKING) : effectiveMaxTokens;
|
|
2642
|
+
const thinkingConfig = options.thinkLevel && options.thinkLevel !== "none" ? _thinkingconfig.buildAnthropicThinking.call(void 0, options.model, options.thinkLevel, thinkingMaxTokens) : void 0;
|
|
2643
|
+
if (thinkingConfig) {
|
|
2644
|
+
effectiveMaxTokens = thinkingMaxTokens;
|
|
2645
|
+
}
|
|
2646
|
+
logger.debug("Anthropic thinking configuration", {
|
|
2647
|
+
thinkLevel: options.thinkLevel,
|
|
2648
|
+
thinkingConfig,
|
|
2649
|
+
effectiveMaxTokens
|
|
2650
|
+
});
|
|
2651
|
+
let anthropicRequest;
|
|
2652
|
+
if (hasImages) {
|
|
2653
|
+
const systemMessages = options.messages.filter((m) => m.role === "system");
|
|
2654
|
+
const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
|
|
2655
|
+
anthropicRequest = {
|
|
2656
|
+
model: options.model,
|
|
2657
|
+
max_tokens: effectiveMaxTokens,
|
|
2658
|
+
// Omit temperature when thinking is enabled (Anthropic will use default temperature=1)
|
|
2659
|
+
...thinkingConfig ? {} : { temperature: options.temperature },
|
|
2660
|
+
...systemMessages.length > 0 ? { system: systemMessages.map((m) => m.content).join("\n\n") } : {},
|
|
2661
|
+
messages: nonSystemMessages.map((m) => convertMessageToAnthropic(m)),
|
|
2662
|
+
stream: true,
|
|
2663
|
+
...thinkingConfig ? { thinking: thinkingConfig } : {}
|
|
2664
|
+
};
|
|
2665
|
+
} else {
|
|
2666
|
+
const config = {
|
|
2667
|
+
defaultModel: options.model
|
|
2668
|
+
};
|
|
2669
|
+
const openaiRequest = {
|
|
2670
|
+
model: options.model,
|
|
2671
|
+
messages: options.messages.map((m) => ({
|
|
2672
|
+
role: m.role,
|
|
2673
|
+
content: m.content
|
|
2674
|
+
})),
|
|
2675
|
+
// Anthropic requires max_tokens; use adjusted value
|
|
2676
|
+
max_tokens: effectiveMaxTokens,
|
|
2677
|
+
// Omit temperature when thinking is enabled (Anthropic will use default temperature=1)
|
|
2678
|
+
temperature: thinkingConfig ? void 0 : options.temperature,
|
|
2679
|
+
stream: true
|
|
2680
|
+
};
|
|
2681
|
+
anthropicRequest = _chunkSUEGJ75Dcjs.convertOpenAIToAnthropic.call(void 0, openaiRequest, config);
|
|
2682
|
+
if (thinkingConfig) {
|
|
2683
|
+
anthropicRequest.thinking = thinkingConfig;
|
|
2684
|
+
delete anthropicRequest.temperature;
|
|
2685
|
+
}
|
|
2686
|
+
}
|
|
2687
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: true });
|
|
2688
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2689
|
+
if (options.nativeSearchAugmentation) {
|
|
2690
|
+
_chunkV5KPWNYXcjs.applyAugmentation.call(void 0, anthropicRequest, options.nativeSearchAugmentation);
|
|
2691
|
+
}
|
|
2692
|
+
logger.info("Streaming Anthropic completion request", {
|
|
2693
|
+
url: apiUrl,
|
|
2694
|
+
model: anthropicRequest.model,
|
|
2695
|
+
messagesCount: _optionalChain([anthropicRequest, 'access', _95 => _95.messages, 'optionalAccess', _96 => _96.length]),
|
|
2696
|
+
max_tokens: anthropicRequest.max_tokens,
|
|
2697
|
+
temperature: anthropicRequest.temperature,
|
|
2698
|
+
stream: anthropicRequest.stream,
|
|
2699
|
+
hasImages,
|
|
2700
|
+
hasThinking: !!anthropicRequest.thinking,
|
|
2701
|
+
hasNativeSearchAugmentation: !!options.nativeSearchAugmentation
|
|
2702
|
+
});
|
|
2703
|
+
const response = await fetch(apiUrl, {
|
|
2704
|
+
method: "POST",
|
|
2705
|
+
headers,
|
|
2706
|
+
body: JSON.stringify(anthropicRequest)
|
|
2707
|
+
});
|
|
2708
|
+
logger.debug("Anthropic response status", { status: response.status });
|
|
2709
|
+
if (!response.ok) {
|
|
2710
|
+
const errorText = await response.text();
|
|
2711
|
+
logger.error("Anthropic API error", void 0, { status: response.status, errorText });
|
|
2712
|
+
_optionalChain([callbacks, 'access', _97 => _97.onError, 'optionalCall', _98 => _98(`API error (${response.status}): ${errorText}`)]);
|
|
2713
|
+
return;
|
|
2714
|
+
}
|
|
2715
|
+
const result = await _chunkHQP3K7PUcjs.streamSSEResponse.call(void 0, response, "anthropic", {
|
|
2716
|
+
onDelta: callbacks.onDelta,
|
|
2717
|
+
onReasoning: callbacks.onReasoning,
|
|
2718
|
+
onError: callbacks.onError,
|
|
2719
|
+
onBlock: callbacks.onBlock
|
|
2720
|
+
});
|
|
2721
|
+
logger.info("Anthropic stream complete", {
|
|
2722
|
+
contentLength: result.content.length,
|
|
2723
|
+
reasoningLength: _optionalChain([result, 'access', _99 => _99.reasoning, 'optionalAccess', _100 => _100.length]) || 0,
|
|
2724
|
+
blocksCount: result.blocks.length
|
|
2725
|
+
});
|
|
2726
|
+
_optionalChain([callbacks, 'access', _101 => _101.onDone, 'optionalCall', _102 => _102(
|
|
2727
|
+
{
|
|
2728
|
+
id: messageId,
|
|
2729
|
+
role: "assistant",
|
|
2730
|
+
content: result.content,
|
|
2731
|
+
timestamp: Date.now(),
|
|
2732
|
+
thinking: result.reasoning ? { content: result.reasoning } : void 0,
|
|
2733
|
+
blocks: result.blocks.length > 0 ? result.blocks : void 0
|
|
2734
|
+
},
|
|
2735
|
+
result.usage,
|
|
2736
|
+
result.metrics
|
|
2737
|
+
)]);
|
|
2738
|
+
}
|
|
2739
|
+
async function streamGeminiCompletion(provider, apiKey, options, messageId, callbacks, logger) {
|
|
2740
|
+
const contents = [];
|
|
2741
|
+
let systemInstruction;
|
|
2742
|
+
for (const msg of options.messages) {
|
|
2743
|
+
if (msg.role === "system") {
|
|
2744
|
+
systemInstruction = { parts: [{ text: msg.content }] };
|
|
2745
|
+
} else {
|
|
2746
|
+
contents.push(convertMessageToGemini(msg));
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
const request = {
|
|
2750
|
+
contents,
|
|
2751
|
+
generationConfig: {
|
|
2752
|
+
...options.maxTokens ? { maxOutputTokens: options.maxTokens } : {},
|
|
2753
|
+
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
2754
|
+
}
|
|
2755
|
+
};
|
|
2756
|
+
if (systemInstruction) {
|
|
2757
|
+
request.systemInstruction = systemInstruction;
|
|
2758
|
+
}
|
|
2759
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: true });
|
|
2760
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2761
|
+
if (options.nativeSearchAugmentation) {
|
|
2762
|
+
_chunkV5KPWNYXcjs.applyAugmentation.call(void 0, request, options.nativeSearchAugmentation);
|
|
2763
|
+
}
|
|
2764
|
+
logger.info("Streaming Gemini completion request", {
|
|
2765
|
+
url: apiUrl,
|
|
2766
|
+
model: options.model,
|
|
2767
|
+
contentsCount: contents.length,
|
|
2768
|
+
hasSystemInstruction: !!systemInstruction,
|
|
2769
|
+
maxOutputTokens: options.maxTokens,
|
|
2770
|
+
temperature: options.temperature,
|
|
2771
|
+
hasNativeSearchAugmentation: !!options.nativeSearchAugmentation
|
|
2772
|
+
});
|
|
2773
|
+
const response = await fetch(apiUrl, {
|
|
2774
|
+
method: "POST",
|
|
2775
|
+
headers,
|
|
2776
|
+
body: JSON.stringify(request)
|
|
2777
|
+
});
|
|
2778
|
+
logger.debug("Gemini response status", { status: response.status });
|
|
2779
|
+
if (!response.ok) {
|
|
2780
|
+
const errorText = await response.text();
|
|
2781
|
+
logger.error("Gemini API error", void 0, { status: response.status, errorText });
|
|
2782
|
+
_optionalChain([callbacks, 'access', _103 => _103.onError, 'optionalCall', _104 => _104(`API error (${response.status}): ${errorText}`)]);
|
|
2783
|
+
return;
|
|
2784
|
+
}
|
|
2785
|
+
const result = await _chunkHQP3K7PUcjs.streamSSEResponse.call(void 0, response, "gemini", {
|
|
2786
|
+
onDelta: callbacks.onDelta,
|
|
2787
|
+
onReasoning: callbacks.onReasoning,
|
|
2788
|
+
onError: callbacks.onError
|
|
2789
|
+
});
|
|
2790
|
+
logger.info("Gemini stream complete", { contentLength: result.content.length });
|
|
2791
|
+
_optionalChain([callbacks, 'access', _105 => _105.onDone, 'optionalCall', _106 => _106(
|
|
2792
|
+
{
|
|
2793
|
+
id: messageId,
|
|
2794
|
+
role: "assistant",
|
|
2795
|
+
content: result.content,
|
|
2796
|
+
timestamp: Date.now(),
|
|
2797
|
+
thinking: result.reasoning ? { content: result.reasoning } : void 0
|
|
2798
|
+
},
|
|
2799
|
+
result.usage,
|
|
2800
|
+
result.metrics
|
|
2801
|
+
)]);
|
|
2802
|
+
}
|
|
2803
|
+
async function streamOpenAIResponseCompletion(provider, apiKey, options, messageId, callbacks, logger) {
|
|
2804
|
+
const input = [];
|
|
2805
|
+
for (const msg of options.messages) {
|
|
2806
|
+
if (msg.role === "system") {
|
|
2807
|
+
input.push({ role: "developer", content: msg.content });
|
|
2808
|
+
} else {
|
|
2809
|
+
input.push({ role: msg.role, content: msg.content });
|
|
2810
|
+
}
|
|
2811
|
+
}
|
|
2812
|
+
const request = {
|
|
2813
|
+
model: options.model,
|
|
2814
|
+
input,
|
|
2815
|
+
stream: true,
|
|
2816
|
+
...options.maxTokens ? { max_output_tokens: options.maxTokens } : {},
|
|
2817
|
+
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
2818
|
+
};
|
|
2819
|
+
if (options.thinkLevel && options.thinkLevel !== "none") {
|
|
2820
|
+
const effort = _thinkingconfig.getOpenAIReasoningEffort.call(void 0, options.thinkLevel);
|
|
2821
|
+
if (effort) {
|
|
2822
|
+
request.reasoning = { effort, summary: "auto" };
|
|
2823
|
+
}
|
|
2824
|
+
}
|
|
2825
|
+
const apiUrl = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: options.model, stream: true });
|
|
2826
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
2827
|
+
logger.info("Streaming OpenAI Response API request", {
|
|
2828
|
+
url: apiUrl,
|
|
2829
|
+
providerId: options.providerId,
|
|
2830
|
+
model: options.model,
|
|
2831
|
+
maxOutputTokens: options.maxTokens,
|
|
2832
|
+
temperature: options.temperature,
|
|
2833
|
+
inputCount: input.length,
|
|
2834
|
+
reasoning: request.reasoning
|
|
2835
|
+
});
|
|
2836
|
+
const response = await fetch(apiUrl, {
|
|
2837
|
+
method: "POST",
|
|
2838
|
+
headers,
|
|
2839
|
+
body: JSON.stringify(request)
|
|
2840
|
+
});
|
|
2841
|
+
if (!response.ok) {
|
|
2842
|
+
const errorText = await response.text();
|
|
2843
|
+
_optionalChain([callbacks, 'access', _107 => _107.onError, 'optionalCall', _108 => _108(`API error (${response.status}): ${errorText}`)]);
|
|
2844
|
+
return;
|
|
2845
|
+
}
|
|
2846
|
+
const result = await _chunkHQP3K7PUcjs.streamSSEResponse.call(void 0, response, "openai-response", {
|
|
2847
|
+
onDelta: callbacks.onDelta,
|
|
2848
|
+
onReasoning: callbacks.onReasoning,
|
|
2849
|
+
onError: callbacks.onError
|
|
2850
|
+
});
|
|
2851
|
+
logger.info("OpenAI Response API stream complete", { contentLength: result.content.length });
|
|
2852
|
+
_optionalChain([callbacks, 'access', _109 => _109.onDone, 'optionalCall', _110 => _110(
|
|
2853
|
+
{
|
|
2854
|
+
id: messageId,
|
|
2855
|
+
role: "assistant",
|
|
2856
|
+
content: result.content,
|
|
2857
|
+
timestamp: Date.now(),
|
|
2858
|
+
thinking: result.reasoning ? { content: result.reasoning } : void 0
|
|
2859
|
+
},
|
|
2860
|
+
result.usage,
|
|
2861
|
+
result.metrics
|
|
2862
|
+
)]);
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
// src/completion/ThinkingResolver.ts
|
|
2866
|
+
|
|
2867
|
+
|
|
2868
|
+
|
|
2869
|
+
|
|
2870
|
+
|
|
2871
|
+
|
|
2872
|
+
|
|
2873
|
+
async function resolveEffectiveMaxTokens(llmConfig, getProvider, logger, providerId, modelId, sessionMaxTokens) {
|
|
2874
|
+
if (sessionMaxTokens !== void 0 && sessionMaxTokens > 0) {
|
|
2875
|
+
logger.debug("Using session maxTokens", { sessionMaxTokens });
|
|
2876
|
+
return sessionMaxTokens;
|
|
2877
|
+
}
|
|
2878
|
+
try {
|
|
2879
|
+
const globalParams = await llmConfig.getGlobalModelParameters();
|
|
2880
|
+
if (_optionalChain([globalParams, 'optionalAccess', _111 => _111.maxTokens, 'optionalAccess', _112 => _112.enabled]) && globalParams.maxTokens.value > 0) {
|
|
2881
|
+
logger.debug("Using global maxTokens", { maxTokens: globalParams.maxTokens.value });
|
|
2882
|
+
return globalParams.maxTokens.value;
|
|
2883
|
+
}
|
|
2884
|
+
} catch (err) {
|
|
2885
|
+
logger.warn("Failed to get global params", err instanceof Error ? err : void 0);
|
|
2886
|
+
}
|
|
2887
|
+
const MAX_TOKENS_CAP = 131072;
|
|
2888
|
+
const provider = await getProvider(providerId);
|
|
2889
|
+
if (provider) {
|
|
2890
|
+
const modelConfig = _optionalChain([provider, 'access', _113 => _113.modelConfigs, 'optionalAccess', _114 => _114.find, 'call', _115 => _115((m) => m.id === modelId)]);
|
|
2891
|
+
if (_optionalChain([modelConfig, 'optionalAccess', _116 => _116.maxTokens]) && modelConfig.maxTokens > 0) {
|
|
2892
|
+
const cappedMaxTokens = Math.min(modelConfig.maxTokens, MAX_TOKENS_CAP);
|
|
2893
|
+
logger.debug("Using model config maxTokens", {
|
|
2894
|
+
maxTokens: modelConfig.maxTokens,
|
|
2895
|
+
cappedMaxTokens
|
|
2896
|
+
});
|
|
2897
|
+
return cappedMaxTokens;
|
|
2898
|
+
}
|
|
2899
|
+
if (provider.modelGroups) {
|
|
2900
|
+
for (const group of provider.modelGroups) {
|
|
2901
|
+
const model = _optionalChain([group, 'access', _117 => _117.models, 'optionalAccess', _118 => _118.find, 'call', _119 => _119((m) => m.id === modelId)]);
|
|
2902
|
+
if (_optionalChain([model, 'optionalAccess', _120 => _120.maxTokens]) && model.maxTokens > 0) {
|
|
2903
|
+
const cappedMaxTokens = Math.min(model.maxTokens, MAX_TOKENS_CAP);
|
|
2904
|
+
logger.debug("Using modelGroup model maxTokens", {
|
|
2905
|
+
maxTokens: model.maxTokens,
|
|
2906
|
+
cappedMaxTokens
|
|
2907
|
+
});
|
|
2908
|
+
return cappedMaxTokens;
|
|
2909
|
+
}
|
|
2910
|
+
}
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
try {
|
|
2914
|
+
const discoveredMaxTokens = await llmConfig.getDiscoveredModelMaxTokens(providerId, modelId);
|
|
2915
|
+
if (discoveredMaxTokens && discoveredMaxTokens > 0) {
|
|
2916
|
+
const cappedMaxTokens = Math.min(discoveredMaxTokens, MAX_TOKENS_CAP);
|
|
2917
|
+
logger.debug("Using discovered model maxTokens", {
|
|
2918
|
+
discoveredMaxTokens,
|
|
2919
|
+
cappedMaxTokens
|
|
2920
|
+
});
|
|
2921
|
+
return cappedMaxTokens;
|
|
2922
|
+
}
|
|
2923
|
+
} catch (err) {
|
|
2924
|
+
logger.warn("Failed to get discovered model maxTokens", err instanceof Error ? err : void 0);
|
|
2925
|
+
}
|
|
2926
|
+
logger.debug("No maxTokens configured, returning undefined");
|
|
2927
|
+
return void 0;
|
|
2928
|
+
}
|
|
2929
|
+
async function getRequiredMaxTokens(llmConfig, getProvider, logger, providerId, modelId, sessionMaxTokens) {
|
|
2930
|
+
const resolved = await resolveEffectiveMaxTokens(llmConfig, getProvider, logger, providerId, modelId, sessionMaxTokens);
|
|
2931
|
+
if (resolved !== void 0) {
|
|
2932
|
+
return resolved;
|
|
2933
|
+
}
|
|
2934
|
+
logger.debug("Using default maxTokens", { defaultMaxTokens: _thinkingconfig.DEFAULT_MAX_TOKENS });
|
|
2935
|
+
return _thinkingconfig.DEFAULT_MAX_TOKENS;
|
|
2936
|
+
}
|
|
2937
|
+
async function resolveThinkingBudget(getProvider, logger, providerId, modelId, maxTokens, thinkLevel) {
|
|
2938
|
+
if (thinkLevel === "none" || !_thinkingconfig.isReasoningModel.call(void 0, modelId)) {
|
|
2939
|
+
return {
|
|
2940
|
+
adjustedMaxTokens: maxTokens,
|
|
2941
|
+
thinkingBudget: void 0,
|
|
2942
|
+
thinkingConfig: void 0
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
const thinkingBudget = _thinkingconfig.calculateThinkingBudget.call(void 0, modelId, thinkLevel, maxTokens);
|
|
2946
|
+
const provider = await getProvider(providerId);
|
|
2947
|
+
const providerName = _optionalChain([provider, 'optionalAccess', _121 => _121.name, 'optionalAccess', _122 => _122.toLowerCase, 'call', _123 => _123()]) || "";
|
|
2948
|
+
const apiFormat = provider ? _chunkA2YMUCBTcjs.resolveApiFormat.call(void 0, provider) : "openai";
|
|
2949
|
+
if (apiFormat === "anthropic" || providerName === "anthropic" || providerName.includes("claude")) {
|
|
2950
|
+
const thinkingConfig = _thinkingconfig.buildAnthropicThinking.call(void 0, modelId, thinkLevel, maxTokens);
|
|
2951
|
+
const adjustedMaxTokens = _thinkingconfig.getClaudeMaxTokens.call(void 0, maxTokens, thinkingBudget) || maxTokens;
|
|
2952
|
+
logger.debug("Claude model thinking budget", {
|
|
2953
|
+
thinkingBudget,
|
|
2954
|
+
adjustedMaxTokens
|
|
2955
|
+
});
|
|
2956
|
+
return {
|
|
2957
|
+
adjustedMaxTokens,
|
|
2958
|
+
thinkingBudget,
|
|
2959
|
+
thinkingConfig
|
|
2960
|
+
};
|
|
2961
|
+
}
|
|
2962
|
+
logger.debug("Non-Claude model thinking budget", { thinkingBudget });
|
|
2963
|
+
return {
|
|
2964
|
+
adjustedMaxTokens: maxTokens,
|
|
2965
|
+
thinkingBudget,
|
|
2966
|
+
thinkingConfig: thinkingBudget ? { type: "enabled", budget_tokens: thinkingBudget } : void 0
|
|
2967
|
+
};
|
|
2968
|
+
}
|
|
2969
|
+
|
|
2970
|
+
// src/completion/ToolExecutor.ts
|
|
2971
|
+
function logToolFormat(tools, logger) {
|
|
2972
|
+
if (!Array.isArray(tools) || tools.length === 0) {
|
|
2973
|
+
logger.warn("No tools provided");
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
const firstTool = tools[0];
|
|
2977
|
+
if ("function" in firstTool) {
|
|
2978
|
+
const openaiTools = tools;
|
|
2979
|
+
logger.info("Tools configured (OpenAI format)", {
|
|
2980
|
+
count: openaiTools.length,
|
|
2981
|
+
tools: openaiTools.map((t) => t.function.name),
|
|
2982
|
+
firstTool: {
|
|
2983
|
+
name: firstTool.function.name,
|
|
2984
|
+
description: _optionalChain([firstTool, 'access', _124 => _124.function, 'access', _125 => _125.description, 'optionalAccess', _126 => _126.slice, 'call', _127 => _127(0, 100)]),
|
|
2985
|
+
parameters: firstTool.function.parameters
|
|
2986
|
+
}
|
|
2987
|
+
});
|
|
2988
|
+
} else if ("input_schema" in firstTool) {
|
|
2989
|
+
const anthropicTools = tools;
|
|
2990
|
+
logger.info("Tools configured (Anthropic format)", {
|
|
2991
|
+
count: anthropicTools.length,
|
|
2992
|
+
tools: anthropicTools.map((t) => t.name),
|
|
2993
|
+
firstTool: {
|
|
2994
|
+
name: firstTool.name,
|
|
2995
|
+
description: _optionalChain([firstTool, 'access', _128 => _128.description, 'optionalAccess', _129 => _129.slice, 'call', _130 => _130(0, 100)]),
|
|
2996
|
+
input_schema: firstTool.input_schema
|
|
2997
|
+
}
|
|
2998
|
+
});
|
|
2999
|
+
} else if ("functionDeclarations" in firstTool) {
|
|
3000
|
+
const geminiTools = tools;
|
|
3001
|
+
const allDeclarations = geminiTools.flatMap((t) => t.functionDeclarations);
|
|
3002
|
+
logger.info("Tools configured (Gemini format)", {
|
|
3003
|
+
count: allDeclarations.length,
|
|
3004
|
+
tools: allDeclarations.map((t) => t.name),
|
|
3005
|
+
firstTool: {
|
|
3006
|
+
name: _optionalChain([allDeclarations, 'access', _131 => _131[0], 'optionalAccess', _132 => _132.name]),
|
|
3007
|
+
description: _optionalChain([allDeclarations, 'access', _133 => _133[0], 'optionalAccess', _134 => _134.description, 'optionalAccess', _135 => _135.slice, 'call', _136 => _136(0, 100)]),
|
|
3008
|
+
parameters: _optionalChain([allDeclarations, 'access', _137 => _137[0], 'optionalAccess', _138 => _138.parameters])
|
|
3009
|
+
}
|
|
3010
|
+
});
|
|
3011
|
+
}
|
|
3012
|
+
}
|
|
3013
|
+
function buildToolRequest(apiFormat, conversationMessages, actualModel, options, provider) {
|
|
3014
|
+
let requestBody;
|
|
3015
|
+
let url;
|
|
3016
|
+
if (apiFormat === "google") {
|
|
3017
|
+
const contents = [];
|
|
3018
|
+
for (const msg of conversationMessages) {
|
|
3019
|
+
if (msg.role !== "system") {
|
|
3020
|
+
contents.push({
|
|
3021
|
+
role: msg.role === "assistant" ? "model" : "user",
|
|
3022
|
+
parts: [{ text: msg.content }]
|
|
3023
|
+
});
|
|
3024
|
+
}
|
|
3025
|
+
}
|
|
3026
|
+
requestBody = {
|
|
3027
|
+
contents,
|
|
3028
|
+
tools: options.tools
|
|
3029
|
+
};
|
|
3030
|
+
url = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: actualModel, stream: true });
|
|
3031
|
+
} else if (apiFormat === "anthropic") {
|
|
3032
|
+
requestBody = {
|
|
3033
|
+
model: actualModel,
|
|
3034
|
+
messages: conversationMessages.filter((m) => m.role !== "system").map((m) => ({
|
|
3035
|
+
role: m.role,
|
|
3036
|
+
content: m.content
|
|
3037
|
+
})),
|
|
3038
|
+
max_tokens: options.maxTokens || 4096,
|
|
3039
|
+
temperature: _nullishCoalesce(options.temperature, () => ( 0.7)),
|
|
3040
|
+
stream: true,
|
|
3041
|
+
tools: options.tools
|
|
3042
|
+
};
|
|
3043
|
+
const systemMsg = conversationMessages.find((m) => m.role === "system");
|
|
3044
|
+
if (systemMsg) {
|
|
3045
|
+
requestBody.system = systemMsg.content;
|
|
3046
|
+
}
|
|
3047
|
+
url = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: actualModel, stream: true });
|
|
3048
|
+
} else {
|
|
3049
|
+
requestBody = {
|
|
3050
|
+
model: actualModel,
|
|
3051
|
+
messages: conversationMessages.map((m) => ({
|
|
3052
|
+
role: m.role,
|
|
3053
|
+
content: m.content
|
|
3054
|
+
})),
|
|
3055
|
+
max_tokens: options.maxTokens || 4096,
|
|
3056
|
+
temperature: _nullishCoalesce(options.temperature, () => ( 0.7)),
|
|
3057
|
+
stream: true,
|
|
3058
|
+
tools: options.tools
|
|
3059
|
+
};
|
|
3060
|
+
url = _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: actualModel, stream: true });
|
|
3061
|
+
}
|
|
3062
|
+
return { requestBody, url };
|
|
3063
|
+
}
|
|
3064
|
+
function extractDeltaContent(rawJson, apiFormat) {
|
|
3065
|
+
const json = rawJson;
|
|
3066
|
+
if (apiFormat === "google") {
|
|
3067
|
+
const parts = _optionalChain([json, 'access', _139 => _139.candidates, 'optionalAccess', _140 => _140[0], 'optionalAccess', _141 => _141.content, 'optionalAccess', _142 => _142.parts]) || [];
|
|
3068
|
+
let text = "";
|
|
3069
|
+
for (const part of parts) {
|
|
3070
|
+
if (part.thought === true) continue;
|
|
3071
|
+
if (part.text) text += part.text;
|
|
3072
|
+
}
|
|
3073
|
+
return text;
|
|
3074
|
+
} else if (apiFormat === "anthropic") {
|
|
3075
|
+
return _optionalChain([json, 'access', _143 => _143.delta, 'optionalAccess', _144 => _144.text]) || "";
|
|
3076
|
+
} else {
|
|
3077
|
+
return _optionalChain([json, 'access', _145 => _145.choices, 'optionalAccess', _146 => _146[0], 'optionalAccess', _147 => _147.delta, 'optionalAccess', _148 => _148.content]) || "";
|
|
3078
|
+
}
|
|
3079
|
+
}
|
|
3080
|
+
function extractDeltaReasoning(rawJson, apiFormat) {
|
|
3081
|
+
const json = rawJson;
|
|
3082
|
+
if (apiFormat === "google") {
|
|
3083
|
+
const parts = _optionalChain([json, 'access', _149 => _149.candidates, 'optionalAccess', _150 => _150[0], 'optionalAccess', _151 => _151.content, 'optionalAccess', _152 => _152.parts]) || [];
|
|
3084
|
+
let reasoning = "";
|
|
3085
|
+
for (const part of parts) {
|
|
3086
|
+
if (part.thought === true && part.text) reasoning += part.text;
|
|
3087
|
+
}
|
|
3088
|
+
return reasoning;
|
|
3089
|
+
} else if (apiFormat === "anthropic") {
|
|
3090
|
+
return "";
|
|
3091
|
+
} else {
|
|
3092
|
+
const delta = _optionalChain([json, 'access', _153 => _153.choices, 'optionalAccess', _154 => _154[0], 'optionalAccess', _155 => _155.delta]);
|
|
3093
|
+
return (_optionalChain([delta, 'optionalAccess', _156 => _156.thinking, 'optionalAccess', _157 => _157.content]) || "") + (_optionalChain([delta, 'optionalAccess', _158 => _158.reasoning_content]) || "");
|
|
3094
|
+
}
|
|
3095
|
+
}
|
|
3096
|
+
function extractToolCalls(rawJson, apiFormat, toolCalls, callbacks, logger, pendingOpenAIToolCalls) {
|
|
3097
|
+
const json = rawJson;
|
|
3098
|
+
if (apiFormat === "google") {
|
|
3099
|
+
const parts = _optionalChain([json, 'access', _159 => _159.candidates, 'optionalAccess', _160 => _160[0], 'optionalAccess', _161 => _161.content, 'optionalAccess', _162 => _162.parts]) || [];
|
|
3100
|
+
for (const part of parts) {
|
|
3101
|
+
if (part.functionCall) {
|
|
3102
|
+
logger.info("Function call detected", { functionCall: part.functionCall });
|
|
3103
|
+
const toolCall = {
|
|
3104
|
+
id: `call_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
3105
|
+
name: part.functionCall.name,
|
|
3106
|
+
args: part.functionCall.args || {}
|
|
3107
|
+
};
|
|
3108
|
+
toolCalls.push(toolCall);
|
|
3109
|
+
_optionalChain([callbacks, 'access', _163 => _163.onToolCall, 'optionalCall', _164 => _164(toolCall)]);
|
|
3110
|
+
logger.info("Tool called", { toolName: toolCall.name });
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
} else if (apiFormat !== "anthropic") {
|
|
3114
|
+
const delta = _optionalChain([json, 'access', _165 => _165.choices, 'optionalAccess', _166 => _166[0], 'optionalAccess', _167 => _167.delta]);
|
|
3115
|
+
if (_optionalChain([delta, 'optionalAccess', _168 => _168.tool_calls]) && pendingOpenAIToolCalls) {
|
|
3116
|
+
for (const tc of delta.tool_calls) {
|
|
3117
|
+
const index = _nullishCoalesce(tc.index, () => ( 0));
|
|
3118
|
+
let pending = pendingOpenAIToolCalls.get(index);
|
|
3119
|
+
if (!pending) {
|
|
3120
|
+
pending = { id: "", name: "", arguments: "" };
|
|
3121
|
+
pendingOpenAIToolCalls.set(index, pending);
|
|
3122
|
+
}
|
|
3123
|
+
if (tc.id) pending.id = tc.id;
|
|
3124
|
+
if (_optionalChain([tc, 'access', _169 => _169.function, 'optionalAccess', _170 => _170.name])) pending.name += tc.function.name;
|
|
3125
|
+
if (_optionalChain([tc, 'access', _171 => _171.function, 'optionalAccess', _172 => _172.arguments])) pending.arguments += tc.function.arguments;
|
|
3126
|
+
}
|
|
3127
|
+
}
|
|
3128
|
+
}
|
|
3129
|
+
}
|
|
3130
|
+
function finalizeOpenAIToolCalls(pendingOpenAIToolCalls, toolCalls, callbacks, logger) {
|
|
3131
|
+
for (const [, pending] of pendingOpenAIToolCalls) {
|
|
3132
|
+
if (pending.name) {
|
|
3133
|
+
try {
|
|
3134
|
+
const toolCall = {
|
|
3135
|
+
id: pending.id || `tool_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
3136
|
+
name: pending.name,
|
|
3137
|
+
args: JSON.parse(pending.arguments || "{}")
|
|
3138
|
+
};
|
|
3139
|
+
toolCalls.push(toolCall);
|
|
3140
|
+
_optionalChain([callbacks, 'access', _173 => _173.onToolCall, 'optionalCall', _174 => _174(toolCall)]);
|
|
3141
|
+
logger.info("Tool called (OpenAI)", { toolName: toolCall.name, args: toolCall.args });
|
|
3142
|
+
} catch (e) {
|
|
3143
|
+
logger.error("Failed to parse OpenAI tool call arguments", e instanceof Error ? e : void 0, {
|
|
3144
|
+
name: pending.name,
|
|
3145
|
+
arguments: pending.arguments
|
|
3146
|
+
});
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
function parseStreamChunk(rawJson, apiFormat, callbacks, logger) {
|
|
3152
|
+
const json = rawJson;
|
|
3153
|
+
if (apiFormat === "google") {
|
|
3154
|
+
const candidate = _optionalChain([json, 'access', _175 => _175.candidates, 'optionalAccess', _176 => _176[0]]);
|
|
3155
|
+
if (!candidate) {
|
|
3156
|
+
logger.warn("No candidate found in Gemini response");
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
const parts = _optionalChain([candidate, 'access', _177 => _177.content, 'optionalAccess', _178 => _178.parts]) || [];
|
|
3160
|
+
for (const part of parts) {
|
|
3161
|
+
if (part.thought === true && part.text) {
|
|
3162
|
+
_optionalChain([callbacks, 'access', _179 => _179.onReasoning, 'optionalCall', _180 => _180(part.text)]);
|
|
3163
|
+
continue;
|
|
3164
|
+
} else if (part.thoughtSignature && !part.thought) {
|
|
3165
|
+
logger.debug("Gemini returned thoughtSignature without thought content");
|
|
3166
|
+
}
|
|
3167
|
+
if (part.text) {
|
|
3168
|
+
_optionalChain([callbacks, 'access', _181 => _181.onDelta, 'optionalCall', _182 => _182(part.text)]);
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
} else if (apiFormat === "anthropic") {
|
|
3172
|
+
const delta = json.delta;
|
|
3173
|
+
if (_optionalChain([delta, 'optionalAccess', _183 => _183.text])) {
|
|
3174
|
+
_optionalChain([callbacks, 'access', _184 => _184.onDelta, 'optionalCall', _185 => _185(delta.text)]);
|
|
3175
|
+
}
|
|
3176
|
+
} else {
|
|
3177
|
+
const delta = _optionalChain([json, 'access', _186 => _186.choices, 'optionalAccess', _187 => _187[0], 'optionalAccess', _188 => _188.delta]);
|
|
3178
|
+
if (_optionalChain([delta, 'optionalAccess', _189 => _189.content])) {
|
|
3179
|
+
_optionalChain([callbacks, 'access', _190 => _190.onDelta, 'optionalCall', _191 => _191(delta.content)]);
|
|
3180
|
+
}
|
|
3181
|
+
if (_optionalChain([delta, 'optionalAccess', _192 => _192.thinking, 'optionalAccess', _193 => _193.content])) {
|
|
3182
|
+
_optionalChain([callbacks, 'access', _194 => _194.onReasoning, 'optionalCall', _195 => _195(delta.thinking.content)]);
|
|
3183
|
+
}
|
|
3184
|
+
if (_optionalChain([delta, 'optionalAccess', _196 => _196.reasoning_content])) {
|
|
3185
|
+
_optionalChain([callbacks, 'access', _197 => _197.onReasoning, 'optionalCall', _198 => _198(delta.reasoning_content)]);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
}
|
|
3189
|
+
function buildIterationBlocks(iteration, content, reasoning, blocks, callbacks) {
|
|
3190
|
+
const blockIdPrefix = `block_${iteration}_${Date.now()}`;
|
|
3191
|
+
if (reasoning && reasoning.trim()) {
|
|
3192
|
+
const thinkingBlock = {
|
|
3193
|
+
id: `${blockIdPrefix}_thinking`,
|
|
3194
|
+
type: "thinking",
|
|
3195
|
+
content: reasoning
|
|
3196
|
+
};
|
|
3197
|
+
blocks.push(thinkingBlock);
|
|
3198
|
+
_optionalChain([callbacks, 'access', _199 => _199.onBlock, 'optionalCall', _200 => _200(thinkingBlock)]);
|
|
3199
|
+
}
|
|
3200
|
+
if (content && content.trim()) {
|
|
3201
|
+
const textBlock = {
|
|
3202
|
+
id: `${blockIdPrefix}_text`,
|
|
3203
|
+
type: "text",
|
|
3204
|
+
content
|
|
3205
|
+
};
|
|
3206
|
+
blocks.push(textBlock);
|
|
3207
|
+
_optionalChain([callbacks, 'access', _201 => _201.onBlock, 'optionalCall', _202 => _202(textBlock)]);
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
async function executeToolCalls(toolCalls, mcpTools, mcpService, blocks, callbacks, logger, builtinExecutor) {
|
|
3211
|
+
const toolResults = [];
|
|
3212
|
+
for (const toolCall of toolCalls) {
|
|
3213
|
+
const toolUseBlock = {
|
|
3214
|
+
id: `${toolCall.id}_use`,
|
|
3215
|
+
type: "tool_use",
|
|
3216
|
+
toolId: toolCall.id,
|
|
3217
|
+
toolName: toolCall.name,
|
|
3218
|
+
input: toolCall.args,
|
|
3219
|
+
status: "running"
|
|
3220
|
+
};
|
|
3221
|
+
blocks.push(toolUseBlock);
|
|
3222
|
+
_optionalChain([callbacks, 'access', _203 => _203.onBlock, 'optionalCall', _204 => _204(toolUseBlock)]);
|
|
3223
|
+
try {
|
|
3224
|
+
logger.info("Executing tool", { toolName: toolCall.name });
|
|
3225
|
+
const mcpTool = mcpTools.find((t) => t.id === toolCall.name);
|
|
3226
|
+
if (!mcpTool) {
|
|
3227
|
+
logger.error("Tool not found", void 0, { toolName: toolCall.name });
|
|
3228
|
+
continue;
|
|
3229
|
+
}
|
|
3230
|
+
logger.debug("Tool info", {
|
|
3231
|
+
serverId: mcpTool.serverId,
|
|
3232
|
+
toolName: mcpTool.name,
|
|
3233
|
+
args: toolCall.args
|
|
3234
|
+
});
|
|
3235
|
+
const mappedArgs = mapToolArguments(toolCall.args, mcpTool, logger);
|
|
3236
|
+
let result;
|
|
3237
|
+
if (mcpTool.serverId === "builtin" && builtinExecutor) {
|
|
3238
|
+
result = await builtinExecutor.execute(mcpTool.name, mappedArgs);
|
|
3239
|
+
} else if (mcpService) {
|
|
3240
|
+
result = await mcpService.callTool(mcpTool.serverId, mcpTool.name, mappedArgs, toolCall.id);
|
|
3241
|
+
} else {
|
|
3242
|
+
result = { isError: true, content: [{ type: "text", text: "MCP service not available" }] };
|
|
3243
|
+
}
|
|
3244
|
+
logger.info("Tool result received", { result });
|
|
3245
|
+
_optionalChain([callbacks, 'access', _205 => _205.onToolResult, 'optionalCall', _206 => _206(toolCall.id, result)]);
|
|
3246
|
+
const resultText = _optionalChain([result, 'access', _207 => _207.content, 'optionalAccess', _208 => _208[0], 'optionalAccess', _209 => _209.text]) || JSON.stringify(result);
|
|
3247
|
+
const toolResultBlock = {
|
|
3248
|
+
id: `${toolCall.id}_result`,
|
|
3249
|
+
type: "tool_result",
|
|
3250
|
+
toolId: toolCall.id,
|
|
3251
|
+
toolName: toolCall.name,
|
|
3252
|
+
output: resultText,
|
|
3253
|
+
isError: result.isError
|
|
3254
|
+
};
|
|
3255
|
+
blocks.push(toolResultBlock);
|
|
3256
|
+
_optionalChain([callbacks, 'access', _210 => _210.onBlock, 'optionalCall', _211 => _211(toolResultBlock)]);
|
|
3257
|
+
const toolUseBlockRef = blocks.find((b) => b.id === `${toolCall.id}_use`);
|
|
3258
|
+
if (toolUseBlockRef && toolUseBlockRef.type === "tool_use") {
|
|
3259
|
+
toolUseBlockRef.status = result.isError ? "error" : "completed";
|
|
3260
|
+
_optionalChain([callbacks, 'access', _212 => _212.onBlock, 'optionalCall', _213 => _213({ ...toolUseBlockRef })]);
|
|
3261
|
+
}
|
|
3262
|
+
toolResults.push({ toolCall, result });
|
|
3263
|
+
} catch (error) {
|
|
3264
|
+
logger.error("Tool execution error", error instanceof Error ? error : void 0);
|
|
3265
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
|
3266
|
+
const errorResult = {
|
|
3267
|
+
isError: true,
|
|
3268
|
+
content: [{ type: "text", text: `Error: ${errorMessage}` }]
|
|
3269
|
+
};
|
|
3270
|
+
_optionalChain([callbacks, 'access', _214 => _214.onToolResult, 'optionalCall', _215 => _215(toolCall.id, errorResult)]);
|
|
3271
|
+
const errorToolResultBlock = {
|
|
3272
|
+
id: `${toolCall.id}_result`,
|
|
3273
|
+
type: "tool_result",
|
|
3274
|
+
toolId: toolCall.id,
|
|
3275
|
+
toolName: toolCall.name,
|
|
3276
|
+
error: errorMessage,
|
|
3277
|
+
isError: true
|
|
3278
|
+
};
|
|
3279
|
+
blocks.push(errorToolResultBlock);
|
|
3280
|
+
_optionalChain([callbacks, 'access', _216 => _216.onBlock, 'optionalCall', _217 => _217(errorToolResultBlock)]);
|
|
3281
|
+
const errorToolUseBlockRef = blocks.find((b) => b.id === `${toolCall.id}_use`);
|
|
3282
|
+
if (errorToolUseBlockRef && errorToolUseBlockRef.type === "tool_use") {
|
|
3283
|
+
errorToolUseBlockRef.status = "error";
|
|
3284
|
+
_optionalChain([callbacks, 'access', _218 => _218.onBlock, 'optionalCall', _219 => _219({ ...errorToolUseBlockRef })]);
|
|
3285
|
+
}
|
|
3286
|
+
toolResults.push({ toolCall, result: errorResult });
|
|
3287
|
+
}
|
|
3288
|
+
}
|
|
3289
|
+
return toolResults;
|
|
3290
|
+
}
|
|
3291
|
+
function mapToolArguments(args, mcpTool, logger) {
|
|
3292
|
+
const mappedArgs = { ...args };
|
|
3293
|
+
const toolSchema = mcpTool.inputSchema;
|
|
3294
|
+
if (_optionalChain([toolSchema, 'optionalAccess', _220 => _220.properties])) {
|
|
3295
|
+
const expectedParams = Object.keys(toolSchema.properties);
|
|
3296
|
+
const paramMappings = {
|
|
3297
|
+
"search_query": ["query", "q", "searchQuery", "search"],
|
|
3298
|
+
"content": ["text", "body", "message"],
|
|
3299
|
+
"file_path": ["path", "filePath", "file"],
|
|
3300
|
+
"url": ["link", "uri"]
|
|
3301
|
+
};
|
|
3302
|
+
for (const expectedParam of expectedParams) {
|
|
3303
|
+
if (!(expectedParam in mappedArgs)) {
|
|
3304
|
+
const aliases = paramMappings[expectedParam];
|
|
3305
|
+
if (aliases) {
|
|
3306
|
+
for (const alias of aliases) {
|
|
3307
|
+
if (alias in mappedArgs) {
|
|
3308
|
+
logger.info("Mapping parameter", { from: alias, to: expectedParam });
|
|
3309
|
+
mappedArgs[expectedParam] = mappedArgs[alias];
|
|
3310
|
+
delete mappedArgs[alias];
|
|
3311
|
+
break;
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
}
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
return mappedArgs;
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
// src/completion/ToolHandler.ts
|
|
3322
|
+
async function streamWithTools(options, callbacks, mcpService, llmConfig, getProvider, resolveApiKey2, logger, builtinExecutor) {
|
|
3323
|
+
try {
|
|
3324
|
+
const routedInfo = await llmConfig.resolveRoutedModel(
|
|
3325
|
+
options.providerId,
|
|
3326
|
+
options.model
|
|
3327
|
+
);
|
|
3328
|
+
const actualProviderId = _optionalChain([routedInfo, 'optionalAccess', _221 => _221.actualProviderId]) || options.providerId;
|
|
3329
|
+
const actualModel = _optionalChain([routedInfo, 'optionalAccess', _222 => _222.actualModelId]) || options.model;
|
|
3330
|
+
const provider = await getProvider(actualProviderId);
|
|
3331
|
+
if (!provider) {
|
|
3332
|
+
_optionalChain([callbacks, 'access', _223 => _223.onError, 'optionalCall', _224 => _224(`Provider not found: ${actualProviderId}`)]);
|
|
3333
|
+
return;
|
|
3334
|
+
}
|
|
3335
|
+
if (!provider.enabled) {
|
|
3336
|
+
_optionalChain([callbacks, 'access', _225 => _225.onError, 'optionalCall', _226 => _226(`Provider is disabled: ${provider.name}`)]);
|
|
3337
|
+
return;
|
|
3338
|
+
}
|
|
3339
|
+
const { apiKey: effectiveKey } = _chunkA2YMUCBTcjs.resolveProviderEndpoint.call(void 0, provider);
|
|
3340
|
+
const apiKey = resolveApiKey2(effectiveKey);
|
|
3341
|
+
if (!apiKey) {
|
|
3342
|
+
_optionalChain([callbacks, 'access', _227 => _227.onError, 'optionalCall', _228 => _228("API key not configured")]);
|
|
3343
|
+
return;
|
|
3344
|
+
}
|
|
3345
|
+
const messageId = `msg_${Date.now()}_${Math.random().toString(36).slice(2)}`;
|
|
3346
|
+
_optionalChain([callbacks, 'access', _229 => _229.onStart, 'optionalCall', _230 => _230(messageId)]);
|
|
3347
|
+
const globalParams = await llmConfig.getGlobalModelParameters();
|
|
3348
|
+
const MAX_ITERATIONS = _nullishCoalesce(globalParams.toolMaxTurns, () => ( 100));
|
|
3349
|
+
let iteration = 0;
|
|
3350
|
+
const conversationMessages = [...options.messages];
|
|
3351
|
+
let finalContent = "";
|
|
3352
|
+
let finalReasoning = "";
|
|
3353
|
+
let finalUsageTokens;
|
|
3354
|
+
const blocks = [];
|
|
3355
|
+
logger.info("Sending request to LLM with tools", {
|
|
3356
|
+
model: actualModel,
|
|
3357
|
+
providerId: actualProviderId,
|
|
3358
|
+
messagesCount: options.messages.length
|
|
3359
|
+
});
|
|
3360
|
+
logToolFormat(options.tools, logger);
|
|
3361
|
+
const apiFormat = _chunkA2YMUCBTcjs.resolveApiFormat.call(void 0, provider);
|
|
3362
|
+
logger.info("API format determined", { apiFormat });
|
|
3363
|
+
while (iteration < MAX_ITERATIONS) {
|
|
3364
|
+
iteration++;
|
|
3365
|
+
logger.info("Agentic loop iteration", {
|
|
3366
|
+
iteration,
|
|
3367
|
+
maxIterations: MAX_ITERATIONS,
|
|
3368
|
+
messagesCount: conversationMessages.length
|
|
3369
|
+
});
|
|
3370
|
+
const { requestBody, url } = buildToolRequest(
|
|
3371
|
+
apiFormat,
|
|
3372
|
+
conversationMessages,
|
|
3373
|
+
actualModel,
|
|
3374
|
+
options,
|
|
3375
|
+
provider
|
|
3376
|
+
);
|
|
3377
|
+
if (options.nativeSearchAugmentation) {
|
|
3378
|
+
_chunkV5KPWNYXcjs.applyAugmentation.call(void 0, requestBody, options.nativeSearchAugmentation);
|
|
3379
|
+
}
|
|
3380
|
+
const headers = _chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey);
|
|
3381
|
+
logger.info("Sending tool call request", {
|
|
3382
|
+
url,
|
|
3383
|
+
headers,
|
|
3384
|
+
requestBody
|
|
3385
|
+
});
|
|
3386
|
+
const response = await fetch(url, {
|
|
3387
|
+
method: "POST",
|
|
3388
|
+
headers,
|
|
3389
|
+
body: JSON.stringify(requestBody)
|
|
3390
|
+
});
|
|
3391
|
+
if (!response.ok) {
|
|
3392
|
+
const errorText = await response.text();
|
|
3393
|
+
_optionalChain([callbacks, 'access', _231 => _231.onError, 'optionalCall', _232 => _232(`API error (${response.status}): ${errorText}`)]);
|
|
3394
|
+
return;
|
|
3395
|
+
}
|
|
3396
|
+
const reader = _optionalChain([response, 'access', _233 => _233.body, 'optionalAccess', _234 => _234.getReader, 'call', _235 => _235()]);
|
|
3397
|
+
if (!reader) {
|
|
3398
|
+
_optionalChain([callbacks, 'access', _236 => _236.onError, 'optionalCall', _237 => _237("No response body")]);
|
|
3399
|
+
return;
|
|
3400
|
+
}
|
|
3401
|
+
const decoder = new TextDecoder();
|
|
3402
|
+
let content = "";
|
|
3403
|
+
let reasoning = "";
|
|
3404
|
+
const toolCalls = [];
|
|
3405
|
+
const pendingOpenAIToolCalls = /* @__PURE__ */ new Map();
|
|
3406
|
+
let buffer = "";
|
|
3407
|
+
try {
|
|
3408
|
+
while (true) {
|
|
3409
|
+
const { done, value } = await reader.read();
|
|
3410
|
+
if (done) break;
|
|
3411
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
3412
|
+
logger.debug("Received raw chunk", { chunkLength: chunk.length });
|
|
3413
|
+
buffer += chunk;
|
|
3414
|
+
const lines = buffer.split("\n");
|
|
3415
|
+
buffer = lines.pop() || "";
|
|
3416
|
+
for (const line of lines) {
|
|
3417
|
+
const trimmedLine = line.trim();
|
|
3418
|
+
if (!trimmedLine) continue;
|
|
3419
|
+
logger.debug("Processing line", { linePreview: trimmedLine.substring(0, 200) });
|
|
3420
|
+
if (trimmedLine.startsWith("data: ")) {
|
|
3421
|
+
const data = trimmedLine.slice(6);
|
|
3422
|
+
if (data === "[DONE]") {
|
|
3423
|
+
logger.debug("Stream done");
|
|
3424
|
+
continue;
|
|
3425
|
+
}
|
|
3426
|
+
try {
|
|
3427
|
+
const json = JSON.parse(data);
|
|
3428
|
+
logger.debug("Parsed JSON", { json });
|
|
3429
|
+
if (json.error) {
|
|
3430
|
+
logger.error("API Error", void 0, { error: json.error });
|
|
3431
|
+
_optionalChain([callbacks, 'access', _238 => _238.onError, 'optionalCall', _239 => _239(`API Error: ${json.error.message || JSON.stringify(json.error)}`)]);
|
|
3432
|
+
return;
|
|
3433
|
+
}
|
|
3434
|
+
parseStreamChunk(json, apiFormat, callbacks, logger);
|
|
3435
|
+
content = content + extractDeltaContent(json, apiFormat);
|
|
3436
|
+
reasoning = reasoning + extractDeltaReasoning(json, apiFormat);
|
|
3437
|
+
extractToolCalls(json, apiFormat, toolCalls, callbacks, logger, pendingOpenAIToolCalls);
|
|
3438
|
+
if (apiFormat !== "google" && apiFormat !== "anthropic" && json.usage) {
|
|
3439
|
+
finalUsageTokens = {
|
|
3440
|
+
promptTokens: json.usage.prompt_tokens || 0,
|
|
3441
|
+
completionTokens: json.usage.completion_tokens || 0,
|
|
3442
|
+
totalTokens: json.usage.total_tokens || 0
|
|
3443
|
+
};
|
|
3444
|
+
}
|
|
3445
|
+
} catch (_e) {
|
|
3446
|
+
}
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
}
|
|
3450
|
+
if (apiFormat !== "google" && apiFormat !== "anthropic") {
|
|
3451
|
+
finalizeOpenAIToolCalls(pendingOpenAIToolCalls, toolCalls, callbacks, logger);
|
|
3452
|
+
pendingOpenAIToolCalls.clear();
|
|
3453
|
+
}
|
|
3454
|
+
finalContent = content;
|
|
3455
|
+
finalReasoning = reasoning;
|
|
3456
|
+
buildIterationBlocks(iteration, content, reasoning, blocks, callbacks);
|
|
3457
|
+
const executableToolCalls = toolCalls.filter(
|
|
3458
|
+
(tc) => !_chunkGMMT7RVNcjs.NATIVE_SEARCH_TOOL_NAMES.includes(tc.name)
|
|
3459
|
+
);
|
|
3460
|
+
logger.info("Tool execution check", {
|
|
3461
|
+
toolCallsLength: toolCalls.length,
|
|
3462
|
+
executableToolCallsLength: executableToolCalls.length,
|
|
3463
|
+
hasMcpTools: !!options.mcpTools,
|
|
3464
|
+
mcpToolsLength: _optionalChain([options, 'access', _240 => _240.mcpTools, 'optionalAccess', _241 => _241.length]),
|
|
3465
|
+
hasMcpService: !!mcpService
|
|
3466
|
+
});
|
|
3467
|
+
if (executableToolCalls.length > 0 && options.mcpTools && (mcpService || builtinExecutor)) {
|
|
3468
|
+
logger.info("Executing tool calls", { count: executableToolCalls.length });
|
|
3469
|
+
conversationMessages.push({
|
|
3470
|
+
id: `assistant_${iteration}`,
|
|
3471
|
+
role: "assistant",
|
|
3472
|
+
content: content || "Calling tools...",
|
|
3473
|
+
timestamp: Date.now()
|
|
3474
|
+
});
|
|
3475
|
+
const toolResults = await executeToolCalls(
|
|
3476
|
+
executableToolCalls,
|
|
3477
|
+
options.mcpTools,
|
|
3478
|
+
mcpService,
|
|
3479
|
+
blocks,
|
|
3480
|
+
callbacks,
|
|
3481
|
+
logger,
|
|
3482
|
+
builtinExecutor
|
|
3483
|
+
);
|
|
3484
|
+
const toolResultsText = toolResults.map(({ toolCall, result }) => {
|
|
3485
|
+
const resultText = typeof result === "string" ? result : _optionalChain([result, 'access', _242 => _242.content, 'optionalAccess', _243 => _243[0], 'optionalAccess', _244 => _244.text]) || JSON.stringify(result);
|
|
3486
|
+
return `Tool ${toolCall.name} result:
|
|
3487
|
+
${resultText}`;
|
|
3488
|
+
}).join("\n\n");
|
|
3489
|
+
conversationMessages.push({
|
|
3490
|
+
id: `tool_results_${iteration}`,
|
|
3491
|
+
role: "user",
|
|
3492
|
+
content: toolResultsText,
|
|
3493
|
+
timestamp: Date.now()
|
|
3494
|
+
});
|
|
3495
|
+
logger.info("Continuing to next iteration with tool results");
|
|
3496
|
+
continue;
|
|
3497
|
+
} else {
|
|
3498
|
+
logger.info("No tool calls, finishing");
|
|
3499
|
+
break;
|
|
3500
|
+
}
|
|
3501
|
+
} catch (streamError) {
|
|
3502
|
+
logger.error("Stream processing error", streamError instanceof Error ? streamError : void 0);
|
|
3503
|
+
_optionalChain([callbacks, 'access', _245 => _245.onError, 'optionalCall', _246 => _246(`Stream error: ${streamError instanceof Error ? streamError.message : "Unknown error"}`)]);
|
|
3504
|
+
return;
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
3507
|
+
logger.info("Sending final callback", { blocksCount: blocks.length });
|
|
3508
|
+
_optionalChain([callbacks, 'access', _247 => _247.onDone, 'optionalCall', _248 => _248({
|
|
3509
|
+
id: messageId,
|
|
3510
|
+
role: "assistant",
|
|
3511
|
+
content: finalContent,
|
|
3512
|
+
timestamp: Date.now(),
|
|
3513
|
+
thinking: finalReasoning ? { content: finalReasoning } : void 0,
|
|
3514
|
+
blocks: blocks.length > 0 ? blocks : void 0
|
|
3515
|
+
}, finalUsageTokens)]);
|
|
3516
|
+
} catch (error) {
|
|
3517
|
+
logger.error("Error in streamWithTools", error instanceof Error ? error : void 0);
|
|
3518
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3519
|
+
_optionalChain([callbacks, 'access', _249 => _249.onError, 'optionalCall', _250 => _250(message)]);
|
|
3520
|
+
}
|
|
3521
|
+
}
|
|
3522
|
+
|
|
3523
|
+
// src/completion/TransformerHandler.ts
|
|
3524
|
+
function readUsageFromOpenAIResponse(usage) {
|
|
3525
|
+
if (!usage) return null;
|
|
3526
|
+
const promptTokens = Number(usage.prompt_tokens) || 0;
|
|
3527
|
+
const completionTokens = Number(usage.completion_tokens) || 0;
|
|
3528
|
+
const promptDetails = _nullishCoalesce(usage.prompt_tokens_details, () => ( {}));
|
|
3529
|
+
const cachedTokens = Number(promptDetails.cached_tokens) || Number(usage.cache_read_input_tokens) || 0;
|
|
3530
|
+
const cacheCreation = Number(usage.cache_creation_input_tokens) || 0;
|
|
3531
|
+
const inputTokens = Math.max(0, promptTokens - cachedTokens - cacheCreation);
|
|
3532
|
+
const reasoningDetails = _nullishCoalesce(_nullishCoalesce(usage.completion_tokens_details, () => ( usage.output_tokens_details)), () => ( {}));
|
|
3533
|
+
const reasoningTokens = Number(reasoningDetails.reasoning_tokens) || 0;
|
|
3534
|
+
return {
|
|
3535
|
+
inputTokens,
|
|
3536
|
+
outputTokens: completionTokens,
|
|
3537
|
+
cacheReadTokens: cachedTokens,
|
|
3538
|
+
cacheCreationTokens: cacheCreation,
|
|
3539
|
+
reasoningTokens
|
|
3540
|
+
};
|
|
3541
|
+
}
|
|
3542
|
+
var sharedExecutor2 = null;
|
|
3543
|
+
function getSharedExecutor2() {
|
|
3544
|
+
if (!sharedExecutor2) {
|
|
3545
|
+
sharedExecutor2 = new (0, _chunk46ZIFWYGcjs.TransformerChainExecutor)();
|
|
3546
|
+
}
|
|
3547
|
+
return sharedExecutor2;
|
|
3548
|
+
}
|
|
3549
|
+
async function resolveChainWithMain(llmConfig, providerId, model) {
|
|
3550
|
+
return _chunkQXD4MV2Xcjs.resolveProviderChain.call(void 0, llmConfig, providerId, model);
|
|
3551
|
+
}
|
|
3552
|
+
async function completeWithTransformers(options, llmConfig, getProvider, resolveApiKey2, completeFallback, logger, recording) {
|
|
3553
|
+
try {
|
|
3554
|
+
const routedInfo = await llmConfig.resolveRoutedModel(
|
|
3555
|
+
options.providerId,
|
|
3556
|
+
options.model
|
|
3557
|
+
);
|
|
3558
|
+
const actualProviderId = _optionalChain([routedInfo, 'optionalAccess', _251 => _251.actualProviderId]) || options.providerId;
|
|
3559
|
+
const actualModel = _optionalChain([routedInfo, 'optionalAccess', _252 => _252.actualModelId]) || options.model;
|
|
3560
|
+
const provider = await getProvider(actualProviderId);
|
|
3561
|
+
if (!provider) {
|
|
3562
|
+
return { success: false, error: `Provider not found: ${actualProviderId}` };
|
|
3563
|
+
}
|
|
3564
|
+
if (!provider.enabled) {
|
|
3565
|
+
return { success: false, error: `Provider is disabled: ${provider.name}` };
|
|
3566
|
+
}
|
|
3567
|
+
const { apiKey: effectiveKey } = _chunkA2YMUCBTcjs.resolveProviderEndpoint.call(void 0, provider);
|
|
3568
|
+
const apiKey = resolveApiKey2(effectiveKey);
|
|
3569
|
+
if (!apiKey) {
|
|
3570
|
+
return { success: false, error: "API key not configured" };
|
|
3571
|
+
}
|
|
3572
|
+
const { chain, hasTransformers } = await resolveChainWithMain(
|
|
3573
|
+
llmConfig,
|
|
3574
|
+
actualProviderId,
|
|
3575
|
+
actualModel
|
|
3576
|
+
);
|
|
3577
|
+
if (!hasTransformers) {
|
|
3578
|
+
return completeFallback(options);
|
|
3579
|
+
}
|
|
3580
|
+
const unifiedRequest = {
|
|
3581
|
+
model: actualModel,
|
|
3582
|
+
messages: options.messages.map((m) => ({
|
|
3583
|
+
role: m.role,
|
|
3584
|
+
content: m.content
|
|
3585
|
+
})),
|
|
3586
|
+
max_tokens: options.maxTokens || 4096,
|
|
3587
|
+
temperature: options.temperature,
|
|
3588
|
+
stream: _nullishCoalesce(options.stream, () => ( false))
|
|
3589
|
+
};
|
|
3590
|
+
const transformerProvider = {
|
|
3591
|
+
name: provider.name,
|
|
3592
|
+
baseUrl: provider.api_base_url,
|
|
3593
|
+
apiKey,
|
|
3594
|
+
models: provider.models || []
|
|
3595
|
+
};
|
|
3596
|
+
const executor = getSharedExecutor2();
|
|
3597
|
+
const { response } = await _chunkKUU2RNG6cjs.executeProviderCall.call(void 0, {
|
|
3598
|
+
executor,
|
|
3599
|
+
request: unifiedRequest,
|
|
3600
|
+
provider: transformerProvider,
|
|
3601
|
+
chain,
|
|
3602
|
+
endpointTransformer: void 0,
|
|
3603
|
+
extendedContext: options.useExtendedContext ? { enabled: true, model: actualModel } : void 0,
|
|
3604
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: actualModel, stream: false }),
|
|
3605
|
+
buildHeaders: (config) => ({
|
|
3606
|
+
..._chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey),
|
|
3607
|
+
...config.headers,
|
|
3608
|
+
..._chunk6VIXXLMXcjs.isOpenRouterProvider.call(void 0, provider) ? _chunk6VIXXLMXcjs.OPENROUTER_APP_HEADERS : {}
|
|
3609
|
+
}),
|
|
3610
|
+
// Add OpenRouter provider routing config if applicable
|
|
3611
|
+
prepareBody: (requestBody) => addOpenRouterProviderToRequest(
|
|
3612
|
+
requestBody,
|
|
3613
|
+
provider,
|
|
3614
|
+
actualModel
|
|
3615
|
+
),
|
|
3616
|
+
fetchFn: (url, headers, body) => {
|
|
3617
|
+
logger.info("Calling completion with transformers", { url });
|
|
3618
|
+
return fetch(url, {
|
|
3619
|
+
method: "POST",
|
|
3620
|
+
headers,
|
|
3621
|
+
body: JSON.stringify(body)
|
|
3622
|
+
});
|
|
3623
|
+
}
|
|
3624
|
+
});
|
|
3625
|
+
if (!response.ok) {
|
|
3626
|
+
const errorText = await response.text();
|
|
3627
|
+
return {
|
|
3628
|
+
success: false,
|
|
3629
|
+
error: `API error (${response.status}): ${errorText}`
|
|
3630
|
+
};
|
|
3631
|
+
}
|
|
3632
|
+
const transformedResponse = await executor.executeResponseChain(
|
|
3633
|
+
unifiedRequest,
|
|
3634
|
+
response,
|
|
3635
|
+
transformerProvider,
|
|
3636
|
+
chain,
|
|
3637
|
+
{ endpointTransformer: void 0 }
|
|
3638
|
+
);
|
|
3639
|
+
const data = await transformedResponse.json();
|
|
3640
|
+
const choice = _optionalChain([data, 'access', _253 => _253.choices, 'optionalAccess', _254 => _254[0]]);
|
|
3641
|
+
if (!choice) {
|
|
3642
|
+
return { success: false, error: "No choices in response" };
|
|
3643
|
+
}
|
|
3644
|
+
const rawToolCalls = _optionalChain([choice, 'access', _255 => _255.message, 'optionalAccess', _256 => _256.tool_calls]);
|
|
3645
|
+
const toolCalls = _optionalChain([rawToolCalls, 'optionalAccess', _257 => _257.map, 'call', _258 => _258((tc) => ({
|
|
3646
|
+
id: tc.id,
|
|
3647
|
+
name: tc.function.name,
|
|
3648
|
+
args: typeof tc.function.arguments === "string" ? JSON.parse(tc.function.arguments) : tc.function.arguments
|
|
3649
|
+
}))]);
|
|
3650
|
+
const tapped = readUsageFromOpenAIResponse(data.usage);
|
|
3651
|
+
if (_optionalChain([recording, 'optionalAccess', _259 => _259.recorder]) && tapped) {
|
|
3652
|
+
recording.recorder.record({
|
|
3653
|
+
messageId: _nullishCoalesce(options.messageId, () => ( null)),
|
|
3654
|
+
parentMessageId: _nullishCoalesce(options.parentMessageId, () => ( null)),
|
|
3655
|
+
sessionId: _nullishCoalesce(options.sessionId, () => ( null)),
|
|
3656
|
+
providerId: actualProviderId,
|
|
3657
|
+
model: actualModel,
|
|
3658
|
+
apiKeyId: _nullishCoalesce(recording.apiKeyId, () => ( null)),
|
|
3659
|
+
engineOrigin: "completion",
|
|
3660
|
+
usage: tapped,
|
|
3661
|
+
rawUsage: data.usage
|
|
3662
|
+
});
|
|
3663
|
+
}
|
|
3664
|
+
return {
|
|
3665
|
+
success: true,
|
|
3666
|
+
message: {
|
|
3667
|
+
id: `msg_${Date.now()}`,
|
|
3668
|
+
role: "assistant",
|
|
3669
|
+
content: _optionalChain([choice, 'access', _260 => _260.message, 'optionalAccess', _261 => _261.content]) || "",
|
|
3670
|
+
timestamp: Date.now(),
|
|
3671
|
+
thinking: data.reasoning_content || _optionalChain([choice, 'access', _262 => _262.message, 'optionalAccess', _263 => _263.thinking, 'optionalAccess', _264 => _264.content]) ? { content: data.reasoning_content || _optionalChain([choice, 'access', _265 => _265.message, 'optionalAccess', _266 => _266.thinking, 'optionalAccess', _267 => _267.content]) || "", signature: _optionalChain([choice, 'access', _268 => _268.message, 'optionalAccess', _269 => _269.thinking, 'optionalAccess', _270 => _270.signature]) } : void 0,
|
|
3672
|
+
toolCalls
|
|
3673
|
+
},
|
|
3674
|
+
usage: data.usage ? {
|
|
3675
|
+
promptTokens: data.usage.prompt_tokens || 0,
|
|
3676
|
+
completionTokens: data.usage.completion_tokens || 0,
|
|
3677
|
+
totalTokens: data.usage.total_tokens || 0
|
|
3678
|
+
} : void 0,
|
|
3679
|
+
finishReason: choice.finish_reason
|
|
3680
|
+
};
|
|
3681
|
+
} catch (error) {
|
|
3682
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3683
|
+
return { success: false, error: message };
|
|
3684
|
+
}
|
|
3685
|
+
}
|
|
3686
|
+
async function completeStreamWithTransformers(options, callbacks, llmConfig, getProvider, resolveApiKey2, completeStreamFallback, logger, recording) {
|
|
3687
|
+
try {
|
|
3688
|
+
const routedInfo = await llmConfig.resolveRoutedModel(
|
|
3689
|
+
options.providerId,
|
|
3690
|
+
options.model
|
|
3691
|
+
);
|
|
3692
|
+
const actualProviderId = _optionalChain([routedInfo, 'optionalAccess', _271 => _271.actualProviderId]) || options.providerId;
|
|
3693
|
+
const actualModel = _optionalChain([routedInfo, 'optionalAccess', _272 => _272.actualModelId]) || options.model;
|
|
3694
|
+
const provider = await getProvider(actualProviderId);
|
|
3695
|
+
if (!provider) {
|
|
3696
|
+
_optionalChain([callbacks, 'access', _273 => _273.onError, 'optionalCall', _274 => _274(`Provider not found: ${actualProviderId}`)]);
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
if (!provider.enabled) {
|
|
3700
|
+
_optionalChain([callbacks, 'access', _275 => _275.onError, 'optionalCall', _276 => _276(`Provider is disabled: ${provider.name}`)]);
|
|
3701
|
+
return;
|
|
3702
|
+
}
|
|
3703
|
+
const { apiKey: effectiveKey } = _chunkA2YMUCBTcjs.resolveProviderEndpoint.call(void 0, provider);
|
|
3704
|
+
const apiKey = resolveApiKey2(effectiveKey);
|
|
3705
|
+
if (!apiKey) {
|
|
3706
|
+
_optionalChain([callbacks, 'access', _277 => _277.onError, 'optionalCall', _278 => _278("API key not configured")]);
|
|
3707
|
+
return;
|
|
3708
|
+
}
|
|
3709
|
+
const { chain, hasTransformers } = await resolveChainWithMain(
|
|
3710
|
+
llmConfig,
|
|
3711
|
+
actualProviderId,
|
|
3712
|
+
actualModel
|
|
3713
|
+
);
|
|
3714
|
+
if (!hasTransformers) {
|
|
3715
|
+
return completeStreamFallback(options, callbacks);
|
|
3716
|
+
}
|
|
3717
|
+
const messageId = `msg_${Date.now()}`;
|
|
3718
|
+
_optionalChain([callbacks, 'access', _279 => _279.onStart, 'optionalCall', _280 => _280(messageId)]);
|
|
3719
|
+
const unifiedRequest = {
|
|
3720
|
+
model: actualModel,
|
|
3721
|
+
messages: options.messages.map((m) => ({
|
|
3722
|
+
role: m.role,
|
|
3723
|
+
content: m.content
|
|
3724
|
+
})),
|
|
3725
|
+
max_tokens: options.maxTokens || 4096,
|
|
3726
|
+
temperature: options.temperature,
|
|
3727
|
+
stream: true
|
|
3728
|
+
};
|
|
3729
|
+
const transformerProvider = {
|
|
3730
|
+
name: provider.name,
|
|
3731
|
+
baseUrl: provider.api_base_url,
|
|
3732
|
+
apiKey,
|
|
3733
|
+
models: provider.models || []
|
|
3734
|
+
};
|
|
3735
|
+
const executor = getSharedExecutor2();
|
|
3736
|
+
const { response } = await _chunkKUU2RNG6cjs.executeProviderCall.call(void 0, {
|
|
3737
|
+
executor,
|
|
3738
|
+
request: unifiedRequest,
|
|
3739
|
+
provider: transformerProvider,
|
|
3740
|
+
chain,
|
|
3741
|
+
endpointTransformer: void 0,
|
|
3742
|
+
extendedContext: options.useExtendedContext ? { enabled: true, model: actualModel } : void 0,
|
|
3743
|
+
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : _chunkA2YMUCBTcjs.buildProviderApiUrl.call(void 0, provider, { model: actualModel, stream: true }),
|
|
3744
|
+
buildHeaders: (config) => ({
|
|
3745
|
+
..._chunkLKZJEL6Ecjs.getProviderHeaders.call(void 0, provider, apiKey),
|
|
3746
|
+
...config.headers,
|
|
3747
|
+
..._chunk6VIXXLMXcjs.isOpenRouterProvider.call(void 0, provider) ? _chunk6VIXXLMXcjs.OPENROUTER_APP_HEADERS : {}
|
|
3748
|
+
}),
|
|
3749
|
+
// Add OpenRouter provider routing config if applicable
|
|
3750
|
+
prepareBody: (requestBody) => addOpenRouterProviderToRequest(
|
|
3751
|
+
requestBody,
|
|
3752
|
+
provider,
|
|
3753
|
+
actualModel
|
|
3754
|
+
),
|
|
3755
|
+
fetchFn: (url, headers, body) => {
|
|
3756
|
+
logger.info("Streaming completion with transformers", { url });
|
|
3757
|
+
return fetch(url, {
|
|
3758
|
+
method: "POST",
|
|
3759
|
+
headers,
|
|
3760
|
+
body: JSON.stringify(body)
|
|
3761
|
+
});
|
|
3762
|
+
}
|
|
3763
|
+
});
|
|
3764
|
+
if (!response.ok) {
|
|
3765
|
+
const errorText = await response.text();
|
|
3766
|
+
_optionalChain([callbacks, 'access', _281 => _281.onError, 'optionalCall', _282 => _282(`API error (${response.status}): ${errorText}`)]);
|
|
3767
|
+
return;
|
|
3768
|
+
}
|
|
3769
|
+
const transformedResponse = await executor.executeResponseChain(
|
|
3770
|
+
unifiedRequest,
|
|
3771
|
+
response,
|
|
3772
|
+
transformerProvider,
|
|
3773
|
+
chain,
|
|
3774
|
+
{ endpointTransformer: void 0 }
|
|
3775
|
+
);
|
|
3776
|
+
const reader = _optionalChain([transformedResponse, 'access', _283 => _283.body, 'optionalAccess', _284 => _284.getReader, 'call', _285 => _285()]);
|
|
3777
|
+
if (!reader) {
|
|
3778
|
+
_optionalChain([callbacks, 'access', _286 => _286.onError, 'optionalCall', _287 => _287("No response body")]);
|
|
3779
|
+
return;
|
|
3780
|
+
}
|
|
3781
|
+
const decoder = new TextDecoder();
|
|
3782
|
+
let content = "";
|
|
3783
|
+
let reasoning = "";
|
|
3784
|
+
try {
|
|
3785
|
+
while (true) {
|
|
3786
|
+
const { done, value } = await reader.read();
|
|
3787
|
+
if (done) break;
|
|
3788
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
3789
|
+
const lines = chunk.split("\n").filter((line) => line.trim() !== "");
|
|
3790
|
+
for (const line of lines) {
|
|
3791
|
+
if (line.startsWith("data: ")) {
|
|
3792
|
+
const data = line.slice(6);
|
|
3793
|
+
if (data === "[DONE]") continue;
|
|
3794
|
+
try {
|
|
3795
|
+
const json = JSON.parse(data);
|
|
3796
|
+
const delta = _optionalChain([json, 'access', _288 => _288.choices, 'optionalAccess', _289 => _289[0], 'optionalAccess', _290 => _290.delta]);
|
|
3797
|
+
if (_optionalChain([delta, 'optionalAccess', _291 => _291.content])) {
|
|
3798
|
+
content += delta.content;
|
|
3799
|
+
_optionalChain([callbacks, 'access', _292 => _292.onDelta, 'optionalCall', _293 => _293(delta.content)]);
|
|
3800
|
+
}
|
|
3801
|
+
if (_optionalChain([delta, 'optionalAccess', _294 => _294.thinking, 'optionalAccess', _295 => _295.content])) {
|
|
3802
|
+
reasoning += delta.thinking.content;
|
|
3803
|
+
_optionalChain([callbacks, 'access', _296 => _296.onReasoning, 'optionalCall', _297 => _297(delta.thinking.content)]);
|
|
3804
|
+
}
|
|
3805
|
+
if (_optionalChain([delta, 'optionalAccess', _298 => _298.reasoning_content])) {
|
|
3806
|
+
reasoning += delta.reasoning_content;
|
|
3807
|
+
_optionalChain([callbacks, 'access', _299 => _299.onReasoning, 'optionalCall', _300 => _300(delta.reasoning_content)]);
|
|
3808
|
+
}
|
|
3809
|
+
if (json.usage) {
|
|
3810
|
+
const tapped = readUsageFromOpenAIResponse(json.usage);
|
|
3811
|
+
if (_optionalChain([recording, 'optionalAccess', _301 => _301.recorder]) && tapped) {
|
|
3812
|
+
recording.recorder.record({
|
|
3813
|
+
messageId: _nullishCoalesce(options.messageId, () => ( null)),
|
|
3814
|
+
parentMessageId: _nullishCoalesce(options.parentMessageId, () => ( null)),
|
|
3815
|
+
sessionId: _nullishCoalesce(options.sessionId, () => ( null)),
|
|
3816
|
+
providerId: actualProviderId,
|
|
3817
|
+
model: actualModel,
|
|
3818
|
+
apiKeyId: _nullishCoalesce(recording.apiKeyId, () => ( null)),
|
|
3819
|
+
engineOrigin: "completion",
|
|
3820
|
+
usage: tapped,
|
|
3821
|
+
rawUsage: json.usage
|
|
3822
|
+
});
|
|
3823
|
+
}
|
|
3824
|
+
_optionalChain([callbacks, 'access', _302 => _302.onDone, 'optionalCall', _303 => _303(
|
|
3825
|
+
{
|
|
3826
|
+
id: messageId,
|
|
3827
|
+
role: "assistant",
|
|
3828
|
+
content,
|
|
3829
|
+
timestamp: Date.now(),
|
|
3830
|
+
thinking: reasoning ? { content: reasoning } : void 0
|
|
3831
|
+
},
|
|
3832
|
+
{
|
|
3833
|
+
promptTokens: json.usage.prompt_tokens || 0,
|
|
3834
|
+
completionTokens: json.usage.completion_tokens || 0,
|
|
3835
|
+
totalTokens: json.usage.total_tokens || 0
|
|
3836
|
+
}
|
|
3837
|
+
)]);
|
|
3838
|
+
return;
|
|
3839
|
+
}
|
|
3840
|
+
} catch (_e) {
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
}
|
|
3845
|
+
_optionalChain([callbacks, 'access', _304 => _304.onDone, 'optionalCall', _305 => _305({
|
|
3846
|
+
id: messageId,
|
|
3847
|
+
role: "assistant",
|
|
3848
|
+
content,
|
|
3849
|
+
timestamp: Date.now(),
|
|
3850
|
+
thinking: reasoning ? { content: reasoning } : void 0
|
|
3851
|
+
})]);
|
|
3852
|
+
} finally {
|
|
3853
|
+
reader.releaseLock();
|
|
3854
|
+
}
|
|
3855
|
+
} catch (error) {
|
|
3856
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3857
|
+
_optionalChain([callbacks, 'access', _306 => _306.onError, 'optionalCall', _307 => _307(message)]);
|
|
3858
|
+
}
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3861
|
+
// src/completion/CompletionService.ts
|
|
3862
|
+
var CompletionService = (_class5 = class {
|
|
3863
|
+
constructor(paths, llmConfig, logger) {;_class5.prototype.__init10.call(this);_class5.prototype.__init11.call(this);_class5.prototype.__init12.call(this);_class5.prototype.__init13.call(this);
|
|
3864
|
+
this.paths = paths;
|
|
3865
|
+
this.llmConfig = llmConfig;
|
|
3866
|
+
this.logger = logger;
|
|
3867
|
+
}
|
|
3868
|
+
|
|
3869
|
+
|
|
3870
|
+
|
|
3871
|
+
__init10() {this.apiKeyPool = null}
|
|
3872
|
+
__init11() {this.usageRecorder = null}
|
|
3873
|
+
__init12() {this.usageEventSink = null}
|
|
3874
|
+
__init13() {this.visionFallbackProvider = null}
|
|
3875
|
+
/**
|
|
3876
|
+
* Set the API key pool service for multi-key load balancing.
|
|
3877
|
+
* When set, keys are resolved via the pool instead of directly from the provider.
|
|
3878
|
+
*/
|
|
3879
|
+
setApiKeyPool(pool) {
|
|
3880
|
+
this.apiKeyPool = pool;
|
|
3881
|
+
}
|
|
3882
|
+
/**
|
|
3883
|
+
* Set the usage recorder so completion paths can persist token/cost stats.
|
|
3884
|
+
* Optional — when unset, all calls succeed but nothing is recorded.
|
|
3885
|
+
*/
|
|
3886
|
+
setUsageRecorder(recorder) {
|
|
3887
|
+
this.usageRecorder = recorder;
|
|
3888
|
+
}
|
|
3889
|
+
/**
|
|
3890
|
+
* Set the usage-event sink so completion paths can push live usage events
|
|
3891
|
+
* (context-meter, aggregate recorder) into the in-process hub. Injected DOWN
|
|
3892
|
+
* at bootstrap with `getUsageEventHub()` immediately after construction, so
|
|
3893
|
+
* emission is unconditional in production. Optional — when unset (unit-test
|
|
3894
|
+
* constructors), the emit calls no-op, identical to `usageRecorder`.
|
|
3895
|
+
*/
|
|
3896
|
+
setUsageEventSink(sink) {
|
|
3897
|
+
this.usageEventSink = sink;
|
|
3898
|
+
}
|
|
3899
|
+
/**
|
|
3900
|
+
* Set the vision-fallback provider used by `applyVisionFallback` to describe
|
|
3901
|
+
* images for non-vision models. Injected DOWN by the host at bootstrap
|
|
3902
|
+
* (the host's impl is built on top of CompletionService).
|
|
3903
|
+
* Optional — when unset, `applyVisionFallback` strips images instead.
|
|
3904
|
+
*/
|
|
3905
|
+
setVisionFallbackProvider(provider) {
|
|
3906
|
+
this.visionFallbackProvider = provider;
|
|
3907
|
+
}
|
|
3908
|
+
/**
|
|
3909
|
+
* Get provider by ID
|
|
3910
|
+
* Delegates to the `ProviderConfigSource` which maintains its own in-memory cache.
|
|
3911
|
+
*/
|
|
3912
|
+
async getProvider(providerId) {
|
|
3913
|
+
return this.llmConfig.getProvider(providerId);
|
|
3914
|
+
}
|
|
3915
|
+
/**
|
|
3916
|
+
* Send a completion request
|
|
3917
|
+
*/
|
|
3918
|
+
async complete(options) {
|
|
3919
|
+
try {
|
|
3920
|
+
const routedInfo = await this.llmConfig.resolveRoutedModel(
|
|
3921
|
+
options.providerId,
|
|
3922
|
+
options.model
|
|
3923
|
+
);
|
|
3924
|
+
const actualProviderId = _optionalChain([routedInfo, 'optionalAccess', _308 => _308.actualProviderId]) || options.providerId;
|
|
3925
|
+
const actualModel = _optionalChain([routedInfo, 'optionalAccess', _309 => _309.actualModelId]) || options.model;
|
|
3926
|
+
const provider = await this.getProvider(actualProviderId);
|
|
3927
|
+
if (!provider) {
|
|
3928
|
+
return { success: false, error: `Provider not found: ${actualProviderId}` };
|
|
3929
|
+
}
|
|
3930
|
+
if (!provider.enabled) {
|
|
3931
|
+
return { success: false, error: `Provider is disabled: ${provider.name}` };
|
|
3932
|
+
}
|
|
3933
|
+
const apiKey = await this.resolveApiKeyForRequest(provider, actualProviderId, options.sessionId);
|
|
3934
|
+
if (!apiKey) {
|
|
3935
|
+
return { success: false, error: "API key not configured" };
|
|
3936
|
+
}
|
|
3937
|
+
const apiFormat = _chunkA2YMUCBTcjs.resolveApiFormat.call(void 0, provider);
|
|
3938
|
+
this.logger.info("Using API format for completion", { apiFormat, providerId: actualProviderId });
|
|
3939
|
+
const resolvedOptions = { ...options, model: actualModel };
|
|
3940
|
+
let result = await this.callDirectHandler(apiFormat, provider, apiKey, resolvedOptions);
|
|
3941
|
+
if (!result.success && result.error && this.apiKeyPool && options.sessionId) {
|
|
3942
|
+
const status = this.extractHttpStatus(result.error);
|
|
3943
|
+
if (status && (status === 429 || status === 529 || status === 401 || status === 403)) {
|
|
3944
|
+
const newKey = await this.apiKeyPool.reportError(actualProviderId, options.sessionId, status);
|
|
3945
|
+
if (newKey) {
|
|
3946
|
+
this.logger.info("Retrying completion with new API key", {
|
|
3947
|
+
providerId: actualProviderId,
|
|
3948
|
+
statusCode: status
|
|
3949
|
+
});
|
|
3950
|
+
result = await this.callDirectHandler(apiFormat, provider, newKey, resolvedOptions);
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
if (result.success && this.apiKeyPool && options.sessionId) {
|
|
3955
|
+
this.apiKeyPool.reportSuccess(options.sessionId);
|
|
3956
|
+
}
|
|
3957
|
+
if (result.success && result.usage && options.sessionId) {
|
|
3958
|
+
_optionalChain([this, 'access', _310 => _310.usageEventSink, 'optionalAccess', _311 => _311.emit, 'call', _312 => _312({
|
|
3959
|
+
sessionId: options.sessionId,
|
|
3960
|
+
modelId: actualModel,
|
|
3961
|
+
usage: result.usage,
|
|
3962
|
+
engineOrigin: "completion"
|
|
3963
|
+
})]);
|
|
3964
|
+
}
|
|
3965
|
+
return result;
|
|
3966
|
+
} catch (error) {
|
|
3967
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3968
|
+
return { success: false, error: message };
|
|
3969
|
+
}
|
|
3970
|
+
}
|
|
3971
|
+
/**
|
|
3972
|
+
* Send a streaming completion request
|
|
3973
|
+
*/
|
|
3974
|
+
async completeStream(options, callbacks) {
|
|
3975
|
+
try {
|
|
3976
|
+
await this.applyVisionFallback(options);
|
|
3977
|
+
this.logger.info("Starting stream completion", {
|
|
3978
|
+
providerId: options.providerId,
|
|
3979
|
+
model: options.model,
|
|
3980
|
+
messagesCount: options.messages.length
|
|
3981
|
+
});
|
|
3982
|
+
const routedInfo = await this.llmConfig.resolveRoutedModel(
|
|
3983
|
+
options.providerId,
|
|
3984
|
+
options.model
|
|
3985
|
+
);
|
|
3986
|
+
this.logger.debug("Resolved routed model", { routedInfo });
|
|
3987
|
+
const actualProviderId = _optionalChain([routedInfo, 'optionalAccess', _313 => _313.actualProviderId]) || options.providerId;
|
|
3988
|
+
const actualModel = _optionalChain([routedInfo, 'optionalAccess', _314 => _314.actualModelId]) || options.model;
|
|
3989
|
+
this.logger.debug("Resolved provider and model", { actualProviderId, actualModel });
|
|
3990
|
+
const provider = await this.getProvider(actualProviderId);
|
|
3991
|
+
this.logger.debug("Retrieved provider", provider ? {
|
|
3992
|
+
id: provider.id,
|
|
3993
|
+
name: provider.name,
|
|
3994
|
+
apiType: provider.apiType,
|
|
3995
|
+
apiFormat: provider.apiFormat,
|
|
3996
|
+
api_base_url: provider.api_base_url,
|
|
3997
|
+
enabled: provider.enabled
|
|
3998
|
+
} : { error: "Provider not found" });
|
|
3999
|
+
if (!provider) {
|
|
4000
|
+
_optionalChain([callbacks, 'access', _315 => _315.onError, 'optionalCall', _316 => _316(`Provider not found: ${actualProviderId}`)]);
|
|
4001
|
+
return;
|
|
4002
|
+
}
|
|
4003
|
+
if (!provider.enabled) {
|
|
4004
|
+
_optionalChain([callbacks, 'access', _317 => _317.onError, 'optionalCall', _318 => _318(`Provider is disabled: ${provider.name}`)]);
|
|
4005
|
+
return;
|
|
4006
|
+
}
|
|
4007
|
+
const apiKey = await this.resolveApiKeyForRequest(provider, actualProviderId, options.sessionId);
|
|
4008
|
+
if (!apiKey) {
|
|
4009
|
+
_optionalChain([callbacks, 'access', _319 => _319.onError, 'optionalCall', _320 => _320("API key not configured")]);
|
|
4010
|
+
return;
|
|
4011
|
+
}
|
|
4012
|
+
const apiFormat = _chunkA2YMUCBTcjs.resolveApiFormat.call(void 0, provider);
|
|
4013
|
+
this.logger.info("Using API format for stream", { apiFormat });
|
|
4014
|
+
const messageId = `msg_${Date.now()}`;
|
|
4015
|
+
_optionalChain([callbacks, 'access', _321 => _321.onStart, 'optionalCall', _322 => _322(messageId)]);
|
|
4016
|
+
const resolvedOptions = { ...options, model: actualModel };
|
|
4017
|
+
if (options.sessionId) {
|
|
4018
|
+
const sid = options.sessionId;
|
|
4019
|
+
const userOnDone = callbacks.onDone;
|
|
4020
|
+
callbacks = {
|
|
4021
|
+
...callbacks,
|
|
4022
|
+
onDone: (message, usage, metrics) => {
|
|
4023
|
+
if (usage) {
|
|
4024
|
+
_optionalChain([this, 'access', _323 => _323.usageEventSink, 'optionalAccess', _324 => _324.emit, 'call', _325 => _325({
|
|
4025
|
+
sessionId: sid,
|
|
4026
|
+
modelId: actualModel,
|
|
4027
|
+
usage,
|
|
4028
|
+
engineOrigin: "completion"
|
|
4029
|
+
})]);
|
|
4030
|
+
}
|
|
4031
|
+
_optionalChain([userOnDone, 'optionalCall', _326 => _326(message, usage, metrics)]);
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
if (this.apiKeyPool && options.sessionId) {
|
|
4036
|
+
const retryState = { error: null };
|
|
4037
|
+
const interceptCallbacks = {
|
|
4038
|
+
...callbacks,
|
|
4039
|
+
onStart: void 0,
|
|
4040
|
+
// already called above
|
|
4041
|
+
onError: (error) => {
|
|
4042
|
+
const status = this.extractHttpStatus(error);
|
|
4043
|
+
if (status && (status === 429 || status === 529 || status === 401 || status === 403)) {
|
|
4044
|
+
retryState.error = { status, message: error };
|
|
4045
|
+
return;
|
|
4046
|
+
}
|
|
4047
|
+
_optionalChain([callbacks, 'access', _327 => _327.onError, 'optionalCall', _328 => _328(error)]);
|
|
4048
|
+
},
|
|
4049
|
+
onDone: (message, usage, metrics) => {
|
|
4050
|
+
this.apiKeyPool.reportSuccess(options.sessionId);
|
|
4051
|
+
_optionalChain([callbacks, 'access', _329 => _329.onDone, 'optionalCall', _330 => _330(message, usage, metrics)]);
|
|
4052
|
+
}
|
|
4053
|
+
};
|
|
4054
|
+
await this.callStreamHandler(apiFormat, provider, apiKey, resolvedOptions, messageId, interceptCallbacks);
|
|
4055
|
+
if (retryState.error) {
|
|
4056
|
+
const newKey = await this.apiKeyPool.reportError(
|
|
4057
|
+
actualProviderId,
|
|
4058
|
+
options.sessionId,
|
|
4059
|
+
retryState.error.status
|
|
4060
|
+
);
|
|
4061
|
+
if (newKey) {
|
|
4062
|
+
this.logger.info("Retrying stream with new API key", {
|
|
4063
|
+
providerId: actualProviderId,
|
|
4064
|
+
statusCode: retryState.error.status
|
|
4065
|
+
});
|
|
4066
|
+
await this.callStreamHandler(apiFormat, provider, newKey, resolvedOptions, messageId, {
|
|
4067
|
+
...callbacks,
|
|
4068
|
+
onStart: void 0
|
|
4069
|
+
// don't fire onStart again
|
|
4070
|
+
});
|
|
4071
|
+
} else {
|
|
4072
|
+
_optionalChain([callbacks, 'access', _331 => _331.onError, 'optionalCall', _332 => _332(retryState.error.message)]);
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
} else {
|
|
4076
|
+
await this.callStreamHandler(apiFormat, provider, apiKey, resolvedOptions, messageId, {
|
|
4077
|
+
...callbacks,
|
|
4078
|
+
onStart: void 0
|
|
4079
|
+
// already called above
|
|
4080
|
+
});
|
|
4081
|
+
}
|
|
4082
|
+
} catch (error) {
|
|
4083
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4084
|
+
this.logger.error("Stream completion error", error instanceof Error ? error : void 0, { message });
|
|
4085
|
+
_optionalChain([callbacks, 'access', _333 => _333.onError, 'optionalCall', _334 => _334(message)]);
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
/**
|
|
4089
|
+
* Get available models for a provider
|
|
4090
|
+
*/
|
|
4091
|
+
async getAvailableModels(providerId) {
|
|
4092
|
+
const provider = await this.getProvider(providerId);
|
|
4093
|
+
if (!provider) return [];
|
|
4094
|
+
return provider.models || [];
|
|
4095
|
+
}
|
|
4096
|
+
/**
|
|
4097
|
+
* Test provider connection with a specific model.
|
|
4098
|
+
* Sends "Hello" and returns the AI response, duration, etc.
|
|
4099
|
+
*/
|
|
4100
|
+
async testModel(providerId, modelId) {
|
|
4101
|
+
try {
|
|
4102
|
+
const provider = await this.getProvider(providerId);
|
|
4103
|
+
if (!provider) {
|
|
4104
|
+
return { success: false, message: "Provider not found", model: modelId };
|
|
4105
|
+
}
|
|
4106
|
+
const apiKey = this.resolveApiKey(provider.api_key);
|
|
4107
|
+
if (!apiKey) {
|
|
4108
|
+
return { success: false, message: "API key not configured", model: modelId };
|
|
4109
|
+
}
|
|
4110
|
+
const testMessages = [
|
|
4111
|
+
{ id: "test", role: "user", content: "Hello", timestamp: Date.now() }
|
|
4112
|
+
];
|
|
4113
|
+
const startTime = Date.now();
|
|
4114
|
+
const result = await this.complete({
|
|
4115
|
+
providerId,
|
|
4116
|
+
model: modelId,
|
|
4117
|
+
messages: testMessages,
|
|
4118
|
+
maxTokens: 100
|
|
4119
|
+
});
|
|
4120
|
+
const durationMs = Date.now() - startTime;
|
|
4121
|
+
if (result.success) {
|
|
4122
|
+
return {
|
|
4123
|
+
success: true,
|
|
4124
|
+
message: "Connection successful",
|
|
4125
|
+
response: _optionalChain([result, 'access', _335 => _335.message, 'optionalAccess', _336 => _336.content]) || "",
|
|
4126
|
+
model: modelId,
|
|
4127
|
+
durationMs
|
|
4128
|
+
};
|
|
4129
|
+
} else {
|
|
4130
|
+
return {
|
|
4131
|
+
success: false,
|
|
4132
|
+
message: result.error || "Unknown error",
|
|
4133
|
+
model: modelId,
|
|
4134
|
+
durationMs
|
|
4135
|
+
};
|
|
4136
|
+
}
|
|
4137
|
+
} catch (error) {
|
|
4138
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4139
|
+
return { success: false, message, model: modelId };
|
|
4140
|
+
}
|
|
4141
|
+
}
|
|
4142
|
+
/**
|
|
4143
|
+
* Check if any messages contain images and the model lacks vision capability.
|
|
4144
|
+
* If so, use the auxiliary vision model to describe images as text and remove
|
|
4145
|
+
* the image attachments from the messages.
|
|
4146
|
+
*
|
|
4147
|
+
* Mutates options.messages in-place.
|
|
4148
|
+
*/
|
|
4149
|
+
async applyVisionFallback(options) {
|
|
4150
|
+
const hasImages = options.messages.some((m) => m.images && m.images.length > 0);
|
|
4151
|
+
if (!hasImages) return;
|
|
4152
|
+
const hasVision = await this.llmConfig.hasVisionCapability(options.providerId, options.model);
|
|
4153
|
+
if (hasVision) return;
|
|
4154
|
+
const { vision: effectiveVisionModel } = await this.llmConfig.resolveEffectiveModels();
|
|
4155
|
+
if (!effectiveVisionModel || !this.visionFallbackProvider) {
|
|
4156
|
+
this.logger.info("Messages contain images but no vision auxiliary model configured; stripping images");
|
|
4157
|
+
for (const msg of options.messages) {
|
|
4158
|
+
if (msg.images && msg.images.length > 0) {
|
|
4159
|
+
msg.images = void 0;
|
|
4160
|
+
}
|
|
4161
|
+
}
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
this.logger.info("Model lacks vision capability, using auxiliary vision model for image descriptions", {
|
|
4165
|
+
providerId: options.providerId,
|
|
4166
|
+
model: options.model
|
|
4167
|
+
});
|
|
4168
|
+
const visionService = this.visionFallbackProvider;
|
|
4169
|
+
for (const msg of options.messages) {
|
|
4170
|
+
if (msg.images && msg.images.length > 0) {
|
|
4171
|
+
const imageDataUrls = msg.images.map((img) => ({ data: img.url }));
|
|
4172
|
+
const description = await visionService.describeImages(imageDataUrls, msg.content, effectiveVisionModel);
|
|
4173
|
+
if (description && description !== "[Image description unavailable]") {
|
|
4174
|
+
msg.content = `${msg.content}
|
|
4175
|
+
|
|
4176
|
+
[Image Description]
|
|
4177
|
+
${description}`;
|
|
4178
|
+
}
|
|
4179
|
+
msg.images = void 0;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
/**
|
|
4184
|
+
* Resolve API key for a request with priority:
|
|
4185
|
+
* 1. Coding Plan override (if enabled)
|
|
4186
|
+
* 2. API key pool (session-affinity weighted round-robin)
|
|
4187
|
+
* 3. Legacy single key from provider config
|
|
4188
|
+
*/
|
|
4189
|
+
async resolveApiKeyForRequest(provider, providerId, sessionId) {
|
|
4190
|
+
if (_optionalChain([provider, 'access', _337 => _337.codingPlan, 'optionalAccess', _338 => _338.enabled]) && provider.codingPlan.apiKey) {
|
|
4191
|
+
return this.resolveApiKey(provider.codingPlan.apiKey);
|
|
4192
|
+
}
|
|
4193
|
+
if (this.apiKeyPool) {
|
|
4194
|
+
const poolKey = sessionId ? await this.apiKeyPool.getKeyForSession(providerId, sessionId) : await this.apiKeyPool.getKey(providerId);
|
|
4195
|
+
if (poolKey) return poolKey;
|
|
4196
|
+
}
|
|
4197
|
+
const { apiKey: effectiveKey } = _chunkA2YMUCBTcjs.resolveProviderEndpoint.call(void 0, provider);
|
|
4198
|
+
return this.resolveApiKey(effectiveKey);
|
|
4199
|
+
}
|
|
4200
|
+
// ==========================================================================
|
|
4201
|
+
// Handler dispatch helpers
|
|
4202
|
+
// ==========================================================================
|
|
4203
|
+
/**
|
|
4204
|
+
* Extract HTTP status code from error messages like "API error (429): ..."
|
|
4205
|
+
*/
|
|
4206
|
+
extractHttpStatus(error) {
|
|
4207
|
+
const match = error.match(/\((\d{3})\):/);
|
|
4208
|
+
return match ? parseInt(match[1], 10) : null;
|
|
4209
|
+
}
|
|
4210
|
+
/**
|
|
4211
|
+
* Dispatch a non-streaming completion to the appropriate handler.
|
|
4212
|
+
*/
|
|
4213
|
+
async callDirectHandler(apiFormat, provider, apiKey, options) {
|
|
4214
|
+
switch (apiFormat) {
|
|
4215
|
+
case "anthropic":
|
|
4216
|
+
return callAnthropicCompletion(provider, apiKey, options, this.logger);
|
|
4217
|
+
case "google":
|
|
4218
|
+
return callGeminiCompletion(provider, apiKey, options, this.logger);
|
|
4219
|
+
case "openai-response":
|
|
4220
|
+
return callOpenAIResponseCompletion(provider, apiKey, options, this.logger);
|
|
4221
|
+
case "azure-openai":
|
|
4222
|
+
case "openai":
|
|
4223
|
+
default:
|
|
4224
|
+
return callOpenAICompletion(provider, apiKey, options, this.logger);
|
|
4225
|
+
}
|
|
4226
|
+
}
|
|
4227
|
+
/**
|
|
4228
|
+
* Dispatch a streaming completion to the appropriate handler.
|
|
4229
|
+
*/
|
|
4230
|
+
async callStreamHandler(apiFormat, provider, apiKey, options, messageId, callbacks) {
|
|
4231
|
+
switch (apiFormat) {
|
|
4232
|
+
case "openai-response":
|
|
4233
|
+
await streamOpenAIResponseCompletion(provider, apiKey, options, messageId, callbacks, this.logger);
|
|
4234
|
+
return;
|
|
4235
|
+
case "anthropic":
|
|
4236
|
+
await streamAnthropicCompletion(provider, apiKey, options, messageId, callbacks, this.logger);
|
|
4237
|
+
return;
|
|
4238
|
+
case "google":
|
|
4239
|
+
await streamGeminiCompletion(provider, apiKey, options, messageId, callbacks, this.logger);
|
|
4240
|
+
return;
|
|
4241
|
+
case "azure-openai":
|
|
4242
|
+
case "openai":
|
|
4243
|
+
default:
|
|
4244
|
+
await streamOpenAICompletion(provider, apiKey, options, messageId, callbacks, this.logger);
|
|
4245
|
+
return;
|
|
4246
|
+
}
|
|
4247
|
+
}
|
|
4248
|
+
/**
|
|
4249
|
+
* Resolve API key (handle environment variable references)
|
|
4250
|
+
*/
|
|
4251
|
+
resolveApiKey(apiKey) {
|
|
4252
|
+
if (!apiKey) return "";
|
|
4253
|
+
if (apiKey.startsWith("$")) {
|
|
4254
|
+
const envVar = apiKey.slice(1);
|
|
4255
|
+
return process.env[envVar] || "";
|
|
4256
|
+
}
|
|
4257
|
+
return apiKey;
|
|
4258
|
+
}
|
|
4259
|
+
/**
|
|
4260
|
+
* Resolve effective max_tokens value with priority:
|
|
4261
|
+
* 1. Session settings (if provided)
|
|
4262
|
+
* 2. Global model parameters (if enabled)
|
|
4263
|
+
* 3. Model's maxTokens from provider config
|
|
4264
|
+
* 4. Discovered models cache (from API)
|
|
4265
|
+
* 5. undefined - let API use its default
|
|
4266
|
+
*/
|
|
4267
|
+
async resolveEffectiveMaxTokens(providerId, modelId, sessionMaxTokens) {
|
|
4268
|
+
return resolveEffectiveMaxTokens(
|
|
4269
|
+
this.llmConfig,
|
|
4270
|
+
this.getProvider.bind(this),
|
|
4271
|
+
this.logger,
|
|
4272
|
+
providerId,
|
|
4273
|
+
modelId,
|
|
4274
|
+
sessionMaxTokens
|
|
4275
|
+
);
|
|
4276
|
+
}
|
|
4277
|
+
/**
|
|
4278
|
+
* Get required max_tokens for providers that need it (e.g., Anthropic)
|
|
4279
|
+
* Falls back to DEFAULT_MAX_TOKENS if no value is configured
|
|
4280
|
+
*/
|
|
4281
|
+
async getRequiredMaxTokens(providerId, modelId, sessionMaxTokens) {
|
|
4282
|
+
return getRequiredMaxTokens(
|
|
4283
|
+
this.llmConfig,
|
|
4284
|
+
this.getProvider.bind(this),
|
|
4285
|
+
this.logger,
|
|
4286
|
+
providerId,
|
|
4287
|
+
modelId,
|
|
4288
|
+
sessionMaxTokens
|
|
4289
|
+
);
|
|
4290
|
+
}
|
|
4291
|
+
/**
|
|
4292
|
+
* Calculate thinking budget and adjust max_tokens for the provider
|
|
4293
|
+
*/
|
|
4294
|
+
async resolveThinkingBudget(providerId, modelId, maxTokens, thinkLevel) {
|
|
4295
|
+
return resolveThinkingBudget(
|
|
4296
|
+
this.getProvider.bind(this),
|
|
4297
|
+
this.logger,
|
|
4298
|
+
providerId,
|
|
4299
|
+
modelId,
|
|
4300
|
+
maxTokens,
|
|
4301
|
+
thinkLevel
|
|
4302
|
+
);
|
|
4303
|
+
}
|
|
4304
|
+
// --------------------------------------------------------------------------
|
|
4305
|
+
// Transformer Chain Completion
|
|
4306
|
+
// --------------------------------------------------------------------------
|
|
4307
|
+
/**
|
|
4308
|
+
* Send a completion request using transformer chain
|
|
4309
|
+
*/
|
|
4310
|
+
async completeWithTransformers(options) {
|
|
4311
|
+
return completeWithTransformers(
|
|
4312
|
+
options,
|
|
4313
|
+
this.llmConfig,
|
|
4314
|
+
this.getProvider.bind(this),
|
|
4315
|
+
this.resolveApiKey.bind(this),
|
|
4316
|
+
this.complete.bind(this),
|
|
4317
|
+
this.logger,
|
|
4318
|
+
this.usageRecorder ? { recorder: this.usageRecorder } : void 0
|
|
4319
|
+
);
|
|
4320
|
+
}
|
|
4321
|
+
/**
|
|
4322
|
+
* Send a streaming completion request using transformer chain
|
|
4323
|
+
*/
|
|
4324
|
+
async completeStreamWithTransformers(options, callbacks) {
|
|
4325
|
+
return completeStreamWithTransformers(
|
|
4326
|
+
options,
|
|
4327
|
+
callbacks,
|
|
4328
|
+
this.llmConfig,
|
|
4329
|
+
this.getProvider.bind(this),
|
|
4330
|
+
this.resolveApiKey.bind(this),
|
|
4331
|
+
this.completeStream.bind(this),
|
|
4332
|
+
this.logger,
|
|
4333
|
+
this.usageRecorder ? { recorder: this.usageRecorder } : void 0
|
|
4334
|
+
);
|
|
4335
|
+
}
|
|
4336
|
+
// --------------------------------------------------------------------------
|
|
4337
|
+
// Tool-based Completion
|
|
4338
|
+
// --------------------------------------------------------------------------
|
|
4339
|
+
/**
|
|
4340
|
+
* Stream completion with MCP tools support (direct API call)
|
|
4341
|
+
* Implements agentic loop: calls LLM -> executes tools -> calls LLM again until done
|
|
4342
|
+
*/
|
|
4343
|
+
async streamWithTools(options, callbacks, mcpService, builtinExecutor) {
|
|
4344
|
+
return streamWithTools(
|
|
4345
|
+
options,
|
|
4346
|
+
callbacks,
|
|
4347
|
+
mcpService,
|
|
4348
|
+
this.llmConfig,
|
|
4349
|
+
this.getProvider.bind(this),
|
|
4350
|
+
this.resolveApiKey.bind(this),
|
|
4351
|
+
this.logger,
|
|
4352
|
+
builtinExecutor
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
4355
|
+
}, _class5);
|
|
4356
|
+
|
|
4357
|
+
|
|
4358
|
+
|
|
4359
|
+
|
|
4360
|
+
|
|
4361
|
+
|
|
4362
|
+
|
|
4363
|
+
|
|
4364
|
+
|
|
4365
|
+
|
|
4366
|
+
|
|
4367
|
+
|
|
4368
|
+
|
|
4369
|
+
|
|
4370
|
+
|
|
4371
|
+
|
|
4372
|
+
|
|
4373
|
+
|
|
4374
|
+
|
|
4375
|
+
|
|
4376
|
+
|
|
4377
|
+
|
|
4378
|
+
|
|
4379
|
+
|
|
4380
|
+
|
|
4381
|
+
|
|
4382
|
+
|
|
4383
|
+
|
|
4384
|
+
|
|
4385
|
+
|
|
4386
|
+
|
|
4387
|
+
|
|
4388
|
+
|
|
4389
|
+
|
|
4390
|
+
|
|
4391
|
+
|
|
4392
|
+
|
|
4393
|
+
|
|
4394
|
+
|
|
4395
|
+
|
|
4396
|
+
|
|
4397
|
+
|
|
4398
|
+
|
|
4399
|
+
|
|
4400
|
+
exports.CompletionService = CompletionService; exports.register = register; exports.emit = emit; exports.attach = attach; exports.release = release; exports.convertMessageToOpenAI = convertMessageToOpenAI; exports.convertMessageToAnthropic = convertMessageToAnthropic; exports.convertMessageToGemini = convertMessageToGemini; exports.getOpenRouterProviderConfig = getOpenRouterProviderConfig; exports.addOpenRouterProviderToRequest = addOpenRouterProviderToRequest; exports.DEFAULT_ROUTE_IDLE_MS = DEFAULT_ROUTE_IDLE_MS; exports.ProviderProxyRouteMap = ProviderProxyRouteMap; exports.getSharedExecutor = getSharedExecutor; exports.getResponsesEndpointTransformer = getResponsesEndpointTransformer; exports.getAnthropicEndpointTransformer = getAnthropicEndpointTransformer; exports.getGeminiEndpointTransformer = getGeminiEndpointTransformer; exports.readBody = readBody; exports.relayResponse = relayResponse; exports.writeError = writeError; exports.resolvePoolBoundKey = resolvePoolBoundKey; exports.extractRouteToken = extractRouteToken; exports.isLoopbackAddress = isLoopbackAddress; exports.ProviderProxy = ProviderProxy; exports.getProviderProxy = getProviderProxy; exports.__resetProviderProxyForTests = __resetProviderProxyForTests; exports.hashKey = hashKey; exports.createNamedKey = createNamedKey; exports.verifyPresentedKey = verifyPresentedKey; exports.detectRequestRole = detectRequestRole; exports.endpointToIngressFormat = endpointToIngressFormat; exports.OutboundRateLimiter = OutboundRateLimiter; exports.DEFAULT_OUTBOUND_PORT = DEFAULT_OUTBOUND_PORT; exports.OutboundApiServer = OutboundApiServer; exports.formatUrls = formatUrls; exports.OUTBOUND_API_SERVER_CONFIG_KEY = OUTBOUND_API_SERVER_CONFIG_KEY; exports.defaultServerConfig = defaultServerConfig; exports.normalizeServerConfig = normalizeServerConfig; exports.loadServerConfig = loadServerConfig; exports.saveServerConfig = saveServerConfig; exports.mergeServerConfig = mergeServerConfig; exports.getOutboundApiServer = getOutboundApiServer; exports.__resetOutboundApiServerForTests = __resetOutboundApiServerForTests;
|