@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.
Files changed (137) hide show
  1. package/dist/ApiConverter.cjs +2 -2
  2. package/dist/ApiConverter.js +1 -1
  3. package/dist/{ProviderProxy-C-xqrkKi.d.ts → ProviderProxy--ROec6VI.d.ts} +22 -3
  4. package/dist/{ProviderProxy-CnMQYN59.d.cts → ProviderProxy-QdwNV6l1.d.cts} +22 -3
  5. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +4 -3
  6. package/dist/auth/GeminiCodeAssistProjectResolver.js +3 -2
  7. package/dist/{chunk-3Z5BQZFM.cjs → chunk-235MXAGX.cjs} +3 -1
  8. package/dist/{chunk-SQYVORNP.cjs → chunk-26K6T2YS.cjs} +1520 -523
  9. package/dist/{chunk-DLXZEZWO.cjs → chunk-2E42SAW3.cjs} +42 -4
  10. package/dist/{chunk-7AK7B36B.cjs → chunk-2Z4XM3H5.cjs} +6 -6
  11. package/dist/{chunk-OEWZUX4G.js → chunk-32O35CIF.js} +9 -20
  12. package/dist/chunk-3SCOLIPT.cjs +63 -0
  13. package/dist/{chunk-SYQF7OIR.js → chunk-3WD4HTR2.js} +2 -0
  14. package/dist/{chunk-AT7B7SOZ.js → chunk-5NJLQ6T6.js} +21 -10
  15. package/dist/chunk-5RSZYQJH.cjs +62 -0
  16. package/dist/{chunk-IFXJ2BG7.js → chunk-7C7DET6S.js} +51 -6
  17. package/dist/{chunk-SNYBEXLB.cjs → chunk-EPN67EGP.cjs} +6 -1
  18. package/dist/chunk-FU7EAR73.js +62 -0
  19. package/dist/{chunk-UX7VWA46.js → chunk-FXR6N3SR.js} +39 -1
  20. package/dist/{chunk-WYTEQ4Q5.cjs → chunk-GDIXXKF2.cjs} +21 -10
  21. package/dist/{chunk-WB6PH56J.js → chunk-GFIZ6LQI.js} +12 -8
  22. package/dist/chunk-GL6XM527.js +61 -0
  23. package/dist/{chunk-EJLH6NFT.cjs → chunk-J3QQ6JS5.cjs} +12 -8
  24. package/dist/{chunk-VRFBB5DB.js → chunk-MYYR3HZL.js} +4 -4
  25. package/dist/{chunk-YJVHCHQX.js → chunk-N7OOJI5V.js} +46 -2
  26. package/dist/chunk-NKVZ6Z3Y.cjs +61 -0
  27. package/dist/chunk-OTZC4TNH.cjs +79 -0
  28. package/dist/{chunk-6VUQPOV3.cjs → chunk-OVQBNVSV.cjs} +10 -21
  29. package/dist/{chunk-4VLRHHFE.cjs → chunk-OXHNSZ53.cjs} +9 -11
  30. package/dist/{chunk-6CL5HTPQ.js → chunk-PEGTIPB3.js} +8 -10
  31. package/dist/chunk-PO3XB56Z.js +79 -0
  32. package/dist/{chunk-UDCC7EMR.js → chunk-QYC54PMG.js} +1242 -245
  33. package/dist/{chunk-SIZQQD6T.js → chunk-RJ5ZGNI3.js} +42 -5
  34. package/dist/{chunk-KCVJWJST.cjs → chunk-RJUONLUQ.cjs} +47 -10
  35. package/dist/{chunk-BPKCU575.js → chunk-UAPBLNN2.js} +6 -1
  36. package/dist/{chunk-UNWKG6EI.cjs → chunk-XPIOJQS2.cjs} +51 -6
  37. package/dist/chunk-XXFYLL7R.js +63 -0
  38. package/dist/{chunk-YK57NMIF.cjs → chunk-YWL7YZWS.cjs} +47 -3
  39. package/dist/completion/ApiKeyPoolService.cjs +32 -1
  40. package/dist/completion/ApiKeyPoolService.d.cts +17 -1
  41. package/dist/completion/ApiKeyPoolService.d.ts +17 -1
  42. package/dist/completion/ApiKeyPoolService.js +31 -0
  43. package/dist/completion/CompletionService.cjs +23 -19
  44. package/dist/completion/CompletionService.d.cts +1 -0
  45. package/dist/completion/CompletionService.d.ts +1 -0
  46. package/dist/completion/CompletionService.js +22 -18
  47. package/dist/completion.cjs +23 -19
  48. package/dist/completion.d.cts +1 -0
  49. package/dist/completion.d.ts +1 -0
  50. package/dist/completion.js +22 -18
  51. package/dist/index.cjs +81 -21
  52. package/dist/index.d.cts +5 -5
  53. package/dist/index.d.ts +5 -5
  54. package/dist/index.js +82 -22
  55. package/dist/outbound-api/auditCapture.cjs +2 -2
  56. package/dist/outbound-api/auditCapture.d.cts +3 -4
  57. package/dist/outbound-api/auditCapture.d.ts +3 -4
  58. package/dist/outbound-api/auditCapture.js +1 -1
  59. package/dist/outbound-api/routeResolver.d.cts +4 -3
  60. package/dist/outbound-api/routeResolver.d.ts +4 -3
  61. package/dist/outbound-api/subscriptionRegistryPort.d.cts +1 -0
  62. package/dist/outbound-api/subscriptionRegistryPort.d.ts +1 -0
  63. package/dist/outbound-api/types.d.cts +3 -2
  64. package/dist/outbound-api/types.d.ts +3 -2
  65. package/dist/outbound-api.cjs +23 -19
  66. package/dist/outbound-api.d.cts +8 -6
  67. package/dist/outbound-api.d.ts +8 -6
  68. package/dist/outbound-api.js +22 -18
  69. package/dist/pipeline/AccountAllowanceScheduling.d.cts +3 -2
  70. package/dist/pipeline/AccountAllowanceScheduling.d.ts +3 -2
  71. package/dist/pipeline/AccountRouteActivity.cjs +10 -0
  72. package/dist/pipeline/AccountRouteActivity.d.cts +61 -0
  73. package/dist/pipeline/AccountRouteActivity.d.ts +61 -0
  74. package/dist/pipeline/AccountRouteActivity.js +10 -0
  75. package/dist/pipeline/ServerOverloadCounter.cjs +16 -0
  76. package/dist/pipeline/ServerOverloadCounter.d.cts +77 -0
  77. package/dist/pipeline/ServerOverloadCounter.d.ts +77 -0
  78. package/dist/pipeline/ServerOverloadCounter.js +16 -0
  79. package/dist/pipeline/SubscriptionAccountHealth.cjs +4 -2
  80. package/dist/pipeline/SubscriptionAccountHealth.d.cts +32 -3
  81. package/dist/pipeline/SubscriptionAccountHealth.d.ts +32 -3
  82. package/dist/pipeline/SubscriptionAccountHealth.js +3 -1
  83. package/dist/pipeline/upstreamFetch.cjs +4 -3
  84. package/dist/pipeline/upstreamFetch.d.cts +35 -6
  85. package/dist/pipeline/upstreamFetch.d.ts +35 -6
  86. package/dist/pipeline/upstreamFetch.js +3 -2
  87. package/dist/pipeline/upstreamTrace.cjs +4 -2
  88. package/dist/pipeline/upstreamTrace.d.cts +22 -8
  89. package/dist/pipeline/upstreamTrace.d.ts +22 -8
  90. package/dist/pipeline/upstreamTrace.js +3 -1
  91. package/dist/ports.d.cts +2 -2
  92. package/dist/ports.d.ts +2 -2
  93. package/dist/provider-proxy/ProviderProxy.cjs +23 -19
  94. package/dist/provider-proxy/ProviderProxy.d.cts +2 -1
  95. package/dist/provider-proxy/ProviderProxy.d.ts +2 -1
  96. package/dist/provider-proxy/ProviderProxy.js +22 -18
  97. package/dist/provider-proxy/identity/codexCliHeaders.cjs +10 -0
  98. package/dist/provider-proxy/identity/codexCliHeaders.d.cts +45 -0
  99. package/dist/provider-proxy/identity/codexCliHeaders.d.ts +45 -0
  100. package/dist/provider-proxy/identity/codexCliHeaders.js +10 -0
  101. package/dist/provider-proxy/ingress/providerProxyShared.cjs +23 -19
  102. package/dist/provider-proxy/ingress/providerProxyShared.d.cts +11 -1
  103. package/dist/provider-proxy/ingress/providerProxyShared.d.ts +11 -1
  104. package/dist/provider-proxy/ingress/providerProxyShared.js +22 -18
  105. package/dist/provider-proxy/matchText.cjs +2 -2
  106. package/dist/provider-proxy/matchText.d.cts +1 -3
  107. package/dist/provider-proxy/matchText.d.ts +1 -3
  108. package/dist/provider-proxy/matchText.js +1 -1
  109. package/dist/provider-proxy/types.d.cts +20 -1
  110. package/dist/provider-proxy/types.d.ts +20 -1
  111. package/dist/provider-proxy.cjs +79 -19
  112. package/dist/provider-proxy.d.cts +255 -8
  113. package/dist/provider-proxy.d.ts +255 -8
  114. package/dist/provider-proxy.js +80 -20
  115. package/dist/{routeResolver-CHTv_Bs5.d.ts → routeResolver-C0mkkZaM.d.ts} +1 -1
  116. package/dist/{routeResolver-kepxnfcC.d.cts → routeResolver-CVFE8KKe.d.cts} +1 -1
  117. package/dist/transformer/transformers/AnthropicTransformer.cjs +3 -2
  118. package/dist/transformer/transformers/AnthropicTransformer.js +2 -1
  119. package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
  120. package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
  121. package/dist/transformer/transformers.cjs +7 -6
  122. package/dist/transformer/transformers.js +6 -5
  123. package/dist/transformer.cjs +9 -8
  124. package/dist/transformer.js +8 -7
  125. package/dist/{types-hsoKrVQS.d.ts → types-BB8pxk_3.d.ts} +40 -1
  126. package/dist/{types-H3wGZSOo.d.cts → types-C2MwkLah.d.cts} +40 -1
  127. package/dist/usage/pricing-engine.cjs +2 -2
  128. package/dist/usage/pricing-engine.d.cts +20 -0
  129. package/dist/usage/pricing-engine.d.ts +20 -0
  130. package/dist/usage/pricing-engine.js +1 -1
  131. package/dist/usage/usage-recorder.cjs +2 -2
  132. package/dist/usage/usage-recorder.d.cts +11 -1
  133. package/dist/usage/usage-recorder.d.ts +11 -1
  134. package/dist/usage/usage-recorder.js +1 -1
  135. package/dist/usage.cjs +3 -3
  136. package/dist/usage.js +2 -2
  137. 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
- TransformerChainExecutor
11
- } from "./chunk-WNKWAEUR.js";
12
- import {
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-OEWZUX4G.js";
13
+ } from "./chunk-32O35CIF.js";
21
14
  import {
22
- AnthropicTransformer,
23
- resolveAnthropicMaxTokens
24
- } from "./chunk-6CL5HTPQ.js";
15
+ TransformerChainExecutor
16
+ } from "./chunk-WNKWAEUR.js";
25
17
  import {
26
- getGeminiCodeAssistResolver
27
- } from "./chunk-DRWHTIIZ.js";
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-YJVHCHQX.js";
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-IFXJ2BG7.js";
107
+ } from "./chunk-7C7DET6S.js";
94
108
  import {
95
109
  convertAnthropicToOpenAI,
96
110
  convertOpenAIToAnthropic
97
- } from "./chunk-AT7B7SOZ.js";
111
+ } from "./chunk-5NJLQ6T6.js";
98
112
  import {
99
113
  fetchUpstream
100
- } from "./chunk-SIZQQD6T.js";
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, idleMs) {
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
- for (const entry of this.routes.values()) {
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
- const raw = usageTap.extractUsage(event);
676
- if (raw) capturedUsage = raw;
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
- { providerId: proxyProviderId(plan), accountId: proxyAccountId }
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
- if (plan.isSubscription && plan.transformerProvider.name === "claude") {
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
- { providerId: proxyProviderId(plan), accountId: proxyAccountId }
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
- await handleAnthropicMessagesByo(res, rawBody, route, deps, { callerAnthropicBeta, callerIdentity });
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(res, route, deps, resolvedModel, isStream, sessionKey) : await buildByoPlan4(res, route, deps, resolvedModel, isStream);
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 providerResponse = route.authMode === "subscription" ? await runPipelineWithSubscriptionRetry3(responsesBody, plan) : await runPipelineWithPoolReporting4(responsesBody, plan);
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=${resolvedModel} stream=${isStream}`);
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
- { providerId: plan.proxyProviderId, accountId: proxyAccountId }
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
- /** Register a route for one run; returns the crypto route token (task 2.2). */
2378
- addRoute(context, idleMs) {
2379
- return this.routes.addRoute(context, idleMs);
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
- /** Remove a route at run end. Returns true if an entry existed. */
2382
- removeRoute(token) {
2383
- return this.routes.removeRoute(token);
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
- /** Live-route count (diagnostics / tests). */
2386
- routeCount() {
2387
- return this.routes.size();
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: Math.trunc(
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 randomBytes2 } from "crypto";
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 = randomBytes2(count - out.length + 8);
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 createHash("sha256").update(secret, "utf8").digest("hex");
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()}_${randomBytes2(6).toString("hex")}`;
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 createHash2 } from "crypto";
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 createHash2("sha256").update(code, "utf8").digest("hex");
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,