@omnicross/core 0.1.5 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ApiConverter.cjs +2 -2
- package/dist/ApiConverter.js +1 -1
- package/dist/{CompletionService-DdWaAxq0.d.ts → CompletionService-C3BX0Ovy.d.ts} +4 -3
- package/dist/{CompletionService-D3a47vdU.d.cts → CompletionService-kNA17cfI.d.cts} +4 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +8 -6
- package/dist/auth/GeminiCodeAssistProjectResolver.js +6 -4
- package/dist/{chunk-3Z5BQZFM.cjs → chunk-235MXAGX.cjs} +3 -1
- package/dist/{chunk-HRRYG2AX.js → chunk-23S5OPIO.js} +10 -1
- package/dist/chunk-25GXJCEZ.cjs +40 -0
- package/dist/chunk-2E42SAW3.cjs +323 -0
- package/dist/chunk-2JYRIADS.js +40 -0
- package/dist/chunk-2Z4XM3H5.cjs +44 -0
- package/dist/{chunk-BODMAL6G.js → chunk-32O35CIF.js} +271 -91
- package/dist/chunk-3SCOLIPT.cjs +63 -0
- package/dist/{chunk-SYQF7OIR.js → chunk-3WD4HTR2.js} +2 -0
- package/dist/{chunk-CGKLE6IW.cjs → chunk-4BB343BI.cjs} +1489 -520
- package/dist/{chunk-VACWOIRE.cjs → chunk-4VZX5F4T.cjs} +39 -15
- package/dist/{chunk-5ZQBEOMD.js → chunk-5NJLQ6T6.js} +46 -12
- package/dist/chunk-5RSZYQJH.cjs +62 -0
- package/dist/chunk-6QQG7YG3.cjs +153 -0
- package/dist/{chunk-IFXJ2BG7.js → chunk-7C7DET6S.js} +51 -6
- package/dist/{chunk-IK3KE3AU.js → chunk-DE2SRBFZ.js} +1 -1
- package/dist/{chunk-FH2P4OCD.js → chunk-E3COOKUP.js} +1240 -271
- package/dist/{chunk-UHGVBFU3.js → chunk-EBLFCNPY.js} +38 -14
- package/dist/chunk-FU7EAR73.js +62 -0
- package/dist/chunk-FXR6N3SR.js +323 -0
- package/dist/{chunk-SUEGJ75D.cjs → chunk-GDIXXKF2.cjs} +46 -12
- package/dist/chunk-GFIZ6LQI.js +170 -0
- package/dist/chunk-GL6XM527.js +61 -0
- package/dist/chunk-HVBBDBS3.cjs +350 -0
- package/dist/{chunk-46ZIFWYG.cjs → chunk-IIRPUVPH.cjs} +7 -1
- package/dist/chunk-J3QQ6JS5.cjs +170 -0
- package/dist/{chunk-JWVTXXPX.cjs → chunk-KBM2W6UY.cjs} +8 -4
- package/dist/chunk-LB6EBS6Y.js +69 -0
- package/dist/{chunk-IWQAX3CS.js → chunk-LVUCAWMK.js} +1 -1
- package/dist/chunk-MKF46GNZ.js +350 -0
- package/dist/{chunk-HYN75H6G.cjs → chunk-MUHWHQVM.cjs} +1 -1
- package/dist/chunk-MYYR3HZL.js +44 -0
- package/dist/{chunk-MFZ5437A.js → chunk-N5T4M4GU.js} +1 -1
- package/dist/{chunk-5ENKBSWO.js → chunk-N7OOJI5V.js} +88 -8
- package/dist/chunk-NKVZ6Z3Y.cjs +61 -0
- package/dist/chunk-OF3BECU2.cjs +69 -0
- package/dist/{chunk-SNYBEXLB.cjs → chunk-OJGQKHN5.cjs} +3 -1
- package/dist/{chunk-DDMRUL76.cjs → chunk-OKMJLEXX.cjs} +5 -5
- package/dist/chunk-OPRS4IQZ.js +153 -0
- package/dist/chunk-OTZC4TNH.cjs +79 -0
- package/dist/{chunk-3Q6Z6JTO.cjs → chunk-OVQBNVSV.cjs} +287 -107
- package/dist/{chunk-4XMYPW46.cjs → chunk-OXHNSZ53.cjs} +84 -44
- package/dist/{chunk-C6OV7GCC.js → chunk-PEGTIPB3.js} +52 -12
- package/dist/chunk-PO3XB56Z.js +79 -0
- package/dist/{chunk-D3TDKWRP.js → chunk-RJ5ZGNI3.js} +54 -5
- package/dist/{chunk-PG7PS2NN.cjs → chunk-RJUONLUQ.cjs} +59 -10
- package/dist/{chunk-BPKCU575.js → chunk-SOO562NM.js} +3 -1
- package/dist/{chunk-6QZGDLL4.js → chunk-TOVPMMHM.js} +5 -1
- package/dist/{chunk-K52THW2C.cjs → chunk-V4TXMEJS.cjs} +5 -5
- package/dist/{chunk-G2UTNSGU.js → chunk-WNKWAEUR.js} +7 -1
- package/dist/{chunk-UNWKG6EI.cjs → chunk-XPIOJQS2.cjs} +51 -6
- package/dist/chunk-XXFYLL7R.js +63 -0
- package/dist/{chunk-KFI44N2R.cjs → chunk-YNJNTCSQ.cjs} +10 -1
- package/dist/{chunk-VQY5W4YW.cjs → chunk-YWL7YZWS.cjs} +90 -10
- package/dist/completion/ApiKeyPoolService.cjs +12 -0
- package/dist/completion/ApiKeyPoolService.d.cts +6 -0
- package/dist/completion/ApiKeyPoolService.d.ts +6 -0
- package/dist/completion/ApiKeyPoolService.js +12 -0
- package/dist/completion/CompletionService.cjs +30 -24
- package/dist/completion/CompletionService.d.cts +3 -1
- package/dist/completion/CompletionService.d.ts +3 -1
- package/dist/completion/CompletionService.js +29 -23
- package/dist/completion.cjs +32 -26
- package/dist/completion.d.cts +3 -1
- package/dist/completion.d.ts +3 -1
- package/dist/completion.js +31 -25
- package/dist/index.cjs +56 -29
- package/dist/index.d.cts +8 -8
- package/dist/index.d.ts +8 -8
- package/dist/index.js +61 -34
- package/dist/{keyPolicy-Cp0FntZl.d.cts → keyPolicy-zdWTu3CQ.d.cts} +1 -1
- package/dist/{keyPolicy-Cp0FntZl.d.ts → keyPolicy-zdWTu3CQ.d.ts} +1 -1
- package/dist/outbound-api/auditCapture.cjs +2 -2
- package/dist/outbound-api/auditCapture.d.cts +3 -4
- package/dist/outbound-api/auditCapture.d.ts +3 -4
- package/dist/outbound-api/auditCapture.js +1 -1
- package/dist/outbound-api/quotaWarn.d.cts +1 -1
- package/dist/outbound-api/quotaWarn.d.ts +1 -1
- package/dist/outbound-api/routeResolver.cjs +2 -2
- package/dist/outbound-api/routeResolver.d.cts +5 -3
- package/dist/outbound-api/routeResolver.d.ts +5 -3
- package/dist/outbound-api/routeResolver.js +1 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +2 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +2 -0
- package/dist/outbound-api/types.d.cts +4 -2
- package/dist/outbound-api/types.d.ts +4 -2
- package/dist/outbound-api.cjs +52 -25
- package/dist/outbound-api.d.cts +131 -87
- package/dist/outbound-api.d.ts +131 -87
- package/dist/outbound-api.js +55 -28
- package/dist/pipeline/AccountAllowanceScheduling.cjs +15 -0
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +91 -0
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +91 -0
- package/dist/pipeline/AccountAllowanceScheduling.js +15 -0
- package/dist/pipeline/AccountAllowanceStore.cjs +20 -0
- package/dist/pipeline/AccountAllowanceStore.d.cts +77 -0
- package/dist/pipeline/AccountAllowanceStore.d.ts +77 -0
- package/dist/pipeline/AccountAllowanceStore.js +20 -0
- package/dist/pipeline/AccountRouteActivity.cjs +10 -0
- package/dist/pipeline/AccountRouteActivity.d.cts +61 -0
- package/dist/pipeline/AccountRouteActivity.d.ts +61 -0
- package/dist/pipeline/AccountRouteActivity.js +10 -0
- package/dist/pipeline/AuthSource.d.cts +11 -2
- package/dist/pipeline/AuthSource.d.ts +11 -2
- package/dist/pipeline/BoundAccountSelectionError.cjs +10 -0
- package/dist/pipeline/BoundAccountSelectionError.d.cts +28 -0
- package/dist/pipeline/BoundAccountSelectionError.d.ts +28 -0
- package/dist/pipeline/BoundAccountSelectionError.js +10 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.cts +1 -0
- package/dist/pipeline/LlmConfigProviderAuth.d.ts +1 -0
- package/dist/pipeline/ServerOverloadCounter.cjs +16 -0
- package/dist/pipeline/ServerOverloadCounter.d.cts +77 -0
- package/dist/pipeline/ServerOverloadCounter.d.ts +77 -0
- package/dist/pipeline/ServerOverloadCounter.js +16 -0
- package/dist/pipeline/SubscriptionAccountHealth.cjs +6 -2
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +65 -8
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +65 -8
- package/dist/pipeline/SubscriptionAccountHealth.js +5 -1
- package/dist/pipeline/SubscriptionAuthSource.cjs +5 -2
- package/dist/pipeline/SubscriptionAuthSource.d.cts +1 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +1 -0
- package/dist/pipeline/SubscriptionAuthSource.js +4 -1
- package/dist/pipeline/SubscriptionAuthStrategy.d.cts +8 -3
- package/dist/pipeline/SubscriptionAuthStrategy.d.ts +8 -3
- package/dist/pipeline/executeProviderCall.cjs +2 -2
- package/dist/pipeline/executeProviderCall.d.cts +8 -3
- package/dist/pipeline/executeProviderCall.d.ts +8 -3
- package/dist/pipeline/executeProviderCall.js +1 -1
- package/dist/pipeline/resolveProviderChain.d.cts +2 -3
- package/dist/pipeline/resolveProviderChain.d.ts +2 -3
- package/dist/pipeline/resolveSubscriptionChain.d.cts +1 -0
- package/dist/pipeline/resolveSubscriptionChain.d.ts +1 -0
- package/dist/pipeline/upstreamFetch.cjs +5 -3
- package/dist/pipeline/upstreamFetch.d.cts +35 -6
- package/dist/pipeline/upstreamFetch.d.ts +35 -6
- package/dist/pipeline/upstreamFetch.js +4 -2
- package/dist/pipeline/upstreamTrace.cjs +4 -2
- package/dist/pipeline/upstreamTrace.d.cts +22 -8
- package/dist/pipeline/upstreamTrace.d.ts +22 -8
- package/dist/pipeline/upstreamTrace.js +3 -1
- package/dist/ports/pricing-store.d.cts +4 -3
- package/dist/ports/pricing-store.d.ts +4 -3
- package/dist/ports.d.cts +4 -3
- package/dist/ports.d.ts +4 -3
- package/dist/provider-proxy/ProviderProxy.cjs +30 -24
- package/dist/provider-proxy/ProviderProxy.d.cts +2 -0
- package/dist/provider-proxy/ProviderProxy.d.ts +2 -0
- package/dist/provider-proxy/ProviderProxy.js +29 -23
- package/dist/provider-proxy/identity/codexCliHeaders.cjs +10 -0
- package/dist/provider-proxy/identity/codexCliHeaders.d.cts +45 -0
- package/dist/provider-proxy/identity/codexCliHeaders.d.ts +45 -0
- package/dist/provider-proxy/identity/codexCliHeaders.js +10 -0
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +32 -24
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +44 -5
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +44 -5
- package/dist/provider-proxy/ingress/providerProxyShared.js +31 -23
- package/dist/provider-proxy/matchText.cjs +4 -2
- package/dist/provider-proxy/matchText.d.cts +39 -6
- package/dist/provider-proxy/matchText.d.ts +39 -6
- package/dist/provider-proxy/matchText.js +3 -1
- package/dist/provider-proxy/types.d.cts +35 -2
- package/dist/provider-proxy/types.d.ts +35 -2
- package/dist/provider-proxy.cjs +30 -24
- package/dist/provider-proxy.d.cts +2 -0
- package/dist/provider-proxy.d.ts +2 -0
- package/dist/provider-proxy.js +29 -23
- package/dist/{routeResolver-C4T7yClx.d.ts → routeResolver-BxOfunbm.d.ts} +1 -1
- package/dist/{routeResolver-CB3tvJKy.d.cts → routeResolver-D5OzD0iM.d.cts} +1 -1
- package/dist/transformer/TransformerChainExecutor.cjs +2 -2
- package/dist/transformer/TransformerChainExecutor.js +1 -1
- package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -2
- package/dist/transformer/transformers/AnthropicTransformer.js +2 -1
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +3 -3
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +2 -2
- package/dist/transformer/transformers/GeminiTransformer.cjs +3 -3
- package/dist/transformer/transformers/GeminiTransformer.js +2 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +1 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +1 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
- package/dist/transformer/transformers/OpenAITransformer.cjs +6 -0
- package/dist/transformer/transformers/OpenAITransformer.d.cts +68 -0
- package/dist/transformer/transformers/OpenAITransformer.d.ts +68 -0
- package/dist/transformer/transformers/OpenAITransformer.js +6 -0
- package/dist/transformer/transformers.cjs +9 -14
- package/dist/transformer/transformers.d.cts +12 -48
- package/dist/transformer/transformers.d.ts +12 -48
- package/dist/transformer/transformers.js +11 -16
- package/dist/transformer/types.d.cts +17 -0
- package/dist/transformer/types.d.ts +17 -0
- package/dist/transformer.cjs +12 -12
- package/dist/transformer.d.cts +1 -2
- package/dist/transformer.d.ts +1 -2
- package/dist/transformer.js +12 -12
- package/dist/{types-lgPYQuZS.d.ts → types-CqZD77rS.d.ts} +173 -7
- package/dist/{types-B8Arpxn0.d.cts → types-gjJ5lJ5n.d.cts} +173 -7
- package/dist/usage/pricing-engine.cjs +2 -2
- package/dist/usage/pricing-engine.d.cts +29 -1
- package/dist/usage/pricing-engine.d.ts +29 -1
- package/dist/usage/pricing-engine.js +1 -1
- package/dist/usage/usage-recorder.cjs +2 -2
- package/dist/usage/usage-recorder.d.cts +5 -1
- package/dist/usage/usage-recorder.d.ts +5 -1
- package/dist/usage/usage-recorder.js +1 -1
- package/dist/usage.cjs +3 -3
- package/dist/usage.js +2 -2
- package/package.json +1 -1
- package/dist/chunk-7FEBOPJO.cjs +0 -50
- package/dist/chunk-DWKQZ2SK.cjs +0 -254
- package/dist/chunk-IAXWICZU.js +0 -69
- package/dist/chunk-IB3FDI4O.js +0 -50
- package/dist/chunk-OQWKZCXP.js +0 -242
- package/dist/chunk-QWBWGODS.cjs +0 -69
- package/dist/chunk-YMFAJFQT.cjs +0 -173
- package/dist/chunk-ZCIQ3L3R.cjs +0 -242
- package/dist/chunk-ZJZAOKI7.js +0 -254
- package/dist/chunk-ZLX3KGXX.js +0 -173
- package/dist/transformer/transformers/OpenCodeGoTransformer.cjs +0 -6
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.cts +0 -51
- package/dist/transformer/transformers/OpenCodeGoTransformer.d.ts +0 -51
- package/dist/transformer/transformers/OpenCodeGoTransformer.js +0 -6
- package/dist/transformer/transformers/ReasoningTransformer.cjs +0 -6
- package/dist/transformer/transformers/ReasoningTransformer.d.cts +0 -47
- package/dist/transformer/transformers/ReasoningTransformer.d.ts +0 -47
- package/dist/transformer/transformers/ReasoningTransformer.js +0 -6
|
@@ -2,20 +2,29 @@ import {
|
|
|
2
2
|
streamSSEResponse
|
|
3
3
|
} from "./chunk-VVEHS2LI.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
AnthropicTransformer
|
|
9
|
-
} from "./chunk-C6OV7GCC.js";
|
|
5
|
+
AnthropicTransformer,
|
|
6
|
+
resolveAnthropicMaxTokens
|
|
7
|
+
} from "./chunk-PEGTIPB3.js";
|
|
10
8
|
import {
|
|
11
9
|
GeminiTransformer
|
|
12
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-LVUCAWMK.js";
|
|
13
11
|
import {
|
|
14
12
|
OpenAIResponseTransformer
|
|
15
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-32O35CIF.js";
|
|
14
|
+
import {
|
|
15
|
+
TransformerChainExecutor
|
|
16
|
+
} from "./chunk-WNKWAEUR.js";
|
|
16
17
|
import {
|
|
17
18
|
injectExtendedContextBeta
|
|
18
19
|
} from "./chunk-MZNPGW5Q.js";
|
|
20
|
+
import {
|
|
21
|
+
DEFAULT_CODEX_CLI_HEADERS,
|
|
22
|
+
NEVER_FORWARD_HEADERS,
|
|
23
|
+
codexAcceptHeader,
|
|
24
|
+
extractCodexClientHeaders,
|
|
25
|
+
fillMissingHeaders,
|
|
26
|
+
flattenHeaderValue
|
|
27
|
+
} from "./chunk-XXFYLL7R.js";
|
|
19
28
|
import {
|
|
20
29
|
getSharedIdentityStore
|
|
21
30
|
} from "./chunk-XTEI64OU.js";
|
|
@@ -25,37 +34,48 @@ import {
|
|
|
25
34
|
} from "./chunk-XX6NQJMA.js";
|
|
26
35
|
import {
|
|
27
36
|
collectMatchText,
|
|
37
|
+
deriveGatewaySessionKey,
|
|
28
38
|
deriveSubscriptionSessionKey
|
|
29
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-GFIZ6LQI.js";
|
|
30
40
|
import {
|
|
31
41
|
emitWebhookEvent
|
|
32
42
|
} from "./chunk-OE6TDIWW.js";
|
|
33
43
|
import {
|
|
34
44
|
getGeminiCodeAssistResolver
|
|
35
45
|
} from "./chunk-DRWHTIIZ.js";
|
|
36
|
-
import {
|
|
37
|
-
executeProviderCall
|
|
38
|
-
} from "./chunk-IK3KE3AU.js";
|
|
39
|
-
import {
|
|
40
|
-
LlmConfigProviderAuth,
|
|
41
|
-
getProviderHeaders
|
|
42
|
-
} from "./chunk-ZY6KU6P3.js";
|
|
43
46
|
import {
|
|
44
47
|
resolveProviderChain
|
|
45
48
|
} from "./chunk-JWC6APFQ.js";
|
|
46
49
|
import {
|
|
47
50
|
resolveSubscriptionChain
|
|
48
51
|
} from "./chunk-5ERIZW3D.js";
|
|
52
|
+
import {
|
|
53
|
+
getSharedOverloadCounter
|
|
54
|
+
} from "./chunk-PO3XB56Z.js";
|
|
49
55
|
import {
|
|
50
56
|
getSharedAccountHealth,
|
|
51
57
|
resolveResetSeconds
|
|
52
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-N7OOJI5V.js";
|
|
53
59
|
import {
|
|
54
60
|
SubscriptionAuthSource
|
|
55
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-23S5OPIO.js";
|
|
56
62
|
import {
|
|
57
63
|
serializeError
|
|
58
64
|
} from "./chunk-AVYJ4YBN.js";
|
|
65
|
+
import {
|
|
66
|
+
isAccountAllowanceExhaustedError
|
|
67
|
+
} from "./chunk-OPRS4IQZ.js";
|
|
68
|
+
import {
|
|
69
|
+
boundAccountSelectionMessage,
|
|
70
|
+
isBoundAccountSelectionError
|
|
71
|
+
} from "./chunk-2JYRIADS.js";
|
|
72
|
+
import {
|
|
73
|
+
executeProviderCall
|
|
74
|
+
} from "./chunk-DE2SRBFZ.js";
|
|
75
|
+
import {
|
|
76
|
+
LlmConfigProviderAuth,
|
|
77
|
+
getProviderHeaders
|
|
78
|
+
} from "./chunk-ZY6KU6P3.js";
|
|
59
79
|
import {
|
|
60
80
|
beginBillingCapture
|
|
61
81
|
} from "./chunk-WLYIIEAF.js";
|
|
@@ -64,15 +84,16 @@ import {
|
|
|
64
84
|
markQuotaWarnedOnce
|
|
65
85
|
} from "./chunk-3QFXFCHG.js";
|
|
66
86
|
import {
|
|
87
|
+
detectModelKind,
|
|
67
88
|
detectRequestRole,
|
|
68
89
|
endpointToIngressFormat,
|
|
69
90
|
extractRequestedModel,
|
|
70
91
|
isKindMappedEndpoint,
|
|
71
92
|
modelKindsForEndpoint,
|
|
72
93
|
parseModelRef,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
} from "./chunk-
|
|
94
|
+
resolvePrefixTarget,
|
|
95
|
+
resolveRoute
|
|
96
|
+
} from "./chunk-EBLFCNPY.js";
|
|
76
97
|
import {
|
|
77
98
|
buildProviderApiUrl,
|
|
78
99
|
resolveApiFormat,
|
|
@@ -80,14 +101,20 @@ import {
|
|
|
80
101
|
} from "./chunk-H5JUT3KV.js";
|
|
81
102
|
import {
|
|
82
103
|
beginAuditCapture
|
|
83
|
-
} from "./chunk-
|
|
104
|
+
} from "./chunk-7C7DET6S.js";
|
|
84
105
|
import {
|
|
85
106
|
convertAnthropicToOpenAI,
|
|
86
107
|
convertOpenAIToAnthropic
|
|
87
|
-
} from "./chunk-
|
|
108
|
+
} from "./chunk-5NJLQ6T6.js";
|
|
88
109
|
import {
|
|
89
110
|
fetchUpstream
|
|
90
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-RJ5ZGNI3.js";
|
|
112
|
+
import {
|
|
113
|
+
getSharedAccountAllowanceStore
|
|
114
|
+
} from "./chunk-MKF46GNZ.js";
|
|
115
|
+
import {
|
|
116
|
+
getSharedAccountRouteActivity
|
|
117
|
+
} from "./chunk-GL6XM527.js";
|
|
91
118
|
import {
|
|
92
119
|
applyAugmentation
|
|
93
120
|
} from "./chunk-Y7FO65VT.js";
|
|
@@ -469,6 +496,66 @@ var ProviderProxyRouteMap = class {
|
|
|
469
496
|
}
|
|
470
497
|
};
|
|
471
498
|
|
|
499
|
+
// src/provider-proxy/identity/claudeCodeHeaders.ts
|
|
500
|
+
var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
501
|
+
var OAUTH_BETA = "oauth-2025-04-20";
|
|
502
|
+
var CLAUDE_CODE_BETA = "claude-code-20250219";
|
|
503
|
+
var INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
504
|
+
var TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
|
|
505
|
+
var DEFAULT_CLAUDE_CODE_HEADERS = Object.freeze({
|
|
506
|
+
"user-agent": "claude-cli/1.0.119 (external, cli)",
|
|
507
|
+
"x-app": "cli",
|
|
508
|
+
accept: "application/json",
|
|
509
|
+
"accept-language": "*",
|
|
510
|
+
"sec-fetch-mode": "cors",
|
|
511
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
512
|
+
"x-stainless-lang": "js",
|
|
513
|
+
"x-stainless-package-version": "0.55.1",
|
|
514
|
+
"x-stainless-os": "Windows",
|
|
515
|
+
"x-stainless-arch": "x64",
|
|
516
|
+
"x-stainless-runtime": "node",
|
|
517
|
+
"x-stainless-runtime-version": "v20.19.2",
|
|
518
|
+
"x-stainless-retry-count": "0",
|
|
519
|
+
"x-stainless-timeout": "60"
|
|
520
|
+
});
|
|
521
|
+
var FORCED_ACCEPT_ENCODING = "identity";
|
|
522
|
+
var CLAUDE_FORWARD_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
523
|
+
"accept",
|
|
524
|
+
"accept-language",
|
|
525
|
+
"sec-fetch-mode",
|
|
526
|
+
"user-agent",
|
|
527
|
+
"x-app",
|
|
528
|
+
"anthropic-dangerous-direct-browser-access",
|
|
529
|
+
"anthropic-version"
|
|
530
|
+
]);
|
|
531
|
+
var STAINLESS_PREFIX = "x-stainless-";
|
|
532
|
+
function extractClaudeClientHeaders(reqHeaders) {
|
|
533
|
+
const out = {};
|
|
534
|
+
for (const [rawKey, rawVal] of Object.entries(reqHeaders)) {
|
|
535
|
+
const key = rawKey.toLowerCase();
|
|
536
|
+
if (NEVER_FORWARD_HEADERS.has(key)) continue;
|
|
537
|
+
if (!CLAUDE_FORWARD_ALLOWLIST.has(key) && !key.startsWith(STAINLESS_PREFIX)) continue;
|
|
538
|
+
const value = flattenHeaderValue(rawVal);
|
|
539
|
+
if (value !== void 0) out[key] = value;
|
|
540
|
+
}
|
|
541
|
+
return out;
|
|
542
|
+
}
|
|
543
|
+
function buildAnthropicBeta(model, callerBeta) {
|
|
544
|
+
const isHaiku = typeof model === "string" && model.toLowerCase().includes("haiku");
|
|
545
|
+
const baseline = isHaiku ? [OAUTH_BETA, INTERLEAVED_THINKING_BETA] : [CLAUDE_CODE_BETA, OAUTH_BETA, INTERLEAVED_THINKING_BETA, TOOL_STREAMING_BETA];
|
|
546
|
+
const seen = /* @__PURE__ */ new Set();
|
|
547
|
+
const out = [];
|
|
548
|
+
const add = (flag) => {
|
|
549
|
+
const trimmed = flag.trim();
|
|
550
|
+
if (!trimmed || seen.has(trimmed)) return;
|
|
551
|
+
seen.add(trimmed);
|
|
552
|
+
out.push(trimmed);
|
|
553
|
+
};
|
|
554
|
+
baseline.forEach(add);
|
|
555
|
+
if (callerBeta) callerBeta.split(",").forEach(add);
|
|
556
|
+
return out.join(",");
|
|
557
|
+
}
|
|
558
|
+
|
|
472
559
|
// src/provider-proxy/usage/recordAnthropicUsage.ts
|
|
473
560
|
function readAnthropicUsage(usage) {
|
|
474
561
|
if (!usage) return null;
|
|
@@ -486,23 +573,36 @@ function readAnthropicUsage(usage) {
|
|
|
486
573
|
reasoningTokens: 0
|
|
487
574
|
};
|
|
488
575
|
}
|
|
576
|
+
function emitAnthropicUsageRecord(recorder, rawUsage, attribution) {
|
|
577
|
+
const tapped = readAnthropicUsage(rawUsage);
|
|
578
|
+
if (!tapped) return;
|
|
579
|
+
recorder.record({
|
|
580
|
+
messageId: null,
|
|
581
|
+
parentMessageId: null,
|
|
582
|
+
sessionId: attribution.sessionId,
|
|
583
|
+
providerId: attribution.providerId,
|
|
584
|
+
model: attribution.model,
|
|
585
|
+
apiKeyId: attribution.apiKeyId,
|
|
586
|
+
auditResponse: attribution.auditResponse,
|
|
587
|
+
engineOrigin: "codex-ingress",
|
|
588
|
+
usage: tapped,
|
|
589
|
+
rawUsage
|
|
590
|
+
});
|
|
591
|
+
}
|
|
489
592
|
function recordAnthropicNonStreamUsage(recorder, bodyText, attribution) {
|
|
490
593
|
try {
|
|
491
594
|
const parsed = JSON.parse(bodyText);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
rawUsage: parsed.usage,
|
|
504
|
-
auditResponse: attribution.auditResponse
|
|
505
|
-
});
|
|
595
|
+
emitAnthropicUsageRecord(
|
|
596
|
+
recorder,
|
|
597
|
+
parsed.usage,
|
|
598
|
+
attribution
|
|
599
|
+
);
|
|
600
|
+
} catch {
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function recordAnthropicStreamUsage(recorder, rawUsage, attribution) {
|
|
604
|
+
try {
|
|
605
|
+
emitAnthropicUsageRecord(recorder, rawUsage, attribution);
|
|
506
606
|
} catch {
|
|
507
607
|
}
|
|
508
608
|
}
|
|
@@ -590,7 +690,18 @@ function rewriteSseLine(line, rewriteModel) {
|
|
|
590
690
|
return line;
|
|
591
691
|
}
|
|
592
692
|
}
|
|
593
|
-
|
|
693
|
+
function tapSseDataLine(line, onEvent) {
|
|
694
|
+
const leadingStripped = line.replace(/^\s+/, "");
|
|
695
|
+
if (!leadingStripped.startsWith("data:")) return;
|
|
696
|
+
const payload = leadingStripped.slice(5).trim();
|
|
697
|
+
if (!payload || payload === "[DONE]") return;
|
|
698
|
+
try {
|
|
699
|
+
onEvent(JSON.parse(payload));
|
|
700
|
+
} catch {
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async function relaySseBody(res, body, options) {
|
|
704
|
+
const { rewriteModel, onSseEvent } = options;
|
|
594
705
|
const reader = body.getReader();
|
|
595
706
|
const decoder = new TextDecoder();
|
|
596
707
|
let buffer = "";
|
|
@@ -604,21 +715,26 @@ async function relayStreamWithModelRewrite(res, body, rewriteModel) {
|
|
|
604
715
|
while ((nlIndex = buffer.indexOf("\n")) !== -1) {
|
|
605
716
|
const linePart = buffer.slice(0, nlIndex + 1);
|
|
606
717
|
buffer = buffer.slice(nlIndex + 1);
|
|
607
|
-
|
|
718
|
+
if (onSseEvent) tapSseDataLine(linePart, onSseEvent);
|
|
719
|
+
out += rewriteModel ? rewriteSseLine(linePart, rewriteModel) : linePart;
|
|
608
720
|
}
|
|
609
721
|
if (out) res.write(out);
|
|
610
722
|
}
|
|
611
723
|
const tail = buffer + decoder.decode();
|
|
612
|
-
if (tail)
|
|
724
|
+
if (tail) {
|
|
725
|
+
if (onSseEvent) tapSseDataLine(tail, onSseEvent);
|
|
726
|
+
res.write(rewriteModel ? rewriteSseLine(tail, rewriteModel) : tail);
|
|
727
|
+
}
|
|
613
728
|
} finally {
|
|
614
729
|
reader.releaseLock();
|
|
615
730
|
res.end();
|
|
616
731
|
}
|
|
617
732
|
}
|
|
618
|
-
async function relayResponse(res, providerResponse, isStream, rewriteModel) {
|
|
733
|
+
async function relayResponse(res, providerResponse, isStream, rewriteModel, usageTap, onSseEvent) {
|
|
619
734
|
const contentType = providerResponse.headers.get("Content-Type") ?? "";
|
|
620
735
|
const status = providerResponse.status && providerResponse.status >= 100 ? providerResponse.status : 200;
|
|
621
|
-
|
|
736
|
+
const upstreamIsSse = contentType.includes("text/event-stream");
|
|
737
|
+
if (upstreamIsSse || isStream && status < 400) {
|
|
622
738
|
res.writeHead(status, {
|
|
623
739
|
"Content-Type": "text/event-stream",
|
|
624
740
|
"Cache-Control": "no-cache",
|
|
@@ -628,8 +744,19 @@ async function relayResponse(res, providerResponse, isStream, rewriteModel) {
|
|
|
628
744
|
res.end();
|
|
629
745
|
return null;
|
|
630
746
|
}
|
|
631
|
-
if (rewriteModel) {
|
|
632
|
-
|
|
747
|
+
if (rewriteModel || usageTap || onSseEvent) {
|
|
748
|
+
let capturedUsage = null;
|
|
749
|
+
await relaySseBody(res, providerResponse.body, {
|
|
750
|
+
rewriteModel,
|
|
751
|
+
onSseEvent: onSseEvent || usageTap ? (event) => {
|
|
752
|
+
onSseEvent?.(event);
|
|
753
|
+
if (usageTap) {
|
|
754
|
+
const raw = usageTap.extractUsage(event);
|
|
755
|
+
if (raw) capturedUsage = raw;
|
|
756
|
+
}
|
|
757
|
+
} : void 0
|
|
758
|
+
});
|
|
759
|
+
if (capturedUsage) usageTap.onUsage(capturedUsage);
|
|
633
760
|
return null;
|
|
634
761
|
}
|
|
635
762
|
const reader = providerResponse.body.getReader();
|
|
@@ -745,6 +872,27 @@ function writeError(res, status, message) {
|
|
|
745
872
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
746
873
|
res.end(JSON.stringify({ error: { type: "provider_proxy_error", message } }));
|
|
747
874
|
}
|
|
875
|
+
function writeBoundAccountError(res, error) {
|
|
876
|
+
if (res.headersSent) return;
|
|
877
|
+
const headers = { "Content-Type": "application/json" };
|
|
878
|
+
if (error.status === 429 && error.resumeAt) {
|
|
879
|
+
const resumeMs = Date.parse(error.resumeAt);
|
|
880
|
+
if (Number.isFinite(resumeMs)) {
|
|
881
|
+
headers["Retry-After"] = String(Math.max(1, Math.ceil((resumeMs - Date.now()) / 1e3)));
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
res.writeHead(error.status, headers);
|
|
885
|
+
res.end(
|
|
886
|
+
JSON.stringify({
|
|
887
|
+
error: {
|
|
888
|
+
type: "provider_proxy_error",
|
|
889
|
+
code: error.code,
|
|
890
|
+
reason: error.reason,
|
|
891
|
+
message: boundAccountSelectionMessage(error.reason)
|
|
892
|
+
}
|
|
893
|
+
})
|
|
894
|
+
);
|
|
895
|
+
}
|
|
748
896
|
function resolveApiKey(apiKey) {
|
|
749
897
|
if (!apiKey) return "";
|
|
750
898
|
if (apiKey.startsWith("$")) {
|
|
@@ -752,7 +900,12 @@ function resolveApiKey(apiKey) {
|
|
|
752
900
|
}
|
|
753
901
|
return apiKey;
|
|
754
902
|
}
|
|
755
|
-
async function resolvePoolBoundKey(deps, providerId, provider, sessionId) {
|
|
903
|
+
async function resolvePoolBoundKey(deps, providerId, provider, sessionId, preferredKeyId, boundKeyFallbackPolicy) {
|
|
904
|
+
if (preferredKeyId) {
|
|
905
|
+
const preferred = await deps.apiKeyPool?.getKeyById(providerId, preferredKeyId, sessionId);
|
|
906
|
+
if (preferred) return preferred;
|
|
907
|
+
if (boundKeyFallbackPolicy !== "pool") return "";
|
|
908
|
+
}
|
|
756
909
|
if (deps.apiKeyPool && sessionId) {
|
|
757
910
|
const poolKey = await deps.apiKeyPool.getKeyForSession(providerId, sessionId);
|
|
758
911
|
if (poolKey) return poolKey;
|
|
@@ -807,6 +960,8 @@ function buildSubscriptionIterationPlan(profile, route, deps, resolvedModel, isS
|
|
|
807
960
|
return {
|
|
808
961
|
auth,
|
|
809
962
|
preferredAccountId: route.preferredAccountId,
|
|
963
|
+
preferredAccountGroup: route.preferredAccountGroup,
|
|
964
|
+
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy,
|
|
810
965
|
chain,
|
|
811
966
|
transformerProvider,
|
|
812
967
|
resolvedModel,
|
|
@@ -881,6 +1036,8 @@ async function runPipeline(anthropicBody, plan, reportSelection) {
|
|
|
881
1036
|
model: resolvedModel,
|
|
882
1037
|
sessionKey: plan.sessionKey,
|
|
883
1038
|
preferredAccountId: plan.preferredAccountId,
|
|
1039
|
+
preferredAccountGroup: plan.preferredAccountGroup,
|
|
1040
|
+
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
884
1041
|
reportSelection: (accountId, isActive) => {
|
|
885
1042
|
proxyAccountId = accountId;
|
|
886
1043
|
reportSelection?.(accountId, isActive);
|
|
@@ -908,7 +1065,16 @@ async function runPipeline(anthropicBody, plan, reportSelection) {
|
|
|
908
1065
|
return fetchUpstream(
|
|
909
1066
|
url,
|
|
910
1067
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
911
|
-
{
|
|
1068
|
+
{
|
|
1069
|
+
providerId: proxyProviderId(plan),
|
|
1070
|
+
accountId: proxyAccountId,
|
|
1071
|
+
routeActivity: plan.isSubscription ? {
|
|
1072
|
+
endpoint: "messages",
|
|
1073
|
+
sessionKey: plan.sessionKey,
|
|
1074
|
+
sessionSource: plan.sessionKey ? "content-fingerprint" : "none",
|
|
1075
|
+
model: resolvedModel
|
|
1076
|
+
} : void 0
|
|
1077
|
+
}
|
|
912
1078
|
).then((r) => {
|
|
913
1079
|
rawStatus = r.status;
|
|
914
1080
|
return r;
|
|
@@ -927,13 +1093,16 @@ async function runSubscriptionSameFormatFetch(rawBody, plan, reportSelection, op
|
|
|
927
1093
|
model: plan.resolvedModel,
|
|
928
1094
|
sessionKey: plan.sessionKey,
|
|
929
1095
|
preferredAccountId: plan.preferredAccountId,
|
|
1096
|
+
preferredAccountGroup: plan.preferredAccountGroup,
|
|
1097
|
+
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
930
1098
|
reportSelection: (accountId, isActive, remapped) => {
|
|
931
1099
|
proxyAccountId = accountId;
|
|
932
1100
|
remappedModel = remapped;
|
|
933
1101
|
reportSelection?.(accountId, isActive, remapped);
|
|
934
1102
|
}
|
|
935
1103
|
});
|
|
936
|
-
|
|
1104
|
+
const isClaudeSubscription = plan.isSubscription && plan.transformerProvider.name === "claude";
|
|
1105
|
+
if (isClaudeSubscription) {
|
|
937
1106
|
applyFingerprint(
|
|
938
1107
|
getSharedIdentityStore(),
|
|
939
1108
|
headers,
|
|
@@ -942,16 +1111,36 @@ async function runSubscriptionSameFormatFetch(rawBody, plan, reportSelection, op
|
|
|
942
1111
|
options.callerIdentity
|
|
943
1112
|
);
|
|
944
1113
|
}
|
|
1114
|
+
if (isClaudeSubscription) {
|
|
1115
|
+
fillMissingHeaders(headers, options.callerClientHeaders ?? {});
|
|
1116
|
+
fillMissingHeaders(headers, DEFAULT_CLAUDE_CODE_HEADERS);
|
|
1117
|
+
}
|
|
1118
|
+
fillMissingHeaders(headers, { "anthropic-version": DEFAULT_ANTHROPIC_VERSION });
|
|
1119
|
+
headers["accept-encoding"] = FORCED_ACCEPT_ENCODING;
|
|
945
1120
|
const applyRemap = remappedModel !== void 0 && outboundRemapApplies(plan);
|
|
946
1121
|
const outboundBody = applyRemap ? rewriteBodyModel(rawBody, remappedModel) : rawBody;
|
|
947
1122
|
const outboundModel = applyRemap ? remappedModel : plan.resolvedModel;
|
|
1123
|
+
if (isClaudeSubscription) {
|
|
1124
|
+
headers["anthropic-beta"] = buildAnthropicBeta(outboundModel, options.callerAnthropicBeta);
|
|
1125
|
+
} else if (options.callerAnthropicBeta) {
|
|
1126
|
+
fillMissingHeaders(headers, { "anthropic-beta": options.callerAnthropicBeta });
|
|
1127
|
+
}
|
|
948
1128
|
console.info(
|
|
949
1129
|
`[ProviderProxy:anthropic] (subscription same-format) -> ${plan.upstreamUrl} model=${outboundModel} stream=${plan.isStream}`
|
|
950
1130
|
);
|
|
951
1131
|
const response = await fetchUpstream(
|
|
952
1132
|
plan.upstreamUrl,
|
|
953
1133
|
{ method: "POST", headers, body: outboundBody },
|
|
954
|
-
{
|
|
1134
|
+
{
|
|
1135
|
+
providerId: proxyProviderId(plan),
|
|
1136
|
+
accountId: proxyAccountId,
|
|
1137
|
+
routeActivity: plan.isSubscription ? {
|
|
1138
|
+
endpoint: "messages",
|
|
1139
|
+
sessionKey: plan.sessionKey,
|
|
1140
|
+
sessionSource: plan.sessionKey ? "content-fingerprint" : "none",
|
|
1141
|
+
model: outboundModel
|
|
1142
|
+
} : void 0
|
|
1143
|
+
}
|
|
955
1144
|
);
|
|
956
1145
|
return { response, rawStatus: response.status };
|
|
957
1146
|
}
|
|
@@ -1119,6 +1308,35 @@ async function handleAnthropicMessagesByo(res, rawBody, route, deps, options = {
|
|
|
1119
1308
|
const upstreamSse = (upstreamResponse.headers.get("content-type") ?? "").includes(
|
|
1120
1309
|
"text/event-stream"
|
|
1121
1310
|
);
|
|
1311
|
+
const usageAttribution = {
|
|
1312
|
+
sessionId: route.sessionId,
|
|
1313
|
+
providerId: route.providerId ?? "anthropic",
|
|
1314
|
+
model: plan.resolvedModel,
|
|
1315
|
+
apiKeyId: route.apiKeyId ?? null,
|
|
1316
|
+
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1317
|
+
auditResponse: res
|
|
1318
|
+
};
|
|
1319
|
+
const usageRecorder = deps.usageRecorder;
|
|
1320
|
+
let anthropicInputUsage;
|
|
1321
|
+
const usageTap = usageRecorder ? {
|
|
1322
|
+
extractUsage(event) {
|
|
1323
|
+
if (event["type"] === "message_start") {
|
|
1324
|
+
const msg = event["message"];
|
|
1325
|
+
const u = msg?.["usage"];
|
|
1326
|
+
if (u) anthropicInputUsage = u;
|
|
1327
|
+
return null;
|
|
1328
|
+
}
|
|
1329
|
+
if (event["type"] === "message_delta") {
|
|
1330
|
+
const outputUsage = event["usage"];
|
|
1331
|
+
if (!outputUsage) return null;
|
|
1332
|
+
return { ...anthropicInputUsage, ...outputUsage };
|
|
1333
|
+
}
|
|
1334
|
+
return null;
|
|
1335
|
+
},
|
|
1336
|
+
onUsage(rawUsage) {
|
|
1337
|
+
recordAnthropicStreamUsage(usageRecorder, rawUsage, usageAttribution);
|
|
1338
|
+
}
|
|
1339
|
+
} : void 0;
|
|
1122
1340
|
let bodyText;
|
|
1123
1341
|
if (!isStream && upstreamSse) {
|
|
1124
1342
|
bodyText = await aggregateAnthropicSseToJsonBody(upstreamResponse, route.requestedModel);
|
|
@@ -1128,22 +1346,19 @@ async function handleAnthropicMessagesByo(res, rawBody, route, deps, options = {
|
|
|
1128
1346
|
);
|
|
1129
1347
|
res.end(bodyText);
|
|
1130
1348
|
} else {
|
|
1131
|
-
bodyText = await relayResponse(res, upstreamResponse, isStream, route.requestedModel);
|
|
1349
|
+
bodyText = await relayResponse(res, upstreamResponse, isStream, route.requestedModel, usageTap);
|
|
1132
1350
|
}
|
|
1133
1351
|
if (bodyText && deps.usageRecorder) {
|
|
1134
|
-
recordAnthropicNonStreamUsage(deps.usageRecorder, bodyText,
|
|
1135
|
-
sessionId: route.sessionId,
|
|
1136
|
-
providerId: route.providerId ?? "anthropic",
|
|
1137
|
-
model: plan.resolvedModel,
|
|
1138
|
-
apiKeyId: route.apiKeyId ?? null,
|
|
1139
|
-
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1140
|
-
auditResponse: res
|
|
1141
|
-
});
|
|
1352
|
+
recordAnthropicNonStreamUsage(deps.usageRecorder, bodyText, usageAttribution);
|
|
1142
1353
|
}
|
|
1143
1354
|
} catch (err) {
|
|
1355
|
+
if (isBoundAccountSelectionError(err)) {
|
|
1356
|
+
writeBoundAccountError(res, err);
|
|
1357
|
+
return;
|
|
1358
|
+
}
|
|
1144
1359
|
const errMsg = serializeError(err);
|
|
1145
1360
|
console.error("[ProviderProxy:anthropic] Pipeline error:", errMsg);
|
|
1146
|
-
writeError(res, 502, errMsg);
|
|
1361
|
+
writeError(res, isAccountAllowanceExhaustedError(err) ? 429 : 502, errMsg);
|
|
1147
1362
|
}
|
|
1148
1363
|
}
|
|
1149
1364
|
async function buildByoPlan(res, route, deps, resolvedModel, isStream) {
|
|
@@ -1157,7 +1372,14 @@ async function buildByoPlan(res, route, deps, resolvedModel, isStream) {
|
|
|
1157
1372
|
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1158
1373
|
return null;
|
|
1159
1374
|
}
|
|
1160
|
-
const apiKey = await resolvePoolBoundKey(
|
|
1375
|
+
const apiKey = await resolvePoolBoundKey(
|
|
1376
|
+
deps,
|
|
1377
|
+
providerId,
|
|
1378
|
+
provider,
|
|
1379
|
+
route.sessionId,
|
|
1380
|
+
route.preferredKeyId,
|
|
1381
|
+
route.boundKeyFallbackPolicy
|
|
1382
|
+
);
|
|
1161
1383
|
if (!apiKey) {
|
|
1162
1384
|
writeError(res, 502, "API key not configured");
|
|
1163
1385
|
return null;
|
|
@@ -1258,7 +1480,12 @@ async function handleAnthropicMessagesRequest(req, res, route, deps) {
|
|
|
1258
1480
|
const callerBetaRaw = req.headers["anthropic-beta"];
|
|
1259
1481
|
const callerAnthropicBeta = Array.isArray(callerBetaRaw) ? callerBetaRaw.join(",") : callerBetaRaw;
|
|
1260
1482
|
const callerIdentity = captureCallerIdentity(getSharedIdentityStore(), req.headers);
|
|
1261
|
-
|
|
1483
|
+
const callerClientHeaders = extractClaudeClientHeaders(req.headers);
|
|
1484
|
+
await handleAnthropicMessagesByo(res, rawBody, route, deps, {
|
|
1485
|
+
callerAnthropicBeta,
|
|
1486
|
+
callerIdentity,
|
|
1487
|
+
callerClientHeaders
|
|
1488
|
+
});
|
|
1262
1489
|
return;
|
|
1263
1490
|
}
|
|
1264
1491
|
const hints = route.anthropicSdkHints;
|
|
@@ -1270,11 +1497,31 @@ async function handleAnthropicMessagesRequest(req, res, route, deps) {
|
|
|
1270
1497
|
writeError(res, 502, "Anthropic route is missing a providerId");
|
|
1271
1498
|
return;
|
|
1272
1499
|
}
|
|
1500
|
+
let delegatedApiKey = hints.apiKey;
|
|
1501
|
+
if (route.authMode === "byo" && route.providerId) {
|
|
1502
|
+
const provider = await deps.llmConfig.getProvider(route.providerId);
|
|
1503
|
+
if (!provider) {
|
|
1504
|
+
writeError(res, 502, `Provider not found: ${route.providerId}`);
|
|
1505
|
+
return;
|
|
1506
|
+
}
|
|
1507
|
+
delegatedApiKey = await resolvePoolBoundKey(
|
|
1508
|
+
deps,
|
|
1509
|
+
route.providerId,
|
|
1510
|
+
provider,
|
|
1511
|
+
route.sessionId,
|
|
1512
|
+
route.preferredKeyId,
|
|
1513
|
+
route.boundKeyFallbackPolicy
|
|
1514
|
+
);
|
|
1515
|
+
if (!delegatedApiKey) {
|
|
1516
|
+
writeError(res, 502, "API key not configured");
|
|
1517
|
+
return;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1273
1520
|
const handler = handlerFactory({
|
|
1274
1521
|
llmConfig: deps.llmConfig,
|
|
1275
1522
|
providerId: route.providerId ?? "",
|
|
1276
1523
|
model: route.model,
|
|
1277
|
-
apiKey:
|
|
1524
|
+
apiKey: delegatedApiKey,
|
|
1278
1525
|
backgroundTaskModel: route.backgroundTaskModel,
|
|
1279
1526
|
isOfficialProvider: hints.isOfficialProvider,
|
|
1280
1527
|
thinkingLevel: hints.thinkingLevel,
|
|
@@ -1283,6 +1530,11 @@ async function handleAnthropicMessagesRequest(req, res, route, deps) {
|
|
|
1283
1530
|
passThroughAuthToken: hints.passThroughAuthToken ?? null,
|
|
1284
1531
|
resolvePassThroughAuthToken: hints.resolvePassThroughAuthToken ?? null,
|
|
1285
1532
|
subscriptionProfile: hints.subscriptionProfile ?? null,
|
|
1533
|
+
preferredAccountId: hints.preferredAccountId,
|
|
1534
|
+
preferredAccountGroup: hints.preferredAccountGroup,
|
|
1535
|
+
boundAccountFallbackPolicy: hints.boundAccountFallbackPolicy,
|
|
1536
|
+
preferredKeyId: route.preferredKeyId,
|
|
1537
|
+
boundKeyFallbackPolicy: route.boundKeyFallbackPolicy,
|
|
1286
1538
|
maxConcurrency: hints.maxConcurrency,
|
|
1287
1539
|
webSearchService: hints.webSearchService ?? null,
|
|
1288
1540
|
onRetry: hints.onRetry,
|
|
@@ -1309,23 +1561,36 @@ function readGeminiUsage(usage) {
|
|
|
1309
1561
|
reasoningTokens
|
|
1310
1562
|
};
|
|
1311
1563
|
}
|
|
1564
|
+
function emitGeminiUsageRecord(recorder, rawUsageMetadata, attribution) {
|
|
1565
|
+
const tapped = readGeminiUsage(rawUsageMetadata);
|
|
1566
|
+
if (!tapped) return;
|
|
1567
|
+
recorder.record({
|
|
1568
|
+
messageId: null,
|
|
1569
|
+
parentMessageId: null,
|
|
1570
|
+
sessionId: attribution.sessionId,
|
|
1571
|
+
providerId: attribution.providerId,
|
|
1572
|
+
model: attribution.model,
|
|
1573
|
+
apiKeyId: attribution.apiKeyId,
|
|
1574
|
+
auditResponse: attribution.auditResponse,
|
|
1575
|
+
engineOrigin: "codex-ingress",
|
|
1576
|
+
usage: tapped,
|
|
1577
|
+
rawUsage: rawUsageMetadata
|
|
1578
|
+
});
|
|
1579
|
+
}
|
|
1312
1580
|
function recordGeminiNonStreamUsage(recorder, bodyText, attribution) {
|
|
1313
1581
|
try {
|
|
1314
1582
|
const parsed = JSON.parse(bodyText);
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
usage: tapped,
|
|
1327
|
-
rawUsage: parsed.usageMetadata
|
|
1328
|
-
});
|
|
1583
|
+
emitGeminiUsageRecord(
|
|
1584
|
+
recorder,
|
|
1585
|
+
parsed.usageMetadata,
|
|
1586
|
+
attribution
|
|
1587
|
+
);
|
|
1588
|
+
} catch {
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
function recordGeminiStreamUsage(recorder, rawUsageMetadata, attribution) {
|
|
1592
|
+
try {
|
|
1593
|
+
emitGeminiUsageRecord(recorder, rawUsageMetadata, attribution);
|
|
1329
1594
|
} catch {
|
|
1330
1595
|
}
|
|
1331
1596
|
}
|
|
@@ -1366,16 +1631,32 @@ async function handleGeminiGenerateContentRequest(res, rawBody, url, route, deps
|
|
|
1366
1631
|
const plan = await buildByoPlan2(res, route, deps, resolvedModel, isStream);
|
|
1367
1632
|
if (!plan) return;
|
|
1368
1633
|
const providerResponse = await runPipelineWithPoolReporting2(geminiBody, plan);
|
|
1369
|
-
const
|
|
1634
|
+
const usageAttribution = {
|
|
1635
|
+
sessionId: route.sessionId,
|
|
1636
|
+
providerId: route.providerId ?? "gemini",
|
|
1637
|
+
model: resolvedModel,
|
|
1638
|
+
apiKeyId: route.apiKeyId ?? null,
|
|
1639
|
+
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1640
|
+
auditResponse: res
|
|
1641
|
+
};
|
|
1642
|
+
const usageRecorder = deps.usageRecorder;
|
|
1643
|
+
const usageTap = usageRecorder ? {
|
|
1644
|
+
extractUsage(event) {
|
|
1645
|
+
return event["usageMetadata"] ?? null;
|
|
1646
|
+
},
|
|
1647
|
+
onUsage(rawUsage) {
|
|
1648
|
+
recordGeminiStreamUsage(usageRecorder, rawUsage, usageAttribution);
|
|
1649
|
+
}
|
|
1650
|
+
} : void 0;
|
|
1651
|
+
const bodyText = await relayResponse(
|
|
1652
|
+
res,
|
|
1653
|
+
providerResponse.response,
|
|
1654
|
+
isStream,
|
|
1655
|
+
void 0,
|
|
1656
|
+
usageTap
|
|
1657
|
+
);
|
|
1370
1658
|
if (bodyText && deps.usageRecorder) {
|
|
1371
|
-
recordGeminiNonStreamUsage(deps.usageRecorder, bodyText,
|
|
1372
|
-
sessionId: route.sessionId,
|
|
1373
|
-
providerId: route.providerId ?? "gemini",
|
|
1374
|
-
model: resolvedModel,
|
|
1375
|
-
apiKeyId: route.apiKeyId ?? null,
|
|
1376
|
-
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1377
|
-
auditResponse: res
|
|
1378
|
-
});
|
|
1659
|
+
recordGeminiNonStreamUsage(deps.usageRecorder, bodyText, usageAttribution);
|
|
1379
1660
|
}
|
|
1380
1661
|
} catch (err) {
|
|
1381
1662
|
const errMsg = serializeError(err);
|
|
@@ -1394,7 +1675,14 @@ async function buildByoPlan2(res, route, deps, resolvedModel, isStream) {
|
|
|
1394
1675
|
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1395
1676
|
return null;
|
|
1396
1677
|
}
|
|
1397
|
-
const apiKey = await resolvePoolBoundKey(
|
|
1678
|
+
const apiKey = await resolvePoolBoundKey(
|
|
1679
|
+
deps,
|
|
1680
|
+
providerId,
|
|
1681
|
+
provider,
|
|
1682
|
+
route.sessionId,
|
|
1683
|
+
route.preferredKeyId,
|
|
1684
|
+
route.boundKeyFallbackPolicy
|
|
1685
|
+
);
|
|
1398
1686
|
if (!apiKey) {
|
|
1399
1687
|
writeError(res, 502, "API key not configured");
|
|
1400
1688
|
return null;
|
|
@@ -1494,23 +1782,36 @@ function readChatCompletionsUsage(usage) {
|
|
|
1494
1782
|
reasoningTokens
|
|
1495
1783
|
};
|
|
1496
1784
|
}
|
|
1785
|
+
function emitChatCompletionsUsageRecord(recorder, rawUsage, attribution) {
|
|
1786
|
+
const tapped = readChatCompletionsUsage(rawUsage);
|
|
1787
|
+
if (!tapped) return;
|
|
1788
|
+
recorder.record({
|
|
1789
|
+
messageId: null,
|
|
1790
|
+
parentMessageId: null,
|
|
1791
|
+
sessionId: attribution.sessionId,
|
|
1792
|
+
providerId: attribution.providerId,
|
|
1793
|
+
model: attribution.model,
|
|
1794
|
+
apiKeyId: attribution.apiKeyId,
|
|
1795
|
+
auditResponse: attribution.auditResponse,
|
|
1796
|
+
engineOrigin: "codex-ingress",
|
|
1797
|
+
usage: tapped,
|
|
1798
|
+
rawUsage
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1497
1801
|
function recordChatCompletionsNonStreamUsage(recorder, bodyText, attribution) {
|
|
1498
1802
|
try {
|
|
1499
1803
|
const parsed = JSON.parse(bodyText);
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
usage: tapped,
|
|
1512
|
-
rawUsage: parsed.usage
|
|
1513
|
-
});
|
|
1804
|
+
emitChatCompletionsUsageRecord(
|
|
1805
|
+
recorder,
|
|
1806
|
+
parsed.usage,
|
|
1807
|
+
attribution
|
|
1808
|
+
);
|
|
1809
|
+
} catch {
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
function recordChatCompletionsStreamUsage(recorder, rawUsage, attribution) {
|
|
1813
|
+
try {
|
|
1814
|
+
emitChatCompletionsUsageRecord(recorder, rawUsage, attribution);
|
|
1514
1815
|
} catch {
|
|
1515
1816
|
}
|
|
1516
1817
|
}
|
|
@@ -1538,21 +1839,41 @@ async function handleOpenAIChatRequest(res, rawBody, route, deps) {
|
|
|
1538
1839
|
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan2(res, route, deps, resolvedModel, isStream) : await buildByoPlan3(res, route, deps, resolvedModel, isStream);
|
|
1539
1840
|
if (!plan) return;
|
|
1540
1841
|
const providerResponse = plan.isSubscription ? await runPipelineWithSubscriptionRetry2(chatBody, plan) : await runPipelineWithPoolReporting3(chatBody, plan);
|
|
1541
|
-
const
|
|
1842
|
+
const usageAttribution = {
|
|
1843
|
+
sessionId: route.sessionId,
|
|
1844
|
+
providerId: route.providerId ?? "openai",
|
|
1845
|
+
model: resolvedModel,
|
|
1846
|
+
apiKeyId: route.apiKeyId ?? null,
|
|
1847
|
+
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1848
|
+
auditResponse: res
|
|
1849
|
+
};
|
|
1850
|
+
const usageRecorder = deps.usageRecorder;
|
|
1851
|
+
const usageTap = usageRecorder ? {
|
|
1852
|
+
extractUsage(event) {
|
|
1853
|
+
return event["usage"] ?? null;
|
|
1854
|
+
},
|
|
1855
|
+
onUsage(rawUsage) {
|
|
1856
|
+
recordChatCompletionsStreamUsage(usageRecorder, rawUsage, usageAttribution);
|
|
1857
|
+
}
|
|
1858
|
+
} : void 0;
|
|
1859
|
+
const bodyText = await relayResponse(
|
|
1860
|
+
res,
|
|
1861
|
+
providerResponse.response,
|
|
1862
|
+
isStream,
|
|
1863
|
+
void 0,
|
|
1864
|
+
usageTap
|
|
1865
|
+
);
|
|
1542
1866
|
if (bodyText && deps.usageRecorder) {
|
|
1543
|
-
recordChatCompletionsNonStreamUsage(deps.usageRecorder, bodyText,
|
|
1544
|
-
sessionId: route.sessionId,
|
|
1545
|
-
providerId: route.providerId ?? "openai",
|
|
1546
|
-
model: resolvedModel,
|
|
1547
|
-
apiKeyId: route.apiKeyId ?? null,
|
|
1548
|
-
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1549
|
-
auditResponse: res
|
|
1550
|
-
});
|
|
1867
|
+
recordChatCompletionsNonStreamUsage(deps.usageRecorder, bodyText, usageAttribution);
|
|
1551
1868
|
}
|
|
1552
1869
|
} catch (err) {
|
|
1870
|
+
if (isBoundAccountSelectionError(err)) {
|
|
1871
|
+
writeBoundAccountError(res, err);
|
|
1872
|
+
return;
|
|
1873
|
+
}
|
|
1553
1874
|
const errMsg = serializeError(err);
|
|
1554
1875
|
console.error("[ProviderProxy:chat] Pipeline error:", errMsg);
|
|
1555
|
-
writeError(res, 502, errMsg);
|
|
1876
|
+
writeError(res, isAccountAllowanceExhaustedError(err) ? 429 : 502, errMsg);
|
|
1556
1877
|
}
|
|
1557
1878
|
}
|
|
1558
1879
|
async function buildByoPlan3(res, route, deps, resolvedModel, isStream) {
|
|
@@ -1566,7 +1887,14 @@ async function buildByoPlan3(res, route, deps, resolvedModel, isStream) {
|
|
|
1566
1887
|
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1567
1888
|
return null;
|
|
1568
1889
|
}
|
|
1569
|
-
const apiKey = await resolvePoolBoundKey(
|
|
1890
|
+
const apiKey = await resolvePoolBoundKey(
|
|
1891
|
+
deps,
|
|
1892
|
+
providerId,
|
|
1893
|
+
provider,
|
|
1894
|
+
route.sessionId,
|
|
1895
|
+
route.preferredKeyId,
|
|
1896
|
+
route.boundKeyFallbackPolicy
|
|
1897
|
+
);
|
|
1570
1898
|
if (!apiKey) {
|
|
1571
1899
|
writeError(res, 502, "API key not configured");
|
|
1572
1900
|
return null;
|
|
@@ -1632,6 +1960,9 @@ async function buildSubscriptionPlan2(res, route, deps, resolvedModel, isStream)
|
|
|
1632
1960
|
};
|
|
1633
1961
|
return {
|
|
1634
1962
|
auth,
|
|
1963
|
+
preferredAccountId: route.preferredAccountId,
|
|
1964
|
+
preferredAccountGroup: route.preferredAccountGroup,
|
|
1965
|
+
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy,
|
|
1635
1966
|
chain,
|
|
1636
1967
|
transformerProvider,
|
|
1637
1968
|
resolvedModel,
|
|
@@ -1650,6 +1981,9 @@ async function runPipeline3(chatBody, plan) {
|
|
|
1650
1981
|
await auth.applyHeaders(authHeaders, {
|
|
1651
1982
|
upstreamUrl,
|
|
1652
1983
|
model: resolvedModel,
|
|
1984
|
+
preferredAccountId: plan.preferredAccountId,
|
|
1985
|
+
preferredAccountGroup: plan.preferredAccountGroup,
|
|
1986
|
+
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
1653
1987
|
reportSelection: (accountId) => {
|
|
1654
1988
|
proxyAccountId = accountId;
|
|
1655
1989
|
}
|
|
@@ -1728,34 +2062,109 @@ function readResponsesUsage(usage) {
|
|
|
1728
2062
|
reasoningTokens
|
|
1729
2063
|
};
|
|
1730
2064
|
}
|
|
2065
|
+
function emitResponsesUsageRecord(recorder, rawUsage, attribution) {
|
|
2066
|
+
const tapped = readResponsesUsage(rawUsage);
|
|
2067
|
+
if (!tapped) return;
|
|
2068
|
+
recorder.record({
|
|
2069
|
+
messageId: null,
|
|
2070
|
+
parentMessageId: null,
|
|
2071
|
+
sessionId: attribution.sessionId,
|
|
2072
|
+
providerId: attribution.providerId,
|
|
2073
|
+
model: attribution.model,
|
|
2074
|
+
apiKeyId: attribution.apiKeyId,
|
|
2075
|
+
cacheKeySource: attribution.cacheKeySource,
|
|
2076
|
+
cacheKeyInjected: attribution.cacheKeyInjected,
|
|
2077
|
+
auditResponse: attribution.auditResponse,
|
|
2078
|
+
engineOrigin: "codex-ingress",
|
|
2079
|
+
usage: tapped,
|
|
2080
|
+
rawUsage
|
|
2081
|
+
});
|
|
2082
|
+
}
|
|
1731
2083
|
function recordResponsesNonStreamUsage(recorder, bodyText, attribution) {
|
|
1732
2084
|
try {
|
|
1733
2085
|
const parsed = JSON.parse(bodyText);
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
usage: tapped,
|
|
1746
|
-
rawUsage: parsed.usage
|
|
1747
|
-
});
|
|
2086
|
+
emitResponsesUsageRecord(
|
|
2087
|
+
recorder,
|
|
2088
|
+
parsed.usage,
|
|
2089
|
+
attribution
|
|
2090
|
+
);
|
|
2091
|
+
} catch {
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
function recordResponsesStreamUsage(recorder, rawUsage, attribution) {
|
|
2095
|
+
try {
|
|
2096
|
+
emitResponsesUsageRecord(recorder, rawUsage, attribution);
|
|
1748
2097
|
} catch {
|
|
1749
2098
|
}
|
|
1750
2099
|
}
|
|
1751
2100
|
|
|
2101
|
+
// src/provider-proxy/ingress/codexUsageLimitDetection.ts
|
|
2102
|
+
var PROVIDER_ID = "codex";
|
|
2103
|
+
var DEFAULT_QUOTA_COOLDOWN_MS = 5 * 6e4;
|
|
2104
|
+
var QUOTA_EXHAUSTED_PERCENT = 90;
|
|
2105
|
+
function parseTryAgainDeadline(bodyText) {
|
|
2106
|
+
const match = bodyText.match(/try again (?:at|on)\s+(.+?)(?:[.\n]|$)/i);
|
|
2107
|
+
if (!match) return void 0;
|
|
2108
|
+
const cleaned = match[1].replace(/(\d+)(st|nd|rd|th)/giu, "$1").trim();
|
|
2109
|
+
const ms = Date.parse(cleaned);
|
|
2110
|
+
return Number.isFinite(ms) ? ms : void 0;
|
|
2111
|
+
}
|
|
2112
|
+
function deadlineFromAllowanceSnapshot(accountId, now) {
|
|
2113
|
+
const snapshot = getSharedAccountAllowanceStore().get(PROVIDER_ID, accountId, now);
|
|
2114
|
+
if (!snapshot) return void 0;
|
|
2115
|
+
const candidates = snapshot.windows.filter(
|
|
2116
|
+
(w) => typeof w.usedPercent === "number" && w.usedPercent >= QUOTA_EXHAUSTED_PERCENT && typeof w.resetsAt === "string"
|
|
2117
|
+
).map((w) => ({ ms: Date.parse(w.resetsAt), usedPercent: w.usedPercent })).filter((x) => Number.isFinite(x.ms) && x.ms > now).sort((a, b) => b.usedPercent - a.usedPercent);
|
|
2118
|
+
return candidates[0]?.ms;
|
|
2119
|
+
}
|
|
2120
|
+
function resolveCodexQuotaDeadline(accountId, bodyText, now = Date.now()) {
|
|
2121
|
+
const parsed = parseTryAgainDeadline(bodyText);
|
|
2122
|
+
const parsedDeadline = parsed !== void 0 && parsed > now ? parsed : void 0;
|
|
2123
|
+
return deadlineFromAllowanceSnapshot(accountId, now) ?? parsedDeadline ?? now + DEFAULT_QUOTA_COOLDOWN_MS;
|
|
2124
|
+
}
|
|
2125
|
+
function markCodexUsageLimitExhaustion(accountId, bodyText, now = Date.now()) {
|
|
2126
|
+
if (!accountId) return;
|
|
2127
|
+
const deadline = resolveCodexQuotaDeadline(accountId, bodyText, now);
|
|
2128
|
+
getSharedAccountHealth().markQuotaExhausted(PROVIDER_ID, accountId, deadline, now);
|
|
2129
|
+
}
|
|
2130
|
+
|
|
1752
2131
|
// src/provider-proxy/ingress/openaiResponsesIngress.ts
|
|
2132
|
+
var SERVER_OVERLOADED_CODES = /* @__PURE__ */ new Set(["server_is_overloaded", "slow_down"]);
|
|
2133
|
+
function isCodexServerOverloadEvent(event) {
|
|
2134
|
+
if (event["type"] !== "response.failed") return false;
|
|
2135
|
+
const error = event["response"]?.["error"];
|
|
2136
|
+
const code = error?.["code"];
|
|
2137
|
+
return typeof code === "string" && SERVER_OVERLOADED_CODES.has(code);
|
|
2138
|
+
}
|
|
1753
2139
|
function isOpenAIResponsesRequest(method, url) {
|
|
1754
2140
|
if (method !== "POST" || !url) return false;
|
|
1755
2141
|
const path = url.split("?")[0]?.replace(/\/+$/, "") ?? "";
|
|
1756
2142
|
return path.endsWith("/responses");
|
|
1757
2143
|
}
|
|
1758
|
-
|
|
2144
|
+
var SAFE_INJECTED_CACHE_KEY_SOURCES = /* @__PURE__ */ new Set([
|
|
2145
|
+
"session-header",
|
|
2146
|
+
"thread-header",
|
|
2147
|
+
"body-session-id",
|
|
2148
|
+
"body-thread-id",
|
|
2149
|
+
"content-fingerprint"
|
|
2150
|
+
]);
|
|
2151
|
+
function isSafeInjectedCacheKeySource(source) {
|
|
2152
|
+
return SAFE_INJECTED_CACHE_KEY_SOURCES.has(source);
|
|
2153
|
+
}
|
|
2154
|
+
function ensureCodexPromptCacheKey(body, proxyProviderId2, derivedSession) {
|
|
2155
|
+
if (typeof body.prompt_cache_key === "string" && body.prompt_cache_key.trim()) {
|
|
2156
|
+
return { cacheKeySource: "client", cacheKeyInjected: false };
|
|
2157
|
+
}
|
|
2158
|
+
if (proxyProviderId2 !== "codex" || !isSafeInjectedCacheKeySource(derivedSession.source)) {
|
|
2159
|
+
return { cacheKeySource: "none", cacheKeyInjected: false };
|
|
2160
|
+
}
|
|
2161
|
+
body.prompt_cache_key = `omnicross:${derivedSession.source}:${derivedSession.key}`;
|
|
2162
|
+
return {
|
|
2163
|
+
cacheKeySource: derivedSession.source,
|
|
2164
|
+
cacheKeyInjected: true
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
async function handleOpenAIResponsesRequest(res, rawBody, route, deps, requestHeaders = {}) {
|
|
1759
2168
|
let responsesBody;
|
|
1760
2169
|
try {
|
|
1761
2170
|
responsesBody = JSON.parse(rawBody);
|
|
@@ -1764,27 +2173,96 @@ async function handleOpenAIResponsesRequest(res, rawBody, route, deps) {
|
|
|
1764
2173
|
return;
|
|
1765
2174
|
}
|
|
1766
2175
|
const isStream = responsesBody.stream === true;
|
|
2176
|
+
const derivedSession = deriveGatewaySessionKey(responsesBody, requestHeaders, {
|
|
2177
|
+
endpoint: "responses",
|
|
2178
|
+
// A verified outbound key is the strongest route-local fallback. Resident
|
|
2179
|
+
// proxy callers without one still get a stable run/session bucket; the
|
|
2180
|
+
// final `anonymous` fallback is handled inside the shared utility.
|
|
2181
|
+
fallbackKey: route.apiKeyId ?? route.sessionId ?? void 0
|
|
2182
|
+
});
|
|
2183
|
+
const sessionKey = derivedSession.key;
|
|
1767
2184
|
const resolvedModel = route.model;
|
|
1768
2185
|
responsesBody.model = resolvedModel;
|
|
1769
2186
|
try {
|
|
1770
|
-
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan3(
|
|
2187
|
+
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan3(
|
|
2188
|
+
res,
|
|
2189
|
+
route,
|
|
2190
|
+
deps,
|
|
2191
|
+
resolvedModel,
|
|
2192
|
+
isStream,
|
|
2193
|
+
sessionKey,
|
|
2194
|
+
derivedSession.source,
|
|
2195
|
+
requestHeaders
|
|
2196
|
+
) : await buildByoPlan4(res, route, deps, resolvedModel, isStream);
|
|
1771
2197
|
if (!plan) return;
|
|
1772
|
-
const
|
|
1773
|
-
|
|
2198
|
+
const cacheKeyAttribution = ensureCodexPromptCacheKey(
|
|
2199
|
+
responsesBody,
|
|
2200
|
+
plan.proxyProviderId,
|
|
2201
|
+
derivedSession
|
|
2202
|
+
);
|
|
2203
|
+
let providerResponse = route.authMode === "subscription" ? await runPipelineWithSubscriptionRetry3(responsesBody, plan) : await runPipelineWithPoolReporting4(responsesBody, plan);
|
|
2204
|
+
providerResponse = await retryAroundCodexUsageLimit(responsesBody, plan, providerResponse);
|
|
2205
|
+
const usageAttribution = {
|
|
2206
|
+
sessionId: route.sessionId,
|
|
2207
|
+
providerId: route.providerId ?? "codex",
|
|
2208
|
+
model: resolvedModel,
|
|
2209
|
+
apiKeyId: route.apiKeyId ?? null,
|
|
2210
|
+
cacheKeySource: cacheKeyAttribution.cacheKeySource,
|
|
2211
|
+
cacheKeyInjected: cacheKeyAttribution.cacheKeyInjected,
|
|
2212
|
+
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
2213
|
+
auditResponse: res
|
|
2214
|
+
};
|
|
2215
|
+
const usageRecorder = deps.usageRecorder;
|
|
2216
|
+
const usageTap = usageRecorder ? {
|
|
2217
|
+
extractUsage(event) {
|
|
2218
|
+
if (event["type"] !== "response.completed") return null;
|
|
2219
|
+
const response = event["response"];
|
|
2220
|
+
const usage = response?.["usage"];
|
|
2221
|
+
return usage ?? null;
|
|
2222
|
+
},
|
|
2223
|
+
onUsage(rawUsage) {
|
|
2224
|
+
recordResponsesStreamUsage(usageRecorder, rawUsage, usageAttribution);
|
|
2225
|
+
}
|
|
2226
|
+
} : void 0;
|
|
2227
|
+
const overloadActivityId = providerResponse.activityRecordId;
|
|
2228
|
+
const overloadAccountId = providerResponse.accountId;
|
|
2229
|
+
const overloadProviderId = plan.proxyProviderId;
|
|
2230
|
+
let overloadRecorded = false;
|
|
2231
|
+
const overloadDetector = (event) => {
|
|
2232
|
+
if (overloadRecorded || !isCodexServerOverloadEvent(event)) return;
|
|
2233
|
+
overloadRecorded = true;
|
|
2234
|
+
if (overloadActivityId) {
|
|
2235
|
+
getSharedAccountRouteActivity().amend(overloadActivityId, {
|
|
2236
|
+
streamError: "server_overloaded"
|
|
2237
|
+
});
|
|
2238
|
+
}
|
|
2239
|
+
if (overloadAccountId) {
|
|
2240
|
+
getSharedOverloadCounter().recordOverload({
|
|
2241
|
+
providerId: overloadProviderId,
|
|
2242
|
+
accountId: overloadAccountId,
|
|
2243
|
+
endpoint: "responses"
|
|
2244
|
+
});
|
|
2245
|
+
}
|
|
2246
|
+
};
|
|
2247
|
+
const bodyText = await relayResponse(
|
|
2248
|
+
res,
|
|
2249
|
+
providerResponse.response,
|
|
2250
|
+
isStream,
|
|
2251
|
+
route.requestedModel,
|
|
2252
|
+
usageTap,
|
|
2253
|
+
overloadDetector
|
|
2254
|
+
);
|
|
1774
2255
|
if (bodyText && deps.usageRecorder) {
|
|
1775
|
-
recordResponsesNonStreamUsage(deps.usageRecorder, bodyText,
|
|
1776
|
-
sessionId: route.sessionId,
|
|
1777
|
-
providerId: route.providerId ?? "codex",
|
|
1778
|
-
model: resolvedModel,
|
|
1779
|
-
apiKeyId: route.apiKeyId ?? null,
|
|
1780
|
-
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1781
|
-
auditResponse: res
|
|
1782
|
-
});
|
|
2256
|
+
recordResponsesNonStreamUsage(deps.usageRecorder, bodyText, usageAttribution);
|
|
1783
2257
|
}
|
|
1784
2258
|
} catch (err) {
|
|
2259
|
+
if (isBoundAccountSelectionError(err)) {
|
|
2260
|
+
writeBoundAccountError(res, err);
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
1785
2263
|
const errMsg = serializeError(err);
|
|
1786
2264
|
console.error("[ProviderProxy:responses] Pipeline error:", errMsg);
|
|
1787
|
-
writeError(res, 502, errMsg);
|
|
2265
|
+
writeError(res, isAccountAllowanceExhaustedError(err) ? 429 : 502, errMsg);
|
|
1788
2266
|
}
|
|
1789
2267
|
}
|
|
1790
2268
|
async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
|
|
@@ -1798,7 +2276,14 @@ async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
|
|
|
1798
2276
|
writeError(res, 502, `Provider not found: ${providerId}`);
|
|
1799
2277
|
return null;
|
|
1800
2278
|
}
|
|
1801
|
-
const apiKey = await resolvePoolBoundKey(
|
|
2279
|
+
const apiKey = await resolvePoolBoundKey(
|
|
2280
|
+
deps,
|
|
2281
|
+
providerId,
|
|
2282
|
+
provider,
|
|
2283
|
+
route.sessionId,
|
|
2284
|
+
route.preferredKeyId,
|
|
2285
|
+
route.boundKeyFallbackPolicy
|
|
2286
|
+
);
|
|
1802
2287
|
if (!apiKey) {
|
|
1803
2288
|
writeError(res, 502, "API key not configured");
|
|
1804
2289
|
return null;
|
|
@@ -1829,7 +2314,7 @@ async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
|
|
|
1829
2314
|
proxyProviderId: "byo"
|
|
1830
2315
|
};
|
|
1831
2316
|
}
|
|
1832
|
-
async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream) {
|
|
2317
|
+
async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream, sessionKey, sessionSource, requestHeaders) {
|
|
1833
2318
|
const profile = route.subscriptionProfile;
|
|
1834
2319
|
if (!profile) {
|
|
1835
2320
|
writeError(res, 502, "Subscription mode requires a codex subscription profile");
|
|
@@ -1854,15 +2339,28 @@ async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream)
|
|
|
1854
2339
|
};
|
|
1855
2340
|
if (profile.authStrategy.providerId === "gemini") {
|
|
1856
2341
|
try {
|
|
1857
|
-
transformerProvider.geminiProject = await resolveGeminiCodeAssistProject(profile
|
|
2342
|
+
transformerProvider.geminiProject = await resolveGeminiCodeAssistProject(profile, {
|
|
2343
|
+
sessionKey,
|
|
2344
|
+
preferredAccountId: route.preferredAccountId,
|
|
2345
|
+
preferredAccountGroup: route.preferredAccountGroup,
|
|
2346
|
+
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy
|
|
2347
|
+
});
|
|
1858
2348
|
} catch (err) {
|
|
2349
|
+
if (isBoundAccountSelectionError(err)) {
|
|
2350
|
+
writeBoundAccountError(res, err);
|
|
2351
|
+
return null;
|
|
2352
|
+
}
|
|
1859
2353
|
writeError(res, 502, serializeError(err));
|
|
1860
2354
|
return null;
|
|
1861
2355
|
}
|
|
1862
2356
|
}
|
|
1863
2357
|
return {
|
|
1864
2358
|
auth,
|
|
2359
|
+
sessionKey,
|
|
2360
|
+
sessionSource,
|
|
1865
2361
|
preferredAccountId: route.preferredAccountId,
|
|
2362
|
+
preferredAccountGroup: route.preferredAccountGroup,
|
|
2363
|
+
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy,
|
|
1866
2364
|
chain,
|
|
1867
2365
|
transformerProvider,
|
|
1868
2366
|
resolvedModel,
|
|
@@ -1872,12 +2370,15 @@ async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream)
|
|
|
1872
2370
|
// profile's `resolveUpstreamUrl` for non-Code-Assist subscriptions.
|
|
1873
2371
|
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : typeof config.url === "string" ? config.url : upstreamUrl,
|
|
1874
2372
|
upstreamUrl,
|
|
1875
|
-
proxyProviderId: profile.authStrategy.providerId
|
|
2373
|
+
proxyProviderId: profile.authStrategy.providerId,
|
|
2374
|
+
// Only the codex backend understands these; other subscription providers
|
|
2375
|
+
// (gemini Code Assist) share this relay and must not receive them.
|
|
2376
|
+
callerClientHeaders: profile.authStrategy.providerId === "codex" ? extractCodexClientHeaders(requestHeaders) : void 0
|
|
1876
2377
|
};
|
|
1877
2378
|
}
|
|
1878
|
-
async function resolveGeminiCodeAssistProject(profile) {
|
|
2379
|
+
async function resolveGeminiCodeAssistProject(profile, hints) {
|
|
1879
2380
|
const probe = {};
|
|
1880
|
-
await profile.authStrategy.applyHeaders(probe);
|
|
2381
|
+
await profile.authStrategy.applyHeaders(probe, hints);
|
|
1881
2382
|
const bearer = probe.Authorization ?? probe.authorization ?? "";
|
|
1882
2383
|
const accessToken = bearer.replace(/^Bearer\s+/i, "").trim();
|
|
1883
2384
|
if (!accessToken) return void 0;
|
|
@@ -1888,13 +2389,17 @@ async function resolveGeminiCodeAssistProject(profile) {
|
|
|
1888
2389
|
async function runPipeline4(responsesBody, plan) {
|
|
1889
2390
|
const executor = getSharedExecutor();
|
|
1890
2391
|
const endpointTransformer = getResponsesEndpointTransformer();
|
|
1891
|
-
const { auth, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
2392
|
+
const { auth, sessionKey, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
1892
2393
|
let proxyAccountId;
|
|
2394
|
+
let activityRecordId;
|
|
1893
2395
|
const authHeaders = {};
|
|
1894
2396
|
await auth.applyHeaders(authHeaders, {
|
|
1895
2397
|
upstreamUrl,
|
|
1896
2398
|
model: resolvedModel,
|
|
2399
|
+
sessionKey,
|
|
1897
2400
|
preferredAccountId: plan.preferredAccountId,
|
|
2401
|
+
preferredAccountGroup: plan.preferredAccountGroup,
|
|
2402
|
+
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
1898
2403
|
reportSelection: (accountId) => {
|
|
1899
2404
|
proxyAccountId = accountId;
|
|
1900
2405
|
}
|
|
@@ -1914,6 +2419,11 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
1914
2419
|
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
1915
2420
|
}
|
|
1916
2421
|
}
|
|
2422
|
+
if (plan.proxyProviderId === "codex") {
|
|
2423
|
+
fillMissingHeaders(headers, plan.callerClientHeaders ?? {});
|
|
2424
|
+
fillMissingHeaders(headers, DEFAULT_CODEX_CLI_HEADERS);
|
|
2425
|
+
fillMissingHeaders(headers, { accept: codexAcceptHeader(isStream) });
|
|
2426
|
+
}
|
|
1917
2427
|
return headers;
|
|
1918
2428
|
},
|
|
1919
2429
|
fetchFn: (url, headers, body) => {
|
|
@@ -1921,7 +2431,19 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
1921
2431
|
return fetchUpstream(
|
|
1922
2432
|
url,
|
|
1923
2433
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
1924
|
-
{
|
|
2434
|
+
{
|
|
2435
|
+
providerId: plan.proxyProviderId,
|
|
2436
|
+
accountId: proxyAccountId,
|
|
2437
|
+
routeActivity: plan.proxyProviderId === "byo" ? void 0 : {
|
|
2438
|
+
endpoint: "responses",
|
|
2439
|
+
sessionKey,
|
|
2440
|
+
sessionSource: plan.sessionSource ?? "none",
|
|
2441
|
+
model: resolvedModel,
|
|
2442
|
+
onRecorded: (record) => {
|
|
2443
|
+
activityRecordId = record.id;
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
}
|
|
1925
2447
|
).then((r) => {
|
|
1926
2448
|
rawStatus = r.status;
|
|
1927
2449
|
return r;
|
|
@@ -1929,7 +2451,7 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
1929
2451
|
},
|
|
1930
2452
|
runResponseChain: true
|
|
1931
2453
|
});
|
|
1932
|
-
return { response, rawStatus };
|
|
2454
|
+
return { response, rawStatus, accountId: proxyAccountId, activityRecordId };
|
|
1933
2455
|
}
|
|
1934
2456
|
async function runPipelineWithPoolReporting4(responsesBody, plan) {
|
|
1935
2457
|
const first = await runPipeline4(responsesBody, plan);
|
|
@@ -1947,7 +2469,7 @@ async function runPipelineWithPoolReporting4(responsesBody, plan) {
|
|
|
1947
2469
|
async function runPipelineWithSubscriptionRetry3(responsesBody, plan) {
|
|
1948
2470
|
const first = await runPipeline4(responsesBody, plan);
|
|
1949
2471
|
if (first.rawStatus !== 401) return first;
|
|
1950
|
-
const refreshed = await plan.auth.onUnauthorized?.();
|
|
2472
|
+
const refreshed = await plan.auth.onUnauthorized?.(plan.sessionKey);
|
|
1951
2473
|
if (!refreshed) {
|
|
1952
2474
|
console.warn("[ProviderProxy:responses] 401 not recoverable (onUnauthorized returned false)");
|
|
1953
2475
|
return first;
|
|
@@ -1955,6 +2477,22 @@ async function runPipelineWithSubscriptionRetry3(responsesBody, plan) {
|
|
|
1955
2477
|
console.log("[ProviderProxy:responses] 401 \u2192 token refreshed; retrying once");
|
|
1956
2478
|
return runPipeline4(responsesBody, plan);
|
|
1957
2479
|
}
|
|
2480
|
+
var MAX_QUOTA_RETRIES = 3;
|
|
2481
|
+
function isCodexAccountQuota429(plan, result) {
|
|
2482
|
+
return plan.proxyProviderId === "codex" && !!result.accountId && result.rawStatus === 429;
|
|
2483
|
+
}
|
|
2484
|
+
async function retryAroundCodexUsageLimit(responsesBody, plan, first, runAttempt = runPipelineWithSubscriptionRetry3) {
|
|
2485
|
+
if (!isCodexAccountQuota429(plan, first)) return first;
|
|
2486
|
+
let current = first;
|
|
2487
|
+
for (let attempt = 0; attempt < MAX_QUOTA_RETRIES; attempt++) {
|
|
2488
|
+
const errorBody = await current.response.text().catch(() => "");
|
|
2489
|
+
markCodexUsageLimitExhaustion(current.accountId, errorBody);
|
|
2490
|
+
const next = await runAttempt(responsesBody, plan);
|
|
2491
|
+
if (!isCodexAccountQuota429(plan, next)) return next;
|
|
2492
|
+
current = next;
|
|
2493
|
+
}
|
|
2494
|
+
return current;
|
|
2495
|
+
}
|
|
1958
2496
|
|
|
1959
2497
|
// src/provider-proxy/providerProxyRouter.ts
|
|
1960
2498
|
function extractRouteToken(authHeader) {
|
|
@@ -1986,7 +2524,7 @@ async function routeRequest(req, res, routes, deps) {
|
|
|
1986
2524
|
}
|
|
1987
2525
|
if (isOpenAIResponsesRequest(method, url)) {
|
|
1988
2526
|
const rawBody = await readBody(req);
|
|
1989
|
-
await handleOpenAIResponsesRequest(res, rawBody, route, deps);
|
|
2527
|
+
await handleOpenAIResponsesRequest(res, rawBody, route, deps, req.headers);
|
|
1990
2528
|
return;
|
|
1991
2529
|
}
|
|
1992
2530
|
if (isOpenAIChatRequest(method, url)) {
|
|
@@ -2196,6 +2734,38 @@ var DEFAULT_ACCOUNT_PROBE = {
|
|
|
2196
2734
|
historySize: 10,
|
|
2197
2735
|
staggerMs: 500
|
|
2198
2736
|
};
|
|
2737
|
+
var DEFAULT_ALLOWANCE_SCHEDULING = {
|
|
2738
|
+
enabled: false,
|
|
2739
|
+
demoteAtPercent: 80,
|
|
2740
|
+
pauseAtPercent: 98,
|
|
2741
|
+
priorityPenalty: 100
|
|
2742
|
+
};
|
|
2743
|
+
function normalizeAllowanceScheduling(raw) {
|
|
2744
|
+
const value = raw?.allowanceScheduling;
|
|
2745
|
+
const demoteAtPercent = clampNumber(
|
|
2746
|
+
value?.demoteAtPercent,
|
|
2747
|
+
0,
|
|
2748
|
+
100,
|
|
2749
|
+
DEFAULT_ALLOWANCE_SCHEDULING.demoteAtPercent
|
|
2750
|
+
);
|
|
2751
|
+
const requestedPause = clampNumber(
|
|
2752
|
+
value?.pauseAtPercent,
|
|
2753
|
+
0,
|
|
2754
|
+
100,
|
|
2755
|
+
DEFAULT_ALLOWANCE_SCHEDULING.pauseAtPercent
|
|
2756
|
+
);
|
|
2757
|
+
return {
|
|
2758
|
+
enabled: value?.enabled === true,
|
|
2759
|
+
demoteAtPercent,
|
|
2760
|
+
pauseAtPercent: Math.max(demoteAtPercent, requestedPause),
|
|
2761
|
+
priorityPenalty: Math.trunc(clampNumber(
|
|
2762
|
+
value?.priorityPenalty,
|
|
2763
|
+
1,
|
|
2764
|
+
1e3,
|
|
2765
|
+
DEFAULT_ALLOWANCE_SCHEDULING.priorityPenalty
|
|
2766
|
+
))
|
|
2767
|
+
};
|
|
2768
|
+
}
|
|
2199
2769
|
function normalizeAccountProbe(raw) {
|
|
2200
2770
|
const ap = raw?.accountProbe;
|
|
2201
2771
|
return {
|
|
@@ -2214,15 +2784,20 @@ function normalizeAudit(raw) {
|
|
|
2214
2784
|
return {
|
|
2215
2785
|
enabled: a?.enabled === true,
|
|
2216
2786
|
captureBodies: a?.captureBodies === true,
|
|
2217
|
-
maxBodyBytes:
|
|
2218
|
-
clampNumber(a?.maxBodyBytes, 256, 1048576, DEFAULT_AUDIT_CONFIG.maxBodyBytes)
|
|
2219
|
-
),
|
|
2787
|
+
maxBodyBytes: normalizeMaxBodyBytes(a?.maxBodyBytes),
|
|
2220
2788
|
retentionDays: Math.trunc(
|
|
2221
2789
|
clampNumber(a?.retentionDays, 1, 365, DEFAULT_AUDIT_CONFIG.retentionDays)
|
|
2222
2790
|
),
|
|
2223
2791
|
trustForwardedFor: a?.trustForwardedFor === true
|
|
2224
2792
|
};
|
|
2225
2793
|
}
|
|
2794
|
+
function normalizeMaxBodyBytes(value) {
|
|
2795
|
+
if (value === -1) return -1;
|
|
2796
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
2797
|
+
return DEFAULT_AUDIT_CONFIG.maxBodyBytes;
|
|
2798
|
+
}
|
|
2799
|
+
return Math.trunc(Math.min(1048576, value));
|
|
2800
|
+
}
|
|
2226
2801
|
function normalizeBilling(raw) {
|
|
2227
2802
|
const b = raw?.billing;
|
|
2228
2803
|
const config = {
|
|
@@ -2390,7 +2965,8 @@ function normalizePrefixTargets(raw) {
|
|
|
2390
2965
|
function normalizeEndpointConfig(e) {
|
|
2391
2966
|
const endpoint = e.endpoint;
|
|
2392
2967
|
const useSubscription = e.useSubscription === true;
|
|
2393
|
-
const boundAccountId = typeof e.boundAccountId === "string" && e.boundAccountId.trim() !== "" ? e.boundAccountId : void 0;
|
|
2968
|
+
const boundAccountId = typeof e.boundAccountId === "string" && e.boundAccountId.trim() !== "" ? e.boundAccountId.trim() : void 0;
|
|
2969
|
+
const boundAccountFallbackPolicy = boundAccountId ? e.boundAccountFallbackPolicy === "pool" ? "pool" : "strict" : void 0;
|
|
2394
2970
|
const boundKeyId = typeof e.boundKeyId === "string" && e.boundKeyId.trim() !== "" ? e.boundKeyId : void 0;
|
|
2395
2971
|
let config;
|
|
2396
2972
|
if (isKindMappedEndpoint(endpoint)) {
|
|
@@ -2420,21 +2996,186 @@ function normalizeEndpointConfig(e) {
|
|
|
2420
2996
|
config.backgroundModelIds = e.backgroundModelIds;
|
|
2421
2997
|
}
|
|
2422
2998
|
}
|
|
2423
|
-
if (boundAccountId)
|
|
2999
|
+
if (boundAccountId) {
|
|
3000
|
+
config.boundAccountId = boundAccountId;
|
|
3001
|
+
config.boundAccountFallbackPolicy = boundAccountFallbackPolicy;
|
|
3002
|
+
}
|
|
2424
3003
|
if (boundKeyId) config.boundKeyId = boundKeyId;
|
|
2425
3004
|
return config;
|
|
2426
3005
|
}
|
|
3006
|
+
function normalizeBindingTarget(raw) {
|
|
3007
|
+
if (!raw || typeof raw !== "object") return null;
|
|
3008
|
+
const target = raw;
|
|
3009
|
+
const providerId = typeof target.providerId === "string" ? target.providerId.trim() : "";
|
|
3010
|
+
if (!providerId) return null;
|
|
3011
|
+
if (target.kind === "account") {
|
|
3012
|
+
const accountId = typeof target.accountId === "string" ? target.accountId.trim() : "";
|
|
3013
|
+
return accountId ? { kind: "account", providerId, accountId } : null;
|
|
3014
|
+
}
|
|
3015
|
+
if (target.kind === "account-group") {
|
|
3016
|
+
const group = typeof target.group === "string" ? target.group.trim() : "";
|
|
3017
|
+
return group ? { kind: "account-group", providerId, group } : null;
|
|
3018
|
+
}
|
|
3019
|
+
if (target.kind === "account-pool") return { kind: "account-pool", providerId };
|
|
3020
|
+
if (target.kind === "provider") {
|
|
3021
|
+
const keyId = typeof target.keyId === "string" ? target.keyId.trim() : "";
|
|
3022
|
+
return keyId ? { kind: "provider", providerId, keyId } : { kind: "provider", providerId };
|
|
3023
|
+
}
|
|
3024
|
+
return null;
|
|
3025
|
+
}
|
|
3026
|
+
function normalizeGatewayModelMappings(raw) {
|
|
3027
|
+
if (!Array.isArray(raw)) return [];
|
|
3028
|
+
const mappings = [];
|
|
3029
|
+
for (const entry of raw) {
|
|
3030
|
+
if (!entry || typeof entry !== "object") continue;
|
|
3031
|
+
const value = entry;
|
|
3032
|
+
const source = typeof value.source === "string" ? value.source.trim() : "";
|
|
3033
|
+
const target = typeof value.target === "string" ? value.target.trim() : "";
|
|
3034
|
+
if (!source || !target) continue;
|
|
3035
|
+
mappings.push({ source, target });
|
|
3036
|
+
}
|
|
3037
|
+
return mappings;
|
|
3038
|
+
}
|
|
3039
|
+
function hasConfiguredLegacyModels(config) {
|
|
3040
|
+
return Object.values(config.modelMap ?? {}).some((value) => typeof value === "string" && value.trim()) || Boolean(config.models?.some((value) => typeof value === "string" && value.trim())) || Boolean(config.defaultModel?.trim()) || Boolean(config.backgroundModel?.trim()) || Boolean(Object.values(config.prefixTargets ?? {}).some((value) => value?.trim()));
|
|
3041
|
+
}
|
|
3042
|
+
function splitModelRef(ref) {
|
|
3043
|
+
if (typeof ref !== "string") return null;
|
|
3044
|
+
const idx = ref.indexOf(",");
|
|
3045
|
+
if (idx <= 0) return null;
|
|
3046
|
+
const providerId = ref.slice(0, idx).trim();
|
|
3047
|
+
const modelId = ref.slice(idx + 1).trim();
|
|
3048
|
+
return providerId && modelId ? { providerId, modelId } : null;
|
|
3049
|
+
}
|
|
3050
|
+
function legacyTarget(config, providerId) {
|
|
3051
|
+
if (config.useSubscription) {
|
|
3052
|
+
if (config.boundAccountId) {
|
|
3053
|
+
return { kind: "account", providerId, accountId: config.boundAccountId };
|
|
3054
|
+
}
|
|
3055
|
+
if (config.boundAccountGroup) {
|
|
3056
|
+
return { kind: "account-group", providerId, group: config.boundAccountGroup };
|
|
3057
|
+
}
|
|
3058
|
+
return { kind: "account-pool", providerId };
|
|
3059
|
+
}
|
|
3060
|
+
return config.boundKeyId ? { kind: "provider", providerId, keyId: config.boundKeyId } : { kind: "provider", providerId };
|
|
3061
|
+
}
|
|
3062
|
+
function legacyEndpointToBindings(config) {
|
|
3063
|
+
const fallback = config.boundAccountId && config.boundAccountFallbackPolicy === "strict" ? "fail" : "next";
|
|
3064
|
+
const byProvider = /* @__PURE__ */ new Map();
|
|
3065
|
+
const routeFor = (providerId) => {
|
|
3066
|
+
const existing = byProvider.get(providerId);
|
|
3067
|
+
if (existing) return existing;
|
|
3068
|
+
const created = {
|
|
3069
|
+
id: `legacy-${config.endpoint}-${providerId}`,
|
|
3070
|
+
name: `${config.endpoint} \xB7 ${providerId}`,
|
|
3071
|
+
enabled: true,
|
|
3072
|
+
keyScope: "all",
|
|
3073
|
+
endpoint: config.endpoint,
|
|
3074
|
+
target: legacyTarget(config, providerId),
|
|
3075
|
+
fallback,
|
|
3076
|
+
modelMode: "mapped"
|
|
3077
|
+
};
|
|
3078
|
+
byProvider.set(providerId, created);
|
|
3079
|
+
return created;
|
|
3080
|
+
};
|
|
3081
|
+
for (const [kind, ref] of Object.entries(config.modelMap ?? {})) {
|
|
3082
|
+
const parsed = splitModelRef(ref);
|
|
3083
|
+
if (!parsed) continue;
|
|
3084
|
+
const route = routeFor(parsed.providerId);
|
|
3085
|
+
route.modelMap = { ...route.modelMap, [kind]: ref };
|
|
3086
|
+
}
|
|
3087
|
+
for (const ref of config.models ?? []) {
|
|
3088
|
+
const parsed = splitModelRef(ref);
|
|
3089
|
+
if (!parsed) continue;
|
|
3090
|
+
const route = routeFor(parsed.providerId);
|
|
3091
|
+
route.models = [...route.models ?? [], ref];
|
|
3092
|
+
}
|
|
3093
|
+
for (const [prefix, ref] of Object.entries(config.prefixTargets ?? {})) {
|
|
3094
|
+
const parsed = splitModelRef(ref);
|
|
3095
|
+
if (!parsed) continue;
|
|
3096
|
+
const route = routeFor(parsed.providerId);
|
|
3097
|
+
route.dispatchMode = "prefix";
|
|
3098
|
+
route.prefixTargets = { ...route.prefixTargets, [prefix]: ref };
|
|
3099
|
+
}
|
|
3100
|
+
for (const [field, ref] of [
|
|
3101
|
+
["defaultModel", config.defaultModel],
|
|
3102
|
+
["backgroundModel", config.backgroundModel]
|
|
3103
|
+
]) {
|
|
3104
|
+
const parsed = splitModelRef(ref);
|
|
3105
|
+
if (!parsed) continue;
|
|
3106
|
+
const route = routeFor(parsed.providerId);
|
|
3107
|
+
route[field] = ref;
|
|
3108
|
+
if (config.backgroundModelIds?.length) {
|
|
3109
|
+
route.backgroundModelIds = [...config.backgroundModelIds];
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
return [...byProvider.values()];
|
|
3113
|
+
}
|
|
3114
|
+
function legacyEndpointsToBindings(endpoints) {
|
|
3115
|
+
return endpoints.filter((endpoint) => hasConfiguredLegacyModels(endpoint)).flatMap((endpoint) => legacyEndpointToBindings(endpoint));
|
|
3116
|
+
}
|
|
3117
|
+
function normalizeGatewayBindings(raw) {
|
|
3118
|
+
if (!Array.isArray(raw)) return [];
|
|
3119
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3120
|
+
const bindings = [];
|
|
3121
|
+
for (const entry of raw) {
|
|
3122
|
+
if (!entry || typeof entry !== "object") continue;
|
|
3123
|
+
const value = entry;
|
|
3124
|
+
const id = typeof value.id === "string" ? value.id.trim() : "";
|
|
3125
|
+
const name = typeof value.name === "string" ? value.name.trim() : "";
|
|
3126
|
+
const endpoint = value.endpoint;
|
|
3127
|
+
const target = normalizeBindingTarget(value.target);
|
|
3128
|
+
if (!id || seen.has(id) || !name || !ALL_ENDPOINTS.includes(endpoint) || !target) continue;
|
|
3129
|
+
seen.add(id);
|
|
3130
|
+
const normalizedRoute = normalizeEndpointConfig({
|
|
3131
|
+
...value,
|
|
3132
|
+
endpoint,
|
|
3133
|
+
useSubscription: target.kind !== "provider"
|
|
3134
|
+
});
|
|
3135
|
+
const apiKeyIds = Array.isArray(value.apiKeyIds) ? [...new Set(value.apiKeyIds.filter((key) => typeof key === "string" && key.trim() !== "").map((key) => key.trim()))] : [];
|
|
3136
|
+
const keyScope = value.keyScope === "selected" ? "selected" : value.keyScope === "all" ? "all" : apiKeyIds.length > 0 ? "selected" : "all";
|
|
3137
|
+
const modelMappings = normalizeGatewayModelMappings(value.modelMappings);
|
|
3138
|
+
const modelMode = value.modelMode === "passthrough" ? "passthrough" : value.modelMode === "mapped" ? "mapped" : modelMappings.length > 0 || hasConfiguredLegacyModels(normalizedRoute) ? "mapped" : "passthrough";
|
|
3139
|
+
const priority = typeof value.priority === "number" && Number.isFinite(value.priority) ? Math.max(0, Math.min(1e4, Math.round(value.priority))) : void 0;
|
|
3140
|
+
const binding = {
|
|
3141
|
+
id,
|
|
3142
|
+
name,
|
|
3143
|
+
enabled: value.enabled !== false,
|
|
3144
|
+
keyScope,
|
|
3145
|
+
endpoint,
|
|
3146
|
+
target,
|
|
3147
|
+
// Legacy `'global'` meant "yield to the removed global endpoint fallback";
|
|
3148
|
+
// it now yields to the next matching route, which is the same default.
|
|
3149
|
+
fallback: value.fallback === "fail" ? "fail" : "next",
|
|
3150
|
+
modelMode
|
|
3151
|
+
};
|
|
3152
|
+
if (apiKeyIds.length > 0) binding.apiKeyIds = apiKeyIds;
|
|
3153
|
+
if (modelMappings.length > 0) binding.modelMappings = modelMappings;
|
|
3154
|
+
if (priority !== void 0) binding.priority = priority;
|
|
3155
|
+
if (normalizedRoute.modelMap) binding.modelMap = normalizedRoute.modelMap;
|
|
3156
|
+
if (normalizedRoute.models) binding.models = normalizedRoute.models;
|
|
3157
|
+
if (normalizedRoute.dispatchMode) binding.dispatchMode = normalizedRoute.dispatchMode;
|
|
3158
|
+
if (normalizedRoute.prefixTargets) binding.prefixTargets = normalizedRoute.prefixTargets;
|
|
3159
|
+
if (normalizedRoute.defaultModel !== void 0) binding.defaultModel = normalizedRoute.defaultModel;
|
|
3160
|
+
if (normalizedRoute.backgroundModel !== void 0) binding.backgroundModel = normalizedRoute.backgroundModel;
|
|
3161
|
+
if (normalizedRoute.backgroundModelIds) binding.backgroundModelIds = normalizedRoute.backgroundModelIds;
|
|
3162
|
+
bindings.push(binding);
|
|
3163
|
+
}
|
|
3164
|
+
return bindings;
|
|
3165
|
+
}
|
|
2427
3166
|
function defaultServerConfig() {
|
|
2428
3167
|
const queues = normalizeQueueSegments(void 0);
|
|
2429
3168
|
return {
|
|
2430
3169
|
enabled: false,
|
|
2431
3170
|
networkBinding: false,
|
|
2432
3171
|
endpoints: ALL_ENDPOINTS.map(defaultEndpointConfig),
|
|
3172
|
+
bindings: [],
|
|
2433
3173
|
port: DEFAULT_OUTBOUND_PORT,
|
|
2434
3174
|
userMessageQueue: queues.userMessageQueue,
|
|
2435
3175
|
concurrencyQueue: queues.concurrencyQueue,
|
|
2436
3176
|
accountHealth: normalizeAccountHealth(void 0),
|
|
2437
3177
|
accountProbe: normalizeAccountProbe(void 0),
|
|
3178
|
+
allowanceScheduling: normalizeAllowanceScheduling(void 0),
|
|
2438
3179
|
audit: normalizeAudit(void 0),
|
|
2439
3180
|
billing: normalizeBilling(void 0),
|
|
2440
3181
|
fingerprint: normalizeFingerprint(void 0),
|
|
@@ -2451,17 +3192,20 @@ function normalizeServerConfig(raw) {
|
|
|
2451
3192
|
}
|
|
2452
3193
|
}
|
|
2453
3194
|
const queues = normalizeQueueSegments(raw);
|
|
3195
|
+
const legacyEndpoints = ALL_ENDPOINTS.map((ep) => byEndpoint.get(ep) ?? defaultEndpointConfig(ep));
|
|
3196
|
+
const persistedBindings = normalizeGatewayBindings(raw.bindings);
|
|
3197
|
+
const bindings = persistedBindings.length > 0 ? persistedBindings : normalizeGatewayBindings(legacyEndpointsToBindings(legacyEndpoints));
|
|
2454
3198
|
const config = {
|
|
2455
3199
|
enabled: raw.enabled === true,
|
|
2456
3200
|
networkBinding: raw.networkBinding === true,
|
|
2457
|
-
endpoints: ALL_ENDPOINTS.map(
|
|
2458
|
-
|
|
2459
|
-
),
|
|
3201
|
+
endpoints: ALL_ENDPOINTS.map(defaultEndpointConfig),
|
|
3202
|
+
bindings,
|
|
2460
3203
|
port: raw.port ?? base.port,
|
|
2461
3204
|
userMessageQueue: queues.userMessageQueue,
|
|
2462
3205
|
concurrencyQueue: queues.concurrencyQueue,
|
|
2463
3206
|
accountHealth: normalizeAccountHealth(raw),
|
|
2464
3207
|
accountProbe: normalizeAccountProbe(raw),
|
|
3208
|
+
allowanceScheduling: normalizeAllowanceScheduling(raw),
|
|
2465
3209
|
audit: normalizeAudit(raw),
|
|
2466
3210
|
billing: normalizeBilling(raw),
|
|
2467
3211
|
fingerprint: normalizeFingerprint(raw),
|
|
@@ -2487,11 +3231,13 @@ function mergeServerConfig(current, patch) {
|
|
|
2487
3231
|
enabled: patch.enabled ?? current.enabled,
|
|
2488
3232
|
networkBinding: patch.networkBinding ?? current.networkBinding,
|
|
2489
3233
|
endpoints: patch.endpoints ?? current.endpoints,
|
|
3234
|
+
bindings: patch.bindings ?? current.bindings,
|
|
2490
3235
|
port: patch.port ?? current.port,
|
|
2491
3236
|
userMessageQueue: patch.userMessageQueue ?? current.userMessageQueue,
|
|
2492
3237
|
concurrencyQueue: patch.concurrencyQueue ?? current.concurrencyQueue,
|
|
2493
3238
|
accountHealth: patch.accountHealth ?? current.accountHealth,
|
|
2494
3239
|
accountProbe: patch.accountProbe ?? current.accountProbe,
|
|
3240
|
+
allowanceScheduling: patch.allowanceScheduling ?? current.allowanceScheduling,
|
|
2495
3241
|
audit: patch.audit ?? current.audit,
|
|
2496
3242
|
billing: patch.billing ?? current.billing,
|
|
2497
3243
|
// Proxy is layer-replaced (not deep-merged): a PUT carrying `proxy` swaps the
|
|
@@ -2509,6 +3255,167 @@ function mergeServerConfig(current, patch) {
|
|
|
2509
3255
|
});
|
|
2510
3256
|
}
|
|
2511
3257
|
|
|
3258
|
+
// src/outbound-api/gatewayBindingResolver.ts
|
|
3259
|
+
var MESSAGE_FALLBACK_KINDS = ["sonnet", "opus", "haiku", "fable"];
|
|
3260
|
+
function nonBlank(value) {
|
|
3261
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3262
|
+
}
|
|
3263
|
+
function gatewayBindingAllowsKey(binding, apiKeyId) {
|
|
3264
|
+
const scope = binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3265
|
+
return scope === "all" || Boolean(binding.apiKeyIds?.includes(apiKeyId));
|
|
3266
|
+
}
|
|
3267
|
+
function wildcardMatches(pattern, value) {
|
|
3268
|
+
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
|
|
3269
|
+
return new RegExp(`^${escaped}$`, "iu").test(value);
|
|
3270
|
+
}
|
|
3271
|
+
function resolveGatewayModelMapping(mappings, requestedModel) {
|
|
3272
|
+
if (!nonBlank(requestedModel)) return void 0;
|
|
3273
|
+
const wanted = requestedModel.trim();
|
|
3274
|
+
const exact = (mappings ?? []).find(
|
|
3275
|
+
(mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
|
|
3276
|
+
);
|
|
3277
|
+
if (exact && nonBlank(exact.target)) return exact.target.trim();
|
|
3278
|
+
const wildcard = (mappings ?? []).find(
|
|
3279
|
+
(mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
|
|
3280
|
+
);
|
|
3281
|
+
return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
|
|
3282
|
+
}
|
|
3283
|
+
function routeCanServe(binding, requestedModel, role) {
|
|
3284
|
+
if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
|
|
3285
|
+
if (binding.modelMappings?.length) {
|
|
3286
|
+
return nonBlank(resolveGatewayModelMapping(binding.modelMappings, requestedModel));
|
|
3287
|
+
}
|
|
3288
|
+
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3289
|
+
const map = binding.modelMap ?? {};
|
|
3290
|
+
const kind = detectModelKind(binding.endpoint, requestedModel);
|
|
3291
|
+
if (kind && nonBlank(map[kind])) return true;
|
|
3292
|
+
if (binding.endpoint === "messages") {
|
|
3293
|
+
return MESSAGE_FALLBACK_KINDS.some((candidate) => nonBlank(map[candidate]));
|
|
3294
|
+
}
|
|
3295
|
+
return nonBlank(map.codex);
|
|
3296
|
+
}
|
|
3297
|
+
if (binding.endpoint === "chat" && binding.dispatchMode === "prefix") {
|
|
3298
|
+
return resolvePrefixTarget(binding.prefixTargets, requestedModel) !== null;
|
|
3299
|
+
}
|
|
3300
|
+
if (binding.endpoint === "chat") {
|
|
3301
|
+
if (!requestedModel) return false;
|
|
3302
|
+
const wanted = requestedModel.trim().toLowerCase();
|
|
3303
|
+
return (binding.models ?? []).some((ref) => modelIdOf(ref)?.toLowerCase() === wanted);
|
|
3304
|
+
}
|
|
3305
|
+
return role === "background" ? nonBlank(binding.backgroundModel) : nonBlank(binding.defaultModel);
|
|
3306
|
+
}
|
|
3307
|
+
function modelIdOf(ref) {
|
|
3308
|
+
if (!nonBlank(ref)) return void 0;
|
|
3309
|
+
const trimmed = ref.trim();
|
|
3310
|
+
const comma = trimmed.indexOf(",");
|
|
3311
|
+
const modelId = comma >= 0 ? trimmed.slice(comma + 1).trim() : trimmed;
|
|
3312
|
+
return modelId || void 0;
|
|
3313
|
+
}
|
|
3314
|
+
function targetProviderId(target) {
|
|
3315
|
+
return target.providerId.trim();
|
|
3316
|
+
}
|
|
3317
|
+
function targetRef(target, ref) {
|
|
3318
|
+
const modelId = modelIdOf(ref) ?? "";
|
|
3319
|
+
return modelId ? `${targetProviderId(target)},${modelId}` : "";
|
|
3320
|
+
}
|
|
3321
|
+
function targetRefs(target, refs) {
|
|
3322
|
+
return (refs ?? []).map((ref) => targetRef(target, ref)).filter(nonBlank);
|
|
3323
|
+
}
|
|
3324
|
+
function targetPrefixRefs(target, refs) {
|
|
3325
|
+
if (!refs) return void 0;
|
|
3326
|
+
const mapped = {};
|
|
3327
|
+
for (const kind of ["claude", "gpt", "gemini"]) {
|
|
3328
|
+
const ref = targetRef(target, refs[kind]);
|
|
3329
|
+
if (ref) mapped[kind] = ref;
|
|
3330
|
+
}
|
|
3331
|
+
return mapped.claude || mapped.gpt || mapped.gemini ? mapped : void 0;
|
|
3332
|
+
}
|
|
3333
|
+
function applySingleModel(config, binding, targetModel) {
|
|
3334
|
+
const ref = targetRef(binding.target, targetModel);
|
|
3335
|
+
if (!ref) return;
|
|
3336
|
+
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3337
|
+
config.modelMap = Object.fromEntries(
|
|
3338
|
+
modelKindsForEndpoint(binding.endpoint).map((kind) => [kind, ref])
|
|
3339
|
+
);
|
|
3340
|
+
} else if (binding.endpoint === "chat") {
|
|
3341
|
+
config.models = [ref];
|
|
3342
|
+
config.dispatchMode = "list";
|
|
3343
|
+
} else {
|
|
3344
|
+
config.defaultModel = ref;
|
|
3345
|
+
config.backgroundModel = ref;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
function gatewayBindingToEndpointConfig(binding, requestedModel) {
|
|
3349
|
+
const target = binding.target;
|
|
3350
|
+
const config = {
|
|
3351
|
+
endpoint: binding.endpoint,
|
|
3352
|
+
useSubscription: target.kind !== "provider"
|
|
3353
|
+
};
|
|
3354
|
+
const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
|
|
3355
|
+
const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : resolveGatewayModelMapping(binding.modelMappings, requestedModel);
|
|
3356
|
+
if (usesGenericModelHandling) {
|
|
3357
|
+
if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
|
|
3358
|
+
} else if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3359
|
+
config.modelMap = Object.fromEntries(
|
|
3360
|
+
Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
|
|
3361
|
+
);
|
|
3362
|
+
} else if (binding.endpoint === "chat") {
|
|
3363
|
+
config.models = targetRefs(target, binding.models);
|
|
3364
|
+
if (binding.dispatchMode === "prefix") {
|
|
3365
|
+
config.dispatchMode = "prefix";
|
|
3366
|
+
config.prefixTargets = targetPrefixRefs(target, binding.prefixTargets);
|
|
3367
|
+
}
|
|
3368
|
+
} else {
|
|
3369
|
+
config.defaultModel = targetRef(target, binding.defaultModel);
|
|
3370
|
+
config.backgroundModel = targetRef(target, binding.backgroundModel);
|
|
3371
|
+
if (binding.backgroundModelIds) config.backgroundModelIds = [...binding.backgroundModelIds];
|
|
3372
|
+
}
|
|
3373
|
+
if (target.kind === "account") {
|
|
3374
|
+
config.boundAccountId = target.accountId;
|
|
3375
|
+
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3376
|
+
} else if (target.kind === "account-group") {
|
|
3377
|
+
config.boundAccountGroup = target.group;
|
|
3378
|
+
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3379
|
+
} else if (target.kind === "provider" && target.keyId) {
|
|
3380
|
+
config.boundKeyId = target.keyId;
|
|
3381
|
+
config.boundKeyFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3382
|
+
}
|
|
3383
|
+
return config;
|
|
3384
|
+
}
|
|
3385
|
+
function candidateGatewayBindings(bindings, apiKeyId, endpoint) {
|
|
3386
|
+
const candidates = (bindings ?? []).filter(
|
|
3387
|
+
(binding) => binding.enabled && binding.endpoint === endpoint && gatewayBindingAllowsKey(binding, apiKeyId)
|
|
3388
|
+
);
|
|
3389
|
+
const scopeOf = (binding) => binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3390
|
+
const scoped = candidates.filter((binding) => scopeOf(binding) === "selected");
|
|
3391
|
+
const pool = scoped.length > 0 ? scoped : candidates.filter((binding) => scopeOf(binding) === "all");
|
|
3392
|
+
return pool.sort(
|
|
3393
|
+
(left, right) => (left.priority ?? 100) - (right.priority ?? 100) || left.id.localeCompare(right.id)
|
|
3394
|
+
);
|
|
3395
|
+
}
|
|
3396
|
+
function candidateBackgroundModelIds(bindings, apiKeyId, endpoint) {
|
|
3397
|
+
const ids = [
|
|
3398
|
+
...new Set(
|
|
3399
|
+
candidateGatewayBindings(bindings, apiKeyId, endpoint).flatMap(
|
|
3400
|
+
(binding) => binding.backgroundModelIds ?? []
|
|
3401
|
+
)
|
|
3402
|
+
)
|
|
3403
|
+
];
|
|
3404
|
+
return ids.length > 0 ? ids : void 0;
|
|
3405
|
+
}
|
|
3406
|
+
function resolveGatewayBinding(input) {
|
|
3407
|
+
const candidates = candidateGatewayBindings(input.bindings, input.apiKeyId, input.endpoint);
|
|
3408
|
+
const binding = candidates.find(
|
|
3409
|
+
(candidate) => candidate.fallback === "fail" || routeCanServe(candidate, input.requestedModel, input.role)
|
|
3410
|
+
) ?? candidates[0];
|
|
3411
|
+
if (!binding) return { source: "none" };
|
|
3412
|
+
return {
|
|
3413
|
+
source: "binding",
|
|
3414
|
+
binding,
|
|
3415
|
+
config: gatewayBindingToEndpointConfig(binding, input.requestedModel)
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3418
|
+
|
|
2512
3419
|
// src/outbound-api/outboundApiKeyAuth.ts
|
|
2513
3420
|
import { createHash, randomBytes as randomBytes2 } from "crypto";
|
|
2514
3421
|
|
|
@@ -2591,7 +3498,30 @@ async function createNamedKey(db, name) {
|
|
|
2591
3498
|
id,
|
|
2592
3499
|
name,
|
|
2593
3500
|
keyHash: hashKey(secret),
|
|
2594
|
-
keyPrefix: prefix
|
|
3501
|
+
keyPrefix: prefix,
|
|
3502
|
+
plaintext: secret
|
|
3503
|
+
});
|
|
3504
|
+
return {
|
|
3505
|
+
id: row.id,
|
|
3506
|
+
name: row.name,
|
|
3507
|
+
keyPrefix: row.keyPrefix,
|
|
3508
|
+
createdAt: row.createdAt,
|
|
3509
|
+
plaintextOnce: secret
|
|
3510
|
+
};
|
|
3511
|
+
}
|
|
3512
|
+
async function createIntegrationKey(db, name, allowedEndpoints = ["responses", "messages"]) {
|
|
3513
|
+
const secret = generateSecret();
|
|
3514
|
+
const id = newKeyId();
|
|
3515
|
+
const prefix = keyPrefix(secret);
|
|
3516
|
+
const row = await db.outboundApiKeysCreate({
|
|
3517
|
+
id,
|
|
3518
|
+
name,
|
|
3519
|
+
keyHash: hashKey(secret),
|
|
3520
|
+
keyPrefix: prefix,
|
|
3521
|
+
kind: "integration",
|
|
3522
|
+
allowedEndpoints: [...new Set(allowedEndpoints)],
|
|
3523
|
+
loopbackOnly: true,
|
|
3524
|
+
plaintext: secret
|
|
2595
3525
|
});
|
|
2596
3526
|
return {
|
|
2597
3527
|
id: row.id,
|
|
@@ -2623,7 +3553,12 @@ function extractModelRestriction(row) {
|
|
|
2623
3553
|
};
|
|
2624
3554
|
}
|
|
2625
3555
|
function toVerifiedKey(row) {
|
|
2626
|
-
const key = {
|
|
3556
|
+
const key = {
|
|
3557
|
+
id: row.id,
|
|
3558
|
+
kind: row.kind,
|
|
3559
|
+
allowedEndpoints: row.allowedEndpoints,
|
|
3560
|
+
loopbackOnly: row.loopbackOnly
|
|
3561
|
+
};
|
|
2627
3562
|
if (row.maxConcurrency !== void 0 && row.maxConcurrency !== null) {
|
|
2628
3563
|
key.maxConcurrency = row.maxConcurrency;
|
|
2629
3564
|
}
|
|
@@ -3267,10 +4202,10 @@ function getFallbackRedeemLimiter() {
|
|
|
3267
4202
|
fallbackRedeemLimiter ??= new OutboundRateLimiter({ maxRequests: 10, windowMs: 6e4 });
|
|
3268
4203
|
return fallbackRedeemLimiter;
|
|
3269
4204
|
}
|
|
3270
|
-
function writeJsonError(res, status, message, headers = {}) {
|
|
4205
|
+
function writeJsonError(res, status, message, headers = {}, details = {}) {
|
|
3271
4206
|
if (res.headersSent) return;
|
|
3272
4207
|
res.writeHead(status, { "Content-Type": "application/json", ...headers });
|
|
3273
|
-
res.end(JSON.stringify({ error: { type: "outbound_api_error", message } }));
|
|
4208
|
+
res.end(JSON.stringify({ error: { type: "outbound_api_error", message, ...details } }));
|
|
3274
4209
|
}
|
|
3275
4210
|
function writeCostLimitError(res, scope, limitUsd, spentUsd) {
|
|
3276
4211
|
if (res.headersSent) return;
|
|
@@ -3314,6 +4249,11 @@ function extractPresentedKey(req) {
|
|
|
3314
4249
|
const g = Array.isArray(goog) ? goog[0] : goog;
|
|
3315
4250
|
return g?.trim() || void 0;
|
|
3316
4251
|
}
|
|
4252
|
+
function isLoopbackPeer(address) {
|
|
4253
|
+
if (!address) return false;
|
|
4254
|
+
const normalized = address.toLowerCase().split("%")[0];
|
|
4255
|
+
return normalized === "::1" || normalized === "127.0.0.1" || normalized.startsWith("127.") || normalized.startsWith("::ffff:127.");
|
|
4256
|
+
}
|
|
3317
4257
|
function selectEndpoint(method, url) {
|
|
3318
4258
|
if (method !== "POST" || !url) return null;
|
|
3319
4259
|
const path = url.split("?")[0]?.replace(/\/+$/, "") ?? "";
|
|
@@ -3337,9 +4277,33 @@ function isModelsListRequest(url) {
|
|
|
3337
4277
|
const path = url.split("?")[0]?.replace(/\/+$/, "") ?? "";
|
|
3338
4278
|
return path.endsWith("/models");
|
|
3339
4279
|
}
|
|
3340
|
-
function
|
|
3341
|
-
const
|
|
3342
|
-
const
|
|
4280
|
+
function writeModelsList(res, config, apiKeyId, allowedEndpoints) {
|
|
4281
|
+
const refs = [];
|
|
4282
|
+
const endpointConfigs = [];
|
|
4283
|
+
for (const endpoint of ["chat", "responses", "messages", "gemini"]) {
|
|
4284
|
+
if (allowedEndpoints && !allowedEndpoints.includes(endpoint)) continue;
|
|
4285
|
+
endpointConfigs.push(
|
|
4286
|
+
...candidateGatewayBindings(config.bindings, apiKeyId, endpoint).map(
|
|
4287
|
+
(binding) => gatewayBindingToEndpointConfig(binding)
|
|
4288
|
+
)
|
|
4289
|
+
);
|
|
4290
|
+
}
|
|
4291
|
+
for (const endpoint of endpointConfigs) {
|
|
4292
|
+
if (allowedEndpoints && !allowedEndpoints.includes(endpoint.endpoint)) continue;
|
|
4293
|
+
if (endpoint.endpoint === "chat") refs.push(...endpoint.models ?? []);
|
|
4294
|
+
else if (endpoint.endpoint === "messages" || endpoint.endpoint === "responses") {
|
|
4295
|
+
refs.push(...Object.values(endpoint.modelMap ?? {}));
|
|
4296
|
+
} else {
|
|
4297
|
+
if (endpoint.defaultModel) refs.push(endpoint.defaultModel);
|
|
4298
|
+
if (endpoint.backgroundModel) refs.push(endpoint.backgroundModel);
|
|
4299
|
+
}
|
|
4300
|
+
}
|
|
4301
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4302
|
+
const data = refs.map((ref) => parseModelRef(ref)).filter((p) => p !== null).filter((p) => {
|
|
4303
|
+
if (seen.has(p.modelId)) return false;
|
|
4304
|
+
seen.add(p.modelId);
|
|
4305
|
+
return true;
|
|
4306
|
+
}).map((p) => ({ id: p.modelId, object: "model", owned_by: "omnicross" }));
|
|
3343
4307
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
3344
4308
|
res.end(JSON.stringify({ object: "list", data }));
|
|
3345
4309
|
}
|
|
@@ -3373,7 +4337,15 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
3373
4337
|
const verified = verification.key;
|
|
3374
4338
|
if (audit) audit.keyId = verified.id;
|
|
3375
4339
|
if (billing) billing.keyId = verified.id;
|
|
4340
|
+
if (verified.loopbackOnly && !isLoopbackPeer(req.socket?.remoteAddress)) {
|
|
4341
|
+
writeJsonError(res, 403, "This integration key is restricted to loopback clients");
|
|
4342
|
+
return;
|
|
4343
|
+
}
|
|
3376
4344
|
if (isRedeemRequest(req.method, req.url)) {
|
|
4345
|
+
if (verified.kind === "integration") {
|
|
4346
|
+
writeJsonError(res, 403, "Integration keys cannot redeem vouchers");
|
|
4347
|
+
return;
|
|
4348
|
+
}
|
|
3377
4349
|
await handleVoucherRedeem(
|
|
3378
4350
|
req,
|
|
3379
4351
|
res,
|
|
@@ -3395,7 +4367,11 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
3395
4367
|
return;
|
|
3396
4368
|
}
|
|
3397
4369
|
if (req.method === "GET" && isModelsListRequest(req.url)) {
|
|
3398
|
-
|
|
4370
|
+
if (verified.allowedEndpoints && verified.allowedEndpoints.length === 0) {
|
|
4371
|
+
writeJsonError(res, 403, "API key is not allowed to access this endpoint");
|
|
4372
|
+
return;
|
|
4373
|
+
}
|
|
4374
|
+
writeModelsList(res, config, verified.id, verified.allowedEndpoints);
|
|
3399
4375
|
return;
|
|
3400
4376
|
}
|
|
3401
4377
|
const endpoint = selectEndpoint(req.method, req.url);
|
|
@@ -3403,9 +4379,12 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
3403
4379
|
writeJsonError(res, 404, `Unsupported: ${req.method} ${req.url}`);
|
|
3404
4380
|
return;
|
|
3405
4381
|
}
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
4382
|
+
if (verified.allowedEndpoints && !verified.allowedEndpoints.includes(endpoint)) {
|
|
4383
|
+
writeJsonError(res, 403, "API key is not allowed to access this endpoint");
|
|
4384
|
+
return;
|
|
4385
|
+
}
|
|
4386
|
+
if (candidateGatewayBindings(config.bindings, verified.id, endpoint).length === 0) {
|
|
4387
|
+
writeJsonError(res, 503, `endpoint '${endpoint}' has no downstream route for this key`);
|
|
3409
4388
|
return;
|
|
3410
4389
|
}
|
|
3411
4390
|
if (verified.costLimits && deps.keySpendTracker) {
|
|
@@ -3478,23 +4457,40 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
3478
4457
|
const urlModel = extractGeminiModelFromUrl(req.url);
|
|
3479
4458
|
if (urlModel) parsedBody["model"] = urlModel;
|
|
3480
4459
|
}
|
|
3481
|
-
const
|
|
4460
|
+
const requestedModel = extractRequestedModel(ingressFormat, parsedBody);
|
|
4461
|
+
const role = isKindMappedEndpoint(endpoint) || endpoint === "chat" ? void 0 : detectRequestRole(ingressFormat, parsedBody, {
|
|
4462
|
+
// Role detection precedes the route pick (the pick consumes the
|
|
4463
|
+
// role), so the hint is the union across the candidate routes.
|
|
4464
|
+
backgroundModelIds: candidateBackgroundModelIds(config.bindings, verified.id, endpoint)
|
|
4465
|
+
});
|
|
4466
|
+
const bindingResolution = resolveGatewayBinding({
|
|
4467
|
+
bindings: config.bindings,
|
|
4468
|
+
apiKeyId: verified.id,
|
|
4469
|
+
endpoint,
|
|
4470
|
+
requestedModel,
|
|
4471
|
+
role
|
|
4472
|
+
});
|
|
4473
|
+
if (bindingResolution.source === "none") {
|
|
4474
|
+
writeJsonError(res, 503, `endpoint '${endpoint}' has no downstream route for this key`);
|
|
4475
|
+
return;
|
|
4476
|
+
}
|
|
4477
|
+
const effectiveEndpointConfig = bindingResolution.config;
|
|
4478
|
+
const bindingAffinitySuffix = `:${bindingResolution.binding.id}`;
|
|
4479
|
+
const sessionId = deps.proxyDeps.apiKeyPool ? `outbound:${verified.id}${bindingAffinitySuffix}` : null;
|
|
3482
4480
|
const resolved = isKindMappedEndpoint(endpoint) || endpoint === "chat" ? await resolveRoute({
|
|
3483
|
-
config:
|
|
4481
|
+
config: effectiveEndpointConfig,
|
|
3484
4482
|
ingressFormat,
|
|
3485
4483
|
llmConfig: deps.llmConfig,
|
|
3486
4484
|
sessionId,
|
|
3487
4485
|
// Capture the ORIGINAL requested id BEFORE any downstream swap; for
|
|
3488
4486
|
// kind-mapped endpoints it selects the kind AND is stamped onto
|
|
3489
4487
|
// `route.requestedModel`; for chat it is matched against the list.
|
|
3490
|
-
requestedModel
|
|
4488
|
+
requestedModel,
|
|
3491
4489
|
// Attribution: stamp the verified named-key id onto the route.
|
|
3492
4490
|
apiKeyId: verified.id
|
|
3493
4491
|
}) : await resolveRoute({
|
|
3494
|
-
config:
|
|
3495
|
-
role
|
|
3496
|
-
backgroundModelIds: endpointConfig.backgroundModelIds
|
|
3497
|
-
}),
|
|
4492
|
+
config: effectiveEndpointConfig,
|
|
4493
|
+
role,
|
|
3498
4494
|
ingressFormat,
|
|
3499
4495
|
llmConfig: deps.llmConfig,
|
|
3500
4496
|
sessionId,
|
|
@@ -3555,12 +4551,29 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
3555
4551
|
const replay = makeReplayRequest(req, rawBody);
|
|
3556
4552
|
await routeRequest(replay, res, routeMap, deps.proxyDeps);
|
|
3557
4553
|
} catch (err) {
|
|
4554
|
+
if (isBoundAccountSelectionError(err)) {
|
|
4555
|
+
const headers = {};
|
|
4556
|
+
if (err.status === 429 && err.resumeAt) {
|
|
4557
|
+
const resumeMs = Date.parse(err.resumeAt);
|
|
4558
|
+
if (Number.isFinite(resumeMs)) {
|
|
4559
|
+
headers["Retry-After"] = String(Math.max(1, Math.ceil((resumeMs - Date.now()) / 1e3)));
|
|
4560
|
+
}
|
|
4561
|
+
}
|
|
4562
|
+
writeJsonError(
|
|
4563
|
+
res,
|
|
4564
|
+
err.status,
|
|
4565
|
+
boundAccountSelectionMessage(err.reason),
|
|
4566
|
+
headers,
|
|
4567
|
+
{ code: err.code, reason: err.reason }
|
|
4568
|
+
);
|
|
4569
|
+
return;
|
|
4570
|
+
}
|
|
3558
4571
|
const message = serializeError(err);
|
|
3559
4572
|
if (deps.logger) deps.logger.error("[OutboundApi] dispatch error:", message);
|
|
3560
4573
|
else console.error("[OutboundApi] dispatch error:", message);
|
|
3561
4574
|
emitWebhookEvent({ kind: "server.error", at: Date.now(), message });
|
|
3562
4575
|
if (audit) audit.error = message;
|
|
3563
|
-
writeJsonError(res, 502, message);
|
|
4576
|
+
writeJsonError(res, isAccountAllowanceExhaustedError(err) ? 429 : 502, message);
|
|
3564
4577
|
} finally {
|
|
3565
4578
|
routeMap.removeRoute(token);
|
|
3566
4579
|
if (releaseSerial) releaseSerial();
|
|
@@ -3584,16 +4597,6 @@ function makeReplayRequest(req, rawBody) {
|
|
|
3584
4597
|
var DEFAULT_OUTBOUND_PORT = 8765;
|
|
3585
4598
|
var LOOPBACK_ADDR = "127.0.0.1";
|
|
3586
4599
|
var LAN_ADDR = "0.0.0.0";
|
|
3587
|
-
var OutboundApiConfigError = class extends Error {
|
|
3588
|
-
missing;
|
|
3589
|
-
constructor(missing) {
|
|
3590
|
-
super(
|
|
3591
|
-
"Outbound API server cannot start: incomplete model-kind configuration \u2014 " + missing.map((m) => `${m.endpoint} missing [${m.missingKinds.join(", ")}]`).join("; ")
|
|
3592
|
-
);
|
|
3593
|
-
this.name = "OutboundApiConfigError";
|
|
3594
|
-
this.missing = missing;
|
|
3595
|
-
}
|
|
3596
|
-
};
|
|
3597
4600
|
var OutboundApiServer = class {
|
|
3598
4601
|
constructor(deps, onPortChange) {
|
|
3599
4602
|
this.deps = deps;
|
|
@@ -3605,6 +4608,7 @@ var OutboundApiServer = class {
|
|
|
3605
4608
|
boundPort = 0;
|
|
3606
4609
|
boundAddr = LOOPBACK_ADDR;
|
|
3607
4610
|
endpoints = [];
|
|
4611
|
+
bindings = [];
|
|
3608
4612
|
userMessageQueue;
|
|
3609
4613
|
concurrencyQueue;
|
|
3610
4614
|
voucherConfig;
|
|
@@ -3631,6 +4635,7 @@ var OutboundApiServer = class {
|
|
|
3631
4635
|
*/
|
|
3632
4636
|
async applyConfig(input) {
|
|
3633
4637
|
this.endpoints = input.endpoints;
|
|
4638
|
+
this.bindings = input.bindings ?? [];
|
|
3634
4639
|
this.userMessageQueue = input.userMessageQueue;
|
|
3635
4640
|
this.concurrencyQueue = input.concurrencyQueue;
|
|
3636
4641
|
this.voucherConfig = input.voucher;
|
|
@@ -3640,16 +4645,6 @@ var OutboundApiServer = class {
|
|
|
3640
4645
|
await this.stop();
|
|
3641
4646
|
return;
|
|
3642
4647
|
}
|
|
3643
|
-
const missing = validateServerModelConfig({
|
|
3644
|
-
enabled: true,
|
|
3645
|
-
networkBinding: input.networkBinding,
|
|
3646
|
-
endpoints: input.endpoints,
|
|
3647
|
-
port: input.port
|
|
3648
|
-
});
|
|
3649
|
-
if (missing.length > 0) {
|
|
3650
|
-
await this.stop();
|
|
3651
|
-
throw new OutboundApiConfigError(missing);
|
|
3652
|
-
}
|
|
3653
4648
|
const running = this.server !== null;
|
|
3654
4649
|
const bindChanged = running && (this.boundAddr !== wantAddr || this.boundPort !== wantPort);
|
|
3655
4650
|
if (running && !bindChanged) {
|
|
@@ -3706,6 +4701,7 @@ var OutboundApiServer = class {
|
|
|
3706
4701
|
this.deps,
|
|
3707
4702
|
{
|
|
3708
4703
|
endpoints: this.endpoints,
|
|
4704
|
+
bindings: this.bindings,
|
|
3709
4705
|
userMessageQueue: this.userMessageQueue,
|
|
3710
4706
|
concurrencyQueue: this.concurrencyQueue,
|
|
3711
4707
|
voucher: this.voucherConfig
|
|
@@ -3937,7 +4933,7 @@ async function callOpenAICompletion(provider, apiKey, options, logger) {
|
|
|
3937
4933
|
model: options.model,
|
|
3938
4934
|
messages: options.messages.map((m) => convertMessageToOpenAI(m)),
|
|
3939
4935
|
// Only set max_tokens if explicitly provided, otherwise let API use its default
|
|
3940
|
-
...options.maxTokens ? { max_tokens: options.maxTokens } : {},
|
|
4936
|
+
...options.maxTokens !== void 0 ? { max_tokens: options.maxTokens } : {},
|
|
3941
4937
|
temperature: options.temperature,
|
|
3942
4938
|
stream: false
|
|
3943
4939
|
// For now, non-streaming only
|
|
@@ -3988,7 +4984,7 @@ async function callAnthropicCompletion(provider, apiKey, options, logger) {
|
|
|
3988
4984
|
const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
|
|
3989
4985
|
const anthropicRequest2 = {
|
|
3990
4986
|
model: options.model,
|
|
3991
|
-
max_tokens: options.maxTokens
|
|
4987
|
+
max_tokens: options.maxTokens ?? 16384,
|
|
3992
4988
|
temperature: options.temperature,
|
|
3993
4989
|
...systemMessages.length > 0 ? { system: systemMessages.map((m) => m.content).join("\n\n") } : {},
|
|
3994
4990
|
messages: nonSystemMessages.map((m) => convertMessageToAnthropic(m)),
|
|
@@ -4034,7 +5030,7 @@ async function callAnthropicCompletion(provider, apiKey, options, logger) {
|
|
|
4034
5030
|
content: m.content
|
|
4035
5031
|
})),
|
|
4036
5032
|
// Anthropic requires max_tokens; use 16384 default if not explicitly set
|
|
4037
|
-
max_tokens: options.maxTokens
|
|
5033
|
+
max_tokens: options.maxTokens ?? 16384,
|
|
4038
5034
|
temperature: options.temperature,
|
|
4039
5035
|
stream: false
|
|
4040
5036
|
};
|
|
@@ -4089,7 +5085,7 @@ async function callGeminiCompletion(provider, apiKey, options, logger) {
|
|
|
4089
5085
|
const request = {
|
|
4090
5086
|
contents,
|
|
4091
5087
|
generationConfig: {
|
|
4092
|
-
...options.maxTokens ? { maxOutputTokens: options.maxTokens } : {},
|
|
5088
|
+
...options.maxTokens !== void 0 ? { maxOutputTokens: options.maxTokens } : {},
|
|
4093
5089
|
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
4094
5090
|
}
|
|
4095
5091
|
};
|
|
@@ -4158,7 +5154,7 @@ async function callOpenAIResponseCompletion(provider, apiKey, options, logger) {
|
|
|
4158
5154
|
model: options.model,
|
|
4159
5155
|
input,
|
|
4160
5156
|
stream: false,
|
|
4161
|
-
...options.maxTokens ? { max_output_tokens: options.maxTokens } : {},
|
|
5157
|
+
...options.maxTokens !== void 0 ? { max_output_tokens: options.maxTokens } : {},
|
|
4162
5158
|
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
4163
5159
|
};
|
|
4164
5160
|
if (options.thinkLevel && options.thinkLevel !== "none") {
|
|
@@ -4223,7 +5219,7 @@ async function streamOpenAICompletion(provider, apiKey, options, messageId, call
|
|
|
4223
5219
|
model: options.model,
|
|
4224
5220
|
messages: options.messages.map((m) => convertMessageToOpenAI(m)),
|
|
4225
5221
|
// Only set max_tokens if explicitly provided, otherwise let API use its default
|
|
4226
|
-
...options.maxTokens ? { max_tokens: options.maxTokens } : {},
|
|
5222
|
+
...options.maxTokens !== void 0 ? { max_tokens: options.maxTokens } : {},
|
|
4227
5223
|
temperature: options.temperature,
|
|
4228
5224
|
stream: true
|
|
4229
5225
|
};
|
|
@@ -4282,7 +5278,7 @@ async function streamOpenAICompletion(provider, apiKey, options, messageId, call
|
|
|
4282
5278
|
async function streamAnthropicCompletion(provider, apiKey, options, messageId, callbacks, logger) {
|
|
4283
5279
|
const hasImages = options.messages.some((m) => m.images && m.images.length > 0);
|
|
4284
5280
|
const MAX_TOKENS_FOR_THINKING = 16384;
|
|
4285
|
-
let effectiveMaxTokens = options.maxTokens
|
|
5281
|
+
let effectiveMaxTokens = options.maxTokens ?? 16384;
|
|
4286
5282
|
const thinkingMaxTokens = options.thinkLevel && options.thinkLevel !== "none" ? Math.min(effectiveMaxTokens, MAX_TOKENS_FOR_THINKING) : effectiveMaxTokens;
|
|
4287
5283
|
const thinkingConfig = options.thinkLevel && options.thinkLevel !== "none" ? buildAnthropicThinking(options.model, options.thinkLevel, thinkingMaxTokens) : void 0;
|
|
4288
5284
|
if (thinkingConfig) {
|
|
@@ -4394,7 +5390,7 @@ async function streamGeminiCompletion(provider, apiKey, options, messageId, call
|
|
|
4394
5390
|
const request = {
|
|
4395
5391
|
contents,
|
|
4396
5392
|
generationConfig: {
|
|
4397
|
-
...options.maxTokens ? { maxOutputTokens: options.maxTokens } : {},
|
|
5393
|
+
...options.maxTokens !== void 0 ? { maxOutputTokens: options.maxTokens } : {},
|
|
4398
5394
|
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
4399
5395
|
}
|
|
4400
5396
|
};
|
|
@@ -4458,7 +5454,7 @@ async function streamOpenAIResponseCompletion(provider, apiKey, options, message
|
|
|
4458
5454
|
model: options.model,
|
|
4459
5455
|
input,
|
|
4460
5456
|
stream: true,
|
|
4461
|
-
...options.maxTokens ? { max_output_tokens: options.maxTokens } : {},
|
|
5457
|
+
...options.maxTokens !== void 0 ? { max_output_tokens: options.maxTokens } : {},
|
|
4462
5458
|
...options.temperature !== void 0 ? { temperature: options.temperature } : {}
|
|
4463
5459
|
};
|
|
4464
5460
|
if (options.thinkLevel && options.thinkLevel !== "none") {
|
|
@@ -4515,7 +5511,7 @@ import {
|
|
|
4515
5511
|
getClaudeMaxTokens,
|
|
4516
5512
|
isReasoningModel
|
|
4517
5513
|
} from "@omnicross/contracts/thinking-config";
|
|
4518
|
-
async function resolveEffectiveMaxTokens(llmConfig,
|
|
5514
|
+
async function resolveEffectiveMaxTokens(llmConfig, _getProvider, logger, _providerId, _modelId, sessionMaxTokens) {
|
|
4519
5515
|
if (sessionMaxTokens !== void 0 && sessionMaxTokens > 0) {
|
|
4520
5516
|
logger.debug("Using session maxTokens", { sessionMaxTokens });
|
|
4521
5517
|
return sessionMaxTokens;
|
|
@@ -4529,45 +5525,6 @@ async function resolveEffectiveMaxTokens(llmConfig, getProvider, logger, provide
|
|
|
4529
5525
|
} catch (err) {
|
|
4530
5526
|
logger.warn("Failed to get global params", err instanceof Error ? err : void 0);
|
|
4531
5527
|
}
|
|
4532
|
-
const MAX_TOKENS_CAP = 131072;
|
|
4533
|
-
const provider = await getProvider(providerId);
|
|
4534
|
-
if (provider) {
|
|
4535
|
-
const modelConfig = provider.modelConfigs?.find((m) => m.id === modelId);
|
|
4536
|
-
if (modelConfig?.maxTokens && modelConfig.maxTokens > 0) {
|
|
4537
|
-
const cappedMaxTokens = Math.min(modelConfig.maxTokens, MAX_TOKENS_CAP);
|
|
4538
|
-
logger.debug("Using model config maxTokens", {
|
|
4539
|
-
maxTokens: modelConfig.maxTokens,
|
|
4540
|
-
cappedMaxTokens
|
|
4541
|
-
});
|
|
4542
|
-
return cappedMaxTokens;
|
|
4543
|
-
}
|
|
4544
|
-
if (provider.modelGroups) {
|
|
4545
|
-
for (const group of provider.modelGroups) {
|
|
4546
|
-
const model = group.models?.find((m) => m.id === modelId);
|
|
4547
|
-
if (model?.maxTokens && model.maxTokens > 0) {
|
|
4548
|
-
const cappedMaxTokens = Math.min(model.maxTokens, MAX_TOKENS_CAP);
|
|
4549
|
-
logger.debug("Using modelGroup model maxTokens", {
|
|
4550
|
-
maxTokens: model.maxTokens,
|
|
4551
|
-
cappedMaxTokens
|
|
4552
|
-
});
|
|
4553
|
-
return cappedMaxTokens;
|
|
4554
|
-
}
|
|
4555
|
-
}
|
|
4556
|
-
}
|
|
4557
|
-
}
|
|
4558
|
-
try {
|
|
4559
|
-
const discoveredMaxTokens = await llmConfig.getDiscoveredModelMaxTokens(providerId, modelId);
|
|
4560
|
-
if (discoveredMaxTokens && discoveredMaxTokens > 0) {
|
|
4561
|
-
const cappedMaxTokens = Math.min(discoveredMaxTokens, MAX_TOKENS_CAP);
|
|
4562
|
-
logger.debug("Using discovered model maxTokens", {
|
|
4563
|
-
discoveredMaxTokens,
|
|
4564
|
-
cappedMaxTokens
|
|
4565
|
-
});
|
|
4566
|
-
return cappedMaxTokens;
|
|
4567
|
-
}
|
|
4568
|
-
} catch (err) {
|
|
4569
|
-
logger.warn("Failed to get discovered model maxTokens", err instanceof Error ? err : void 0);
|
|
4570
|
-
}
|
|
4571
5528
|
logger.debug("No maxTokens configured, returning undefined");
|
|
4572
5529
|
return void 0;
|
|
4573
5530
|
}
|
|
@@ -4670,7 +5627,8 @@ function buildToolRequest(apiFormat, conversationMessages, actualModel, options,
|
|
|
4670
5627
|
}
|
|
4671
5628
|
requestBody = {
|
|
4672
5629
|
contents,
|
|
4673
|
-
tools: options.tools
|
|
5630
|
+
tools: options.tools,
|
|
5631
|
+
...options.maxTokens !== void 0 ? { generationConfig: { maxOutputTokens: options.maxTokens } } : {}
|
|
4674
5632
|
};
|
|
4675
5633
|
url = buildProviderApiUrl(provider, { model: actualModel, stream: true });
|
|
4676
5634
|
} else if (apiFormat === "anthropic") {
|
|
@@ -4680,7 +5638,10 @@ function buildToolRequest(apiFormat, conversationMessages, actualModel, options,
|
|
|
4680
5638
|
role: m.role,
|
|
4681
5639
|
content: m.content
|
|
4682
5640
|
})),
|
|
4683
|
-
|
|
5641
|
+
// Native Anthropic Messages requires this field. Resolve an absent
|
|
5642
|
+
// caller cap to the model's real ceiling rather than a small constant —
|
|
5643
|
+
// see `resolveAnthropicMaxTokens`.
|
|
5644
|
+
max_tokens: resolveAnthropicMaxTokens(actualModel, options.maxTokens),
|
|
4684
5645
|
temperature: options.temperature ?? 0.7,
|
|
4685
5646
|
stream: true,
|
|
4686
5647
|
tools: options.tools
|
|
@@ -4697,7 +5658,7 @@ function buildToolRequest(apiFormat, conversationMessages, actualModel, options,
|
|
|
4697
5658
|
role: m.role,
|
|
4698
5659
|
content: m.content
|
|
4699
5660
|
})),
|
|
4700
|
-
max_tokens: options.maxTokens
|
|
5661
|
+
...options.maxTokens !== void 0 ? { max_tokens: options.maxTokens } : {},
|
|
4701
5662
|
temperature: options.temperature ?? 0.7,
|
|
4702
5663
|
stream: true,
|
|
4703
5664
|
tools: options.tools
|
|
@@ -5194,6 +6155,18 @@ function getSharedExecutor2() {
|
|
|
5194
6155
|
async function resolveChainWithMain(llmConfig, providerId, model) {
|
|
5195
6156
|
return resolveProviderChain(llmConfig, providerId, model);
|
|
5196
6157
|
}
|
|
6158
|
+
function buildUnifiedCompletionRequest(actualModel, options, stream) {
|
|
6159
|
+
return {
|
|
6160
|
+
model: actualModel,
|
|
6161
|
+
messages: options.messages.map((m) => ({
|
|
6162
|
+
role: m.role,
|
|
6163
|
+
content: m.content
|
|
6164
|
+
})),
|
|
6165
|
+
...options.maxTokens !== void 0 ? { max_tokens: options.maxTokens } : {},
|
|
6166
|
+
temperature: options.temperature,
|
|
6167
|
+
stream
|
|
6168
|
+
};
|
|
6169
|
+
}
|
|
5197
6170
|
async function completeWithTransformers(options, llmConfig, getProvider, resolveApiKey2, completeFallback, logger, recording) {
|
|
5198
6171
|
try {
|
|
5199
6172
|
const routedInfo = await llmConfig.resolveRoutedModel(
|
|
@@ -5222,16 +6195,11 @@ async function completeWithTransformers(options, llmConfig, getProvider, resolve
|
|
|
5222
6195
|
if (!hasTransformers) {
|
|
5223
6196
|
return completeFallback(options);
|
|
5224
6197
|
}
|
|
5225
|
-
const unifiedRequest =
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
})),
|
|
5231
|
-
max_tokens: options.maxTokens || 4096,
|
|
5232
|
-
temperature: options.temperature,
|
|
5233
|
-
stream: options.stream ?? false
|
|
5234
|
-
};
|
|
6198
|
+
const unifiedRequest = buildUnifiedCompletionRequest(
|
|
6199
|
+
actualModel,
|
|
6200
|
+
options,
|
|
6201
|
+
options.stream ?? false
|
|
6202
|
+
);
|
|
5235
6203
|
const transformerProvider = {
|
|
5236
6204
|
name: provider.name,
|
|
5237
6205
|
baseUrl: provider.api_base_url,
|
|
@@ -5361,16 +6329,7 @@ async function completeStreamWithTransformers(options, callbacks, llmConfig, get
|
|
|
5361
6329
|
}
|
|
5362
6330
|
const messageId = `msg_${Date.now()}`;
|
|
5363
6331
|
callbacks.onStart?.(messageId);
|
|
5364
|
-
const unifiedRequest =
|
|
5365
|
-
model: actualModel,
|
|
5366
|
-
messages: options.messages.map((m) => ({
|
|
5367
|
-
role: m.role,
|
|
5368
|
-
content: m.content
|
|
5369
|
-
})),
|
|
5370
|
-
max_tokens: options.maxTokens || 4096,
|
|
5371
|
-
temperature: options.temperature,
|
|
5372
|
-
stream: true
|
|
5373
|
-
};
|
|
6332
|
+
const unifiedRequest = buildUnifiedCompletionRequest(actualModel, options, true);
|
|
5374
6333
|
const transformerProvider = {
|
|
5375
6334
|
name: provider.name,
|
|
5376
6335
|
baseUrl: provider.api_base_url,
|
|
@@ -5905,9 +6864,10 @@ ${description}`;
|
|
|
5905
6864
|
* Resolve effective max_tokens value with priority:
|
|
5906
6865
|
* 1. Session settings (if provided)
|
|
5907
6866
|
* 2. Global model parameters (if enabled)
|
|
5908
|
-
* 3.
|
|
5909
|
-
*
|
|
5910
|
-
*
|
|
6867
|
+
* 3. undefined - let API use its default
|
|
6868
|
+
*
|
|
6869
|
+
* Provider and discovery maxTokens values are capability metadata, not
|
|
6870
|
+
* implicit per-request output limits.
|
|
5911
6871
|
*/
|
|
5912
6872
|
async resolveEffectiveMaxTokens(providerId, modelId, sessionMaxTokens) {
|
|
5913
6873
|
return resolveEffectiveMaxTokens(
|
|
@@ -6020,6 +6980,7 @@ export {
|
|
|
6020
6980
|
relayResponse,
|
|
6021
6981
|
aggregateAnthropicSseToJsonBody,
|
|
6022
6982
|
writeError,
|
|
6983
|
+
writeBoundAccountError,
|
|
6023
6984
|
resolvePoolBoundKey,
|
|
6024
6985
|
extractRouteToken,
|
|
6025
6986
|
isLoopbackAddress,
|
|
@@ -6031,6 +6992,8 @@ export {
|
|
|
6031
6992
|
DEFAULT_USER_MESSAGE_QUEUE,
|
|
6032
6993
|
DEFAULT_CONCURRENCY_QUEUE,
|
|
6033
6994
|
DEFAULT_ACCOUNT_PROBE,
|
|
6995
|
+
DEFAULT_ALLOWANCE_SCHEDULING,
|
|
6996
|
+
normalizeAllowanceScheduling,
|
|
6034
6997
|
normalizeAccountProbe,
|
|
6035
6998
|
normalizeAudit,
|
|
6036
6999
|
normalizeBilling,
|
|
@@ -6043,16 +7006,23 @@ export {
|
|
|
6043
7006
|
normalizeWebhookSegment,
|
|
6044
7007
|
normalizeQueueSegments,
|
|
6045
7008
|
normalizePrefixTargets,
|
|
7009
|
+
legacyEndpointsToBindings,
|
|
7010
|
+
normalizeGatewayBindings,
|
|
6046
7011
|
defaultServerConfig,
|
|
6047
7012
|
normalizeServerConfig,
|
|
6048
7013
|
loadServerConfig,
|
|
6049
7014
|
saveServerConfig,
|
|
6050
7015
|
mergeServerConfig,
|
|
7016
|
+
gatewayBindingToEndpointConfig,
|
|
7017
|
+
candidateGatewayBindings,
|
|
7018
|
+
candidateBackgroundModelIds,
|
|
7019
|
+
resolveGatewayBinding,
|
|
6051
7020
|
computeKeyExpiry,
|
|
6052
7021
|
checkKeyQuota,
|
|
6053
7022
|
randomBase62,
|
|
6054
7023
|
hashKey,
|
|
6055
7024
|
createNamedKey,
|
|
7025
|
+
createIntegrationKey,
|
|
6056
7026
|
verifyKey,
|
|
6057
7027
|
verifyPresentedKey,
|
|
6058
7028
|
ConcurrencyQueueFullError,
|
|
@@ -6074,7 +7044,6 @@ export {
|
|
|
6074
7044
|
isRedeemRequest,
|
|
6075
7045
|
handleVoucherRedeem,
|
|
6076
7046
|
DEFAULT_OUTBOUND_PORT,
|
|
6077
|
-
OutboundApiConfigError,
|
|
6078
7047
|
OutboundApiServer,
|
|
6079
7048
|
formatUrls,
|
|
6080
7049
|
startOfLocalDay,
|