@omnicross/core 0.1.6 → 0.1.8
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/{ProviderProxy-C-xqrkKi.d.ts → ProviderProxy--ROec6VI.d.ts} +22 -3
- package/dist/{ProviderProxy-CnMQYN59.d.cts → ProviderProxy-QdwNV6l1.d.cts} +22 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +4 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.js +3 -2
- package/dist/{chunk-3Z5BQZFM.cjs → chunk-235MXAGX.cjs} +3 -1
- package/dist/{chunk-SQYVORNP.cjs → chunk-26K6T2YS.cjs} +1520 -523
- package/dist/{chunk-DLXZEZWO.cjs → chunk-2E42SAW3.cjs} +42 -4
- package/dist/{chunk-7AK7B36B.cjs → chunk-2Z4XM3H5.cjs} +6 -6
- package/dist/{chunk-OEWZUX4G.js → chunk-32O35CIF.js} +9 -20
- package/dist/chunk-3SCOLIPT.cjs +63 -0
- package/dist/{chunk-SYQF7OIR.js → chunk-3WD4HTR2.js} +2 -0
- package/dist/{chunk-AT7B7SOZ.js → chunk-5NJLQ6T6.js} +21 -10
- package/dist/chunk-5RSZYQJH.cjs +62 -0
- package/dist/{chunk-IFXJ2BG7.js → chunk-7C7DET6S.js} +51 -6
- package/dist/{chunk-SNYBEXLB.cjs → chunk-EPN67EGP.cjs} +6 -1
- package/dist/chunk-FU7EAR73.js +62 -0
- package/dist/{chunk-UX7VWA46.js → chunk-FXR6N3SR.js} +39 -1
- package/dist/{chunk-WYTEQ4Q5.cjs → chunk-GDIXXKF2.cjs} +21 -10
- package/dist/{chunk-WB6PH56J.js → chunk-GFIZ6LQI.js} +12 -8
- package/dist/chunk-GL6XM527.js +61 -0
- package/dist/{chunk-EJLH6NFT.cjs → chunk-J3QQ6JS5.cjs} +12 -8
- package/dist/{chunk-VRFBB5DB.js → chunk-MYYR3HZL.js} +4 -4
- package/dist/{chunk-YJVHCHQX.js → chunk-N7OOJI5V.js} +46 -2
- package/dist/chunk-NKVZ6Z3Y.cjs +61 -0
- package/dist/chunk-OTZC4TNH.cjs +79 -0
- package/dist/{chunk-6VUQPOV3.cjs → chunk-OVQBNVSV.cjs} +10 -21
- package/dist/{chunk-4VLRHHFE.cjs → chunk-OXHNSZ53.cjs} +9 -11
- package/dist/{chunk-6CL5HTPQ.js → chunk-PEGTIPB3.js} +8 -10
- package/dist/chunk-PO3XB56Z.js +79 -0
- package/dist/{chunk-UDCC7EMR.js → chunk-QYC54PMG.js} +1242 -245
- package/dist/{chunk-SIZQQD6T.js → chunk-RJ5ZGNI3.js} +42 -5
- package/dist/{chunk-KCVJWJST.cjs → chunk-RJUONLUQ.cjs} +47 -10
- package/dist/{chunk-BPKCU575.js → chunk-UAPBLNN2.js} +6 -1
- package/dist/{chunk-UNWKG6EI.cjs → chunk-XPIOJQS2.cjs} +51 -6
- package/dist/chunk-XXFYLL7R.js +63 -0
- package/dist/{chunk-YK57NMIF.cjs → chunk-YWL7YZWS.cjs} +47 -3
- package/dist/completion/ApiKeyPoolService.cjs +32 -1
- package/dist/completion/ApiKeyPoolService.d.cts +17 -1
- package/dist/completion/ApiKeyPoolService.d.ts +17 -1
- package/dist/completion/ApiKeyPoolService.js +31 -0
- package/dist/completion/CompletionService.cjs +23 -19
- package/dist/completion/CompletionService.d.cts +1 -0
- package/dist/completion/CompletionService.d.ts +1 -0
- package/dist/completion/CompletionService.js +22 -18
- package/dist/completion.cjs +23 -19
- package/dist/completion.d.cts +1 -0
- package/dist/completion.d.ts +1 -0
- package/dist/completion.js +22 -18
- package/dist/index.cjs +81 -21
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +82 -22
- 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/routeResolver.d.cts +4 -3
- package/dist/outbound-api/routeResolver.d.ts +4 -3
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +1 -0
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +1 -0
- package/dist/outbound-api/types.d.cts +3 -2
- package/dist/outbound-api/types.d.ts +3 -2
- package/dist/outbound-api.cjs +23 -19
- package/dist/outbound-api.d.cts +8 -6
- package/dist/outbound-api.d.ts +8 -6
- package/dist/outbound-api.js +22 -18
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +3 -2
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +3 -2
- 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/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 +4 -2
- package/dist/pipeline/SubscriptionAccountHealth.d.cts +32 -3
- package/dist/pipeline/SubscriptionAccountHealth.d.ts +32 -3
- package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
- package/dist/pipeline/upstreamFetch.cjs +4 -3
- package/dist/pipeline/upstreamFetch.d.cts +35 -6
- package/dist/pipeline/upstreamFetch.d.ts +35 -6
- package/dist/pipeline/upstreamFetch.js +3 -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.d.cts +2 -2
- package/dist/ports.d.ts +2 -2
- package/dist/provider-proxy/ProviderProxy.cjs +23 -19
- package/dist/provider-proxy/ProviderProxy.d.cts +2 -1
- package/dist/provider-proxy/ProviderProxy.d.ts +2 -1
- package/dist/provider-proxy/ProviderProxy.js +22 -18
- 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 +23 -19
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +11 -1
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +11 -1
- package/dist/provider-proxy/ingress/providerProxyShared.js +22 -18
- package/dist/provider-proxy/matchText.cjs +2 -2
- package/dist/provider-proxy/matchText.d.cts +1 -3
- package/dist/provider-proxy/matchText.d.ts +1 -3
- package/dist/provider-proxy/matchText.js +1 -1
- package/dist/provider-proxy/types.d.cts +20 -1
- package/dist/provider-proxy/types.d.ts +20 -1
- package/dist/provider-proxy.cjs +79 -19
- package/dist/provider-proxy.d.cts +255 -8
- package/dist/provider-proxy.d.ts +255 -8
- package/dist/provider-proxy.js +80 -20
- package/dist/{routeResolver-CHTv_Bs5.d.ts → routeResolver-C0mkkZaM.d.ts} +1 -1
- package/dist/{routeResolver-kepxnfcC.d.cts → routeResolver-CVFE8KKe.d.cts} +1 -1
- package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -2
- package/dist/transformer/transformers/AnthropicTransformer.js +2 -1
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
- package/dist/transformer/transformers.cjs +7 -6
- package/dist/transformer/transformers.js +6 -5
- package/dist/transformer.cjs +9 -8
- package/dist/transformer.js +8 -7
- package/dist/{types-hsoKrVQS.d.ts → types-BB8pxk_3.d.ts} +40 -1
- package/dist/{types-H3wGZSOo.d.cts → types-C2MwkLah.d.cts} +40 -1
- package/dist/usage/pricing-engine.cjs +2 -2
- package/dist/usage/pricing-engine.d.cts +20 -0
- package/dist/usage/pricing-engine.d.ts +20 -0
- package/dist/usage/pricing-engine.js +1 -1
- package/dist/usage/usage-recorder.cjs +2 -2
- package/dist/usage/usage-recorder.d.cts +11 -1
- package/dist/usage/usage-recorder.d.ts +11 -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
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
collectMatchText,
|
|
3
|
-
deriveGatewaySessionKey,
|
|
4
|
-
deriveSubscriptionSessionKey
|
|
5
|
-
} from "./chunk-WB6PH56J.js";
|
|
6
1
|
import {
|
|
7
2
|
streamSSEResponse
|
|
8
3
|
} from "./chunk-VVEHS2LI.js";
|
|
9
4
|
import {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
injectExtendedContextBeta
|
|
14
|
-
} from "./chunk-MZNPGW5Q.js";
|
|
5
|
+
AnthropicTransformer,
|
|
6
|
+
resolveAnthropicMaxTokens
|
|
7
|
+
} from "./chunk-PEGTIPB3.js";
|
|
15
8
|
import {
|
|
16
9
|
GeminiTransformer
|
|
17
10
|
} from "./chunk-LVUCAWMK.js";
|
|
18
11
|
import {
|
|
19
12
|
OpenAIResponseTransformer
|
|
20
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-32O35CIF.js";
|
|
21
14
|
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} from "./chunk-6CL5HTPQ.js";
|
|
15
|
+
TransformerChainExecutor
|
|
16
|
+
} from "./chunk-WNKWAEUR.js";
|
|
25
17
|
import {
|
|
26
|
-
|
|
27
|
-
} from "./chunk-
|
|
18
|
+
injectExtendedContextBeta
|
|
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";
|
|
28
28
|
import {
|
|
29
29
|
getSharedIdentityStore
|
|
30
30
|
} from "./chunk-XTEI64OU.js";
|
|
@@ -32,22 +32,36 @@ import {
|
|
|
32
32
|
applyFingerprint,
|
|
33
33
|
captureCallerIdentity
|
|
34
34
|
} from "./chunk-XX6NQJMA.js";
|
|
35
|
+
import {
|
|
36
|
+
collectMatchText,
|
|
37
|
+
deriveGatewaySessionKey,
|
|
38
|
+
deriveSubscriptionSessionKey
|
|
39
|
+
} from "./chunk-GFIZ6LQI.js";
|
|
40
|
+
import {
|
|
41
|
+
emitWebhookEvent
|
|
42
|
+
} from "./chunk-OE6TDIWW.js";
|
|
43
|
+
import {
|
|
44
|
+
getGeminiCodeAssistResolver
|
|
45
|
+
} from "./chunk-DRWHTIIZ.js";
|
|
46
|
+
import {
|
|
47
|
+
resolveProviderChain
|
|
48
|
+
} from "./chunk-JWC6APFQ.js";
|
|
35
49
|
import {
|
|
36
50
|
resolveSubscriptionChain
|
|
37
51
|
} from "./chunk-5ERIZW3D.js";
|
|
52
|
+
import {
|
|
53
|
+
getSharedOverloadCounter
|
|
54
|
+
} from "./chunk-PO3XB56Z.js";
|
|
38
55
|
import {
|
|
39
56
|
getSharedAccountHealth,
|
|
40
57
|
resolveResetSeconds
|
|
41
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-N7OOJI5V.js";
|
|
42
59
|
import {
|
|
43
60
|
SubscriptionAuthSource
|
|
44
61
|
} from "./chunk-23S5OPIO.js";
|
|
45
62
|
import {
|
|
46
63
|
serializeError
|
|
47
64
|
} from "./chunk-AVYJ4YBN.js";
|
|
48
|
-
import {
|
|
49
|
-
emitWebhookEvent
|
|
50
|
-
} from "./chunk-OE6TDIWW.js";
|
|
51
65
|
import {
|
|
52
66
|
isAccountAllowanceExhaustedError
|
|
53
67
|
} from "./chunk-OPRS4IQZ.js";
|
|
@@ -62,9 +76,6 @@ import {
|
|
|
62
76
|
LlmConfigProviderAuth,
|
|
63
77
|
getProviderHeaders
|
|
64
78
|
} from "./chunk-ZY6KU6P3.js";
|
|
65
|
-
import {
|
|
66
|
-
resolveProviderChain
|
|
67
|
-
} from "./chunk-JWC6APFQ.js";
|
|
68
79
|
import {
|
|
69
80
|
beginBillingCapture
|
|
70
81
|
} from "./chunk-WLYIIEAF.js";
|
|
@@ -83,6 +94,9 @@ import {
|
|
|
83
94
|
resolvePrefixTarget,
|
|
84
95
|
resolveRoute
|
|
85
96
|
} from "./chunk-EBLFCNPY.js";
|
|
97
|
+
import {
|
|
98
|
+
getSubscriptionRegistryForOutbound
|
|
99
|
+
} from "./chunk-AQ5TNQB7.js";
|
|
86
100
|
import {
|
|
87
101
|
buildProviderApiUrl,
|
|
88
102
|
resolveApiFormat,
|
|
@@ -90,14 +104,20 @@ import {
|
|
|
90
104
|
} from "./chunk-H5JUT3KV.js";
|
|
91
105
|
import {
|
|
92
106
|
beginAuditCapture
|
|
93
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-7C7DET6S.js";
|
|
94
108
|
import {
|
|
95
109
|
convertAnthropicToOpenAI,
|
|
96
110
|
convertOpenAIToAnthropic
|
|
97
|
-
} from "./chunk-
|
|
111
|
+
} from "./chunk-5NJLQ6T6.js";
|
|
98
112
|
import {
|
|
99
113
|
fetchUpstream
|
|
100
|
-
} from "./chunk-
|
|
114
|
+
} from "./chunk-RJ5ZGNI3.js";
|
|
115
|
+
import {
|
|
116
|
+
getSharedAccountAllowanceStore
|
|
117
|
+
} from "./chunk-MKF46GNZ.js";
|
|
118
|
+
import {
|
|
119
|
+
getSharedAccountRouteActivity
|
|
120
|
+
} from "./chunk-GL6XM527.js";
|
|
101
121
|
import {
|
|
102
122
|
applyAugmentation
|
|
103
123
|
} from "./chunk-Y7FO65VT.js";
|
|
@@ -415,12 +435,16 @@ var ProviderProxyRouteMap = class {
|
|
|
415
435
|
* (`ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY`). Optionally override the idle
|
|
416
436
|
* timeout (tests use a short one).
|
|
417
437
|
*/
|
|
418
|
-
addRoute(context,
|
|
438
|
+
addRoute(context, idleMsOrOptions) {
|
|
439
|
+
const options = typeof idleMsOrOptions === "number" ? { idleMs: idleMsOrOptions } : idleMsOrOptions ?? {};
|
|
419
440
|
const token = randomBytes(TOKEN_BYTES).toString("hex");
|
|
420
441
|
const entry = {
|
|
421
442
|
context,
|
|
422
443
|
idleTimer: null,
|
|
423
|
-
idleMs: idleMs ?? this.defaultIdleMs
|
|
444
|
+
idleMs: options.idleMs ?? this.defaultIdleMs,
|
|
445
|
+
onActivity: options.onActivity,
|
|
446
|
+
onEvicted: options.onEvicted,
|
|
447
|
+
evicted: false
|
|
424
448
|
};
|
|
425
449
|
this.routes.set(token, entry);
|
|
426
450
|
this.armIdleTimer(token, entry);
|
|
@@ -435,6 +459,10 @@ var ProviderProxyRouteMap = class {
|
|
|
435
459
|
if (!token) return void 0;
|
|
436
460
|
const entry = this.routes.get(token);
|
|
437
461
|
if (!entry) return void 0;
|
|
462
|
+
try {
|
|
463
|
+
entry.onActivity?.(Date.now());
|
|
464
|
+
} catch {
|
|
465
|
+
}
|
|
438
466
|
this.armIdleTimer(token, entry);
|
|
439
467
|
return entry.context;
|
|
440
468
|
}
|
|
@@ -442,8 +470,16 @@ var ProviderProxyRouteMap = class {
|
|
|
442
470
|
removeRoute(token) {
|
|
443
471
|
const entry = this.routes.get(token);
|
|
444
472
|
if (!entry) return false;
|
|
445
|
-
this.clearIdleTimer(entry);
|
|
446
473
|
this.routes.delete(token);
|
|
474
|
+
this.evict(entry, "removed");
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
/** Re-arm one existing route without rotating its token. */
|
|
478
|
+
renewRoute(token, idleMs) {
|
|
479
|
+
const entry = this.routes.get(token);
|
|
480
|
+
if (!entry) return false;
|
|
481
|
+
if (idleMs !== void 0) entry.idleMs = idleMs;
|
|
482
|
+
this.armIdleTimer(token, entry);
|
|
447
483
|
return true;
|
|
448
484
|
}
|
|
449
485
|
/** Current live-route count (tests / diagnostics). */
|
|
@@ -456,10 +492,11 @@ var ProviderProxyRouteMap = class {
|
|
|
456
492
|
}
|
|
457
493
|
/** Tear down every route (proxy stop / app teardown). */
|
|
458
494
|
clear() {
|
|
459
|
-
|
|
460
|
-
this.clearIdleTimer(entry);
|
|
461
|
-
}
|
|
495
|
+
const entries2 = [...this.routes.values()];
|
|
462
496
|
this.routes.clear();
|
|
497
|
+
for (const entry of entries2) {
|
|
498
|
+
this.evict(entry, "cleared");
|
|
499
|
+
}
|
|
463
500
|
}
|
|
464
501
|
// ---------------------------------------------------------------------------
|
|
465
502
|
// Idle reaping (modeled on AcpSessionManager.armIdleTimer / clearIdleTimer)
|
|
@@ -467,7 +504,8 @@ var ProviderProxyRouteMap = class {
|
|
|
467
504
|
armIdleTimer(token, entry) {
|
|
468
505
|
this.clearIdleTimer(entry);
|
|
469
506
|
entry.idleTimer = setTimeout(() => {
|
|
470
|
-
this.routes.delete(token);
|
|
507
|
+
if (!this.routes.delete(token)) return;
|
|
508
|
+
this.evict(entry, "idle");
|
|
471
509
|
}, entry.idleMs);
|
|
472
510
|
entry.idleTimer.unref?.();
|
|
473
511
|
}
|
|
@@ -477,8 +515,77 @@ var ProviderProxyRouteMap = class {
|
|
|
477
515
|
entry.idleTimer = null;
|
|
478
516
|
}
|
|
479
517
|
}
|
|
518
|
+
evict(entry, reason) {
|
|
519
|
+
if (entry.evicted) return;
|
|
520
|
+
entry.evicted = true;
|
|
521
|
+
this.clearIdleTimer(entry);
|
|
522
|
+
try {
|
|
523
|
+
entry.onEvicted?.(reason);
|
|
524
|
+
} catch {
|
|
525
|
+
}
|
|
526
|
+
}
|
|
480
527
|
};
|
|
481
528
|
|
|
529
|
+
// src/provider-proxy/identity/claudeCodeHeaders.ts
|
|
530
|
+
var DEFAULT_ANTHROPIC_VERSION = "2023-06-01";
|
|
531
|
+
var OAUTH_BETA = "oauth-2025-04-20";
|
|
532
|
+
var CLAUDE_CODE_BETA = "claude-code-20250219";
|
|
533
|
+
var INTERLEAVED_THINKING_BETA = "interleaved-thinking-2025-05-14";
|
|
534
|
+
var TOOL_STREAMING_BETA = "fine-grained-tool-streaming-2025-05-14";
|
|
535
|
+
var DEFAULT_CLAUDE_CODE_HEADERS = Object.freeze({
|
|
536
|
+
"user-agent": "claude-cli/1.0.119 (external, cli)",
|
|
537
|
+
"x-app": "cli",
|
|
538
|
+
accept: "application/json",
|
|
539
|
+
"accept-language": "*",
|
|
540
|
+
"sec-fetch-mode": "cors",
|
|
541
|
+
"anthropic-dangerous-direct-browser-access": "true",
|
|
542
|
+
"x-stainless-lang": "js",
|
|
543
|
+
"x-stainless-package-version": "0.55.1",
|
|
544
|
+
"x-stainless-os": "Windows",
|
|
545
|
+
"x-stainless-arch": "x64",
|
|
546
|
+
"x-stainless-runtime": "node",
|
|
547
|
+
"x-stainless-runtime-version": "v20.19.2",
|
|
548
|
+
"x-stainless-retry-count": "0",
|
|
549
|
+
"x-stainless-timeout": "60"
|
|
550
|
+
});
|
|
551
|
+
var FORCED_ACCEPT_ENCODING = "identity";
|
|
552
|
+
var CLAUDE_FORWARD_ALLOWLIST = /* @__PURE__ */ new Set([
|
|
553
|
+
"accept",
|
|
554
|
+
"accept-language",
|
|
555
|
+
"sec-fetch-mode",
|
|
556
|
+
"user-agent",
|
|
557
|
+
"x-app",
|
|
558
|
+
"anthropic-dangerous-direct-browser-access",
|
|
559
|
+
"anthropic-version"
|
|
560
|
+
]);
|
|
561
|
+
var STAINLESS_PREFIX = "x-stainless-";
|
|
562
|
+
function extractClaudeClientHeaders(reqHeaders) {
|
|
563
|
+
const out = {};
|
|
564
|
+
for (const [rawKey, rawVal] of Object.entries(reqHeaders)) {
|
|
565
|
+
const key = rawKey.toLowerCase();
|
|
566
|
+
if (NEVER_FORWARD_HEADERS.has(key)) continue;
|
|
567
|
+
if (!CLAUDE_FORWARD_ALLOWLIST.has(key) && !key.startsWith(STAINLESS_PREFIX)) continue;
|
|
568
|
+
const value = flattenHeaderValue(rawVal);
|
|
569
|
+
if (value !== void 0) out[key] = value;
|
|
570
|
+
}
|
|
571
|
+
return out;
|
|
572
|
+
}
|
|
573
|
+
function buildAnthropicBeta(model, callerBeta) {
|
|
574
|
+
const isHaiku = typeof model === "string" && model.toLowerCase().includes("haiku");
|
|
575
|
+
const baseline = isHaiku ? [OAUTH_BETA, INTERLEAVED_THINKING_BETA] : [CLAUDE_CODE_BETA, OAUTH_BETA, INTERLEAVED_THINKING_BETA, TOOL_STREAMING_BETA];
|
|
576
|
+
const seen = /* @__PURE__ */ new Set();
|
|
577
|
+
const out = [];
|
|
578
|
+
const add = (flag) => {
|
|
579
|
+
const trimmed = flag.trim();
|
|
580
|
+
if (!trimmed || seen.has(trimmed)) return;
|
|
581
|
+
seen.add(trimmed);
|
|
582
|
+
out.push(trimmed);
|
|
583
|
+
};
|
|
584
|
+
baseline.forEach(add);
|
|
585
|
+
if (callerBeta) callerBeta.split(",").forEach(add);
|
|
586
|
+
return out.join(",");
|
|
587
|
+
}
|
|
588
|
+
|
|
482
589
|
// src/provider-proxy/usage/recordAnthropicUsage.ts
|
|
483
590
|
function readAnthropicUsage(usage) {
|
|
484
591
|
if (!usage) return null;
|
|
@@ -506,6 +613,10 @@ function emitAnthropicUsageRecord(recorder, rawUsage, attribution) {
|
|
|
506
613
|
providerId: attribution.providerId,
|
|
507
614
|
model: attribution.model,
|
|
508
615
|
apiKeyId: attribution.apiKeyId,
|
|
616
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
617
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
618
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
619
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
509
620
|
auditResponse: attribution.auditResponse,
|
|
510
621
|
engineOrigin: "codex-ingress",
|
|
511
622
|
usage: tapped,
|
|
@@ -653,7 +764,7 @@ async function relaySseBody(res, body, options) {
|
|
|
653
764
|
res.end();
|
|
654
765
|
}
|
|
655
766
|
}
|
|
656
|
-
async function relayResponse(res, providerResponse, isStream, rewriteModel, usageTap) {
|
|
767
|
+
async function relayResponse(res, providerResponse, isStream, rewriteModel, usageTap, onSseEvent) {
|
|
657
768
|
const contentType = providerResponse.headers.get("Content-Type") ?? "";
|
|
658
769
|
const status = providerResponse.status && providerResponse.status >= 100 ? providerResponse.status : 200;
|
|
659
770
|
const upstreamIsSse = contentType.includes("text/event-stream");
|
|
@@ -667,13 +778,16 @@ async function relayResponse(res, providerResponse, isStream, rewriteModel, usag
|
|
|
667
778
|
res.end();
|
|
668
779
|
return null;
|
|
669
780
|
}
|
|
670
|
-
if (rewriteModel || usageTap) {
|
|
781
|
+
if (rewriteModel || usageTap || onSseEvent) {
|
|
671
782
|
let capturedUsage = null;
|
|
672
783
|
await relaySseBody(res, providerResponse.body, {
|
|
673
784
|
rewriteModel,
|
|
674
|
-
onSseEvent: usageTap ? (event) => {
|
|
675
|
-
|
|
676
|
-
if (
|
|
785
|
+
onSseEvent: onSseEvent || usageTap ? (event) => {
|
|
786
|
+
onSseEvent?.(event);
|
|
787
|
+
if (usageTap) {
|
|
788
|
+
const raw = usageTap.extractUsage(event);
|
|
789
|
+
if (raw) capturedUsage = raw;
|
|
790
|
+
}
|
|
677
791
|
} : void 0
|
|
678
792
|
});
|
|
679
793
|
if (capturedUsage) usageTap.onUsage(capturedUsage);
|
|
@@ -985,7 +1099,16 @@ async function runPipeline(anthropicBody, plan, reportSelection) {
|
|
|
985
1099
|
return fetchUpstream(
|
|
986
1100
|
url,
|
|
987
1101
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
988
|
-
{
|
|
1102
|
+
{
|
|
1103
|
+
providerId: proxyProviderId(plan),
|
|
1104
|
+
accountId: proxyAccountId,
|
|
1105
|
+
routeActivity: plan.isSubscription ? {
|
|
1106
|
+
endpoint: "messages",
|
|
1107
|
+
sessionKey: plan.sessionKey,
|
|
1108
|
+
sessionSource: plan.sessionKey ? "content-fingerprint" : "none",
|
|
1109
|
+
model: resolvedModel
|
|
1110
|
+
} : void 0
|
|
1111
|
+
}
|
|
989
1112
|
).then((r) => {
|
|
990
1113
|
rawStatus = r.status;
|
|
991
1114
|
return r;
|
|
@@ -1012,7 +1135,8 @@ async function runSubscriptionSameFormatFetch(rawBody, plan, reportSelection, op
|
|
|
1012
1135
|
reportSelection?.(accountId, isActive, remapped);
|
|
1013
1136
|
}
|
|
1014
1137
|
});
|
|
1015
|
-
|
|
1138
|
+
const isClaudeSubscription = plan.isSubscription && plan.transformerProvider.name === "claude";
|
|
1139
|
+
if (isClaudeSubscription) {
|
|
1016
1140
|
applyFingerprint(
|
|
1017
1141
|
getSharedIdentityStore(),
|
|
1018
1142
|
headers,
|
|
@@ -1021,18 +1145,38 @@ async function runSubscriptionSameFormatFetch(rawBody, plan, reportSelection, op
|
|
|
1021
1145
|
options.callerIdentity
|
|
1022
1146
|
);
|
|
1023
1147
|
}
|
|
1148
|
+
if (isClaudeSubscription) {
|
|
1149
|
+
fillMissingHeaders(headers, options.callerClientHeaders ?? {});
|
|
1150
|
+
fillMissingHeaders(headers, DEFAULT_CLAUDE_CODE_HEADERS);
|
|
1151
|
+
}
|
|
1152
|
+
fillMissingHeaders(headers, { "anthropic-version": DEFAULT_ANTHROPIC_VERSION });
|
|
1153
|
+
headers["accept-encoding"] = FORCED_ACCEPT_ENCODING;
|
|
1024
1154
|
const applyRemap = remappedModel !== void 0 && outboundRemapApplies(plan);
|
|
1025
1155
|
const outboundBody = applyRemap ? rewriteBodyModel(rawBody, remappedModel) : rawBody;
|
|
1026
1156
|
const outboundModel = applyRemap ? remappedModel : plan.resolvedModel;
|
|
1157
|
+
if (isClaudeSubscription) {
|
|
1158
|
+
headers["anthropic-beta"] = buildAnthropicBeta(outboundModel, options.callerAnthropicBeta);
|
|
1159
|
+
} else if (options.callerAnthropicBeta) {
|
|
1160
|
+
fillMissingHeaders(headers, { "anthropic-beta": options.callerAnthropicBeta });
|
|
1161
|
+
}
|
|
1027
1162
|
console.info(
|
|
1028
1163
|
`[ProviderProxy:anthropic] (subscription same-format) -> ${plan.upstreamUrl} model=${outboundModel} stream=${plan.isStream}`
|
|
1029
1164
|
);
|
|
1030
1165
|
const response = await fetchUpstream(
|
|
1031
1166
|
plan.upstreamUrl,
|
|
1032
1167
|
{ method: "POST", headers, body: outboundBody },
|
|
1033
|
-
{
|
|
1168
|
+
{
|
|
1169
|
+
providerId: proxyProviderId(plan),
|
|
1170
|
+
accountId: proxyAccountId,
|
|
1171
|
+
routeActivity: plan.isSubscription ? {
|
|
1172
|
+
endpoint: "messages",
|
|
1173
|
+
sessionKey: plan.sessionKey,
|
|
1174
|
+
sessionSource: plan.sessionKey ? "content-fingerprint" : "none",
|
|
1175
|
+
model: outboundModel
|
|
1176
|
+
} : void 0
|
|
1177
|
+
}
|
|
1034
1178
|
);
|
|
1035
|
-
return { response, rawStatus: response.status };
|
|
1179
|
+
return { response, rawStatus: response.status, actualModel: outboundModel };
|
|
1036
1180
|
}
|
|
1037
1181
|
function outboundRemapApplies(plan) {
|
|
1038
1182
|
return plan.isSubscription && plan.transformerProvider.name === "claude";
|
|
@@ -1198,11 +1342,13 @@ async function handleAnthropicMessagesByo(res, rawBody, route, deps, options = {
|
|
|
1198
1342
|
const upstreamSse = (upstreamResponse.headers.get("content-type") ?? "").includes(
|
|
1199
1343
|
"text/event-stream"
|
|
1200
1344
|
);
|
|
1345
|
+
const actualModel = "actualModel" in providerResponse && typeof providerResponse.actualModel === "string" ? providerResponse.actualModel : void 0;
|
|
1201
1346
|
const usageAttribution = {
|
|
1202
1347
|
sessionId: route.sessionId,
|
|
1203
1348
|
providerId: route.providerId ?? "anthropic",
|
|
1204
|
-
model: plan.resolvedModel,
|
|
1349
|
+
model: actualModel ?? plan.resolvedModel,
|
|
1205
1350
|
apiKeyId: route.apiKeyId ?? null,
|
|
1351
|
+
routeLease: route.routeLease,
|
|
1206
1352
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1207
1353
|
auditResponse: res
|
|
1208
1354
|
};
|
|
@@ -1370,7 +1516,12 @@ async function handleAnthropicMessagesRequest(req, res, route, deps) {
|
|
|
1370
1516
|
const callerBetaRaw = req.headers["anthropic-beta"];
|
|
1371
1517
|
const callerAnthropicBeta = Array.isArray(callerBetaRaw) ? callerBetaRaw.join(",") : callerBetaRaw;
|
|
1372
1518
|
const callerIdentity = captureCallerIdentity(getSharedIdentityStore(), req.headers);
|
|
1373
|
-
|
|
1519
|
+
const callerClientHeaders = extractClaudeClientHeaders(req.headers);
|
|
1520
|
+
await handleAnthropicMessagesByo(res, rawBody, route, deps, {
|
|
1521
|
+
callerAnthropicBeta,
|
|
1522
|
+
callerIdentity,
|
|
1523
|
+
callerClientHeaders
|
|
1524
|
+
});
|
|
1374
1525
|
return;
|
|
1375
1526
|
}
|
|
1376
1527
|
const hints = route.anthropicSdkHints;
|
|
@@ -1456,6 +1607,10 @@ function emitGeminiUsageRecord(recorder, rawUsageMetadata, attribution) {
|
|
|
1456
1607
|
providerId: attribution.providerId,
|
|
1457
1608
|
model: attribution.model,
|
|
1458
1609
|
apiKeyId: attribution.apiKeyId,
|
|
1610
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
1611
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
1612
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
1613
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
1459
1614
|
auditResponse: attribution.auditResponse,
|
|
1460
1615
|
engineOrigin: "codex-ingress",
|
|
1461
1616
|
usage: tapped,
|
|
@@ -1521,6 +1676,7 @@ async function handleGeminiGenerateContentRequest(res, rawBody, url, route, deps
|
|
|
1521
1676
|
providerId: route.providerId ?? "gemini",
|
|
1522
1677
|
model: resolvedModel,
|
|
1523
1678
|
apiKeyId: route.apiKeyId ?? null,
|
|
1679
|
+
routeLease: route.routeLease,
|
|
1524
1680
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1525
1681
|
auditResponse: res
|
|
1526
1682
|
};
|
|
@@ -1677,6 +1833,10 @@ function emitChatCompletionsUsageRecord(recorder, rawUsage, attribution) {
|
|
|
1677
1833
|
providerId: attribution.providerId,
|
|
1678
1834
|
model: attribution.model,
|
|
1679
1835
|
apiKeyId: attribution.apiKeyId,
|
|
1836
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
1837
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
1838
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
1839
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
1680
1840
|
auditResponse: attribution.auditResponse,
|
|
1681
1841
|
engineOrigin: "codex-ingress",
|
|
1682
1842
|
usage: tapped,
|
|
@@ -1729,6 +1889,7 @@ async function handleOpenAIChatRequest(res, rawBody, route, deps) {
|
|
|
1729
1889
|
providerId: route.providerId ?? "openai",
|
|
1730
1890
|
model: resolvedModel,
|
|
1731
1891
|
apiKeyId: route.apiKeyId ?? null,
|
|
1892
|
+
routeLease: route.routeLease,
|
|
1732
1893
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1733
1894
|
auditResponse: res
|
|
1734
1895
|
};
|
|
@@ -1957,6 +2118,12 @@ function emitResponsesUsageRecord(recorder, rawUsage, attribution) {
|
|
|
1957
2118
|
providerId: attribution.providerId,
|
|
1958
2119
|
model: attribution.model,
|
|
1959
2120
|
apiKeyId: attribution.apiKeyId,
|
|
2121
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
2122
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
2123
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
2124
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
2125
|
+
cacheKeySource: attribution.cacheKeySource,
|
|
2126
|
+
cacheKeyInjected: attribution.cacheKeyInjected,
|
|
1960
2127
|
auditResponse: attribution.auditResponse,
|
|
1961
2128
|
engineOrigin: "codex-ingress",
|
|
1962
2129
|
usage: tapped,
|
|
@@ -1981,12 +2148,72 @@ function recordResponsesStreamUsage(recorder, rawUsage, attribution) {
|
|
|
1981
2148
|
}
|
|
1982
2149
|
}
|
|
1983
2150
|
|
|
2151
|
+
// src/provider-proxy/ingress/codexUsageLimitDetection.ts
|
|
2152
|
+
var PROVIDER_ID = "codex";
|
|
2153
|
+
var DEFAULT_QUOTA_COOLDOWN_MS = 5 * 6e4;
|
|
2154
|
+
var QUOTA_EXHAUSTED_PERCENT = 90;
|
|
2155
|
+
function parseTryAgainDeadline(bodyText) {
|
|
2156
|
+
const match = bodyText.match(/try again (?:at|on)\s+(.+?)(?:[.\n]|$)/i);
|
|
2157
|
+
if (!match) return void 0;
|
|
2158
|
+
const cleaned = match[1].replace(/(\d+)(st|nd|rd|th)/giu, "$1").trim();
|
|
2159
|
+
const ms = Date.parse(cleaned);
|
|
2160
|
+
return Number.isFinite(ms) ? ms : void 0;
|
|
2161
|
+
}
|
|
2162
|
+
function deadlineFromAllowanceSnapshot(accountId, now) {
|
|
2163
|
+
const snapshot = getSharedAccountAllowanceStore().get(PROVIDER_ID, accountId, now);
|
|
2164
|
+
if (!snapshot) return void 0;
|
|
2165
|
+
const candidates = snapshot.windows.filter(
|
|
2166
|
+
(w) => typeof w.usedPercent === "number" && w.usedPercent >= QUOTA_EXHAUSTED_PERCENT && typeof w.resetsAt === "string"
|
|
2167
|
+
).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);
|
|
2168
|
+
return candidates[0]?.ms;
|
|
2169
|
+
}
|
|
2170
|
+
function resolveCodexQuotaDeadline(accountId, bodyText, now = Date.now()) {
|
|
2171
|
+
const parsed = parseTryAgainDeadline(bodyText);
|
|
2172
|
+
const parsedDeadline = parsed !== void 0 && parsed > now ? parsed : void 0;
|
|
2173
|
+
return deadlineFromAllowanceSnapshot(accountId, now) ?? parsedDeadline ?? now + DEFAULT_QUOTA_COOLDOWN_MS;
|
|
2174
|
+
}
|
|
2175
|
+
function markCodexUsageLimitExhaustion(accountId, bodyText, now = Date.now()) {
|
|
2176
|
+
if (!accountId) return;
|
|
2177
|
+
const deadline = resolveCodexQuotaDeadline(accountId, bodyText, now);
|
|
2178
|
+
getSharedAccountHealth().markQuotaExhausted(PROVIDER_ID, accountId, deadline, now);
|
|
2179
|
+
}
|
|
2180
|
+
|
|
1984
2181
|
// src/provider-proxy/ingress/openaiResponsesIngress.ts
|
|
2182
|
+
var SERVER_OVERLOADED_CODES = /* @__PURE__ */ new Set(["server_is_overloaded", "slow_down"]);
|
|
2183
|
+
function isCodexServerOverloadEvent(event) {
|
|
2184
|
+
if (event["type"] !== "response.failed") return false;
|
|
2185
|
+
const error = event["response"]?.["error"];
|
|
2186
|
+
const code = error?.["code"];
|
|
2187
|
+
return typeof code === "string" && SERVER_OVERLOADED_CODES.has(code);
|
|
2188
|
+
}
|
|
1985
2189
|
function isOpenAIResponsesRequest(method, url) {
|
|
1986
2190
|
if (method !== "POST" || !url) return false;
|
|
1987
2191
|
const path = url.split("?")[0]?.replace(/\/+$/, "") ?? "";
|
|
1988
2192
|
return path.endsWith("/responses");
|
|
1989
2193
|
}
|
|
2194
|
+
var SAFE_INJECTED_CACHE_KEY_SOURCES = /* @__PURE__ */ new Set([
|
|
2195
|
+
"session-header",
|
|
2196
|
+
"thread-header",
|
|
2197
|
+
"body-session-id",
|
|
2198
|
+
"body-thread-id",
|
|
2199
|
+
"content-fingerprint"
|
|
2200
|
+
]);
|
|
2201
|
+
function isSafeInjectedCacheKeySource(source) {
|
|
2202
|
+
return SAFE_INJECTED_CACHE_KEY_SOURCES.has(source);
|
|
2203
|
+
}
|
|
2204
|
+
function ensureCodexPromptCacheKey(body, proxyProviderId2, derivedSession) {
|
|
2205
|
+
if (typeof body.prompt_cache_key === "string" && body.prompt_cache_key.trim()) {
|
|
2206
|
+
return { cacheKeySource: "client", cacheKeyInjected: false };
|
|
2207
|
+
}
|
|
2208
|
+
if (proxyProviderId2 !== "codex" || !isSafeInjectedCacheKeySource(derivedSession.source)) {
|
|
2209
|
+
return { cacheKeySource: "none", cacheKeyInjected: false };
|
|
2210
|
+
}
|
|
2211
|
+
body.prompt_cache_key = `omnicross:${derivedSession.source}:${derivedSession.key}`;
|
|
2212
|
+
return {
|
|
2213
|
+
cacheKeySource: derivedSession.source,
|
|
2214
|
+
cacheKeyInjected: true
|
|
2215
|
+
};
|
|
2216
|
+
}
|
|
1990
2217
|
async function handleOpenAIResponsesRequest(res, rawBody, route, deps, requestHeaders = {}) {
|
|
1991
2218
|
let responsesBody;
|
|
1992
2219
|
try {
|
|
@@ -2007,14 +2234,32 @@ async function handleOpenAIResponsesRequest(res, rawBody, route, deps, requestHe
|
|
|
2007
2234
|
const resolvedModel = route.model;
|
|
2008
2235
|
responsesBody.model = resolvedModel;
|
|
2009
2236
|
try {
|
|
2010
|
-
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan3(
|
|
2237
|
+
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan3(
|
|
2238
|
+
res,
|
|
2239
|
+
route,
|
|
2240
|
+
deps,
|
|
2241
|
+
resolvedModel,
|
|
2242
|
+
isStream,
|
|
2243
|
+
sessionKey,
|
|
2244
|
+
derivedSession.source,
|
|
2245
|
+
requestHeaders
|
|
2246
|
+
) : await buildByoPlan4(res, route, deps, resolvedModel, isStream);
|
|
2011
2247
|
if (!plan) return;
|
|
2012
|
-
const
|
|
2248
|
+
const cacheKeyAttribution = ensureCodexPromptCacheKey(
|
|
2249
|
+
responsesBody,
|
|
2250
|
+
plan.proxyProviderId,
|
|
2251
|
+
derivedSession
|
|
2252
|
+
);
|
|
2253
|
+
let providerResponse = route.authMode === "subscription" ? await runPipelineWithSubscriptionRetry3(responsesBody, plan) : await runPipelineWithPoolReporting4(responsesBody, plan);
|
|
2254
|
+
providerResponse = await retryAroundCodexUsageLimit(responsesBody, plan, providerResponse);
|
|
2013
2255
|
const usageAttribution = {
|
|
2014
2256
|
sessionId: route.sessionId,
|
|
2015
2257
|
providerId: route.providerId ?? "codex",
|
|
2016
|
-
model: resolvedModel,
|
|
2258
|
+
model: providerResponse.actualModel ?? resolvedModel,
|
|
2017
2259
|
apiKeyId: route.apiKeyId ?? null,
|
|
2260
|
+
routeLease: route.routeLease,
|
|
2261
|
+
cacheKeySource: cacheKeyAttribution.cacheKeySource,
|
|
2262
|
+
cacheKeyInjected: cacheKeyAttribution.cacheKeyInjected,
|
|
2018
2263
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
2019
2264
|
auditResponse: res
|
|
2020
2265
|
};
|
|
@@ -2030,12 +2275,33 @@ async function handleOpenAIResponsesRequest(res, rawBody, route, deps, requestHe
|
|
|
2030
2275
|
recordResponsesStreamUsage(usageRecorder, rawUsage, usageAttribution);
|
|
2031
2276
|
}
|
|
2032
2277
|
} : void 0;
|
|
2278
|
+
const overloadActivityId = providerResponse.activityRecordId;
|
|
2279
|
+
const overloadAccountId = providerResponse.accountId;
|
|
2280
|
+
const overloadProviderId = plan.proxyProviderId;
|
|
2281
|
+
let overloadRecorded = false;
|
|
2282
|
+
const overloadDetector = (event) => {
|
|
2283
|
+
if (overloadRecorded || !isCodexServerOverloadEvent(event)) return;
|
|
2284
|
+
overloadRecorded = true;
|
|
2285
|
+
if (overloadActivityId) {
|
|
2286
|
+
getSharedAccountRouteActivity().amend(overloadActivityId, {
|
|
2287
|
+
streamError: "server_overloaded"
|
|
2288
|
+
});
|
|
2289
|
+
}
|
|
2290
|
+
if (overloadAccountId) {
|
|
2291
|
+
getSharedOverloadCounter().recordOverload({
|
|
2292
|
+
providerId: overloadProviderId,
|
|
2293
|
+
accountId: overloadAccountId,
|
|
2294
|
+
endpoint: "responses"
|
|
2295
|
+
});
|
|
2296
|
+
}
|
|
2297
|
+
};
|
|
2033
2298
|
const bodyText = await relayResponse(
|
|
2034
2299
|
res,
|
|
2035
2300
|
providerResponse.response,
|
|
2036
2301
|
isStream,
|
|
2037
2302
|
route.requestedModel,
|
|
2038
|
-
usageTap
|
|
2303
|
+
usageTap,
|
|
2304
|
+
overloadDetector
|
|
2039
2305
|
);
|
|
2040
2306
|
if (bodyText && deps.usageRecorder) {
|
|
2041
2307
|
recordResponsesNonStreamUsage(deps.usageRecorder, bodyText, usageAttribution);
|
|
@@ -2099,7 +2365,7 @@ async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
|
|
|
2099
2365
|
proxyProviderId: "byo"
|
|
2100
2366
|
};
|
|
2101
2367
|
}
|
|
2102
|
-
async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream, sessionKey) {
|
|
2368
|
+
async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream, sessionKey, sessionSource, requestHeaders) {
|
|
2103
2369
|
const profile = route.subscriptionProfile;
|
|
2104
2370
|
if (!profile) {
|
|
2105
2371
|
writeError(res, 502, "Subscription mode requires a codex subscription profile");
|
|
@@ -2142,6 +2408,7 @@ async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream,
|
|
|
2142
2408
|
return {
|
|
2143
2409
|
auth,
|
|
2144
2410
|
sessionKey,
|
|
2411
|
+
sessionSource,
|
|
2145
2412
|
preferredAccountId: route.preferredAccountId,
|
|
2146
2413
|
preferredAccountGroup: route.preferredAccountGroup,
|
|
2147
2414
|
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy,
|
|
@@ -2154,7 +2421,10 @@ async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream,
|
|
|
2154
2421
|
// profile's `resolveUpstreamUrl` for non-Code-Assist subscriptions.
|
|
2155
2422
|
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : typeof config.url === "string" ? config.url : upstreamUrl,
|
|
2156
2423
|
upstreamUrl,
|
|
2157
|
-
proxyProviderId: profile.authStrategy.providerId
|
|
2424
|
+
proxyProviderId: profile.authStrategy.providerId,
|
|
2425
|
+
// Only the codex backend understands these; other subscription providers
|
|
2426
|
+
// (gemini Code Assist) share this relay and must not receive them.
|
|
2427
|
+
callerClientHeaders: profile.authStrategy.providerId === "codex" ? extractCodexClientHeaders(requestHeaders) : void 0
|
|
2158
2428
|
};
|
|
2159
2429
|
}
|
|
2160
2430
|
async function resolveGeminiCodeAssistProject(profile, hints) {
|
|
@@ -2172,6 +2442,8 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2172
2442
|
const endpointTransformer = getResponsesEndpointTransformer();
|
|
2173
2443
|
const { auth, sessionKey, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
2174
2444
|
let proxyAccountId;
|
|
2445
|
+
let actualModel = resolvedModel;
|
|
2446
|
+
let activityRecordId;
|
|
2175
2447
|
const authHeaders = {};
|
|
2176
2448
|
await auth.applyHeaders(authHeaders, {
|
|
2177
2449
|
upstreamUrl,
|
|
@@ -2180,10 +2452,13 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2180
2452
|
preferredAccountId: plan.preferredAccountId,
|
|
2181
2453
|
preferredAccountGroup: plan.preferredAccountGroup,
|
|
2182
2454
|
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
2183
|
-
reportSelection: (accountId) => {
|
|
2455
|
+
reportSelection: (accountId, _isActive, remappedModel) => {
|
|
2184
2456
|
proxyAccountId = accountId;
|
|
2457
|
+
if (remappedModel) actualModel = remappedModel;
|
|
2185
2458
|
}
|
|
2186
2459
|
});
|
|
2460
|
+
responsesBody.model = actualModel;
|
|
2461
|
+
transformerProvider.models = [actualModel];
|
|
2187
2462
|
let rawStatus = null;
|
|
2188
2463
|
const { response } = await executeProviderCall({
|
|
2189
2464
|
executor,
|
|
@@ -2199,14 +2474,31 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2199
2474
|
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
2200
2475
|
}
|
|
2201
2476
|
}
|
|
2477
|
+
if (plan.proxyProviderId === "codex") {
|
|
2478
|
+
fillMissingHeaders(headers, plan.callerClientHeaders ?? {});
|
|
2479
|
+
fillMissingHeaders(headers, DEFAULT_CODEX_CLI_HEADERS);
|
|
2480
|
+
fillMissingHeaders(headers, { accept: codexAcceptHeader(isStream) });
|
|
2481
|
+
}
|
|
2202
2482
|
return headers;
|
|
2203
2483
|
},
|
|
2204
2484
|
fetchFn: (url, headers, body) => {
|
|
2205
|
-
console.log(`[ProviderProxy:responses] -> ${url} model=${
|
|
2485
|
+
console.log(`[ProviderProxy:responses] -> ${url} model=${actualModel} stream=${isStream}`);
|
|
2206
2486
|
return fetchUpstream(
|
|
2207
2487
|
url,
|
|
2208
2488
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
2209
|
-
{
|
|
2489
|
+
{
|
|
2490
|
+
providerId: plan.proxyProviderId,
|
|
2491
|
+
accountId: proxyAccountId,
|
|
2492
|
+
routeActivity: plan.proxyProviderId === "byo" ? void 0 : {
|
|
2493
|
+
endpoint: "responses",
|
|
2494
|
+
sessionKey,
|
|
2495
|
+
sessionSource: plan.sessionSource ?? "none",
|
|
2496
|
+
model: actualModel,
|
|
2497
|
+
onRecorded: (record) => {
|
|
2498
|
+
activityRecordId = record.id;
|
|
2499
|
+
}
|
|
2500
|
+
}
|
|
2501
|
+
}
|
|
2210
2502
|
).then((r) => {
|
|
2211
2503
|
rawStatus = r.status;
|
|
2212
2504
|
return r;
|
|
@@ -2214,7 +2506,7 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2214
2506
|
},
|
|
2215
2507
|
runResponseChain: true
|
|
2216
2508
|
});
|
|
2217
|
-
return { response, rawStatus };
|
|
2509
|
+
return { response, rawStatus, accountId: proxyAccountId, actualModel, activityRecordId };
|
|
2218
2510
|
}
|
|
2219
2511
|
async function runPipelineWithPoolReporting4(responsesBody, plan) {
|
|
2220
2512
|
const first = await runPipeline4(responsesBody, plan);
|
|
@@ -2240,6 +2532,22 @@ async function runPipelineWithSubscriptionRetry3(responsesBody, plan) {
|
|
|
2240
2532
|
console.log("[ProviderProxy:responses] 401 \u2192 token refreshed; retrying once");
|
|
2241
2533
|
return runPipeline4(responsesBody, plan);
|
|
2242
2534
|
}
|
|
2535
|
+
var MAX_QUOTA_RETRIES = 3;
|
|
2536
|
+
function isCodexAccountQuota429(plan, result) {
|
|
2537
|
+
return plan.proxyProviderId === "codex" && !!result.accountId && result.rawStatus === 429;
|
|
2538
|
+
}
|
|
2539
|
+
async function retryAroundCodexUsageLimit(responsesBody, plan, first, runAttempt = runPipelineWithSubscriptionRetry3) {
|
|
2540
|
+
if (!isCodexAccountQuota429(plan, first)) return first;
|
|
2541
|
+
let current = first;
|
|
2542
|
+
for (let attempt = 0; attempt < MAX_QUOTA_RETRIES; attempt++) {
|
|
2543
|
+
const errorBody = await current.response.text().catch(() => "");
|
|
2544
|
+
markCodexUsageLimitExhaustion(current.accountId, errorBody);
|
|
2545
|
+
const next = await runAttempt(responsesBody, plan);
|
|
2546
|
+
if (!isCodexAccountQuota429(plan, next)) return next;
|
|
2547
|
+
current = next;
|
|
2548
|
+
}
|
|
2549
|
+
return current;
|
|
2550
|
+
}
|
|
2243
2551
|
|
|
2244
2552
|
// src/provider-proxy/providerProxyRouter.ts
|
|
2245
2553
|
function extractRouteToken(authHeader) {
|
|
@@ -2302,6 +2610,7 @@ var ProviderProxy = class {
|
|
|
2302
2610
|
server = null;
|
|
2303
2611
|
port = 0;
|
|
2304
2612
|
routes;
|
|
2613
|
+
beforeStop = /* @__PURE__ */ new Set();
|
|
2305
2614
|
/**
|
|
2306
2615
|
* Start the resident listener on a stable port on 127.0.0.1. Idempotent —
|
|
2307
2616
|
* a second `start()` returns the already-bound port.
|
|
@@ -2342,6 +2651,12 @@ var ProviderProxy = class {
|
|
|
2342
2651
|
}
|
|
2343
2652
|
/** Stop the listener, clear all routes, and release the port. */
|
|
2344
2653
|
async stop() {
|
|
2654
|
+
for (const cleanup of [...this.beforeStop]) {
|
|
2655
|
+
try {
|
|
2656
|
+
await cleanup();
|
|
2657
|
+
} catch {
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2345
2660
|
this.routes.clear();
|
|
2346
2661
|
const server = this.server;
|
|
2347
2662
|
if (!server) return;
|
|
@@ -2358,6 +2673,15 @@ var ProviderProxy = class {
|
|
|
2358
2673
|
getBaseUrl() {
|
|
2359
2674
|
return `http://127.0.0.1:${this.port}`;
|
|
2360
2675
|
}
|
|
2676
|
+
/** Register daemon-owned cleanup that must run before route-map teardown. */
|
|
2677
|
+
registerBeforeStop(cleanup) {
|
|
2678
|
+
this.beforeStop.add(cleanup);
|
|
2679
|
+
return () => this.beforeStop.delete(cleanup);
|
|
2680
|
+
}
|
|
2681
|
+
/** Whether the resident listener has completed binding. */
|
|
2682
|
+
isReady() {
|
|
2683
|
+
return this.server !== null && this.port > 0;
|
|
2684
|
+
}
|
|
2361
2685
|
/**
|
|
2362
2686
|
* The SHARED route map. Exposed so the outbound API server
|
|
2363
2687
|
* (`outbound-api-server`) can mint per-request routes on the SAME map and
|
|
@@ -2367,24 +2691,824 @@ var ProviderProxy = class {
|
|
|
2367
2691
|
getRouteMap() {
|
|
2368
2692
|
return this.routes;
|
|
2369
2693
|
}
|
|
2370
|
-
/**
|
|
2371
|
-
* The app-session deps the proxy services all routes with. Exposed so the
|
|
2372
|
-
* outbound server can pass them verbatim into `routeRequest()`.
|
|
2373
|
-
*/
|
|
2374
|
-
getDeps() {
|
|
2375
|
-
return this.deps;
|
|
2694
|
+
/**
|
|
2695
|
+
* The app-session deps the proxy services all routes with. Exposed so the
|
|
2696
|
+
* outbound server can pass them verbatim into `routeRequest()`.
|
|
2697
|
+
*/
|
|
2698
|
+
getDeps() {
|
|
2699
|
+
return this.deps;
|
|
2700
|
+
}
|
|
2701
|
+
/** Register a route for one run; returns the crypto route token (task 2.2). */
|
|
2702
|
+
addRoute(context, idleMsOrOptions) {
|
|
2703
|
+
return this.routes.addRoute(context, idleMsOrOptions);
|
|
2704
|
+
}
|
|
2705
|
+
/** Re-arm one route while retaining the exact same bearer token. */
|
|
2706
|
+
renewRoute(token, idleMs) {
|
|
2707
|
+
return this.routes.renewRoute(token, idleMs);
|
|
2708
|
+
}
|
|
2709
|
+
/** Remove a route at run end. Returns true if an entry existed. */
|
|
2710
|
+
removeRoute(token) {
|
|
2711
|
+
return this.routes.removeRoute(token);
|
|
2712
|
+
}
|
|
2713
|
+
/** Live-route count (diagnostics / tests). */
|
|
2714
|
+
routeCount() {
|
|
2715
|
+
return this.routes.size();
|
|
2716
|
+
}
|
|
2717
|
+
};
|
|
2718
|
+
|
|
2719
|
+
// src/provider-proxy/RouteLeaseManager.ts
|
|
2720
|
+
import { randomBytes as randomBytes3, randomUUID } from "crypto";
|
|
2721
|
+
|
|
2722
|
+
// src/provider-proxy/routeLeaseSchema.ts
|
|
2723
|
+
import { createHash, createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
2724
|
+
var ROUTE_LEASE_REQUEST_SCHEMA = "omnicross.route-lease.request/1";
|
|
2725
|
+
var ROUTE_LEASE_RESULT_SCHEMA = "omnicross.route-lease/1";
|
|
2726
|
+
var ROUTE_LEASE_CAPABILITIES_SCHEMA = "omnicross.route-lease.capabilities/1";
|
|
2727
|
+
var ROUTE_LEASE_API_VERSION = 1;
|
|
2728
|
+
var ROUTE_LEASE_DEFAULT_TTL_SECONDS = 600;
|
|
2729
|
+
var ROUTE_LEASE_MAX_TTL_SECONDS = 3600;
|
|
2730
|
+
var ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES = 256;
|
|
2731
|
+
var ROUTE_LEASE_MAX_CONSUMER_BYTES = 64;
|
|
2732
|
+
var ROUTE_LEASE_MAX_EXECUTION_ID_BYTES = 128;
|
|
2733
|
+
var ROUTE_LEASE_MAX_SESSION_ID_BYTES = 512;
|
|
2734
|
+
var ROUTE_LEASE_MAX_MODEL_BYTES = 256;
|
|
2735
|
+
var ROUTE_LEASE_SESSION_HASH_DOMAIN = "omnicross.route-lease.session/v1";
|
|
2736
|
+
var ROUTE_LEASE_CODEX_TOKEN_ENV = "OMNICROSS_CODEX_ROUTE_TOKEN";
|
|
2737
|
+
var ROUTE_LEASE_RUNTIMES = ["claude", "codex"];
|
|
2738
|
+
var ROUTE_LEASE_CAPABILITIES = Object.freeze({
|
|
2739
|
+
schemaVersion: ROUTE_LEASE_CAPABILITIES_SCHEMA,
|
|
2740
|
+
runtimes: Object.freeze(["claude", "codex"]),
|
|
2741
|
+
upstreamKinds: Object.freeze(["provider", "account", "account-group", "account-pool"]),
|
|
2742
|
+
leaseApiVersion: ROUTE_LEASE_API_VERSION,
|
|
2743
|
+
codexAuthMode: "env_key",
|
|
2744
|
+
maxTtlSeconds: ROUTE_LEASE_MAX_TTL_SECONDS
|
|
2745
|
+
});
|
|
2746
|
+
var ROUTE_LEASE_RUNTIME_TABLE = Object.freeze({
|
|
2747
|
+
claude: Object.freeze({ endpoint: "messages", ingressFormat: "anthropic-messages", wirePath: "/v1/messages" }),
|
|
2748
|
+
codex: Object.freeze({ endpoint: "responses", ingressFormat: "openai-responses", wirePath: "/openai/responses" })
|
|
2749
|
+
});
|
|
2750
|
+
var ERROR_DEFAULTS = {
|
|
2751
|
+
invalid_request: { status: 400, retryable: false },
|
|
2752
|
+
runtime_unsupported: { status: 400, retryable: false },
|
|
2753
|
+
model_not_configured: { status: 400, retryable: false },
|
|
2754
|
+
format_unsupported: { status: 400, retryable: false },
|
|
2755
|
+
control_unauthorized: { status: 403, retryable: false },
|
|
2756
|
+
upstream_not_found: { status: 404, retryable: false },
|
|
2757
|
+
lease_not_found: { status: 404, retryable: false },
|
|
2758
|
+
idempotency_conflict: { status: 409, retryable: false },
|
|
2759
|
+
upstream_unavailable: { status: 409, retryable: true },
|
|
2760
|
+
lease_expired: { status: 410, retryable: false },
|
|
2761
|
+
upstream_exhausted: { status: 429, retryable: true },
|
|
2762
|
+
daemon_not_ready: { status: 503, retryable: true }
|
|
2763
|
+
};
|
|
2764
|
+
var RouteLeaseError = class extends Error {
|
|
2765
|
+
constructor(code, message, options = {}) {
|
|
2766
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
2767
|
+
this.code = code;
|
|
2768
|
+
this.name = "RouteLeaseError";
|
|
2769
|
+
const defaults = ERROR_DEFAULTS[code];
|
|
2770
|
+
this.status = options.status ?? defaults.status;
|
|
2771
|
+
this.retryable = options.retryable ?? defaults.retryable;
|
|
2772
|
+
if (options.retryAfterSeconds !== void 0) {
|
|
2773
|
+
this.retryAfterSeconds = Math.max(1, Math.min(3600, Math.floor(options.retryAfterSeconds)));
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
code;
|
|
2777
|
+
type = "route_lease_error";
|
|
2778
|
+
status;
|
|
2779
|
+
retryable;
|
|
2780
|
+
retryAfterSeconds;
|
|
2781
|
+
toResponse() {
|
|
2782
|
+
return { error: { type: this.type, code: this.code, message: this.message, retryable: this.retryable } };
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
var IDEMPOTENCY_RE = /^[A-Za-z0-9._:-]+$/u;
|
|
2786
|
+
var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
2787
|
+
var PROCESS_SESSION_HMAC_KEY = randomBytes2(32);
|
|
2788
|
+
function isRecord(value) {
|
|
2789
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2790
|
+
}
|
|
2791
|
+
function byteLength(value) {
|
|
2792
|
+
return Buffer.byteLength(value, "utf8");
|
|
2793
|
+
}
|
|
2794
|
+
function normalizedString(value, field, maxBytes, options = {}) {
|
|
2795
|
+
if (typeof value !== "string") throw invalid(`${field} must be a string`);
|
|
2796
|
+
const normalized = value.trim();
|
|
2797
|
+
if (!normalized || byteLength(normalized) > maxBytes || CONTROL_RE.test(normalized)) {
|
|
2798
|
+
throw invalid(`${field} is invalid`);
|
|
2799
|
+
}
|
|
2800
|
+
if (options.asciiPattern && !options.asciiPattern.test(normalized)) {
|
|
2801
|
+
throw invalid(`${field} is invalid`);
|
|
2802
|
+
}
|
|
2803
|
+
return normalized;
|
|
2804
|
+
}
|
|
2805
|
+
function optionalExecutionId(value, field) {
|
|
2806
|
+
return value === void 0 ? void 0 : normalizedString(value, field, ROUTE_LEASE_MAX_EXECUTION_ID_BYTES);
|
|
2807
|
+
}
|
|
2808
|
+
function invalid(message) {
|
|
2809
|
+
return new RouteLeaseError("invalid_request", message);
|
|
2810
|
+
}
|
|
2811
|
+
function normalizeRouteLeaseIdempotencyKey(value) {
|
|
2812
|
+
return normalizedString(value, "Idempotency-Key", ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES, {
|
|
2813
|
+
asciiPattern: IDEMPOTENCY_RE
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
function normalizeRouteLeaseTtl(value) {
|
|
2817
|
+
if (value === void 0) return ROUTE_LEASE_DEFAULT_TTL_SECONDS;
|
|
2818
|
+
if (!Number.isInteger(value) || value < 1 || value > ROUTE_LEASE_MAX_TTL_SECONDS) {
|
|
2819
|
+
throw invalid(`ttlSeconds must be an integer from 1 to ${ROUTE_LEASE_MAX_TTL_SECONDS}`);
|
|
2820
|
+
}
|
|
2821
|
+
return value;
|
|
2822
|
+
}
|
|
2823
|
+
function normalizeUpstream(value) {
|
|
2824
|
+
if (!isRecord(value) || typeof value.kind !== "string") throw invalid("upstream is invalid");
|
|
2825
|
+
const providerId = normalizedString(value.providerId, "upstream.providerId", 128);
|
|
2826
|
+
switch (value.kind) {
|
|
2827
|
+
case "provider": {
|
|
2828
|
+
const keyId = value.keyId === void 0 ? void 0 : normalizedString(value.keyId, "upstream.keyId", 128);
|
|
2829
|
+
return keyId ? { kind: "provider", providerId, keyId } : { kind: "provider", providerId };
|
|
2830
|
+
}
|
|
2831
|
+
case "account":
|
|
2832
|
+
return {
|
|
2833
|
+
kind: "account",
|
|
2834
|
+
providerId,
|
|
2835
|
+
accountId: normalizedString(value.accountId, "upstream.accountId", 128)
|
|
2836
|
+
};
|
|
2837
|
+
case "account-group":
|
|
2838
|
+
return {
|
|
2839
|
+
kind: "account-group",
|
|
2840
|
+
providerId,
|
|
2841
|
+
group: normalizedString(value.group, "upstream.group", 128)
|
|
2842
|
+
};
|
|
2843
|
+
case "account-pool":
|
|
2844
|
+
return { kind: "account-pool", providerId };
|
|
2845
|
+
default:
|
|
2846
|
+
throw invalid("upstream kind is unsupported");
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
function hashRouteLeaseSessionId(sessionId, hmacKey = PROCESS_SESSION_HMAC_KEY) {
|
|
2850
|
+
return createHmac("sha256", hmacKey).update(ROUTE_LEASE_SESSION_HASH_DOMAIN, "utf8").update("\0", "utf8").update(sessionId, "utf8").digest("hex");
|
|
2851
|
+
}
|
|
2852
|
+
function canonicalizeRouteLeasePayload(value) {
|
|
2853
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
2854
|
+
if (Array.isArray(value)) {
|
|
2855
|
+
return `[${value.map((entry) => canonicalizeRouteLeasePayload(entry)).join(",")}]`;
|
|
2856
|
+
}
|
|
2857
|
+
const record = value;
|
|
2858
|
+
const fields = Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalizeRouteLeasePayload(record[key])}`);
|
|
2859
|
+
return `{${fields.join(",")}}`;
|
|
2860
|
+
}
|
|
2861
|
+
function hashRouteLeasePayload(value) {
|
|
2862
|
+
return createHash("sha256").update(canonicalizeRouteLeasePayload(value), "utf8").digest("hex");
|
|
2863
|
+
}
|
|
2864
|
+
function parseRouteLeaseCreate(raw, idempotencyHeader, hmacKey = PROCESS_SESSION_HMAC_KEY) {
|
|
2865
|
+
if (!isRecord(raw)) throw invalid("request body must be a JSON object");
|
|
2866
|
+
if (raw.schemaVersion !== ROUTE_LEASE_REQUEST_SCHEMA) {
|
|
2867
|
+
throw invalid(`schemaVersion must be ${ROUTE_LEASE_REQUEST_SCHEMA}`);
|
|
2868
|
+
}
|
|
2869
|
+
const consumer = normalizedString(raw.consumer, "consumer", ROUTE_LEASE_MAX_CONSUMER_BYTES);
|
|
2870
|
+
const runtime = raw.runtime;
|
|
2871
|
+
if (runtime !== "claude" && runtime !== "codex") {
|
|
2872
|
+
throw new RouteLeaseError("runtime_unsupported", "runtime is unsupported");
|
|
2873
|
+
}
|
|
2874
|
+
const upstream = normalizeUpstream(raw.upstream);
|
|
2875
|
+
if (typeof raw.model !== "string" || raw.model.trim().length === 0) {
|
|
2876
|
+
throw new RouteLeaseError("model_not_configured", "model is not configured");
|
|
2877
|
+
}
|
|
2878
|
+
const model = normalizedString(raw.model, "model", ROUTE_LEASE_MAX_MODEL_BYTES);
|
|
2879
|
+
const ttlSeconds = normalizeRouteLeaseTtl(raw.ttlSeconds);
|
|
2880
|
+
const idempotencyKey = normalizeRouteLeaseIdempotencyKey(idempotencyHeader);
|
|
2881
|
+
let execution;
|
|
2882
|
+
let semanticExecution;
|
|
2883
|
+
if (raw.execution !== void 0) {
|
|
2884
|
+
if (!isRecord(raw.execution)) throw invalid("execution must be a JSON object");
|
|
2885
|
+
const runId = optionalExecutionId(raw.execution.runId, "execution.runId");
|
|
2886
|
+
const stageId = optionalExecutionId(raw.execution.stageId, "execution.stageId");
|
|
2887
|
+
let attempt;
|
|
2888
|
+
if (raw.execution.attempt !== void 0) {
|
|
2889
|
+
if (!Number.isSafeInteger(raw.execution.attempt) || raw.execution.attempt < 1) {
|
|
2890
|
+
throw invalid("execution.attempt must be a positive integer");
|
|
2891
|
+
}
|
|
2892
|
+
attempt = raw.execution.attempt;
|
|
2893
|
+
}
|
|
2894
|
+
let sessionId;
|
|
2895
|
+
let sessionIdHash;
|
|
2896
|
+
if (raw.execution.sessionId !== void 0) {
|
|
2897
|
+
sessionId = normalizedString(raw.execution.sessionId, "execution.sessionId", ROUTE_LEASE_MAX_SESSION_ID_BYTES);
|
|
2898
|
+
sessionIdHash = hashRouteLeaseSessionId(sessionId, hmacKey);
|
|
2899
|
+
}
|
|
2900
|
+
if (runId !== void 0 || stageId !== void 0 || attempt !== void 0 || sessionIdHash !== void 0) {
|
|
2901
|
+
execution = { runId, stageId, attempt, sessionIdHash };
|
|
2902
|
+
semanticExecution = { runId, stageId, attempt, sessionId };
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
const request = {
|
|
2906
|
+
schemaVersion: ROUTE_LEASE_REQUEST_SCHEMA,
|
|
2907
|
+
consumer,
|
|
2908
|
+
runtime,
|
|
2909
|
+
upstream,
|
|
2910
|
+
model,
|
|
2911
|
+
...execution ? { execution } : {},
|
|
2912
|
+
ttlSeconds
|
|
2913
|
+
};
|
|
2914
|
+
const semanticPayload = {
|
|
2915
|
+
schemaVersion: request.schemaVersion,
|
|
2916
|
+
consumer,
|
|
2917
|
+
runtime,
|
|
2918
|
+
upstream,
|
|
2919
|
+
model,
|
|
2920
|
+
...semanticExecution ? { execution: semanticExecution } : {},
|
|
2921
|
+
ttlSeconds
|
|
2922
|
+
};
|
|
2923
|
+
return { request, idempotencyKey, payloadHash: hashRouteLeasePayload(semanticPayload) };
|
|
2924
|
+
}
|
|
2925
|
+
function routeLeaseRuntime(runtime) {
|
|
2926
|
+
switch (runtime) {
|
|
2927
|
+
case "claude":
|
|
2928
|
+
return ROUTE_LEASE_RUNTIME_TABLE.claude;
|
|
2929
|
+
case "codex":
|
|
2930
|
+
return ROUTE_LEASE_RUNTIME_TABLE.codex;
|
|
2931
|
+
default: {
|
|
2932
|
+
const exhaustive = runtime;
|
|
2933
|
+
throw new RouteLeaseError("runtime_unsupported", `runtime is unsupported: ${String(exhaustive)}`);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
// src/provider-proxy/RouteLeaseManager.ts
|
|
2939
|
+
var SYSTEM_ROUTE_LEASE_CLOCK = {
|
|
2940
|
+
now: () => Date.now(),
|
|
2941
|
+
setTimeout(callback, delayMs) {
|
|
2942
|
+
const timer = setTimeout(callback, delayMs);
|
|
2943
|
+
timer.unref?.();
|
|
2944
|
+
return timer;
|
|
2945
|
+
},
|
|
2946
|
+
clearTimeout(handle) {
|
|
2947
|
+
clearTimeout(handle);
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
function cloneUpstream(upstream) {
|
|
2951
|
+
return { ...upstream };
|
|
2952
|
+
}
|
|
2953
|
+
function metadataCopy(metadata) {
|
|
2954
|
+
return {
|
|
2955
|
+
...metadata,
|
|
2956
|
+
upstream: cloneUpstream(metadata.upstream),
|
|
2957
|
+
...metadata.execution ? { execution: { ...metadata.execution } } : {}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
var RouteLeaseManager = class {
|
|
2961
|
+
constructor(routePort, targetResolver, descriptors, options = {}) {
|
|
2962
|
+
this.routePort = routePort;
|
|
2963
|
+
this.targetResolver = targetResolver;
|
|
2964
|
+
this.descriptors = descriptors;
|
|
2965
|
+
this.clock = options.clock ?? SYSTEM_ROUTE_LEASE_CLOCK;
|
|
2966
|
+
this.hmacKey = options.hmacKey ?? randomBytes3(32);
|
|
2967
|
+
this.tombstoneMaxCount = Math.max(1, options.tombstoneMaxCount ?? 512);
|
|
2968
|
+
this.tombstoneMaxAgeMs = Math.max(1, options.tombstoneMaxAgeMs ?? 60 * 60 * 1e3);
|
|
2969
|
+
this.logger = options.logger;
|
|
2970
|
+
this.afterPublish = options.afterPublish;
|
|
2971
|
+
}
|
|
2972
|
+
routePort;
|
|
2973
|
+
targetResolver;
|
|
2974
|
+
descriptors;
|
|
2975
|
+
active = /* @__PURE__ */ new Map();
|
|
2976
|
+
idempotency = /* @__PURE__ */ new Map();
|
|
2977
|
+
pendingCreates = /* @__PURE__ */ new Map();
|
|
2978
|
+
tombstones = /* @__PURE__ */ new Map();
|
|
2979
|
+
clock;
|
|
2980
|
+
hmacKey;
|
|
2981
|
+
tombstoneMaxCount;
|
|
2982
|
+
tombstoneMaxAgeMs;
|
|
2983
|
+
logger;
|
|
2984
|
+
afterPublish;
|
|
2985
|
+
closed = false;
|
|
2986
|
+
parse(raw, idempotencyKey) {
|
|
2987
|
+
return parseRouteLeaseCreate(raw, idempotencyKey, this.hmacKey);
|
|
2988
|
+
}
|
|
2989
|
+
async createFromRequest(raw, idempotencyKey) {
|
|
2990
|
+
return this.create(this.parse(raw, idempotencyKey));
|
|
2991
|
+
}
|
|
2992
|
+
async create(input) {
|
|
2993
|
+
if (this.closed) throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
|
|
2994
|
+
const request = input.request;
|
|
2995
|
+
const scope = `${request.consumer}\0${input.idempotencyKey}`;
|
|
2996
|
+
const existing = this.idempotency.get(scope);
|
|
2997
|
+
if (existing) {
|
|
2998
|
+
const lease = this.active.get(existing.leaseId);
|
|
2999
|
+
if (!lease) {
|
|
3000
|
+
this.idempotency.delete(scope);
|
|
3001
|
+
} else if (existing.payloadHash !== input.payloadHash) {
|
|
3002
|
+
throw new RouteLeaseError("idempotency_conflict", "idempotency key is already used for a different live request");
|
|
3003
|
+
} else {
|
|
3004
|
+
return { created: false, result: lease.result };
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
const pending = this.pendingCreates.get(scope);
|
|
3008
|
+
if (pending) {
|
|
3009
|
+
if (pending.payloadHash !== input.payloadHash) {
|
|
3010
|
+
throw new RouteLeaseError("idempotency_conflict", "idempotency key is already used for a different live request");
|
|
3011
|
+
}
|
|
3012
|
+
const outcome2 = await pending.outcome;
|
|
3013
|
+
return { created: false, result: outcome2.result };
|
|
3014
|
+
}
|
|
3015
|
+
const outcome = this.createNew(input, scope);
|
|
3016
|
+
const pendingCreate = { payloadHash: input.payloadHash, outcome };
|
|
3017
|
+
this.pendingCreates.set(scope, pendingCreate);
|
|
3018
|
+
try {
|
|
3019
|
+
return await outcome;
|
|
3020
|
+
} finally {
|
|
3021
|
+
if (this.pendingCreates.get(scope) === pendingCreate) this.pendingCreates.delete(scope);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
async createNew(input, scope) {
|
|
3025
|
+
const request = input.request;
|
|
3026
|
+
if (!this.routePort.isReady()) {
|
|
3027
|
+
throw new RouteLeaseError("daemon_not_ready", "resident provider proxy is not ready");
|
|
3028
|
+
}
|
|
3029
|
+
if (!this.descriptors.has(request.runtime)) {
|
|
3030
|
+
throw new RouteLeaseError("runtime_unsupported", "runtime launch adapter is unavailable");
|
|
3031
|
+
}
|
|
3032
|
+
const leaseId = randomUUID();
|
|
3033
|
+
const resolved = await this.targetResolver.resolve(request);
|
|
3034
|
+
this.assertOpen();
|
|
3035
|
+
const route = {
|
|
3036
|
+
...resolved,
|
|
3037
|
+
sessionId: request.execution?.sessionIdHash ?? null,
|
|
3038
|
+
routeLease: {
|
|
3039
|
+
leaseId,
|
|
3040
|
+
consumer: request.consumer,
|
|
3041
|
+
...request.execution?.runId ? { runId: request.execution.runId } : {},
|
|
3042
|
+
...request.execution?.stageId ? { stageId: request.execution.stageId } : {}
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
const now = this.clock.now();
|
|
3046
|
+
const ttlMs = request.ttlSeconds * 1e3;
|
|
3047
|
+
const expiresAtMs = now + ttlMs;
|
|
3048
|
+
let routeToken;
|
|
3049
|
+
let published = false;
|
|
3050
|
+
let activeLease;
|
|
3051
|
+
try {
|
|
3052
|
+
routeToken = this.routePort.addRoute(route, {
|
|
3053
|
+
idleMs: ttlMs,
|
|
3054
|
+
onActivity: (at) => this.noteActivity(leaseId, at),
|
|
3055
|
+
onEvicted: () => this.onRouteEvicted(leaseId)
|
|
3056
|
+
});
|
|
3057
|
+
const launch = this.descriptors.build(request.runtime, {
|
|
3058
|
+
proxyBaseUrl: this.routePort.getBaseUrl(),
|
|
3059
|
+
model: request.model,
|
|
3060
|
+
routeToken
|
|
3061
|
+
});
|
|
3062
|
+
this.assertOpen();
|
|
3063
|
+
const metadata = {
|
|
3064
|
+
leaseId,
|
|
3065
|
+
consumer: request.consumer,
|
|
3066
|
+
runtime: request.runtime,
|
|
3067
|
+
upstream: cloneUpstream(request.upstream),
|
|
3068
|
+
model: request.model,
|
|
3069
|
+
createdAt: new Date(now).toISOString(),
|
|
3070
|
+
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
3071
|
+
status: "active",
|
|
3072
|
+
...request.execution ? { execution: { ...request.execution } } : {}
|
|
3073
|
+
};
|
|
3074
|
+
const result = {
|
|
3075
|
+
schemaVersion: ROUTE_LEASE_RESULT_SCHEMA,
|
|
3076
|
+
...metadata,
|
|
3077
|
+
status: "active",
|
|
3078
|
+
launch: { env: { ...launch.env }, extraArgs: [...launch.extraArgs] }
|
|
3079
|
+
};
|
|
3080
|
+
const timer = this.clock.setTimeout(() => this.expire(leaseId), ttlMs);
|
|
3081
|
+
activeLease = {
|
|
3082
|
+
leaseId,
|
|
3083
|
+
routeToken,
|
|
3084
|
+
idempotencyScope: scope,
|
|
3085
|
+
payloadHash: input.payloadHash,
|
|
3086
|
+
request,
|
|
3087
|
+
route,
|
|
3088
|
+
result,
|
|
3089
|
+
metadata,
|
|
3090
|
+
expiresAtMs,
|
|
3091
|
+
timer
|
|
3092
|
+
};
|
|
3093
|
+
this.assertOpen();
|
|
3094
|
+
this.active.set(leaseId, activeLease);
|
|
3095
|
+
this.idempotency.set(scope, { leaseId, payloadHash: input.payloadHash });
|
|
3096
|
+
published = true;
|
|
3097
|
+
this.afterPublish?.();
|
|
3098
|
+
if (this.closed) {
|
|
3099
|
+
this.finalize(activeLease, "released");
|
|
3100
|
+
throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
|
|
3101
|
+
}
|
|
3102
|
+
return { created: true, result };
|
|
3103
|
+
} catch (error) {
|
|
3104
|
+
if (published && activeLease) {
|
|
3105
|
+
this.active.delete(leaseId);
|
|
3106
|
+
this.idempotency.delete(scope);
|
|
3107
|
+
this.clock.clearTimeout(activeLease.timer);
|
|
3108
|
+
}
|
|
3109
|
+
if (routeToken) this.safeRemoveRoute(routeToken, leaseId);
|
|
3110
|
+
if (error instanceof RouteLeaseError) throw error;
|
|
3111
|
+
throw new RouteLeaseError("upstream_unavailable", "route lease creation failed safely", { cause: error });
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
list() {
|
|
3115
|
+
this.pruneTombstones();
|
|
3116
|
+
return [
|
|
3117
|
+
...[...this.active.values()].map((lease) => metadataCopy(lease.metadata)),
|
|
3118
|
+
...[...this.tombstones.values()].map((entry) => metadataCopy(entry.metadata))
|
|
3119
|
+
].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
3120
|
+
}
|
|
3121
|
+
get(leaseId) {
|
|
3122
|
+
const active = this.active.get(leaseId);
|
|
3123
|
+
if (active) return metadataCopy(active.metadata);
|
|
3124
|
+
this.pruneTombstones();
|
|
3125
|
+
const tombstone = this.tombstones.get(leaseId);
|
|
3126
|
+
if (tombstone) return metadataCopy(tombstone.metadata);
|
|
3127
|
+
throw new RouteLeaseError("lease_not_found", "route lease was not found");
|
|
3128
|
+
}
|
|
3129
|
+
renew(leaseId, ttlSeconds) {
|
|
3130
|
+
const lease = this.active.get(leaseId);
|
|
3131
|
+
if (!lease) {
|
|
3132
|
+
this.pruneTombstones();
|
|
3133
|
+
const known = this.tombstones.get(leaseId);
|
|
3134
|
+
if (known?.metadata.status === "expired") {
|
|
3135
|
+
throw new RouteLeaseError("lease_expired", "route lease has expired");
|
|
3136
|
+
}
|
|
3137
|
+
throw new RouteLeaseError("lease_not_found", "route lease was not found");
|
|
3138
|
+
}
|
|
3139
|
+
const now = this.clock.now();
|
|
3140
|
+
if (now >= lease.expiresAtMs) {
|
|
3141
|
+
this.finalize(lease, "expired");
|
|
3142
|
+
throw new RouteLeaseError("lease_expired", "route lease has expired");
|
|
3143
|
+
}
|
|
3144
|
+
const ttlMs = ttlSeconds * 1e3;
|
|
3145
|
+
const expiresAtMs = now + ttlMs;
|
|
3146
|
+
const nextTimer = this.clock.setTimeout(() => this.expire(leaseId), ttlMs);
|
|
3147
|
+
const previousTimer = lease.timer;
|
|
3148
|
+
lease.timer = nextTimer;
|
|
3149
|
+
lease.expiresAtMs = expiresAtMs;
|
|
3150
|
+
lease.metadata = { ...lease.metadata, expiresAt: new Date(expiresAtMs).toISOString() };
|
|
3151
|
+
this.clock.clearTimeout(previousTimer);
|
|
3152
|
+
if (!this.routePort.renewRoute(lease.routeToken, ttlMs)) {
|
|
3153
|
+
this.finalize(lease, "expired", true);
|
|
3154
|
+
throw new RouteLeaseError("lease_expired", "route lease route has expired");
|
|
3155
|
+
}
|
|
3156
|
+
return { leaseId, expiresAt: lease.metadata.expiresAt, status: "active" };
|
|
3157
|
+
}
|
|
3158
|
+
release(leaseId) {
|
|
3159
|
+
const lease = this.active.get(leaseId);
|
|
3160
|
+
if (!lease) return { leaseId, released: false };
|
|
3161
|
+
this.finalize(lease, "released");
|
|
3162
|
+
return { leaseId, released: true };
|
|
3163
|
+
}
|
|
3164
|
+
shutdown() {
|
|
3165
|
+
if (this.closed) return;
|
|
3166
|
+
this.closed = true;
|
|
3167
|
+
for (const lease of [...this.active.values()]) {
|
|
3168
|
+
try {
|
|
3169
|
+
this.finalize(lease, "released");
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
this.logger?.warn("route lease cleanup failed during shutdown", { leaseId: lease.leaseId, error: safeErrorName(error) });
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
activeCount() {
|
|
3176
|
+
return this.active.size;
|
|
3177
|
+
}
|
|
3178
|
+
assertOpen() {
|
|
3179
|
+
if (this.closed) {
|
|
3180
|
+
throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
noteActivity(leaseId, at) {
|
|
3184
|
+
const lease = this.active.get(leaseId);
|
|
3185
|
+
if (!lease || at >= lease.expiresAtMs) return;
|
|
3186
|
+
lease.metadata = { ...lease.metadata, lastActivityAt: new Date(at).toISOString() };
|
|
3187
|
+
}
|
|
3188
|
+
onRouteEvicted(leaseId) {
|
|
3189
|
+
const lease = this.active.get(leaseId);
|
|
3190
|
+
if (lease) this.finalize(lease, "expired", true);
|
|
3191
|
+
}
|
|
3192
|
+
expire(leaseId) {
|
|
3193
|
+
const lease = this.active.get(leaseId);
|
|
3194
|
+
if (!lease) return;
|
|
3195
|
+
const remaining = lease.expiresAtMs - this.clock.now();
|
|
3196
|
+
if (remaining > 0) {
|
|
3197
|
+
lease.timer = this.clock.setTimeout(() => this.expire(leaseId), remaining);
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
this.finalize(lease, "expired");
|
|
3201
|
+
}
|
|
3202
|
+
finalize(lease, status, routeAlreadyGone = false) {
|
|
3203
|
+
if (this.active.get(lease.leaseId) !== lease) return;
|
|
3204
|
+
this.active.delete(lease.leaseId);
|
|
3205
|
+
this.idempotency.delete(lease.idempotencyScope);
|
|
3206
|
+
this.clock.clearTimeout(lease.timer);
|
|
3207
|
+
if (!routeAlreadyGone) this.safeRemoveRoute(lease.routeToken, lease.leaseId);
|
|
3208
|
+
const metadata = { ...lease.metadata, status };
|
|
3209
|
+
this.tombstones.delete(lease.leaseId);
|
|
3210
|
+
this.tombstones.set(lease.leaseId, { metadata, terminalAtMs: this.clock.now() });
|
|
3211
|
+
this.pruneTombstones();
|
|
3212
|
+
}
|
|
3213
|
+
safeRemoveRoute(routeToken, leaseId) {
|
|
3214
|
+
try {
|
|
3215
|
+
this.routePort.removeRoute(routeToken);
|
|
3216
|
+
} catch (error) {
|
|
3217
|
+
this.logger?.warn("route lease route cleanup failed", { leaseId, error: safeErrorName(error) });
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
pruneTombstones() {
|
|
3221
|
+
const cutoff = this.clock.now() - this.tombstoneMaxAgeMs;
|
|
3222
|
+
for (const [leaseId, tombstone] of this.tombstones) {
|
|
3223
|
+
if (tombstone.terminalAtMs < cutoff) this.tombstones.delete(leaseId);
|
|
3224
|
+
}
|
|
3225
|
+
while (this.tombstones.size > this.tombstoneMaxCount) {
|
|
3226
|
+
const oldest = this.tombstones.keys().next().value;
|
|
3227
|
+
if (!oldest) break;
|
|
3228
|
+
this.tombstones.delete(oldest);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
function safeErrorName(error) {
|
|
3233
|
+
return error instanceof Error ? error.name : "unknown";
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
// src/outbound-api/gatewayBindingResolver.ts
|
|
3237
|
+
var MESSAGE_FALLBACK_KINDS = ["sonnet", "opus", "haiku", "fable"];
|
|
3238
|
+
function nonBlank(value) {
|
|
3239
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3240
|
+
}
|
|
3241
|
+
function gatewayBindingAllowsKey(binding, apiKeyId) {
|
|
3242
|
+
const scope = binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3243
|
+
return scope === "all" || Boolean(binding.apiKeyIds?.includes(apiKeyId));
|
|
3244
|
+
}
|
|
3245
|
+
function wildcardMatches(pattern, value) {
|
|
3246
|
+
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
|
|
3247
|
+
return new RegExp(`^${escaped}$`, "iu").test(value);
|
|
3248
|
+
}
|
|
3249
|
+
function resolveGatewayModelMapping(mappings, requestedModel) {
|
|
3250
|
+
if (!nonBlank(requestedModel)) return void 0;
|
|
3251
|
+
const wanted = requestedModel.trim();
|
|
3252
|
+
const exact = (mappings ?? []).find(
|
|
3253
|
+
(mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
|
|
3254
|
+
);
|
|
3255
|
+
if (exact && nonBlank(exact.target)) return exact.target.trim();
|
|
3256
|
+
const wildcard = (mappings ?? []).find(
|
|
3257
|
+
(mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
|
|
3258
|
+
);
|
|
3259
|
+
return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
|
|
3260
|
+
}
|
|
3261
|
+
function routeCanServe(binding, requestedModel, role) {
|
|
3262
|
+
if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
|
|
3263
|
+
if (binding.modelMappings?.length) {
|
|
3264
|
+
return nonBlank(resolveGatewayModelMapping(binding.modelMappings, requestedModel));
|
|
3265
|
+
}
|
|
3266
|
+
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3267
|
+
const map = binding.modelMap ?? {};
|
|
3268
|
+
const kind = detectModelKind(binding.endpoint, requestedModel);
|
|
3269
|
+
if (kind && nonBlank(map[kind])) return true;
|
|
3270
|
+
if (binding.endpoint === "messages") {
|
|
3271
|
+
return MESSAGE_FALLBACK_KINDS.some((candidate) => nonBlank(map[candidate]));
|
|
3272
|
+
}
|
|
3273
|
+
return nonBlank(map.codex);
|
|
3274
|
+
}
|
|
3275
|
+
if (binding.endpoint === "chat" && binding.dispatchMode === "prefix") {
|
|
3276
|
+
return resolvePrefixTarget(binding.prefixTargets, requestedModel) !== null;
|
|
3277
|
+
}
|
|
3278
|
+
if (binding.endpoint === "chat") {
|
|
3279
|
+
if (!requestedModel) return false;
|
|
3280
|
+
const wanted = requestedModel.trim().toLowerCase();
|
|
3281
|
+
return (binding.models ?? []).some((ref) => modelIdOf(ref)?.toLowerCase() === wanted);
|
|
3282
|
+
}
|
|
3283
|
+
return role === "background" ? nonBlank(binding.backgroundModel) : nonBlank(binding.defaultModel);
|
|
3284
|
+
}
|
|
3285
|
+
function modelIdOf(ref) {
|
|
3286
|
+
if (!nonBlank(ref)) return void 0;
|
|
3287
|
+
const trimmed = ref.trim();
|
|
3288
|
+
const comma = trimmed.indexOf(",");
|
|
3289
|
+
const modelId = comma >= 0 ? trimmed.slice(comma + 1).trim() : trimmed;
|
|
3290
|
+
return modelId || void 0;
|
|
3291
|
+
}
|
|
3292
|
+
function targetProviderId(target) {
|
|
3293
|
+
return target.providerId.trim();
|
|
3294
|
+
}
|
|
3295
|
+
function targetRef(target, ref) {
|
|
3296
|
+
const modelId = modelIdOf(ref) ?? "";
|
|
3297
|
+
return modelId ? `${targetProviderId(target)},${modelId}` : "";
|
|
3298
|
+
}
|
|
3299
|
+
function targetRefs(target, refs) {
|
|
3300
|
+
return (refs ?? []).map((ref) => targetRef(target, ref)).filter(nonBlank);
|
|
3301
|
+
}
|
|
3302
|
+
function targetPrefixRefs(target, refs) {
|
|
3303
|
+
if (!refs) return void 0;
|
|
3304
|
+
const mapped = {};
|
|
3305
|
+
for (const kind of ["claude", "gpt", "gemini"]) {
|
|
3306
|
+
const ref = targetRef(target, refs[kind]);
|
|
3307
|
+
if (ref) mapped[kind] = ref;
|
|
3308
|
+
}
|
|
3309
|
+
return mapped.claude || mapped.gpt || mapped.gemini ? mapped : void 0;
|
|
3310
|
+
}
|
|
3311
|
+
function applySingleModel(config, binding, targetModel) {
|
|
3312
|
+
const ref = targetRef(binding.target, targetModel);
|
|
3313
|
+
if (!ref) return;
|
|
3314
|
+
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3315
|
+
config.modelMap = Object.fromEntries(
|
|
3316
|
+
modelKindsForEndpoint(binding.endpoint).map((kind) => [kind, ref])
|
|
3317
|
+
);
|
|
3318
|
+
} else if (binding.endpoint === "chat") {
|
|
3319
|
+
config.models = [ref];
|
|
3320
|
+
config.dispatchMode = "list";
|
|
3321
|
+
} else {
|
|
3322
|
+
config.defaultModel = ref;
|
|
3323
|
+
config.backgroundModel = ref;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
function gatewayBindingToEndpointConfig(binding, requestedModel) {
|
|
3327
|
+
const target = binding.target;
|
|
3328
|
+
const config = {
|
|
3329
|
+
endpoint: binding.endpoint,
|
|
3330
|
+
useSubscription: target.kind !== "provider"
|
|
3331
|
+
};
|
|
3332
|
+
const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
|
|
3333
|
+
const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : resolveGatewayModelMapping(binding.modelMappings, requestedModel);
|
|
3334
|
+
if (usesGenericModelHandling) {
|
|
3335
|
+
if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
|
|
3336
|
+
} else if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3337
|
+
config.modelMap = Object.fromEntries(
|
|
3338
|
+
Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
|
|
3339
|
+
);
|
|
3340
|
+
} else if (binding.endpoint === "chat") {
|
|
3341
|
+
config.models = targetRefs(target, binding.models);
|
|
3342
|
+
if (binding.dispatchMode === "prefix") {
|
|
3343
|
+
config.dispatchMode = "prefix";
|
|
3344
|
+
config.prefixTargets = targetPrefixRefs(target, binding.prefixTargets);
|
|
3345
|
+
}
|
|
3346
|
+
} else {
|
|
3347
|
+
config.defaultModel = targetRef(target, binding.defaultModel);
|
|
3348
|
+
config.backgroundModel = targetRef(target, binding.backgroundModel);
|
|
3349
|
+
if (binding.backgroundModelIds) config.backgroundModelIds = [...binding.backgroundModelIds];
|
|
3350
|
+
}
|
|
3351
|
+
if (target.kind === "account") {
|
|
3352
|
+
config.boundAccountId = target.accountId;
|
|
3353
|
+
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3354
|
+
} else if (target.kind === "account-group") {
|
|
3355
|
+
config.boundAccountGroup = target.group;
|
|
3356
|
+
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3357
|
+
} else if (target.kind === "provider" && target.keyId) {
|
|
3358
|
+
config.boundKeyId = target.keyId;
|
|
3359
|
+
config.boundKeyFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3360
|
+
}
|
|
3361
|
+
return config;
|
|
3362
|
+
}
|
|
3363
|
+
function candidateGatewayBindings(bindings, apiKeyId, endpoint) {
|
|
3364
|
+
const candidates = (bindings ?? []).filter(
|
|
3365
|
+
(binding) => binding.enabled && binding.endpoint === endpoint && gatewayBindingAllowsKey(binding, apiKeyId)
|
|
3366
|
+
);
|
|
3367
|
+
const scopeOf = (binding) => binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3368
|
+
const scoped = candidates.filter((binding) => scopeOf(binding) === "selected");
|
|
3369
|
+
const pool = scoped.length > 0 ? scoped : candidates.filter((binding) => scopeOf(binding) === "all");
|
|
3370
|
+
return pool.sort(
|
|
3371
|
+
(left, right) => (left.priority ?? 100) - (right.priority ?? 100) || left.id.localeCompare(right.id)
|
|
3372
|
+
);
|
|
3373
|
+
}
|
|
3374
|
+
function candidateBackgroundModelIds(bindings, apiKeyId, endpoint) {
|
|
3375
|
+
const ids = [
|
|
3376
|
+
...new Set(
|
|
3377
|
+
candidateGatewayBindings(bindings, apiKeyId, endpoint).flatMap(
|
|
3378
|
+
(binding) => binding.backgroundModelIds ?? []
|
|
3379
|
+
)
|
|
3380
|
+
)
|
|
3381
|
+
];
|
|
3382
|
+
return ids.length > 0 ? ids : void 0;
|
|
3383
|
+
}
|
|
3384
|
+
function resolveGatewayBinding(input) {
|
|
3385
|
+
const candidates = candidateGatewayBindings(input.bindings, input.apiKeyId, input.endpoint);
|
|
3386
|
+
const binding = candidates.find(
|
|
3387
|
+
(candidate) => candidate.fallback === "fail" || routeCanServe(candidate, input.requestedModel, input.role)
|
|
3388
|
+
) ?? candidates[0];
|
|
3389
|
+
if (!binding) return { source: "none" };
|
|
3390
|
+
return {
|
|
3391
|
+
source: "binding",
|
|
3392
|
+
binding,
|
|
3393
|
+
config: gatewayBindingToEndpointConfig(binding, input.requestedModel)
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// src/provider-proxy/RouteLeaseTargetResolver.ts
|
|
3398
|
+
function resolveEnvKey(raw) {
|
|
3399
|
+
if (!raw) return "";
|
|
3400
|
+
return raw.startsWith("$") ? process.env[raw.slice(1)] ?? "" : raw;
|
|
3401
|
+
}
|
|
3402
|
+
function providerSupportsModel(provider, model) {
|
|
3403
|
+
const flat = provider.models ?? [];
|
|
3404
|
+
const configured = provider.modelConfigs ?? [];
|
|
3405
|
+
if (flat.length === 0 && configured.length === 0) return false;
|
|
3406
|
+
return flat.includes(model) || configured.some((entry) => entry.id === model && entry.enabled !== false);
|
|
3407
|
+
}
|
|
3408
|
+
function syntheticBinding(request) {
|
|
3409
|
+
const runtime = routeLeaseRuntime(request.runtime);
|
|
3410
|
+
return {
|
|
3411
|
+
id: "route-lease-ephemeral",
|
|
3412
|
+
name: "Route Lease ephemeral target",
|
|
3413
|
+
enabled: true,
|
|
3414
|
+
endpoint: runtime.endpoint,
|
|
3415
|
+
target: request.upstream,
|
|
3416
|
+
fallback: "fail",
|
|
3417
|
+
modelMode: "passthrough"
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
function transformerRegistered(llmConfig, name) {
|
|
3421
|
+
return Boolean(llmConfig.getTransformerService()?.getTransformer(name));
|
|
3422
|
+
}
|
|
3423
|
+
var RouteLeaseTargetResolver = class {
|
|
3424
|
+
constructor(llmConfig, options = {}) {
|
|
3425
|
+
this.llmConfig = llmConfig;
|
|
3426
|
+
this.options = options;
|
|
2376
3427
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
3428
|
+
llmConfig;
|
|
3429
|
+
options;
|
|
3430
|
+
async resolve(request) {
|
|
3431
|
+
const runtime = routeLeaseRuntime(request.runtime);
|
|
3432
|
+
await this.preflightTarget(request);
|
|
3433
|
+
const binding = syntheticBinding(request);
|
|
3434
|
+
const config = gatewayBindingToEndpointConfig(binding, request.model);
|
|
3435
|
+
const result = await resolveRoute({
|
|
3436
|
+
config,
|
|
3437
|
+
ingressFormat: runtime.ingressFormat,
|
|
3438
|
+
llmConfig: this.llmConfig,
|
|
3439
|
+
requestedModel: request.model,
|
|
3440
|
+
sessionId: request.execution?.sessionIdHash ?? null
|
|
3441
|
+
});
|
|
3442
|
+
if (!result.ok) {
|
|
3443
|
+
throw new RouteLeaseError(
|
|
3444
|
+
result.error.status === 404 ? "upstream_not_found" : "upstream_unavailable",
|
|
3445
|
+
"the selected upstream could not be resolved"
|
|
3446
|
+
);
|
|
3447
|
+
}
|
|
3448
|
+
return { ...result.route, requestedModel: void 0 };
|
|
2380
3449
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
3450
|
+
async preflightTarget(request) {
|
|
3451
|
+
const target = request.upstream;
|
|
3452
|
+
if (target.kind !== "provider") {
|
|
3453
|
+
await this.preflightSubscription(target, request.model);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
const provider = await this.llmConfig.getProvider(target.providerId);
|
|
3457
|
+
if (!provider) throw new RouteLeaseError("upstream_not_found", "provider was not found");
|
|
3458
|
+
if (provider.enabled === false) throw new RouteLeaseError("upstream_unavailable", "provider is disabled");
|
|
3459
|
+
if (!providerSupportsModel(provider, request.model)) {
|
|
3460
|
+
throw new RouteLeaseError("model_not_configured", "model is not configured for the provider");
|
|
3461
|
+
}
|
|
3462
|
+
if (target.keyId) {
|
|
3463
|
+
const availability = await this.options.providerKeys?.getKeyAvailability?.(
|
|
3464
|
+
target.providerId,
|
|
3465
|
+
target.keyId
|
|
3466
|
+
);
|
|
3467
|
+
if (availability === "not-found") {
|
|
3468
|
+
throw new RouteLeaseError("upstream_not_found", "the selected provider key was not found");
|
|
3469
|
+
}
|
|
3470
|
+
const usable = availability === "usable" || availability === void 0 && Boolean(this.options.providerKeys) && await this.options.providerKeys.hasUsableKey(target.providerId, target.keyId);
|
|
3471
|
+
if (!usable) {
|
|
3472
|
+
throw new RouteLeaseError("upstream_unavailable", "the selected provider key is unavailable");
|
|
3473
|
+
}
|
|
3474
|
+
} else {
|
|
3475
|
+
const directKey = resolveEnvKey(provider.api_key);
|
|
3476
|
+
const poolAvailability = await this.options.providerKeys?.getPoolAvailability?.(target.providerId);
|
|
3477
|
+
const poolReady = poolAvailability ? poolAvailability.outcome === "usable" : this.options.providerKeys ? await this.options.providerKeys.hasUsableKeys(target.providerId) : false;
|
|
3478
|
+
if (!directKey && poolAvailability?.outcome === "exhausted") {
|
|
3479
|
+
throw new RouteLeaseError("upstream_exhausted", "provider key pool is temporarily exhausted", {
|
|
3480
|
+
retryAfterSeconds: poolAvailability.retryAfterSeconds
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
if (!directKey && !poolReady) {
|
|
3484
|
+
throw new RouteLeaseError("upstream_unavailable", "provider has no usable credential");
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
const sameFormat = request.runtime === "claude" && provider.apiFormat === "anthropic" || request.runtime === "codex" && provider.apiFormat === "openai-response";
|
|
3488
|
+
if (!sameFormat) {
|
|
3489
|
+
try {
|
|
3490
|
+
const chain = await resolveProviderChain(this.llmConfig, provider.id, request.model);
|
|
3491
|
+
if (!chain.hasTransformers) {
|
|
3492
|
+
throw new RouteLeaseError("format_unsupported", "no transformer chain supports this runtime and upstream");
|
|
3493
|
+
}
|
|
3494
|
+
} catch (error) {
|
|
3495
|
+
if (error instanceof RouteLeaseError) throw error;
|
|
3496
|
+
throw new RouteLeaseError("format_unsupported", "the transformer chain could not be resolved", { cause: error });
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
2384
3499
|
}
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
3500
|
+
async preflightSubscription(target, model) {
|
|
3501
|
+
const registry = getSubscriptionRegistryForOutbound();
|
|
3502
|
+
const profile = registry?.getProfile(target.providerId) ?? null;
|
|
3503
|
+
if (!profile) throw new RouteLeaseError("upstream_not_found", "subscription provider was not found");
|
|
3504
|
+
await this.options.subscriptions?.assertAvailable(target, model);
|
|
3505
|
+
const names = profile.resolveProviderTransformerNames?.(model) ?? profile.providerTransformerNames ?? [];
|
|
3506
|
+
if (names.some((name) => !transformerRegistered(this.llmConfig, name))) {
|
|
3507
|
+
throw new RouteLeaseError("format_unsupported", "a required transformer is unavailable");
|
|
3508
|
+
}
|
|
3509
|
+
if (profile.modelTransformerNames?.some((name) => !transformerRegistered(this.llmConfig, name))) {
|
|
3510
|
+
throw new RouteLeaseError("format_unsupported", "a required model transformer is unavailable");
|
|
3511
|
+
}
|
|
2388
3512
|
}
|
|
2389
3513
|
};
|
|
2390
3514
|
|
|
@@ -2531,15 +3655,20 @@ function normalizeAudit(raw) {
|
|
|
2531
3655
|
return {
|
|
2532
3656
|
enabled: a?.enabled === true,
|
|
2533
3657
|
captureBodies: a?.captureBodies === true,
|
|
2534
|
-
maxBodyBytes:
|
|
2535
|
-
clampNumber(a?.maxBodyBytes, 256, 1048576, DEFAULT_AUDIT_CONFIG.maxBodyBytes)
|
|
2536
|
-
),
|
|
3658
|
+
maxBodyBytes: normalizeMaxBodyBytes(a?.maxBodyBytes),
|
|
2537
3659
|
retentionDays: Math.trunc(
|
|
2538
3660
|
clampNumber(a?.retentionDays, 1, 365, DEFAULT_AUDIT_CONFIG.retentionDays)
|
|
2539
3661
|
),
|
|
2540
3662
|
trustForwardedFor: a?.trustForwardedFor === true
|
|
2541
3663
|
};
|
|
2542
3664
|
}
|
|
3665
|
+
function normalizeMaxBodyBytes(value) {
|
|
3666
|
+
if (value === -1) return -1;
|
|
3667
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0) {
|
|
3668
|
+
return DEFAULT_AUDIT_CONFIG.maxBodyBytes;
|
|
3669
|
+
}
|
|
3670
|
+
return Math.trunc(Math.min(1048576, value));
|
|
3671
|
+
}
|
|
2543
3672
|
function normalizeBilling(raw) {
|
|
2544
3673
|
const b = raw?.billing;
|
|
2545
3674
|
const config = {
|
|
@@ -2997,169 +4126,8 @@ function mergeServerConfig(current, patch) {
|
|
|
2997
4126
|
});
|
|
2998
4127
|
}
|
|
2999
4128
|
|
|
3000
|
-
// src/outbound-api/gatewayBindingResolver.ts
|
|
3001
|
-
var MESSAGE_FALLBACK_KINDS = ["sonnet", "opus", "haiku", "fable"];
|
|
3002
|
-
function nonBlank(value) {
|
|
3003
|
-
return typeof value === "string" && value.trim() !== "";
|
|
3004
|
-
}
|
|
3005
|
-
function gatewayBindingAllowsKey(binding, apiKeyId) {
|
|
3006
|
-
const scope = binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3007
|
-
return scope === "all" || Boolean(binding.apiKeyIds?.includes(apiKeyId));
|
|
3008
|
-
}
|
|
3009
|
-
function wildcardMatches(pattern, value) {
|
|
3010
|
-
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
|
|
3011
|
-
return new RegExp(`^${escaped}$`, "iu").test(value);
|
|
3012
|
-
}
|
|
3013
|
-
function resolveGatewayModelMapping(mappings, requestedModel) {
|
|
3014
|
-
if (!nonBlank(requestedModel)) return void 0;
|
|
3015
|
-
const wanted = requestedModel.trim();
|
|
3016
|
-
const exact = (mappings ?? []).find(
|
|
3017
|
-
(mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
|
|
3018
|
-
);
|
|
3019
|
-
if (exact && nonBlank(exact.target)) return exact.target.trim();
|
|
3020
|
-
const wildcard = (mappings ?? []).find(
|
|
3021
|
-
(mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
|
|
3022
|
-
);
|
|
3023
|
-
return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
|
|
3024
|
-
}
|
|
3025
|
-
function routeCanServe(binding, requestedModel, role) {
|
|
3026
|
-
if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
|
|
3027
|
-
if (binding.modelMappings?.length) {
|
|
3028
|
-
return nonBlank(resolveGatewayModelMapping(binding.modelMappings, requestedModel));
|
|
3029
|
-
}
|
|
3030
|
-
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3031
|
-
const map = binding.modelMap ?? {};
|
|
3032
|
-
const kind = detectModelKind(binding.endpoint, requestedModel);
|
|
3033
|
-
if (kind && nonBlank(map[kind])) return true;
|
|
3034
|
-
if (binding.endpoint === "messages") {
|
|
3035
|
-
return MESSAGE_FALLBACK_KINDS.some((candidate) => nonBlank(map[candidate]));
|
|
3036
|
-
}
|
|
3037
|
-
return nonBlank(map.codex);
|
|
3038
|
-
}
|
|
3039
|
-
if (binding.endpoint === "chat" && binding.dispatchMode === "prefix") {
|
|
3040
|
-
return resolvePrefixTarget(binding.prefixTargets, requestedModel) !== null;
|
|
3041
|
-
}
|
|
3042
|
-
if (binding.endpoint === "chat") {
|
|
3043
|
-
if (!requestedModel) return false;
|
|
3044
|
-
const wanted = requestedModel.trim().toLowerCase();
|
|
3045
|
-
return (binding.models ?? []).some((ref) => modelIdOf(ref)?.toLowerCase() === wanted);
|
|
3046
|
-
}
|
|
3047
|
-
return role === "background" ? nonBlank(binding.backgroundModel) : nonBlank(binding.defaultModel);
|
|
3048
|
-
}
|
|
3049
|
-
function modelIdOf(ref) {
|
|
3050
|
-
if (!nonBlank(ref)) return void 0;
|
|
3051
|
-
const trimmed = ref.trim();
|
|
3052
|
-
const comma = trimmed.indexOf(",");
|
|
3053
|
-
const modelId = comma >= 0 ? trimmed.slice(comma + 1).trim() : trimmed;
|
|
3054
|
-
return modelId || void 0;
|
|
3055
|
-
}
|
|
3056
|
-
function targetProviderId(target) {
|
|
3057
|
-
return target.providerId.trim();
|
|
3058
|
-
}
|
|
3059
|
-
function targetRef(target, ref) {
|
|
3060
|
-
const modelId = modelIdOf(ref) ?? "";
|
|
3061
|
-
return modelId ? `${targetProviderId(target)},${modelId}` : "";
|
|
3062
|
-
}
|
|
3063
|
-
function targetRefs(target, refs) {
|
|
3064
|
-
return (refs ?? []).map((ref) => targetRef(target, ref)).filter(nonBlank);
|
|
3065
|
-
}
|
|
3066
|
-
function targetPrefixRefs(target, refs) {
|
|
3067
|
-
if (!refs) return void 0;
|
|
3068
|
-
const mapped = {};
|
|
3069
|
-
for (const kind of ["claude", "gpt", "gemini"]) {
|
|
3070
|
-
const ref = targetRef(target, refs[kind]);
|
|
3071
|
-
if (ref) mapped[kind] = ref;
|
|
3072
|
-
}
|
|
3073
|
-
return mapped.claude || mapped.gpt || mapped.gemini ? mapped : void 0;
|
|
3074
|
-
}
|
|
3075
|
-
function applySingleModel(config, binding, targetModel) {
|
|
3076
|
-
const ref = targetRef(binding.target, targetModel);
|
|
3077
|
-
if (!ref) return;
|
|
3078
|
-
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3079
|
-
config.modelMap = Object.fromEntries(
|
|
3080
|
-
modelKindsForEndpoint(binding.endpoint).map((kind) => [kind, ref])
|
|
3081
|
-
);
|
|
3082
|
-
} else if (binding.endpoint === "chat") {
|
|
3083
|
-
config.models = [ref];
|
|
3084
|
-
config.dispatchMode = "list";
|
|
3085
|
-
} else {
|
|
3086
|
-
config.defaultModel = ref;
|
|
3087
|
-
config.backgroundModel = ref;
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3090
|
-
function gatewayBindingToEndpointConfig(binding, requestedModel) {
|
|
3091
|
-
const target = binding.target;
|
|
3092
|
-
const config = {
|
|
3093
|
-
endpoint: binding.endpoint,
|
|
3094
|
-
useSubscription: target.kind !== "provider"
|
|
3095
|
-
};
|
|
3096
|
-
const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
|
|
3097
|
-
const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : resolveGatewayModelMapping(binding.modelMappings, requestedModel);
|
|
3098
|
-
if (usesGenericModelHandling) {
|
|
3099
|
-
if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
|
|
3100
|
-
} else if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3101
|
-
config.modelMap = Object.fromEntries(
|
|
3102
|
-
Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
|
|
3103
|
-
);
|
|
3104
|
-
} else if (binding.endpoint === "chat") {
|
|
3105
|
-
config.models = targetRefs(target, binding.models);
|
|
3106
|
-
if (binding.dispatchMode === "prefix") {
|
|
3107
|
-
config.dispatchMode = "prefix";
|
|
3108
|
-
config.prefixTargets = targetPrefixRefs(target, binding.prefixTargets);
|
|
3109
|
-
}
|
|
3110
|
-
} else {
|
|
3111
|
-
config.defaultModel = targetRef(target, binding.defaultModel);
|
|
3112
|
-
config.backgroundModel = targetRef(target, binding.backgroundModel);
|
|
3113
|
-
if (binding.backgroundModelIds) config.backgroundModelIds = [...binding.backgroundModelIds];
|
|
3114
|
-
}
|
|
3115
|
-
if (target.kind === "account") {
|
|
3116
|
-
config.boundAccountId = target.accountId;
|
|
3117
|
-
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3118
|
-
} else if (target.kind === "account-group") {
|
|
3119
|
-
config.boundAccountGroup = target.group;
|
|
3120
|
-
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3121
|
-
} else if (target.kind === "provider" && target.keyId) {
|
|
3122
|
-
config.boundKeyId = target.keyId;
|
|
3123
|
-
config.boundKeyFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3124
|
-
}
|
|
3125
|
-
return config;
|
|
3126
|
-
}
|
|
3127
|
-
function candidateGatewayBindings(bindings, apiKeyId, endpoint) {
|
|
3128
|
-
const candidates = (bindings ?? []).filter(
|
|
3129
|
-
(binding) => binding.enabled && binding.endpoint === endpoint && gatewayBindingAllowsKey(binding, apiKeyId)
|
|
3130
|
-
);
|
|
3131
|
-
const scopeOf = (binding) => binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3132
|
-
const scoped = candidates.filter((binding) => scopeOf(binding) === "selected");
|
|
3133
|
-
const pool = scoped.length > 0 ? scoped : candidates.filter((binding) => scopeOf(binding) === "all");
|
|
3134
|
-
return pool.sort(
|
|
3135
|
-
(left, right) => (left.priority ?? 100) - (right.priority ?? 100) || left.id.localeCompare(right.id)
|
|
3136
|
-
);
|
|
3137
|
-
}
|
|
3138
|
-
function candidateBackgroundModelIds(bindings, apiKeyId, endpoint) {
|
|
3139
|
-
const ids = [
|
|
3140
|
-
...new Set(
|
|
3141
|
-
candidateGatewayBindings(bindings, apiKeyId, endpoint).flatMap(
|
|
3142
|
-
(binding) => binding.backgroundModelIds ?? []
|
|
3143
|
-
)
|
|
3144
|
-
)
|
|
3145
|
-
];
|
|
3146
|
-
return ids.length > 0 ? ids : void 0;
|
|
3147
|
-
}
|
|
3148
|
-
function resolveGatewayBinding(input) {
|
|
3149
|
-
const candidates = candidateGatewayBindings(input.bindings, input.apiKeyId, input.endpoint);
|
|
3150
|
-
const binding = candidates.find(
|
|
3151
|
-
(candidate) => candidate.fallback === "fail" || routeCanServe(candidate, input.requestedModel, input.role)
|
|
3152
|
-
) ?? candidates[0];
|
|
3153
|
-
if (!binding) return { source: "none" };
|
|
3154
|
-
return {
|
|
3155
|
-
source: "binding",
|
|
3156
|
-
binding,
|
|
3157
|
-
config: gatewayBindingToEndpointConfig(binding, input.requestedModel)
|
|
3158
|
-
};
|
|
3159
|
-
}
|
|
3160
|
-
|
|
3161
4129
|
// src/outbound-api/outboundApiKeyAuth.ts
|
|
3162
|
-
import { createHash, randomBytes as
|
|
4130
|
+
import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
|
|
3163
4131
|
|
|
3164
4132
|
// src/outbound-api/keyPolicy.ts
|
|
3165
4133
|
var DAY_MS = 864e5;
|
|
@@ -3211,7 +4179,7 @@ var BASE62_REJECT_THRESHOLD = 248;
|
|
|
3211
4179
|
function randomBase62(count) {
|
|
3212
4180
|
let out = "";
|
|
3213
4181
|
while (out.length < count) {
|
|
3214
|
-
const buf =
|
|
4182
|
+
const buf = randomBytes4(count - out.length + 8);
|
|
3215
4183
|
for (const byte of buf) {
|
|
3216
4184
|
if (out.length >= count) break;
|
|
3217
4185
|
if (byte >= BASE62_REJECT_THRESHOLD) continue;
|
|
@@ -3224,13 +4192,13 @@ function generateSecret() {
|
|
|
3224
4192
|
return KEY_PREFIX + randomBase62(SECRET_BYTES);
|
|
3225
4193
|
}
|
|
3226
4194
|
function hashKey(secret) {
|
|
3227
|
-
return
|
|
4195
|
+
return createHash2("sha256").update(secret, "utf8").digest("hex");
|
|
3228
4196
|
}
|
|
3229
4197
|
function keyPrefix(secret) {
|
|
3230
4198
|
return secret.slice(0, PREFIX_LEN);
|
|
3231
4199
|
}
|
|
3232
4200
|
function newKeyId() {
|
|
3233
|
-
return `oak_${Date.now()}_${
|
|
4201
|
+
return `oak_${Date.now()}_${randomBytes4(6).toString("hex")}`;
|
|
3234
4202
|
}
|
|
3235
4203
|
async function createNamedKey(db, name) {
|
|
3236
4204
|
const secret = generateSecret();
|
|
@@ -3240,7 +4208,8 @@ async function createNamedKey(db, name) {
|
|
|
3240
4208
|
id,
|
|
3241
4209
|
name,
|
|
3242
4210
|
keyHash: hashKey(secret),
|
|
3243
|
-
keyPrefix: prefix
|
|
4211
|
+
keyPrefix: prefix,
|
|
4212
|
+
plaintext: secret
|
|
3244
4213
|
});
|
|
3245
4214
|
return {
|
|
3246
4215
|
id: row.id,
|
|
@@ -3261,7 +4230,8 @@ async function createIntegrationKey(db, name, allowedEndpoints = ["responses", "
|
|
|
3261
4230
|
keyPrefix: prefix,
|
|
3262
4231
|
kind: "integration",
|
|
3263
4232
|
allowedEndpoints: [...new Set(allowedEndpoints)],
|
|
3264
|
-
loopbackOnly: true
|
|
4233
|
+
loopbackOnly: true,
|
|
4234
|
+
plaintext: secret
|
|
3265
4235
|
});
|
|
3266
4236
|
return {
|
|
3267
4237
|
id: row.id,
|
|
@@ -3748,7 +4718,7 @@ var UserMessageSerialQueue = class {
|
|
|
3748
4718
|
};
|
|
3749
4719
|
|
|
3750
4720
|
// src/outbound-api/voucher.ts
|
|
3751
|
-
import { createHash as
|
|
4721
|
+
import { createHash as createHash3 } from "crypto";
|
|
3752
4722
|
var CODE_ENTROPY_CHARS = 32;
|
|
3753
4723
|
var CODE_PREFIX = "CC_";
|
|
3754
4724
|
var DISPLAY_PREFIX_LEN = 8;
|
|
@@ -3757,7 +4727,7 @@ function generateVoucherCode() {
|
|
|
3757
4727
|
return CODE_PREFIX + randomBase62(CODE_ENTROPY_CHARS);
|
|
3758
4728
|
}
|
|
3759
4729
|
function hashVoucherCode(code) {
|
|
3760
|
-
return
|
|
4730
|
+
return createHash3("sha256").update(code, "utf8").digest("hex");
|
|
3761
4731
|
}
|
|
3762
4732
|
function voucherCodePrefix(code) {
|
|
3763
4733
|
return code.slice(0, DISPLAY_PREFIX_LEN);
|
|
@@ -6725,6 +7695,37 @@ export {
|
|
|
6725
7695
|
extractRouteToken,
|
|
6726
7696
|
isLoopbackAddress,
|
|
6727
7697
|
ProviderProxy,
|
|
7698
|
+
ROUTE_LEASE_REQUEST_SCHEMA,
|
|
7699
|
+
ROUTE_LEASE_RESULT_SCHEMA,
|
|
7700
|
+
ROUTE_LEASE_CAPABILITIES_SCHEMA,
|
|
7701
|
+
ROUTE_LEASE_API_VERSION,
|
|
7702
|
+
ROUTE_LEASE_DEFAULT_TTL_SECONDS,
|
|
7703
|
+
ROUTE_LEASE_MAX_TTL_SECONDS,
|
|
7704
|
+
ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES,
|
|
7705
|
+
ROUTE_LEASE_MAX_CONSUMER_BYTES,
|
|
7706
|
+
ROUTE_LEASE_MAX_EXECUTION_ID_BYTES,
|
|
7707
|
+
ROUTE_LEASE_MAX_SESSION_ID_BYTES,
|
|
7708
|
+
ROUTE_LEASE_MAX_MODEL_BYTES,
|
|
7709
|
+
ROUTE_LEASE_SESSION_HASH_DOMAIN,
|
|
7710
|
+
ROUTE_LEASE_CODEX_TOKEN_ENV,
|
|
7711
|
+
ROUTE_LEASE_RUNTIMES,
|
|
7712
|
+
ROUTE_LEASE_CAPABILITIES,
|
|
7713
|
+
ROUTE_LEASE_RUNTIME_TABLE,
|
|
7714
|
+
RouteLeaseError,
|
|
7715
|
+
normalizeRouteLeaseIdempotencyKey,
|
|
7716
|
+
normalizeRouteLeaseTtl,
|
|
7717
|
+
hashRouteLeaseSessionId,
|
|
7718
|
+
canonicalizeRouteLeasePayload,
|
|
7719
|
+
hashRouteLeasePayload,
|
|
7720
|
+
parseRouteLeaseCreate,
|
|
7721
|
+
routeLeaseRuntime,
|
|
7722
|
+
SYSTEM_ROUTE_LEASE_CLOCK,
|
|
7723
|
+
RouteLeaseManager,
|
|
7724
|
+
gatewayBindingToEndpointConfig,
|
|
7725
|
+
candidateGatewayBindings,
|
|
7726
|
+
candidateBackgroundModelIds,
|
|
7727
|
+
resolveGatewayBinding,
|
|
7728
|
+
RouteLeaseTargetResolver,
|
|
6728
7729
|
getProviderProxy,
|
|
6729
7730
|
__resetProviderProxyForTests,
|
|
6730
7731
|
KeyedMutex,
|
|
@@ -6753,10 +7754,6 @@ export {
|
|
|
6753
7754
|
loadServerConfig,
|
|
6754
7755
|
saveServerConfig,
|
|
6755
7756
|
mergeServerConfig,
|
|
6756
|
-
gatewayBindingToEndpointConfig,
|
|
6757
|
-
candidateGatewayBindings,
|
|
6758
|
-
candidateBackgroundModelIds,
|
|
6759
|
-
resolveGatewayBinding,
|
|
6760
7757
|
computeKeyExpiry,
|
|
6761
7758
|
checkKeyQuota,
|
|
6762
7759
|
randomBase62,
|