@omnicross/core 0.1.7 → 0.1.9

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 (117) hide show
  1. package/dist/{ProviderProxy-C-xqrkKi.d.ts → ProviderProxy--ROec6VI.d.ts} +22 -3
  2. package/dist/{ProviderProxy-CnMQYN59.d.cts → ProviderProxy-QdwNV6l1.d.cts} +22 -3
  3. package/dist/auth/GeminiCodeAssistProjectResolver.cjs +5 -3
  4. package/dist/auth/GeminiCodeAssistProjectResolver.js +4 -2
  5. package/dist/{chunk-N5T4M4GU.js → chunk-2UTGX6FM.js} +2 -2
  6. package/dist/{chunk-32O35CIF.js → chunk-45FHEOKW.js} +22 -7
  7. package/dist/{chunk-LVUCAWMK.js → chunk-55FMGZ3L.js} +13 -5
  8. package/dist/{chunk-OF3BECU2.cjs → chunk-56QD3YW6.cjs} +18 -5
  9. package/dist/{chunk-V4TXMEJS.cjs → chunk-5LNYGCIW.cjs} +15 -7
  10. package/dist/chunk-77USGNRW.cjs +37 -0
  11. package/dist/chunk-AOFWNWLT.cjs +203 -0
  12. package/dist/{chunk-LB6EBS6Y.js → chunk-CCVRRJOX.js} +17 -4
  13. package/dist/{chunk-OVQBNVSV.cjs → chunk-CMVXX7ON.cjs} +42 -27
  14. package/dist/{chunk-E3COOKUP.js → chunk-CQIJCPMF.js} +1108 -271
  15. package/dist/{chunk-OJGQKHN5.cjs → chunk-EPN67EGP.cjs} +3 -0
  16. package/dist/{chunk-PEGTIPB3.js → chunk-HKB656B4.js} +37 -43
  17. package/dist/chunk-N2EIM2XJ.js +203 -0
  18. package/dist/{chunk-MYYR3HZL.js → chunk-NGYOO5TO.js} +5 -5
  19. package/dist/{chunk-OKMJLEXX.cjs → chunk-OC6DOOFX.cjs} +5 -5
  20. package/dist/{chunk-2Z4XM3H5.cjs → chunk-P7EM6BND.cjs} +10 -10
  21. package/dist/{chunk-KBM2W6UY.cjs → chunk-RICPANBY.cjs} +41 -24
  22. package/dist/{chunk-TOVPMMHM.js → chunk-S3DYFEBE.js} +37 -20
  23. package/dist/{chunk-SOO562NM.js → chunk-UAPBLNN2.js} +3 -0
  24. package/dist/chunk-WYVGULTQ.js +37 -0
  25. package/dist/{chunk-4BB343BI.cjs → chunk-XELFM7KL.cjs} +1381 -544
  26. package/dist/{chunk-OXHNSZ53.cjs → chunk-ZQQUD66M.cjs} +54 -60
  27. package/dist/completion/ApiKeyPoolService.cjs +32 -1
  28. package/dist/completion/ApiKeyPoolService.d.cts +17 -1
  29. package/dist/completion/ApiKeyPoolService.d.ts +17 -1
  30. package/dist/completion/ApiKeyPoolService.js +31 -0
  31. package/dist/completion/CompletionService.cjs +13 -11
  32. package/dist/completion/CompletionService.js +12 -10
  33. package/dist/completion.cjs +13 -11
  34. package/dist/completion.js +12 -10
  35. package/dist/index.cjs +69 -11
  36. package/dist/index.d.cts +7 -7
  37. package/dist/index.d.ts +7 -7
  38. package/dist/index.js +68 -10
  39. package/dist/outbound-api/routeResolver.d.cts +4 -4
  40. package/dist/outbound-api/routeResolver.d.ts +4 -4
  41. package/dist/outbound-api/types.d.cts +3 -3
  42. package/dist/outbound-api/types.d.ts +3 -3
  43. package/dist/outbound-api.cjs +13 -11
  44. package/dist/outbound-api.d.cts +5 -5
  45. package/dist/outbound-api.d.ts +5 -5
  46. package/dist/outbound-api.js +12 -10
  47. package/dist/pipeline/AccountAllowanceScheduling.d.cts +3 -3
  48. package/dist/pipeline/AccountAllowanceScheduling.d.ts +3 -3
  49. package/dist/pipeline/executeProviderCall.d.cts +2 -0
  50. package/dist/pipeline/executeProviderCall.d.ts +2 -0
  51. package/dist/pipeline/resolveProviderChain.d.cts +1 -0
  52. package/dist/pipeline/resolveProviderChain.d.ts +1 -0
  53. package/dist/pipeline/resolveSubscriptionChain.d.cts +2 -0
  54. package/dist/pipeline/resolveSubscriptionChain.d.ts +2 -0
  55. package/dist/ports/provider-config-source.d.cts +1 -0
  56. package/dist/ports/provider-config-source.d.ts +1 -0
  57. package/dist/ports.d.cts +3 -3
  58. package/dist/ports.d.ts +3 -3
  59. package/dist/provider-proxy/ProviderProxy.cjs +13 -11
  60. package/dist/provider-proxy/ProviderProxy.d.cts +1 -1
  61. package/dist/provider-proxy/ProviderProxy.d.ts +1 -1
  62. package/dist/provider-proxy/ProviderProxy.js +12 -10
  63. package/dist/provider-proxy/ingress/providerProxyShared.cjs +13 -11
  64. package/dist/provider-proxy/ingress/providerProxyShared.js +12 -10
  65. package/dist/provider-proxy/types.d.cts +15 -1
  66. package/dist/provider-proxy/types.d.ts +15 -1
  67. package/dist/provider-proxy.cjs +69 -11
  68. package/dist/provider-proxy.d.cts +254 -8
  69. package/dist/provider-proxy.d.ts +254 -8
  70. package/dist/provider-proxy.js +70 -12
  71. package/dist/{routeResolver-BxOfunbm.d.ts → routeResolver-C0mkkZaM.d.ts} +1 -1
  72. package/dist/{routeResolver-D5OzD0iM.d.cts → routeResolver-CVFE8KKe.d.cts} +1 -1
  73. package/dist/transformer/TransformerChainExecutor.d.cts +2 -0
  74. package/dist/transformer/TransformerChainExecutor.d.ts +2 -0
  75. package/dist/transformer/TransformerService.d.cts +2 -0
  76. package/dist/transformer/TransformerService.d.ts +2 -0
  77. package/dist/transformer/transformers/AnthropicTransformer.cjs +4 -2
  78. package/dist/transformer/transformers/AnthropicTransformer.d.cts +7 -2
  79. package/dist/transformer/transformers/AnthropicTransformer.d.ts +7 -2
  80. package/dist/transformer/transformers/AnthropicTransformer.js +3 -1
  81. package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +5 -3
  82. package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.cts +2 -0
  83. package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.ts +2 -0
  84. package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +4 -2
  85. package/dist/transformer/transformers/GeminiTransformer.cjs +5 -3
  86. package/dist/transformer/transformers/GeminiTransformer.d.cts +4 -2
  87. package/dist/transformer/transformers/GeminiTransformer.d.ts +4 -2
  88. package/dist/transformer/transformers/GeminiTransformer.js +4 -2
  89. package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
  90. package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +4 -2
  91. package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +4 -2
  92. package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
  93. package/dist/transformer/transformers/OpenAITransformer.cjs +3 -2
  94. package/dist/transformer/transformers/OpenAITransformer.d.cts +3 -1
  95. package/dist/transformer/transformers/OpenAITransformer.d.ts +3 -1
  96. package/dist/transformer/transformers/OpenAITransformer.js +2 -1
  97. package/dist/transformer/transformers.cjs +10 -8
  98. package/dist/transformer/transformers.d.cts +2 -0
  99. package/dist/transformer/transformers.d.ts +2 -0
  100. package/dist/transformer/transformers.js +9 -7
  101. package/dist/transformer/types.d.cts +16 -5
  102. package/dist/transformer/types.d.ts +16 -5
  103. package/dist/transformer.cjs +28 -9
  104. package/dist/transformer.d.cts +76 -1
  105. package/dist/transformer.d.ts +76 -1
  106. package/dist/transformer.js +28 -9
  107. package/dist/{types-CqZD77rS.d.ts → types-BB8pxk_3.d.ts} +1 -1
  108. package/dist/{types-gjJ5lJ5n.d.cts → types-C2MwkLah.d.cts} +1 -1
  109. package/dist/usage/usage-recorder.cjs +2 -2
  110. package/dist/usage/usage-recorder.d.cts +6 -0
  111. package/dist/usage/usage-recorder.d.ts +6 -0
  112. package/dist/usage/usage-recorder.js +1 -1
  113. package/dist/usage.cjs +2 -2
  114. package/dist/usage.js +1 -1
  115. package/package.json +1 -1
  116. /package/dist/{chunk-FZ46M34D.cjs → chunk-QVOB2DNX.cjs} +0 -0
  117. /package/dist/{chunk-46RLHI7D.js → chunk-WTQMX2Q6.js} +0 -0
@@ -4,13 +4,13 @@ import {
4
4
  import {
5
5
  AnthropicTransformer,
6
6
  resolveAnthropicMaxTokens
7
- } from "./chunk-PEGTIPB3.js";
7
+ } from "./chunk-HKB656B4.js";
8
8
  import {
9
9
  GeminiTransformer
10
- } from "./chunk-LVUCAWMK.js";
10
+ } from "./chunk-55FMGZ3L.js";
11
11
  import {
12
12
  OpenAIResponseTransformer
13
- } from "./chunk-32O35CIF.js";
13
+ } from "./chunk-45FHEOKW.js";
14
14
  import {
15
15
  TransformerChainExecutor
16
16
  } from "./chunk-WNKWAEUR.js";
@@ -94,6 +94,9 @@ import {
94
94
  resolvePrefixTarget,
95
95
  resolveRoute
96
96
  } from "./chunk-EBLFCNPY.js";
97
+ import {
98
+ getSubscriptionRegistryForOutbound
99
+ } from "./chunk-AQ5TNQB7.js";
97
100
  import {
98
101
  buildProviderApiUrl,
99
102
  resolveApiFormat,
@@ -106,6 +109,9 @@ import {
106
109
  convertAnthropicToOpenAI,
107
110
  convertOpenAIToAnthropic
108
111
  } from "./chunk-5NJLQ6T6.js";
112
+ import {
113
+ resolveReasoningPlan
114
+ } from "./chunk-N2EIM2XJ.js";
109
115
  import {
110
116
  fetchUpstream
111
117
  } from "./chunk-RJ5ZGNI3.js";
@@ -410,9 +416,6 @@ function addOpenRouterProviderToRequest(requestBody, provider, modelId) {
410
416
  };
411
417
  }
412
418
 
413
- // src/completion/DirectApiHandler.ts
414
- import { getOpenAIReasoningEffort } from "@omnicross/contracts/thinking-config";
415
-
416
419
  // src/provider-proxy/ProviderProxy.ts
417
420
  import http from "http";
418
421
 
@@ -432,12 +435,16 @@ var ProviderProxyRouteMap = class {
432
435
  * (`ANTHROPIC_AUTH_TOKEN` / `OPENAI_API_KEY`). Optionally override the idle
433
436
  * timeout (tests use a short one).
434
437
  */
435
- addRoute(context, idleMs) {
438
+ addRoute(context, idleMsOrOptions) {
439
+ const options = typeof idleMsOrOptions === "number" ? { idleMs: idleMsOrOptions } : idleMsOrOptions ?? {};
436
440
  const token = randomBytes(TOKEN_BYTES).toString("hex");
437
441
  const entry = {
438
442
  context,
439
443
  idleTimer: null,
440
- idleMs: idleMs ?? this.defaultIdleMs
444
+ idleMs: options.idleMs ?? this.defaultIdleMs,
445
+ onActivity: options.onActivity,
446
+ onEvicted: options.onEvicted,
447
+ evicted: false
441
448
  };
442
449
  this.routes.set(token, entry);
443
450
  this.armIdleTimer(token, entry);
@@ -452,6 +459,10 @@ var ProviderProxyRouteMap = class {
452
459
  if (!token) return void 0;
453
460
  const entry = this.routes.get(token);
454
461
  if (!entry) return void 0;
462
+ try {
463
+ entry.onActivity?.(Date.now());
464
+ } catch {
465
+ }
455
466
  this.armIdleTimer(token, entry);
456
467
  return entry.context;
457
468
  }
@@ -459,8 +470,16 @@ var ProviderProxyRouteMap = class {
459
470
  removeRoute(token) {
460
471
  const entry = this.routes.get(token);
461
472
  if (!entry) return false;
462
- this.clearIdleTimer(entry);
463
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);
464
483
  return true;
465
484
  }
466
485
  /** Current live-route count (tests / diagnostics). */
@@ -473,10 +492,11 @@ var ProviderProxyRouteMap = class {
473
492
  }
474
493
  /** Tear down every route (proxy stop / app teardown). */
475
494
  clear() {
476
- for (const entry of this.routes.values()) {
477
- this.clearIdleTimer(entry);
478
- }
495
+ const entries2 = [...this.routes.values()];
479
496
  this.routes.clear();
497
+ for (const entry of entries2) {
498
+ this.evict(entry, "cleared");
499
+ }
480
500
  }
481
501
  // ---------------------------------------------------------------------------
482
502
  // Idle reaping (modeled on AcpSessionManager.armIdleTimer / clearIdleTimer)
@@ -484,7 +504,8 @@ var ProviderProxyRouteMap = class {
484
504
  armIdleTimer(token, entry) {
485
505
  this.clearIdleTimer(entry);
486
506
  entry.idleTimer = setTimeout(() => {
487
- this.routes.delete(token);
507
+ if (!this.routes.delete(token)) return;
508
+ this.evict(entry, "idle");
488
509
  }, entry.idleMs);
489
510
  entry.idleTimer.unref?.();
490
511
  }
@@ -494,6 +515,15 @@ var ProviderProxyRouteMap = class {
494
515
  entry.idleTimer = null;
495
516
  }
496
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
+ }
497
527
  };
498
528
 
499
529
  // src/provider-proxy/identity/claudeCodeHeaders.ts
@@ -583,6 +613,10 @@ function emitAnthropicUsageRecord(recorder, rawUsage, attribution) {
583
613
  providerId: attribution.providerId,
584
614
  model: attribution.model,
585
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,
586
620
  auditResponse: attribution.auditResponse,
587
621
  engineOrigin: "codex-ingress",
588
622
  usage: tapped,
@@ -1142,7 +1176,7 @@ async function runSubscriptionSameFormatFetch(rawBody, plan, reportSelection, op
1142
1176
  } : void 0
1143
1177
  }
1144
1178
  );
1145
- return { response, rawStatus: response.status };
1179
+ return { response, rawStatus: response.status, actualModel: outboundModel };
1146
1180
  }
1147
1181
  function outboundRemapApplies(plan) {
1148
1182
  return plan.isSubscription && plan.transformerProvider.name === "claude";
@@ -1308,11 +1342,13 @@ async function handleAnthropicMessagesByo(res, rawBody, route, deps, options = {
1308
1342
  const upstreamSse = (upstreamResponse.headers.get("content-type") ?? "").includes(
1309
1343
  "text/event-stream"
1310
1344
  );
1345
+ const actualModel = "actualModel" in providerResponse && typeof providerResponse.actualModel === "string" ? providerResponse.actualModel : void 0;
1311
1346
  const usageAttribution = {
1312
1347
  sessionId: route.sessionId,
1313
1348
  providerId: route.providerId ?? "anthropic",
1314
- model: plan.resolvedModel,
1349
+ model: actualModel ?? plan.resolvedModel,
1315
1350
  apiKeyId: route.apiKeyId ?? null,
1351
+ routeLease: route.routeLease,
1316
1352
  // request-audit-log: correlate this request's tokens/cost to its audit record.
1317
1353
  auditResponse: res
1318
1354
  };
@@ -1396,7 +1432,8 @@ async function buildByoPlan(res, route, deps, resolvedModel, isStream) {
1396
1432
  name: provider.name,
1397
1433
  baseUrl: provider.api_base_url,
1398
1434
  apiKey,
1399
- models: provider.models || []
1435
+ models: provider.models || [],
1436
+ modelConfigs: provider.modelConfigs
1400
1437
  };
1401
1438
  const byoUrl = buildProviderApiUrl(provider, { model: resolvedModel, stream: isStream });
1402
1439
  const sameFormat = route.targetProviderFormat === "anthropic" || resolveApiFormat(provider) === "anthropic";
@@ -1571,6 +1608,10 @@ function emitGeminiUsageRecord(recorder, rawUsageMetadata, attribution) {
1571
1608
  providerId: attribution.providerId,
1572
1609
  model: attribution.model,
1573
1610
  apiKeyId: attribution.apiKeyId,
1611
+ runId: attribution.routeLease?.runId ?? null,
1612
+ routeLeaseId: attribution.routeLease?.leaseId ?? null,
1613
+ routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
1614
+ routeLeaseStageId: attribution.routeLease?.stageId ?? null,
1574
1615
  auditResponse: attribution.auditResponse,
1575
1616
  engineOrigin: "codex-ingress",
1576
1617
  usage: tapped,
@@ -1636,6 +1677,7 @@ async function handleGeminiGenerateContentRequest(res, rawBody, url, route, deps
1636
1677
  providerId: route.providerId ?? "gemini",
1637
1678
  model: resolvedModel,
1638
1679
  apiKeyId: route.apiKeyId ?? null,
1680
+ routeLease: route.routeLease,
1639
1681
  // request-audit-log: correlate this request's tokens/cost to its audit record.
1640
1682
  auditResponse: res
1641
1683
  };
@@ -1699,7 +1741,8 @@ async function buildByoPlan2(res, route, deps, resolvedModel, isStream) {
1699
1741
  name: provider.name,
1700
1742
  baseUrl: provider.api_base_url,
1701
1743
  apiKey,
1702
- models: provider.models || []
1744
+ models: provider.models || [],
1745
+ modelConfigs: provider.modelConfigs
1703
1746
  };
1704
1747
  const byoUrl = buildProviderApiUrl(provider, { model: resolvedModel, stream: isStream });
1705
1748
  return {
@@ -1792,6 +1835,10 @@ function emitChatCompletionsUsageRecord(recorder, rawUsage, attribution) {
1792
1835
  providerId: attribution.providerId,
1793
1836
  model: attribution.model,
1794
1837
  apiKeyId: attribution.apiKeyId,
1838
+ runId: attribution.routeLease?.runId ?? null,
1839
+ routeLeaseId: attribution.routeLease?.leaseId ?? null,
1840
+ routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
1841
+ routeLeaseStageId: attribution.routeLease?.stageId ?? null,
1795
1842
  auditResponse: attribution.auditResponse,
1796
1843
  engineOrigin: "codex-ingress",
1797
1844
  usage: tapped,
@@ -1844,6 +1891,7 @@ async function handleOpenAIChatRequest(res, rawBody, route, deps) {
1844
1891
  providerId: route.providerId ?? "openai",
1845
1892
  model: resolvedModel,
1846
1893
  apiKeyId: route.apiKeyId ?? null,
1894
+ routeLease: route.routeLease,
1847
1895
  // request-audit-log: correlate this request's tokens/cost to its audit record.
1848
1896
  auditResponse: res
1849
1897
  };
@@ -1911,7 +1959,8 @@ async function buildByoPlan3(res, route, deps, resolvedModel, isStream) {
1911
1959
  name: provider.name,
1912
1960
  baseUrl: provider.api_base_url,
1913
1961
  apiKey,
1914
- models: provider.models || []
1962
+ models: provider.models || [],
1963
+ modelConfigs: provider.modelConfigs
1915
1964
  };
1916
1965
  const byoUrl = buildProviderApiUrl(provider, { model: resolvedModel, stream: isStream });
1917
1966
  return {
@@ -2072,6 +2121,10 @@ function emitResponsesUsageRecord(recorder, rawUsage, attribution) {
2072
2121
  providerId: attribution.providerId,
2073
2122
  model: attribution.model,
2074
2123
  apiKeyId: attribution.apiKeyId,
2124
+ runId: attribution.routeLease?.runId ?? null,
2125
+ routeLeaseId: attribution.routeLease?.leaseId ?? null,
2126
+ routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
2127
+ routeLeaseStageId: attribution.routeLease?.stageId ?? null,
2075
2128
  cacheKeySource: attribution.cacheKeySource,
2076
2129
  cacheKeyInjected: attribution.cacheKeyInjected,
2077
2130
  auditResponse: attribution.auditResponse,
@@ -2205,8 +2258,9 @@ async function handleOpenAIResponsesRequest(res, rawBody, route, deps, requestHe
2205
2258
  const usageAttribution = {
2206
2259
  sessionId: route.sessionId,
2207
2260
  providerId: route.providerId ?? "codex",
2208
- model: resolvedModel,
2261
+ model: providerResponse.actualModel ?? resolvedModel,
2209
2262
  apiKeyId: route.apiKeyId ?? null,
2263
+ routeLease: route.routeLease,
2210
2264
  cacheKeySource: cacheKeyAttribution.cacheKeySource,
2211
2265
  cacheKeyInjected: cacheKeyAttribution.cacheKeyInjected,
2212
2266
  // request-audit-log: correlate this request's tokens/cost to its audit record.
@@ -2300,7 +2354,8 @@ async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
2300
2354
  name: provider.name,
2301
2355
  baseUrl: provider.api_base_url,
2302
2356
  apiKey,
2303
- models: provider.models || []
2357
+ models: provider.models || [],
2358
+ modelConfigs: provider.modelConfigs
2304
2359
  };
2305
2360
  const byoUrl = buildProviderApiUrl(provider, { model: resolvedModel, stream: isStream });
2306
2361
  return {
@@ -2391,6 +2446,7 @@ async function runPipeline4(responsesBody, plan) {
2391
2446
  const endpointTransformer = getResponsesEndpointTransformer();
2392
2447
  const { auth, sessionKey, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
2393
2448
  let proxyAccountId;
2449
+ let actualModel = resolvedModel;
2394
2450
  let activityRecordId;
2395
2451
  const authHeaders = {};
2396
2452
  await auth.applyHeaders(authHeaders, {
@@ -2400,10 +2456,13 @@ async function runPipeline4(responsesBody, plan) {
2400
2456
  preferredAccountId: plan.preferredAccountId,
2401
2457
  preferredAccountGroup: plan.preferredAccountGroup,
2402
2458
  boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
2403
- reportSelection: (accountId) => {
2459
+ reportSelection: (accountId, _isActive, remappedModel) => {
2404
2460
  proxyAccountId = accountId;
2461
+ if (remappedModel) actualModel = remappedModel;
2405
2462
  }
2406
2463
  });
2464
+ responsesBody.model = actualModel;
2465
+ transformerProvider.models = [actualModel];
2407
2466
  let rawStatus = null;
2408
2467
  const { response } = await executeProviderCall({
2409
2468
  executor,
@@ -2427,7 +2486,7 @@ async function runPipeline4(responsesBody, plan) {
2427
2486
  return headers;
2428
2487
  },
2429
2488
  fetchFn: (url, headers, body) => {
2430
- console.log(`[ProviderProxy:responses] -> ${url} model=${resolvedModel} stream=${isStream}`);
2489
+ console.log(`[ProviderProxy:responses] -> ${url} model=${actualModel} stream=${isStream}`);
2431
2490
  return fetchUpstream(
2432
2491
  url,
2433
2492
  { method: "POST", headers, body: JSON.stringify(body) },
@@ -2438,7 +2497,7 @@ async function runPipeline4(responsesBody, plan) {
2438
2497
  endpoint: "responses",
2439
2498
  sessionKey,
2440
2499
  sessionSource: plan.sessionSource ?? "none",
2441
- model: resolvedModel,
2500
+ model: actualModel,
2442
2501
  onRecorded: (record) => {
2443
2502
  activityRecordId = record.id;
2444
2503
  }
@@ -2451,7 +2510,7 @@ async function runPipeline4(responsesBody, plan) {
2451
2510
  },
2452
2511
  runResponseChain: true
2453
2512
  });
2454
- return { response, rawStatus, accountId: proxyAccountId, activityRecordId };
2513
+ return { response, rawStatus, accountId: proxyAccountId, actualModel, activityRecordId };
2455
2514
  }
2456
2515
  async function runPipelineWithPoolReporting4(responsesBody, plan) {
2457
2516
  const first = await runPipeline4(responsesBody, plan);
@@ -2555,6 +2614,7 @@ var ProviderProxy = class {
2555
2614
  server = null;
2556
2615
  port = 0;
2557
2616
  routes;
2617
+ beforeStop = /* @__PURE__ */ new Set();
2558
2618
  /**
2559
2619
  * Start the resident listener on a stable port on 127.0.0.1. Idempotent —
2560
2620
  * a second `start()` returns the already-bound port.
@@ -2595,6 +2655,12 @@ var ProviderProxy = class {
2595
2655
  }
2596
2656
  /** Stop the listener, clear all routes, and release the port. */
2597
2657
  async stop() {
2658
+ for (const cleanup of [...this.beforeStop]) {
2659
+ try {
2660
+ await cleanup();
2661
+ } catch {
2662
+ }
2663
+ }
2598
2664
  this.routes.clear();
2599
2665
  const server = this.server;
2600
2666
  if (!server) return;
@@ -2611,6 +2677,15 @@ var ProviderProxy = class {
2611
2677
  getBaseUrl() {
2612
2678
  return `http://127.0.0.1:${this.port}`;
2613
2679
  }
2680
+ /** Register daemon-owned cleanup that must run before route-map teardown. */
2681
+ registerBeforeStop(cleanup) {
2682
+ this.beforeStop.add(cleanup);
2683
+ return () => this.beforeStop.delete(cleanup);
2684
+ }
2685
+ /** Whether the resident listener has completed binding. */
2686
+ isReady() {
2687
+ return this.server !== null && this.port > 0;
2688
+ }
2614
2689
  /**
2615
2690
  * The SHARED route map. Exposed so the outbound API server
2616
2691
  * (`outbound-api-server`) can mint per-request routes on the SAME map and
@@ -2627,17 +2702,817 @@ var ProviderProxy = class {
2627
2702
  getDeps() {
2628
2703
  return this.deps;
2629
2704
  }
2630
- /** Register a route for one run; returns the crypto route token (task 2.2). */
2631
- addRoute(context, idleMs) {
2632
- return this.routes.addRoute(context, idleMs);
2705
+ /** Register a route for one run; returns the crypto route token (task 2.2). */
2706
+ addRoute(context, idleMsOrOptions) {
2707
+ return this.routes.addRoute(context, idleMsOrOptions);
2708
+ }
2709
+ /** Re-arm one route while retaining the exact same bearer token. */
2710
+ renewRoute(token, idleMs) {
2711
+ return this.routes.renewRoute(token, idleMs);
2712
+ }
2713
+ /** Remove a route at run end. Returns true if an entry existed. */
2714
+ removeRoute(token) {
2715
+ return this.routes.removeRoute(token);
2716
+ }
2717
+ /** Live-route count (diagnostics / tests). */
2718
+ routeCount() {
2719
+ return this.routes.size();
2720
+ }
2721
+ };
2722
+
2723
+ // src/provider-proxy/RouteLeaseManager.ts
2724
+ import { randomBytes as randomBytes3, randomUUID } from "crypto";
2725
+
2726
+ // src/provider-proxy/routeLeaseSchema.ts
2727
+ import { createHash, createHmac, randomBytes as randomBytes2 } from "crypto";
2728
+ var ROUTE_LEASE_REQUEST_SCHEMA = "omnicross.route-lease.request/1";
2729
+ var ROUTE_LEASE_RESULT_SCHEMA = "omnicross.route-lease/1";
2730
+ var ROUTE_LEASE_CAPABILITIES_SCHEMA = "omnicross.route-lease.capabilities/1";
2731
+ var ROUTE_LEASE_API_VERSION = 1;
2732
+ var ROUTE_LEASE_DEFAULT_TTL_SECONDS = 600;
2733
+ var ROUTE_LEASE_MAX_TTL_SECONDS = 3600;
2734
+ var ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES = 256;
2735
+ var ROUTE_LEASE_MAX_CONSUMER_BYTES = 64;
2736
+ var ROUTE_LEASE_MAX_EXECUTION_ID_BYTES = 128;
2737
+ var ROUTE_LEASE_MAX_SESSION_ID_BYTES = 512;
2738
+ var ROUTE_LEASE_MAX_MODEL_BYTES = 256;
2739
+ var ROUTE_LEASE_SESSION_HASH_DOMAIN = "omnicross.route-lease.session/v1";
2740
+ var ROUTE_LEASE_CODEX_TOKEN_ENV = "OMNICROSS_CODEX_ROUTE_TOKEN";
2741
+ var ROUTE_LEASE_RUNTIMES = ["claude", "codex"];
2742
+ var ROUTE_LEASE_CAPABILITIES = Object.freeze({
2743
+ schemaVersion: ROUTE_LEASE_CAPABILITIES_SCHEMA,
2744
+ runtimes: Object.freeze(["claude", "codex"]),
2745
+ upstreamKinds: Object.freeze(["provider", "account", "account-group", "account-pool"]),
2746
+ leaseApiVersion: ROUTE_LEASE_API_VERSION,
2747
+ codexAuthMode: "env_key",
2748
+ maxTtlSeconds: ROUTE_LEASE_MAX_TTL_SECONDS
2749
+ });
2750
+ var ROUTE_LEASE_RUNTIME_TABLE = Object.freeze({
2751
+ claude: Object.freeze({ endpoint: "messages", ingressFormat: "anthropic-messages", wirePath: "/v1/messages" }),
2752
+ codex: Object.freeze({ endpoint: "responses", ingressFormat: "openai-responses", wirePath: "/openai/responses" })
2753
+ });
2754
+ var ERROR_DEFAULTS = {
2755
+ invalid_request: { status: 400, retryable: false },
2756
+ runtime_unsupported: { status: 400, retryable: false },
2757
+ model_not_configured: { status: 400, retryable: false },
2758
+ format_unsupported: { status: 400, retryable: false },
2759
+ control_unauthorized: { status: 403, retryable: false },
2760
+ upstream_not_found: { status: 404, retryable: false },
2761
+ lease_not_found: { status: 404, retryable: false },
2762
+ idempotency_conflict: { status: 409, retryable: false },
2763
+ upstream_unavailable: { status: 409, retryable: true },
2764
+ lease_expired: { status: 410, retryable: false },
2765
+ upstream_exhausted: { status: 429, retryable: true },
2766
+ daemon_not_ready: { status: 503, retryable: true }
2767
+ };
2768
+ var RouteLeaseError = class extends Error {
2769
+ constructor(code, message, options = {}) {
2770
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
2771
+ this.code = code;
2772
+ this.name = "RouteLeaseError";
2773
+ const defaults = ERROR_DEFAULTS[code];
2774
+ this.status = options.status ?? defaults.status;
2775
+ this.retryable = options.retryable ?? defaults.retryable;
2776
+ if (options.retryAfterSeconds !== void 0) {
2777
+ this.retryAfterSeconds = Math.max(1, Math.min(3600, Math.floor(options.retryAfterSeconds)));
2778
+ }
2779
+ }
2780
+ code;
2781
+ type = "route_lease_error";
2782
+ status;
2783
+ retryable;
2784
+ retryAfterSeconds;
2785
+ toResponse() {
2786
+ return { error: { type: this.type, code: this.code, message: this.message, retryable: this.retryable } };
2787
+ }
2788
+ };
2789
+ var IDEMPOTENCY_RE = /^[A-Za-z0-9._:-]+$/u;
2790
+ var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/u;
2791
+ var PROCESS_SESSION_HMAC_KEY = randomBytes2(32);
2792
+ function isRecord(value) {
2793
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2794
+ }
2795
+ function byteLength(value) {
2796
+ return Buffer.byteLength(value, "utf8");
2797
+ }
2798
+ function normalizedString(value, field, maxBytes, options = {}) {
2799
+ if (typeof value !== "string") throw invalid(`${field} must be a string`);
2800
+ const normalized = value.trim();
2801
+ if (!normalized || byteLength(normalized) > maxBytes || CONTROL_RE.test(normalized)) {
2802
+ throw invalid(`${field} is invalid`);
2803
+ }
2804
+ if (options.asciiPattern && !options.asciiPattern.test(normalized)) {
2805
+ throw invalid(`${field} is invalid`);
2806
+ }
2807
+ return normalized;
2808
+ }
2809
+ function optionalExecutionId(value, field) {
2810
+ return value === void 0 ? void 0 : normalizedString(value, field, ROUTE_LEASE_MAX_EXECUTION_ID_BYTES);
2811
+ }
2812
+ function invalid(message) {
2813
+ return new RouteLeaseError("invalid_request", message);
2814
+ }
2815
+ function normalizeRouteLeaseIdempotencyKey(value) {
2816
+ return normalizedString(value, "Idempotency-Key", ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES, {
2817
+ asciiPattern: IDEMPOTENCY_RE
2818
+ });
2819
+ }
2820
+ function normalizeRouteLeaseTtl(value) {
2821
+ if (value === void 0) return ROUTE_LEASE_DEFAULT_TTL_SECONDS;
2822
+ if (!Number.isInteger(value) || value < 1 || value > ROUTE_LEASE_MAX_TTL_SECONDS) {
2823
+ throw invalid(`ttlSeconds must be an integer from 1 to ${ROUTE_LEASE_MAX_TTL_SECONDS}`);
2824
+ }
2825
+ return value;
2826
+ }
2827
+ function normalizeUpstream(value) {
2828
+ if (!isRecord(value) || typeof value.kind !== "string") throw invalid("upstream is invalid");
2829
+ const providerId = normalizedString(value.providerId, "upstream.providerId", 128);
2830
+ switch (value.kind) {
2831
+ case "provider": {
2832
+ const keyId = value.keyId === void 0 ? void 0 : normalizedString(value.keyId, "upstream.keyId", 128);
2833
+ return keyId ? { kind: "provider", providerId, keyId } : { kind: "provider", providerId };
2834
+ }
2835
+ case "account":
2836
+ return {
2837
+ kind: "account",
2838
+ providerId,
2839
+ accountId: normalizedString(value.accountId, "upstream.accountId", 128)
2840
+ };
2841
+ case "account-group":
2842
+ return {
2843
+ kind: "account-group",
2844
+ providerId,
2845
+ group: normalizedString(value.group, "upstream.group", 128)
2846
+ };
2847
+ case "account-pool":
2848
+ return { kind: "account-pool", providerId };
2849
+ default:
2850
+ throw invalid("upstream kind is unsupported");
2851
+ }
2852
+ }
2853
+ function hashRouteLeaseSessionId(sessionId, hmacKey = PROCESS_SESSION_HMAC_KEY) {
2854
+ return createHmac("sha256", hmacKey).update(ROUTE_LEASE_SESSION_HASH_DOMAIN, "utf8").update("\0", "utf8").update(sessionId, "utf8").digest("hex");
2855
+ }
2856
+ function canonicalizeRouteLeasePayload(value) {
2857
+ if (value === null || typeof value !== "object") return JSON.stringify(value);
2858
+ if (Array.isArray(value)) {
2859
+ return `[${value.map((entry) => canonicalizeRouteLeasePayload(entry)).join(",")}]`;
2860
+ }
2861
+ const record = value;
2862
+ const fields = Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalizeRouteLeasePayload(record[key])}`);
2863
+ return `{${fields.join(",")}}`;
2864
+ }
2865
+ function hashRouteLeasePayload(value) {
2866
+ return createHash("sha256").update(canonicalizeRouteLeasePayload(value), "utf8").digest("hex");
2867
+ }
2868
+ function parseRouteLeaseCreate(raw, idempotencyHeader, hmacKey = PROCESS_SESSION_HMAC_KEY) {
2869
+ if (!isRecord(raw)) throw invalid("request body must be a JSON object");
2870
+ if (raw.schemaVersion !== ROUTE_LEASE_REQUEST_SCHEMA) {
2871
+ throw invalid(`schemaVersion must be ${ROUTE_LEASE_REQUEST_SCHEMA}`);
2872
+ }
2873
+ const consumer = normalizedString(raw.consumer, "consumer", ROUTE_LEASE_MAX_CONSUMER_BYTES);
2874
+ const runtime = raw.runtime;
2875
+ if (runtime !== "claude" && runtime !== "codex") {
2876
+ throw new RouteLeaseError("runtime_unsupported", "runtime is unsupported");
2877
+ }
2878
+ const upstream = normalizeUpstream(raw.upstream);
2879
+ if (typeof raw.model !== "string" || raw.model.trim().length === 0) {
2880
+ throw new RouteLeaseError("model_not_configured", "model is not configured");
2881
+ }
2882
+ const model = normalizedString(raw.model, "model", ROUTE_LEASE_MAX_MODEL_BYTES);
2883
+ const ttlSeconds = normalizeRouteLeaseTtl(raw.ttlSeconds);
2884
+ const idempotencyKey = normalizeRouteLeaseIdempotencyKey(idempotencyHeader);
2885
+ let execution;
2886
+ let semanticExecution;
2887
+ if (raw.execution !== void 0) {
2888
+ if (!isRecord(raw.execution)) throw invalid("execution must be a JSON object");
2889
+ const runId = optionalExecutionId(raw.execution.runId, "execution.runId");
2890
+ const stageId = optionalExecutionId(raw.execution.stageId, "execution.stageId");
2891
+ let attempt;
2892
+ if (raw.execution.attempt !== void 0) {
2893
+ if (!Number.isSafeInteger(raw.execution.attempt) || raw.execution.attempt < 1) {
2894
+ throw invalid("execution.attempt must be a positive integer");
2895
+ }
2896
+ attempt = raw.execution.attempt;
2897
+ }
2898
+ let sessionId;
2899
+ let sessionIdHash;
2900
+ if (raw.execution.sessionId !== void 0) {
2901
+ sessionId = normalizedString(raw.execution.sessionId, "execution.sessionId", ROUTE_LEASE_MAX_SESSION_ID_BYTES);
2902
+ sessionIdHash = hashRouteLeaseSessionId(sessionId, hmacKey);
2903
+ }
2904
+ if (runId !== void 0 || stageId !== void 0 || attempt !== void 0 || sessionIdHash !== void 0) {
2905
+ execution = { runId, stageId, attempt, sessionIdHash };
2906
+ semanticExecution = { runId, stageId, attempt, sessionId };
2907
+ }
2908
+ }
2909
+ const request = {
2910
+ schemaVersion: ROUTE_LEASE_REQUEST_SCHEMA,
2911
+ consumer,
2912
+ runtime,
2913
+ upstream,
2914
+ model,
2915
+ ...execution ? { execution } : {},
2916
+ ttlSeconds
2917
+ };
2918
+ const semanticPayload = {
2919
+ schemaVersion: request.schemaVersion,
2920
+ consumer,
2921
+ runtime,
2922
+ upstream,
2923
+ model,
2924
+ ...semanticExecution ? { execution: semanticExecution } : {},
2925
+ ttlSeconds
2926
+ };
2927
+ return { request, idempotencyKey, payloadHash: hashRouteLeasePayload(semanticPayload) };
2928
+ }
2929
+ function routeLeaseRuntime(runtime) {
2930
+ switch (runtime) {
2931
+ case "claude":
2932
+ return ROUTE_LEASE_RUNTIME_TABLE.claude;
2933
+ case "codex":
2934
+ return ROUTE_LEASE_RUNTIME_TABLE.codex;
2935
+ default: {
2936
+ const exhaustive = runtime;
2937
+ throw new RouteLeaseError("runtime_unsupported", `runtime is unsupported: ${String(exhaustive)}`);
2938
+ }
2939
+ }
2940
+ }
2941
+
2942
+ // src/provider-proxy/RouteLeaseManager.ts
2943
+ var SYSTEM_ROUTE_LEASE_CLOCK = {
2944
+ now: () => Date.now(),
2945
+ setTimeout(callback, delayMs) {
2946
+ const timer = setTimeout(callback, delayMs);
2947
+ timer.unref?.();
2948
+ return timer;
2949
+ },
2950
+ clearTimeout(handle) {
2951
+ clearTimeout(handle);
2952
+ }
2953
+ };
2954
+ function cloneUpstream(upstream) {
2955
+ return { ...upstream };
2956
+ }
2957
+ function metadataCopy(metadata) {
2958
+ return {
2959
+ ...metadata,
2960
+ upstream: cloneUpstream(metadata.upstream),
2961
+ ...metadata.execution ? { execution: { ...metadata.execution } } : {}
2962
+ };
2963
+ }
2964
+ var RouteLeaseManager = class {
2965
+ constructor(routePort, targetResolver, descriptors, options = {}) {
2966
+ this.routePort = routePort;
2967
+ this.targetResolver = targetResolver;
2968
+ this.descriptors = descriptors;
2969
+ this.clock = options.clock ?? SYSTEM_ROUTE_LEASE_CLOCK;
2970
+ this.hmacKey = options.hmacKey ?? randomBytes3(32);
2971
+ this.tombstoneMaxCount = Math.max(1, options.tombstoneMaxCount ?? 512);
2972
+ this.tombstoneMaxAgeMs = Math.max(1, options.tombstoneMaxAgeMs ?? 60 * 60 * 1e3);
2973
+ this.logger = options.logger;
2974
+ this.afterPublish = options.afterPublish;
2975
+ }
2976
+ routePort;
2977
+ targetResolver;
2978
+ descriptors;
2979
+ active = /* @__PURE__ */ new Map();
2980
+ idempotency = /* @__PURE__ */ new Map();
2981
+ pendingCreates = /* @__PURE__ */ new Map();
2982
+ tombstones = /* @__PURE__ */ new Map();
2983
+ clock;
2984
+ hmacKey;
2985
+ tombstoneMaxCount;
2986
+ tombstoneMaxAgeMs;
2987
+ logger;
2988
+ afterPublish;
2989
+ closed = false;
2990
+ parse(raw, idempotencyKey) {
2991
+ return parseRouteLeaseCreate(raw, idempotencyKey, this.hmacKey);
2992
+ }
2993
+ async createFromRequest(raw, idempotencyKey) {
2994
+ return this.create(this.parse(raw, idempotencyKey));
2995
+ }
2996
+ async create(input) {
2997
+ if (this.closed) throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
2998
+ const request = input.request;
2999
+ const scope = `${request.consumer}\0${input.idempotencyKey}`;
3000
+ const existing = this.idempotency.get(scope);
3001
+ if (existing) {
3002
+ const lease = this.active.get(existing.leaseId);
3003
+ if (!lease) {
3004
+ this.idempotency.delete(scope);
3005
+ } else if (existing.payloadHash !== input.payloadHash) {
3006
+ throw new RouteLeaseError("idempotency_conflict", "idempotency key is already used for a different live request");
3007
+ } else {
3008
+ return { created: false, result: lease.result };
3009
+ }
3010
+ }
3011
+ const pending = this.pendingCreates.get(scope);
3012
+ if (pending) {
3013
+ if (pending.payloadHash !== input.payloadHash) {
3014
+ throw new RouteLeaseError("idempotency_conflict", "idempotency key is already used for a different live request");
3015
+ }
3016
+ const outcome2 = await pending.outcome;
3017
+ return { created: false, result: outcome2.result };
3018
+ }
3019
+ const outcome = this.createNew(input, scope);
3020
+ const pendingCreate = { payloadHash: input.payloadHash, outcome };
3021
+ this.pendingCreates.set(scope, pendingCreate);
3022
+ try {
3023
+ return await outcome;
3024
+ } finally {
3025
+ if (this.pendingCreates.get(scope) === pendingCreate) this.pendingCreates.delete(scope);
3026
+ }
3027
+ }
3028
+ async createNew(input, scope) {
3029
+ const request = input.request;
3030
+ if (!this.routePort.isReady()) {
3031
+ throw new RouteLeaseError("daemon_not_ready", "resident provider proxy is not ready");
3032
+ }
3033
+ if (!this.descriptors.has(request.runtime)) {
3034
+ throw new RouteLeaseError("runtime_unsupported", "runtime launch adapter is unavailable");
3035
+ }
3036
+ const leaseId = randomUUID();
3037
+ const resolved = await this.targetResolver.resolve(request);
3038
+ this.assertOpen();
3039
+ const route = {
3040
+ ...resolved,
3041
+ sessionId: request.execution?.sessionIdHash ?? null,
3042
+ routeLease: {
3043
+ leaseId,
3044
+ consumer: request.consumer,
3045
+ ...request.execution?.runId ? { runId: request.execution.runId } : {},
3046
+ ...request.execution?.stageId ? { stageId: request.execution.stageId } : {}
3047
+ }
3048
+ };
3049
+ const now = this.clock.now();
3050
+ const ttlMs = request.ttlSeconds * 1e3;
3051
+ const expiresAtMs = now + ttlMs;
3052
+ let routeToken;
3053
+ let published = false;
3054
+ let activeLease;
3055
+ try {
3056
+ routeToken = this.routePort.addRoute(route, {
3057
+ idleMs: ttlMs,
3058
+ onActivity: (at) => this.noteActivity(leaseId, at),
3059
+ onEvicted: () => this.onRouteEvicted(leaseId)
3060
+ });
3061
+ const launch = this.descriptors.build(request.runtime, {
3062
+ proxyBaseUrl: this.routePort.getBaseUrl(),
3063
+ model: request.model,
3064
+ routeToken
3065
+ });
3066
+ this.assertOpen();
3067
+ const metadata = {
3068
+ leaseId,
3069
+ consumer: request.consumer,
3070
+ runtime: request.runtime,
3071
+ upstream: cloneUpstream(request.upstream),
3072
+ model: request.model,
3073
+ createdAt: new Date(now).toISOString(),
3074
+ expiresAt: new Date(expiresAtMs).toISOString(),
3075
+ status: "active",
3076
+ ...request.execution ? { execution: { ...request.execution } } : {}
3077
+ };
3078
+ const result = {
3079
+ schemaVersion: ROUTE_LEASE_RESULT_SCHEMA,
3080
+ ...metadata,
3081
+ status: "active",
3082
+ launch: { env: { ...launch.env }, extraArgs: [...launch.extraArgs] }
3083
+ };
3084
+ const timer = this.clock.setTimeout(() => this.expire(leaseId), ttlMs);
3085
+ activeLease = {
3086
+ leaseId,
3087
+ routeToken,
3088
+ idempotencyScope: scope,
3089
+ payloadHash: input.payloadHash,
3090
+ request,
3091
+ route,
3092
+ result,
3093
+ metadata,
3094
+ expiresAtMs,
3095
+ timer
3096
+ };
3097
+ this.assertOpen();
3098
+ this.active.set(leaseId, activeLease);
3099
+ this.idempotency.set(scope, { leaseId, payloadHash: input.payloadHash });
3100
+ published = true;
3101
+ this.afterPublish?.();
3102
+ if (this.closed) {
3103
+ this.finalize(activeLease, "released");
3104
+ throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
3105
+ }
3106
+ return { created: true, result };
3107
+ } catch (error) {
3108
+ if (published && activeLease) {
3109
+ this.active.delete(leaseId);
3110
+ this.idempotency.delete(scope);
3111
+ this.clock.clearTimeout(activeLease.timer);
3112
+ }
3113
+ if (routeToken) this.safeRemoveRoute(routeToken, leaseId);
3114
+ if (error instanceof RouteLeaseError) throw error;
3115
+ throw new RouteLeaseError("upstream_unavailable", "route lease creation failed safely", { cause: error });
3116
+ }
3117
+ }
3118
+ list() {
3119
+ this.pruneTombstones();
3120
+ return [
3121
+ ...[...this.active.values()].map((lease) => metadataCopy(lease.metadata)),
3122
+ ...[...this.tombstones.values()].map((entry) => metadataCopy(entry.metadata))
3123
+ ].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
3124
+ }
3125
+ get(leaseId) {
3126
+ const active = this.active.get(leaseId);
3127
+ if (active) return metadataCopy(active.metadata);
3128
+ this.pruneTombstones();
3129
+ const tombstone = this.tombstones.get(leaseId);
3130
+ if (tombstone) return metadataCopy(tombstone.metadata);
3131
+ throw new RouteLeaseError("lease_not_found", "route lease was not found");
3132
+ }
3133
+ renew(leaseId, ttlSeconds) {
3134
+ const lease = this.active.get(leaseId);
3135
+ if (!lease) {
3136
+ this.pruneTombstones();
3137
+ const known = this.tombstones.get(leaseId);
3138
+ if (known?.metadata.status === "expired") {
3139
+ throw new RouteLeaseError("lease_expired", "route lease has expired");
3140
+ }
3141
+ throw new RouteLeaseError("lease_not_found", "route lease was not found");
3142
+ }
3143
+ const now = this.clock.now();
3144
+ if (now >= lease.expiresAtMs) {
3145
+ this.finalize(lease, "expired");
3146
+ throw new RouteLeaseError("lease_expired", "route lease has expired");
3147
+ }
3148
+ const ttlMs = ttlSeconds * 1e3;
3149
+ const expiresAtMs = now + ttlMs;
3150
+ const nextTimer = this.clock.setTimeout(() => this.expire(leaseId), ttlMs);
3151
+ const previousTimer = lease.timer;
3152
+ lease.timer = nextTimer;
3153
+ lease.expiresAtMs = expiresAtMs;
3154
+ lease.metadata = { ...lease.metadata, expiresAt: new Date(expiresAtMs).toISOString() };
3155
+ this.clock.clearTimeout(previousTimer);
3156
+ if (!this.routePort.renewRoute(lease.routeToken, ttlMs)) {
3157
+ this.finalize(lease, "expired", true);
3158
+ throw new RouteLeaseError("lease_expired", "route lease route has expired");
3159
+ }
3160
+ return { leaseId, expiresAt: lease.metadata.expiresAt, status: "active" };
3161
+ }
3162
+ release(leaseId) {
3163
+ const lease = this.active.get(leaseId);
3164
+ if (!lease) return { leaseId, released: false };
3165
+ this.finalize(lease, "released");
3166
+ return { leaseId, released: true };
3167
+ }
3168
+ shutdown() {
3169
+ if (this.closed) return;
3170
+ this.closed = true;
3171
+ for (const lease of [...this.active.values()]) {
3172
+ try {
3173
+ this.finalize(lease, "released");
3174
+ } catch (error) {
3175
+ this.logger?.warn("route lease cleanup failed during shutdown", { leaseId: lease.leaseId, error: safeErrorName(error) });
3176
+ }
3177
+ }
3178
+ }
3179
+ activeCount() {
3180
+ return this.active.size;
3181
+ }
3182
+ assertOpen() {
3183
+ if (this.closed) {
3184
+ throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
3185
+ }
3186
+ }
3187
+ noteActivity(leaseId, at) {
3188
+ const lease = this.active.get(leaseId);
3189
+ if (!lease || at >= lease.expiresAtMs) return;
3190
+ lease.metadata = { ...lease.metadata, lastActivityAt: new Date(at).toISOString() };
3191
+ }
3192
+ onRouteEvicted(leaseId) {
3193
+ const lease = this.active.get(leaseId);
3194
+ if (lease) this.finalize(lease, "expired", true);
3195
+ }
3196
+ expire(leaseId) {
3197
+ const lease = this.active.get(leaseId);
3198
+ if (!lease) return;
3199
+ const remaining = lease.expiresAtMs - this.clock.now();
3200
+ if (remaining > 0) {
3201
+ lease.timer = this.clock.setTimeout(() => this.expire(leaseId), remaining);
3202
+ return;
3203
+ }
3204
+ this.finalize(lease, "expired");
3205
+ }
3206
+ finalize(lease, status, routeAlreadyGone = false) {
3207
+ if (this.active.get(lease.leaseId) !== lease) return;
3208
+ this.active.delete(lease.leaseId);
3209
+ this.idempotency.delete(lease.idempotencyScope);
3210
+ this.clock.clearTimeout(lease.timer);
3211
+ if (!routeAlreadyGone) this.safeRemoveRoute(lease.routeToken, lease.leaseId);
3212
+ const metadata = { ...lease.metadata, status };
3213
+ this.tombstones.delete(lease.leaseId);
3214
+ this.tombstones.set(lease.leaseId, { metadata, terminalAtMs: this.clock.now() });
3215
+ this.pruneTombstones();
3216
+ }
3217
+ safeRemoveRoute(routeToken, leaseId) {
3218
+ try {
3219
+ this.routePort.removeRoute(routeToken);
3220
+ } catch (error) {
3221
+ this.logger?.warn("route lease route cleanup failed", { leaseId, error: safeErrorName(error) });
3222
+ }
3223
+ }
3224
+ pruneTombstones() {
3225
+ const cutoff = this.clock.now() - this.tombstoneMaxAgeMs;
3226
+ for (const [leaseId, tombstone] of this.tombstones) {
3227
+ if (tombstone.terminalAtMs < cutoff) this.tombstones.delete(leaseId);
3228
+ }
3229
+ while (this.tombstones.size > this.tombstoneMaxCount) {
3230
+ const oldest = this.tombstones.keys().next().value;
3231
+ if (!oldest) break;
3232
+ this.tombstones.delete(oldest);
3233
+ }
3234
+ }
3235
+ };
3236
+ function safeErrorName(error) {
3237
+ return error instanceof Error ? error.name : "unknown";
3238
+ }
3239
+
3240
+ // src/outbound-api/gatewayBindingResolver.ts
3241
+ var MESSAGE_FALLBACK_KINDS = ["sonnet", "opus", "haiku", "fable"];
3242
+ function nonBlank(value) {
3243
+ return typeof value === "string" && value.trim() !== "";
3244
+ }
3245
+ function gatewayBindingAllowsKey(binding, apiKeyId) {
3246
+ const scope = binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
3247
+ return scope === "all" || Boolean(binding.apiKeyIds?.includes(apiKeyId));
3248
+ }
3249
+ function wildcardMatches(pattern, value) {
3250
+ const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
3251
+ return new RegExp(`^${escaped}$`, "iu").test(value);
3252
+ }
3253
+ function resolveGatewayModelMapping(mappings, requestedModel) {
3254
+ if (!nonBlank(requestedModel)) return void 0;
3255
+ const wanted = requestedModel.trim();
3256
+ const exact = (mappings ?? []).find(
3257
+ (mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
3258
+ );
3259
+ if (exact && nonBlank(exact.target)) return exact.target.trim();
3260
+ const wildcard = (mappings ?? []).find(
3261
+ (mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
3262
+ );
3263
+ return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
3264
+ }
3265
+ function routeCanServe(binding, requestedModel, role) {
3266
+ if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
3267
+ if (binding.modelMappings?.length) {
3268
+ return nonBlank(resolveGatewayModelMapping(binding.modelMappings, requestedModel));
3269
+ }
3270
+ if (isKindMappedEndpoint(binding.endpoint)) {
3271
+ const map = binding.modelMap ?? {};
3272
+ const kind = detectModelKind(binding.endpoint, requestedModel);
3273
+ if (kind && nonBlank(map[kind])) return true;
3274
+ if (binding.endpoint === "messages") {
3275
+ return MESSAGE_FALLBACK_KINDS.some((candidate) => nonBlank(map[candidate]));
3276
+ }
3277
+ return nonBlank(map.codex);
3278
+ }
3279
+ if (binding.endpoint === "chat" && binding.dispatchMode === "prefix") {
3280
+ return resolvePrefixTarget(binding.prefixTargets, requestedModel) !== null;
3281
+ }
3282
+ if (binding.endpoint === "chat") {
3283
+ if (!requestedModel) return false;
3284
+ const wanted = requestedModel.trim().toLowerCase();
3285
+ return (binding.models ?? []).some((ref) => modelIdOf(ref)?.toLowerCase() === wanted);
3286
+ }
3287
+ return role === "background" ? nonBlank(binding.backgroundModel) : nonBlank(binding.defaultModel);
3288
+ }
3289
+ function modelIdOf(ref) {
3290
+ if (!nonBlank(ref)) return void 0;
3291
+ const trimmed = ref.trim();
3292
+ const comma = trimmed.indexOf(",");
3293
+ const modelId = comma >= 0 ? trimmed.slice(comma + 1).trim() : trimmed;
3294
+ return modelId || void 0;
3295
+ }
3296
+ function targetProviderId(target) {
3297
+ return target.providerId.trim();
3298
+ }
3299
+ function targetRef(target, ref) {
3300
+ const modelId = modelIdOf(ref) ?? "";
3301
+ return modelId ? `${targetProviderId(target)},${modelId}` : "";
3302
+ }
3303
+ function targetRefs(target, refs) {
3304
+ return (refs ?? []).map((ref) => targetRef(target, ref)).filter(nonBlank);
3305
+ }
3306
+ function targetPrefixRefs(target, refs) {
3307
+ if (!refs) return void 0;
3308
+ const mapped = {};
3309
+ for (const kind of ["claude", "gpt", "gemini"]) {
3310
+ const ref = targetRef(target, refs[kind]);
3311
+ if (ref) mapped[kind] = ref;
3312
+ }
3313
+ return mapped.claude || mapped.gpt || mapped.gemini ? mapped : void 0;
3314
+ }
3315
+ function applySingleModel(config, binding, targetModel) {
3316
+ const ref = targetRef(binding.target, targetModel);
3317
+ if (!ref) return;
3318
+ if (isKindMappedEndpoint(binding.endpoint)) {
3319
+ config.modelMap = Object.fromEntries(
3320
+ modelKindsForEndpoint(binding.endpoint).map((kind) => [kind, ref])
3321
+ );
3322
+ } else if (binding.endpoint === "chat") {
3323
+ config.models = [ref];
3324
+ config.dispatchMode = "list";
3325
+ } else {
3326
+ config.defaultModel = ref;
3327
+ config.backgroundModel = ref;
3328
+ }
3329
+ }
3330
+ function gatewayBindingToEndpointConfig(binding, requestedModel) {
3331
+ const target = binding.target;
3332
+ const config = {
3333
+ endpoint: binding.endpoint,
3334
+ useSubscription: target.kind !== "provider"
3335
+ };
3336
+ const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
3337
+ const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : resolveGatewayModelMapping(binding.modelMappings, requestedModel);
3338
+ if (usesGenericModelHandling) {
3339
+ if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
3340
+ } else if (isKindMappedEndpoint(binding.endpoint)) {
3341
+ config.modelMap = Object.fromEntries(
3342
+ Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
3343
+ );
3344
+ } else if (binding.endpoint === "chat") {
3345
+ config.models = targetRefs(target, binding.models);
3346
+ if (binding.dispatchMode === "prefix") {
3347
+ config.dispatchMode = "prefix";
3348
+ config.prefixTargets = targetPrefixRefs(target, binding.prefixTargets);
3349
+ }
3350
+ } else {
3351
+ config.defaultModel = targetRef(target, binding.defaultModel);
3352
+ config.backgroundModel = targetRef(target, binding.backgroundModel);
3353
+ if (binding.backgroundModelIds) config.backgroundModelIds = [...binding.backgroundModelIds];
3354
+ }
3355
+ if (target.kind === "account") {
3356
+ config.boundAccountId = target.accountId;
3357
+ config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
3358
+ } else if (target.kind === "account-group") {
3359
+ config.boundAccountGroup = target.group;
3360
+ config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
3361
+ } else if (target.kind === "provider" && target.keyId) {
3362
+ config.boundKeyId = target.keyId;
3363
+ config.boundKeyFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
3364
+ }
3365
+ return config;
3366
+ }
3367
+ function candidateGatewayBindings(bindings, apiKeyId, endpoint) {
3368
+ const candidates = (bindings ?? []).filter(
3369
+ (binding) => binding.enabled && binding.endpoint === endpoint && gatewayBindingAllowsKey(binding, apiKeyId)
3370
+ );
3371
+ const scopeOf = (binding) => binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
3372
+ const scoped = candidates.filter((binding) => scopeOf(binding) === "selected");
3373
+ const pool = scoped.length > 0 ? scoped : candidates.filter((binding) => scopeOf(binding) === "all");
3374
+ return pool.sort(
3375
+ (left, right) => (left.priority ?? 100) - (right.priority ?? 100) || left.id.localeCompare(right.id)
3376
+ );
3377
+ }
3378
+ function candidateBackgroundModelIds(bindings, apiKeyId, endpoint) {
3379
+ const ids = [
3380
+ ...new Set(
3381
+ candidateGatewayBindings(bindings, apiKeyId, endpoint).flatMap(
3382
+ (binding) => binding.backgroundModelIds ?? []
3383
+ )
3384
+ )
3385
+ ];
3386
+ return ids.length > 0 ? ids : void 0;
3387
+ }
3388
+ function resolveGatewayBinding(input) {
3389
+ const candidates = candidateGatewayBindings(input.bindings, input.apiKeyId, input.endpoint);
3390
+ const binding = candidates.find(
3391
+ (candidate) => candidate.fallback === "fail" || routeCanServe(candidate, input.requestedModel, input.role)
3392
+ ) ?? candidates[0];
3393
+ if (!binding) return { source: "none" };
3394
+ return {
3395
+ source: "binding",
3396
+ binding,
3397
+ config: gatewayBindingToEndpointConfig(binding, input.requestedModel)
3398
+ };
3399
+ }
3400
+
3401
+ // src/provider-proxy/RouteLeaseTargetResolver.ts
3402
+ function resolveEnvKey(raw) {
3403
+ if (!raw) return "";
3404
+ return raw.startsWith("$") ? process.env[raw.slice(1)] ?? "" : raw;
3405
+ }
3406
+ function providerSupportsModel(provider, model) {
3407
+ const flat = provider.models ?? [];
3408
+ const configured = provider.modelConfigs ?? [];
3409
+ if (flat.length === 0 && configured.length === 0) return false;
3410
+ return flat.includes(model) || configured.some((entry) => entry.id === model && entry.enabled !== false);
3411
+ }
3412
+ function syntheticBinding(request) {
3413
+ const runtime = routeLeaseRuntime(request.runtime);
3414
+ return {
3415
+ id: "route-lease-ephemeral",
3416
+ name: "Route Lease ephemeral target",
3417
+ enabled: true,
3418
+ endpoint: runtime.endpoint,
3419
+ target: request.upstream,
3420
+ fallback: "fail",
3421
+ modelMode: "passthrough"
3422
+ };
3423
+ }
3424
+ function transformerRegistered(llmConfig, name) {
3425
+ return Boolean(llmConfig.getTransformerService()?.getTransformer(name));
3426
+ }
3427
+ var RouteLeaseTargetResolver = class {
3428
+ constructor(llmConfig, options = {}) {
3429
+ this.llmConfig = llmConfig;
3430
+ this.options = options;
3431
+ }
3432
+ llmConfig;
3433
+ options;
3434
+ async resolve(request) {
3435
+ const runtime = routeLeaseRuntime(request.runtime);
3436
+ await this.preflightTarget(request);
3437
+ const binding = syntheticBinding(request);
3438
+ const config = gatewayBindingToEndpointConfig(binding, request.model);
3439
+ const result = await resolveRoute({
3440
+ config,
3441
+ ingressFormat: runtime.ingressFormat,
3442
+ llmConfig: this.llmConfig,
3443
+ requestedModel: request.model,
3444
+ sessionId: request.execution?.sessionIdHash ?? null
3445
+ });
3446
+ if (!result.ok) {
3447
+ throw new RouteLeaseError(
3448
+ result.error.status === 404 ? "upstream_not_found" : "upstream_unavailable",
3449
+ "the selected upstream could not be resolved"
3450
+ );
3451
+ }
3452
+ return { ...result.route, requestedModel: void 0 };
2633
3453
  }
2634
- /** Remove a route at run end. Returns true if an entry existed. */
2635
- removeRoute(token) {
2636
- return this.routes.removeRoute(token);
3454
+ async preflightTarget(request) {
3455
+ const target = request.upstream;
3456
+ if (target.kind !== "provider") {
3457
+ await this.preflightSubscription(target, request.model);
3458
+ return;
3459
+ }
3460
+ const provider = await this.llmConfig.getProvider(target.providerId);
3461
+ if (!provider) throw new RouteLeaseError("upstream_not_found", "provider was not found");
3462
+ if (provider.enabled === false) throw new RouteLeaseError("upstream_unavailable", "provider is disabled");
3463
+ if (!providerSupportsModel(provider, request.model)) {
3464
+ throw new RouteLeaseError("model_not_configured", "model is not configured for the provider");
3465
+ }
3466
+ if (target.keyId) {
3467
+ const availability = await this.options.providerKeys?.getKeyAvailability?.(
3468
+ target.providerId,
3469
+ target.keyId
3470
+ );
3471
+ if (availability === "not-found") {
3472
+ throw new RouteLeaseError("upstream_not_found", "the selected provider key was not found");
3473
+ }
3474
+ const usable = availability === "usable" || availability === void 0 && Boolean(this.options.providerKeys) && await this.options.providerKeys.hasUsableKey(target.providerId, target.keyId);
3475
+ if (!usable) {
3476
+ throw new RouteLeaseError("upstream_unavailable", "the selected provider key is unavailable");
3477
+ }
3478
+ } else {
3479
+ const directKey = resolveEnvKey(provider.api_key);
3480
+ const poolAvailability = await this.options.providerKeys?.getPoolAvailability?.(target.providerId);
3481
+ const poolReady = poolAvailability ? poolAvailability.outcome === "usable" : this.options.providerKeys ? await this.options.providerKeys.hasUsableKeys(target.providerId) : false;
3482
+ if (!directKey && poolAvailability?.outcome === "exhausted") {
3483
+ throw new RouteLeaseError("upstream_exhausted", "provider key pool is temporarily exhausted", {
3484
+ retryAfterSeconds: poolAvailability.retryAfterSeconds
3485
+ });
3486
+ }
3487
+ if (!directKey && !poolReady) {
3488
+ throw new RouteLeaseError("upstream_unavailable", "provider has no usable credential");
3489
+ }
3490
+ }
3491
+ const sameFormat = request.runtime === "claude" && provider.apiFormat === "anthropic" || request.runtime === "codex" && provider.apiFormat === "openai-response";
3492
+ if (!sameFormat) {
3493
+ try {
3494
+ const chain = await resolveProviderChain(this.llmConfig, provider.id, request.model);
3495
+ if (!chain.hasTransformers) {
3496
+ throw new RouteLeaseError("format_unsupported", "no transformer chain supports this runtime and upstream");
3497
+ }
3498
+ } catch (error) {
3499
+ if (error instanceof RouteLeaseError) throw error;
3500
+ throw new RouteLeaseError("format_unsupported", "the transformer chain could not be resolved", { cause: error });
3501
+ }
3502
+ }
2637
3503
  }
2638
- /** Live-route count (diagnostics / tests). */
2639
- routeCount() {
2640
- return this.routes.size();
3504
+ async preflightSubscription(target, model) {
3505
+ const registry = getSubscriptionRegistryForOutbound();
3506
+ const profile = registry?.getProfile(target.providerId) ?? null;
3507
+ if (!profile) throw new RouteLeaseError("upstream_not_found", "subscription provider was not found");
3508
+ await this.options.subscriptions?.assertAvailable(target, model);
3509
+ const names = profile.resolveProviderTransformerNames?.(model) ?? profile.providerTransformerNames ?? [];
3510
+ if (names.some((name) => !transformerRegistered(this.llmConfig, name))) {
3511
+ throw new RouteLeaseError("format_unsupported", "a required transformer is unavailable");
3512
+ }
3513
+ if (profile.modelTransformerNames?.some((name) => !transformerRegistered(this.llmConfig, name))) {
3514
+ throw new RouteLeaseError("format_unsupported", "a required model transformer is unavailable");
3515
+ }
2641
3516
  }
2642
3517
  };
2643
3518
 
@@ -2691,6 +3566,7 @@ var KeyedMutex = class {
2691
3566
 
2692
3567
  // src/outbound-api/outboundApiRouter.ts
2693
3568
  import { Readable } from "stream";
3569
+ import { SUBSCRIPTION_MODEL_CATALOG } from "@omnicross/contracts/subscription-model-catalog";
2694
3570
 
2695
3571
  // src/outbound-api/apiServerConfig.ts
2696
3572
  import { DEFAULT_AUDIT_CONFIG } from "@omnicross/contracts/audit-types";
@@ -3255,169 +4131,8 @@ function mergeServerConfig(current, patch) {
3255
4131
  });
3256
4132
  }
3257
4133
 
3258
- // src/outbound-api/gatewayBindingResolver.ts
3259
- var MESSAGE_FALLBACK_KINDS = ["sonnet", "opus", "haiku", "fable"];
3260
- function nonBlank(value) {
3261
- return typeof value === "string" && value.trim() !== "";
3262
- }
3263
- function gatewayBindingAllowsKey(binding, apiKeyId) {
3264
- const scope = binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
3265
- return scope === "all" || Boolean(binding.apiKeyIds?.includes(apiKeyId));
3266
- }
3267
- function wildcardMatches(pattern, value) {
3268
- const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
3269
- return new RegExp(`^${escaped}$`, "iu").test(value);
3270
- }
3271
- function resolveGatewayModelMapping(mappings, requestedModel) {
3272
- if (!nonBlank(requestedModel)) return void 0;
3273
- const wanted = requestedModel.trim();
3274
- const exact = (mappings ?? []).find(
3275
- (mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
3276
- );
3277
- if (exact && nonBlank(exact.target)) return exact.target.trim();
3278
- const wildcard = (mappings ?? []).find(
3279
- (mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
3280
- );
3281
- return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
3282
- }
3283
- function routeCanServe(binding, requestedModel, role) {
3284
- if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
3285
- if (binding.modelMappings?.length) {
3286
- return nonBlank(resolveGatewayModelMapping(binding.modelMappings, requestedModel));
3287
- }
3288
- if (isKindMappedEndpoint(binding.endpoint)) {
3289
- const map = binding.modelMap ?? {};
3290
- const kind = detectModelKind(binding.endpoint, requestedModel);
3291
- if (kind && nonBlank(map[kind])) return true;
3292
- if (binding.endpoint === "messages") {
3293
- return MESSAGE_FALLBACK_KINDS.some((candidate) => nonBlank(map[candidate]));
3294
- }
3295
- return nonBlank(map.codex);
3296
- }
3297
- if (binding.endpoint === "chat" && binding.dispatchMode === "prefix") {
3298
- return resolvePrefixTarget(binding.prefixTargets, requestedModel) !== null;
3299
- }
3300
- if (binding.endpoint === "chat") {
3301
- if (!requestedModel) return false;
3302
- const wanted = requestedModel.trim().toLowerCase();
3303
- return (binding.models ?? []).some((ref) => modelIdOf(ref)?.toLowerCase() === wanted);
3304
- }
3305
- return role === "background" ? nonBlank(binding.backgroundModel) : nonBlank(binding.defaultModel);
3306
- }
3307
- function modelIdOf(ref) {
3308
- if (!nonBlank(ref)) return void 0;
3309
- const trimmed = ref.trim();
3310
- const comma = trimmed.indexOf(",");
3311
- const modelId = comma >= 0 ? trimmed.slice(comma + 1).trim() : trimmed;
3312
- return modelId || void 0;
3313
- }
3314
- function targetProviderId(target) {
3315
- return target.providerId.trim();
3316
- }
3317
- function targetRef(target, ref) {
3318
- const modelId = modelIdOf(ref) ?? "";
3319
- return modelId ? `${targetProviderId(target)},${modelId}` : "";
3320
- }
3321
- function targetRefs(target, refs) {
3322
- return (refs ?? []).map((ref) => targetRef(target, ref)).filter(nonBlank);
3323
- }
3324
- function targetPrefixRefs(target, refs) {
3325
- if (!refs) return void 0;
3326
- const mapped = {};
3327
- for (const kind of ["claude", "gpt", "gemini"]) {
3328
- const ref = targetRef(target, refs[kind]);
3329
- if (ref) mapped[kind] = ref;
3330
- }
3331
- return mapped.claude || mapped.gpt || mapped.gemini ? mapped : void 0;
3332
- }
3333
- function applySingleModel(config, binding, targetModel) {
3334
- const ref = targetRef(binding.target, targetModel);
3335
- if (!ref) return;
3336
- if (isKindMappedEndpoint(binding.endpoint)) {
3337
- config.modelMap = Object.fromEntries(
3338
- modelKindsForEndpoint(binding.endpoint).map((kind) => [kind, ref])
3339
- );
3340
- } else if (binding.endpoint === "chat") {
3341
- config.models = [ref];
3342
- config.dispatchMode = "list";
3343
- } else {
3344
- config.defaultModel = ref;
3345
- config.backgroundModel = ref;
3346
- }
3347
- }
3348
- function gatewayBindingToEndpointConfig(binding, requestedModel) {
3349
- const target = binding.target;
3350
- const config = {
3351
- endpoint: binding.endpoint,
3352
- useSubscription: target.kind !== "provider"
3353
- };
3354
- const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
3355
- const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : resolveGatewayModelMapping(binding.modelMappings, requestedModel);
3356
- if (usesGenericModelHandling) {
3357
- if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
3358
- } else if (isKindMappedEndpoint(binding.endpoint)) {
3359
- config.modelMap = Object.fromEntries(
3360
- Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
3361
- );
3362
- } else if (binding.endpoint === "chat") {
3363
- config.models = targetRefs(target, binding.models);
3364
- if (binding.dispatchMode === "prefix") {
3365
- config.dispatchMode = "prefix";
3366
- config.prefixTargets = targetPrefixRefs(target, binding.prefixTargets);
3367
- }
3368
- } else {
3369
- config.defaultModel = targetRef(target, binding.defaultModel);
3370
- config.backgroundModel = targetRef(target, binding.backgroundModel);
3371
- if (binding.backgroundModelIds) config.backgroundModelIds = [...binding.backgroundModelIds];
3372
- }
3373
- if (target.kind === "account") {
3374
- config.boundAccountId = target.accountId;
3375
- config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
3376
- } else if (target.kind === "account-group") {
3377
- config.boundAccountGroup = target.group;
3378
- config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
3379
- } else if (target.kind === "provider" && target.keyId) {
3380
- config.boundKeyId = target.keyId;
3381
- config.boundKeyFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
3382
- }
3383
- return config;
3384
- }
3385
- function candidateGatewayBindings(bindings, apiKeyId, endpoint) {
3386
- const candidates = (bindings ?? []).filter(
3387
- (binding) => binding.enabled && binding.endpoint === endpoint && gatewayBindingAllowsKey(binding, apiKeyId)
3388
- );
3389
- const scopeOf = (binding) => binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
3390
- const scoped = candidates.filter((binding) => scopeOf(binding) === "selected");
3391
- const pool = scoped.length > 0 ? scoped : candidates.filter((binding) => scopeOf(binding) === "all");
3392
- return pool.sort(
3393
- (left, right) => (left.priority ?? 100) - (right.priority ?? 100) || left.id.localeCompare(right.id)
3394
- );
3395
- }
3396
- function candidateBackgroundModelIds(bindings, apiKeyId, endpoint) {
3397
- const ids = [
3398
- ...new Set(
3399
- candidateGatewayBindings(bindings, apiKeyId, endpoint).flatMap(
3400
- (binding) => binding.backgroundModelIds ?? []
3401
- )
3402
- )
3403
- ];
3404
- return ids.length > 0 ? ids : void 0;
3405
- }
3406
- function resolveGatewayBinding(input) {
3407
- const candidates = candidateGatewayBindings(input.bindings, input.apiKeyId, input.endpoint);
3408
- const binding = candidates.find(
3409
- (candidate) => candidate.fallback === "fail" || routeCanServe(candidate, input.requestedModel, input.role)
3410
- ) ?? candidates[0];
3411
- if (!binding) return { source: "none" };
3412
- return {
3413
- source: "binding",
3414
- binding,
3415
- config: gatewayBindingToEndpointConfig(binding, input.requestedModel)
3416
- };
3417
- }
3418
-
3419
4134
  // src/outbound-api/outboundApiKeyAuth.ts
3420
- import { createHash, randomBytes as randomBytes2 } from "crypto";
4135
+ import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
3421
4136
 
3422
4137
  // src/outbound-api/keyPolicy.ts
3423
4138
  var DAY_MS = 864e5;
@@ -3469,7 +4184,7 @@ var BASE62_REJECT_THRESHOLD = 248;
3469
4184
  function randomBase62(count) {
3470
4185
  let out = "";
3471
4186
  while (out.length < count) {
3472
- const buf = randomBytes2(count - out.length + 8);
4187
+ const buf = randomBytes4(count - out.length + 8);
3473
4188
  for (const byte of buf) {
3474
4189
  if (out.length >= count) break;
3475
4190
  if (byte >= BASE62_REJECT_THRESHOLD) continue;
@@ -3482,13 +4197,13 @@ function generateSecret() {
3482
4197
  return KEY_PREFIX + randomBase62(SECRET_BYTES);
3483
4198
  }
3484
4199
  function hashKey(secret) {
3485
- return createHash("sha256").update(secret, "utf8").digest("hex");
4200
+ return createHash2("sha256").update(secret, "utf8").digest("hex");
3486
4201
  }
3487
4202
  function keyPrefix(secret) {
3488
4203
  return secret.slice(0, PREFIX_LEN);
3489
4204
  }
3490
4205
  function newKeyId() {
3491
- return `oak_${Date.now()}_${randomBytes2(6).toString("hex")}`;
4206
+ return `oak_${Date.now()}_${randomBytes4(6).toString("hex")}`;
3492
4207
  }
3493
4208
  async function createNamedKey(db, name) {
3494
4209
  const secret = generateSecret();
@@ -4008,7 +4723,7 @@ var UserMessageSerialQueue = class {
4008
4723
  };
4009
4724
 
4010
4725
  // src/outbound-api/voucher.ts
4011
- import { createHash as createHash2 } from "crypto";
4726
+ import { createHash as createHash3 } from "crypto";
4012
4727
  var CODE_ENTROPY_CHARS = 32;
4013
4728
  var CODE_PREFIX = "CC_";
4014
4729
  var DISPLAY_PREFIX_LEN = 8;
@@ -4017,7 +4732,7 @@ function generateVoucherCode() {
4017
4732
  return CODE_PREFIX + randomBase62(CODE_ENTROPY_CHARS);
4018
4733
  }
4019
4734
  function hashVoucherCode(code) {
4020
- return createHash2("sha256").update(code, "utf8").digest("hex");
4735
+ return createHash3("sha256").update(code, "utf8").digest("hex");
4021
4736
  }
4022
4737
  function voucherCodePrefix(code) {
4023
4738
  return code.slice(0, DISPLAY_PREFIX_LEN);
@@ -4277,33 +4992,51 @@ function isModelsListRequest(url) {
4277
4992
  const path = url.split("?")[0]?.replace(/\/+$/, "") ?? "";
4278
4993
  return path.endsWith("/models");
4279
4994
  }
4280
- function writeModelsList(res, config, apiKeyId, allowedEndpoints) {
4281
- const refs = [];
4282
- const endpointConfigs = [];
4995
+ async function writeModelsList(res, llmConfig, config, apiKeyId, allowedEndpoints) {
4996
+ const modelIds = [];
4283
4997
  for (const endpoint of ["chat", "responses", "messages", "gemini"]) {
4284
4998
  if (allowedEndpoints && !allowedEndpoints.includes(endpoint)) continue;
4285
- endpointConfigs.push(
4286
- ...candidateGatewayBindings(config.bindings, apiKeyId, endpoint).map(
4287
- (binding) => gatewayBindingToEndpointConfig(binding)
4288
- )
4289
- );
4290
- }
4291
- for (const endpoint of endpointConfigs) {
4292
- if (allowedEndpoints && !allowedEndpoints.includes(endpoint.endpoint)) continue;
4293
- if (endpoint.endpoint === "chat") refs.push(...endpoint.models ?? []);
4294
- else if (endpoint.endpoint === "messages" || endpoint.endpoint === "responses") {
4295
- refs.push(...Object.values(endpoint.modelMap ?? {}));
4296
- } else {
4297
- if (endpoint.defaultModel) refs.push(endpoint.defaultModel);
4298
- if (endpoint.backgroundModel) refs.push(endpoint.backgroundModel);
4999
+ for (const binding of candidateGatewayBindings(config.bindings, apiKeyId, endpoint)) {
5000
+ if (binding.modelMode === "passthrough") {
5001
+ if (binding.target.kind === "provider") {
5002
+ const provider = await llmConfig.getProvider(binding.target.providerId);
5003
+ modelIds.push(...provider?.models ?? []);
5004
+ } else if (Object.prototype.hasOwnProperty.call(
5005
+ SUBSCRIPTION_MODEL_CATALOG,
5006
+ binding.target.providerId
5007
+ )) {
5008
+ modelIds.push(
5009
+ ...SUBSCRIPTION_MODEL_CATALOG[binding.target.providerId]
5010
+ );
5011
+ }
5012
+ continue;
5013
+ }
5014
+ if (binding.modelMappings?.length) {
5015
+ modelIds.push(
5016
+ ...binding.modelMappings.map((mapping) => mapping.source.trim()).filter((source) => source !== "" && !source.includes("*"))
5017
+ );
5018
+ continue;
5019
+ }
5020
+ const endpointConfig = gatewayBindingToEndpointConfig(binding);
5021
+ const refs = [];
5022
+ if (endpointConfig.endpoint === "chat") refs.push(...endpointConfig.models ?? []);
5023
+ else if (endpointConfig.endpoint === "messages" || endpointConfig.endpoint === "responses") {
5024
+ refs.push(...Object.values(endpointConfig.modelMap ?? {}));
5025
+ } else {
5026
+ if (endpointConfig.defaultModel) refs.push(endpointConfig.defaultModel);
5027
+ if (endpointConfig.backgroundModel) refs.push(endpointConfig.backgroundModel);
5028
+ }
5029
+ modelIds.push(
5030
+ ...refs.map((ref) => parseModelRef(ref)?.modelId).filter((modelId) => modelId !== void 0)
5031
+ );
4299
5032
  }
4300
5033
  }
4301
5034
  const seen = /* @__PURE__ */ new Set();
4302
- const data = refs.map((ref) => parseModelRef(ref)).filter((p) => p !== null).filter((p) => {
4303
- if (seen.has(p.modelId)) return false;
4304
- seen.add(p.modelId);
5035
+ const data = modelIds.filter((modelId) => {
5036
+ if (seen.has(modelId)) return false;
5037
+ seen.add(modelId);
4305
5038
  return true;
4306
- }).map((p) => ({ id: p.modelId, object: "model", owned_by: "omnicross" }));
5039
+ }).map((modelId) => ({ id: modelId, object: "model", owned_by: "omnicross" }));
4307
5040
  res.writeHead(200, { "Content-Type": "application/json" });
4308
5041
  res.end(JSON.stringify({ object: "list", data }));
4309
5042
  }
@@ -4371,7 +5104,7 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
4371
5104
  writeJsonError(res, 403, "API key is not allowed to access this endpoint");
4372
5105
  return;
4373
5106
  }
4374
- writeModelsList(res, config, verified.id, verified.allowedEndpoints);
5107
+ await writeModelsList(res, deps.llmConfig, config, verified.id, verified.allowedEndpoints);
4375
5108
  return;
4376
5109
  }
4377
5110
  const endpoint = selectEndpoint(req.method, req.url);
@@ -4927,6 +5660,70 @@ function __resetOutboundApiServerForTests() {
4927
5660
  instance2 = null;
4928
5661
  }
4929
5662
 
5663
+ // src/completion/ReasoningRequestBuilder.ts
5664
+ var LEGACY_ANTHROPIC_MAX_TOKENS = 16384;
5665
+ function intentFromOptions(options) {
5666
+ return options.thinkLevel ? { effort: options.thinkLevel, enabled: options.thinkLevel !== "none" } : void 0;
5667
+ }
5668
+ function resolveCompletionReasoningPlan(provider, options, target, requestMaxTokens = options.maxTokens) {
5669
+ return resolveReasoningPlan({
5670
+ intent: intentFromOptions(options),
5671
+ model: options.model,
5672
+ provider,
5673
+ target,
5674
+ requestMaxTokens
5675
+ });
5676
+ }
5677
+ function resolveOpenAIEffort(provider, options, target) {
5678
+ const plan = resolveCompletionReasoningPlan(provider, options, target);
5679
+ return plan?.kind === "level" && plan.enabled ? plan.effort : void 0;
5680
+ }
5681
+ function buildAnthropicReasoningWire(provider, options) {
5682
+ const configuredMaxTokens = options.maxTokens ?? LEGACY_ANTHROPIC_MAX_TOKENS;
5683
+ const legacyBound = Math.min(configuredMaxTokens, LEGACY_ANTHROPIC_MAX_TOKENS);
5684
+ const plan = resolveCompletionReasoningPlan(
5685
+ provider,
5686
+ options,
5687
+ "anthropic",
5688
+ legacyBound
5689
+ );
5690
+ if (plan?.kind === "level" && plan.enabled) {
5691
+ return {
5692
+ effectiveMaxTokens: configuredMaxTokens,
5693
+ thinking: { type: "adaptive" },
5694
+ outputConfig: { effort: plan.effort },
5695
+ legacyBudgetEnabled: false
5696
+ };
5697
+ }
5698
+ if (plan?.kind === "budget" && plan.enabled) {
5699
+ return {
5700
+ effectiveMaxTokens: legacyBound,
5701
+ thinking: { type: "enabled", budget_tokens: plan.budgetTokens },
5702
+ legacyBudgetEnabled: true
5703
+ };
5704
+ }
5705
+ return {
5706
+ effectiveMaxTokens: configuredMaxTokens,
5707
+ legacyBudgetEnabled: false
5708
+ };
5709
+ }
5710
+ function buildGeminiThinkingConfig(provider, options) {
5711
+ const plan = resolveCompletionReasoningPlan(provider, options, "gemini");
5712
+ if (plan?.kind === "level") {
5713
+ return {
5714
+ includeThoughts: plan.enabled,
5715
+ thinkingLevel: plan.effort
5716
+ };
5717
+ }
5718
+ if (plan?.kind === "budget") {
5719
+ return {
5720
+ includeThoughts: plan.enabled,
5721
+ thinkingBudget: plan.budgetTokens
5722
+ };
5723
+ }
5724
+ return void 0;
5725
+ }
5726
+
4930
5727
  // src/completion/DirectApiHandler.ts
4931
5728
  async function callOpenAICompletion(provider, apiKey, options, logger) {
4932
5729
  const request = {
@@ -4938,11 +5735,9 @@ async function callOpenAICompletion(provider, apiKey, options, logger) {
4938
5735
  stream: false
4939
5736
  // For now, non-streaming only
4940
5737
  };
4941
- if (options.thinkLevel && options.thinkLevel !== "none") {
4942
- const effort = getOpenAIReasoningEffort(options.thinkLevel);
4943
- if (effort) {
4944
- request.reasoning_effort = effort;
4945
- }
5738
+ const effort = resolveOpenAIEffort(provider, options, "openai-chat");
5739
+ if (effort) {
5740
+ request.reasoning_effort = effort;
4946
5741
  }
4947
5742
  const apiUrl = buildProviderApiUrl(provider, { model: options.model, stream: false });
4948
5743
  const headers = getProviderHeaders(provider, apiKey);
@@ -4979,16 +5774,19 @@ async function callOpenAICompletion(provider, apiKey, options, logger) {
4979
5774
  }
4980
5775
  async function callAnthropicCompletion(provider, apiKey, options, logger) {
4981
5776
  const hasImages = options.messages.some((m) => m.images && m.images.length > 0);
5777
+ const reasoningWire = buildAnthropicReasoningWire(provider, options);
4982
5778
  if (hasImages) {
4983
5779
  const systemMessages = options.messages.filter((m) => m.role === "system");
4984
5780
  const nonSystemMessages = options.messages.filter((m) => m.role !== "system");
4985
5781
  const anthropicRequest2 = {
4986
5782
  model: options.model,
4987
- max_tokens: options.maxTokens ?? 16384,
4988
- temperature: options.temperature,
5783
+ max_tokens: reasoningWire.effectiveMaxTokens,
5784
+ ...reasoningWire.legacyBudgetEnabled ? {} : { temperature: options.temperature },
4989
5785
  ...systemMessages.length > 0 ? { system: systemMessages.map((m) => m.content).join("\n\n") } : {},
4990
5786
  messages: nonSystemMessages.map((m) => convertMessageToAnthropic(m)),
4991
- stream: false
5787
+ stream: false,
5788
+ ...reasoningWire.thinking ? { thinking: reasoningWire.thinking } : {},
5789
+ ...reasoningWire.outputConfig ? { output_config: reasoningWire.outputConfig } : {}
4992
5790
  };
4993
5791
  const apiUrl2 = buildProviderApiUrl(provider, { model: options.model, stream: false });
4994
5792
  const headers2 = getProviderHeaders(provider, apiKey);
@@ -5030,14 +5828,24 @@ async function callAnthropicCompletion(provider, apiKey, options, logger) {
5030
5828
  content: m.content
5031
5829
  })),
5032
5830
  // Anthropic requires max_tokens; use 16384 default if not explicitly set
5033
- max_tokens: options.maxTokens ?? 16384,
5034
- temperature: options.temperature,
5831
+ max_tokens: reasoningWire.effectiveMaxTokens,
5832
+ temperature: reasoningWire.legacyBudgetEnabled ? void 0 : options.temperature,
5035
5833
  stream: false
5036
5834
  };
5037
5835
  const config = {
5038
5836
  defaultModel: options.model
5039
5837
  };
5040
5838
  const anthropicRequest = convertOpenAIToAnthropic(openaiRequest, config);
5839
+ const anthropicRequestBody = anthropicRequest;
5840
+ if (reasoningWire.thinking) {
5841
+ anthropicRequestBody.thinking = reasoningWire.thinking;
5842
+ }
5843
+ if (reasoningWire.outputConfig) {
5844
+ anthropicRequestBody.output_config = reasoningWire.outputConfig;
5845
+ }
5846
+ if (reasoningWire.legacyBudgetEnabled) {
5847
+ delete anthropicRequestBody.temperature;
5848
+ }
5041
5849
  const apiUrl = buildProviderApiUrl(provider, { model: options.model, stream: false });
5042
5850
  const headers = getProviderHeaders(provider, apiKey);
5043
5851
  logger.info("Calling Anthropic completion API", { url: apiUrl, model: options.model });
@@ -5082,11 +5890,13 @@ async function callGeminiCompletion(provider, apiKey, options, logger) {
5082
5890
  contents.push(convertMessageToGemini(msg));
5083
5891
  }
5084
5892
  }
5893
+ const thinkingConfig = buildGeminiThinkingConfig(provider, options);
5085
5894
  const request = {
5086
5895
  contents,
5087
5896
  generationConfig: {
5088
5897
  ...options.maxTokens !== void 0 ? { maxOutputTokens: options.maxTokens } : {},
5089
- ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
5898
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
5899
+ ...thinkingConfig ? { thinkingConfig } : {}
5090
5900
  }
5091
5901
  };
5092
5902
  if (systemInstruction) {
@@ -5157,11 +5967,9 @@ async function callOpenAIResponseCompletion(provider, apiKey, options, logger) {
5157
5967
  ...options.maxTokens !== void 0 ? { max_output_tokens: options.maxTokens } : {},
5158
5968
  ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
5159
5969
  };
5160
- if (options.thinkLevel && options.thinkLevel !== "none") {
5161
- const effort = getOpenAIReasoningEffort(options.thinkLevel);
5162
- if (effort) {
5163
- request.reasoning = { effort, summary: "auto" };
5164
- }
5970
+ const responseEffort = resolveOpenAIEffort(provider, options, "openai-responses");
5971
+ if (responseEffort) {
5972
+ request.reasoning = { effort: responseEffort, summary: "auto" };
5165
5973
  }
5166
5974
  const apiUrl = buildProviderApiUrl(provider, { model: options.model, stream: false });
5167
5975
  const headers = getProviderHeaders(provider, apiKey);
@@ -5213,7 +6021,6 @@ async function callOpenAIResponseCompletion(provider, apiKey, options, logger) {
5213
6021
  }
5214
6022
 
5215
6023
  // src/completion/StreamHandler.ts
5216
- import { buildAnthropicThinking, getOpenAIReasoningEffort as getOpenAIReasoningEffort2 } from "@omnicross/contracts/thinking-config";
5217
6024
  async function streamOpenAICompletion(provider, apiKey, options, messageId, callbacks, logger) {
5218
6025
  const request = {
5219
6026
  model: options.model,
@@ -5223,11 +6030,9 @@ async function streamOpenAICompletion(provider, apiKey, options, messageId, call
5223
6030
  temperature: options.temperature,
5224
6031
  stream: true
5225
6032
  };
5226
- if (options.thinkLevel && options.thinkLevel !== "none") {
5227
- const effort = getOpenAIReasoningEffort2(options.thinkLevel);
5228
- if (effort) {
5229
- request.reasoning_effort = effort;
5230
- }
6033
+ const effort = resolveOpenAIEffort(provider, options, "openai-chat");
6034
+ if (effort) {
6035
+ request.reasoning_effort = effort;
5231
6036
  }
5232
6037
  const apiUrl = buildProviderApiUrl(provider, { model: options.model, stream: true });
5233
6038
  const headers = getProviderHeaders(provider, apiKey);
@@ -5277,16 +6082,11 @@ async function streamOpenAICompletion(provider, apiKey, options, messageId, call
5277
6082
  }
5278
6083
  async function streamAnthropicCompletion(provider, apiKey, options, messageId, callbacks, logger) {
5279
6084
  const hasImages = options.messages.some((m) => m.images && m.images.length > 0);
5280
- const MAX_TOKENS_FOR_THINKING = 16384;
5281
- let effectiveMaxTokens = options.maxTokens ?? 16384;
5282
- const thinkingMaxTokens = options.thinkLevel && options.thinkLevel !== "none" ? Math.min(effectiveMaxTokens, MAX_TOKENS_FOR_THINKING) : effectiveMaxTokens;
5283
- const thinkingConfig = options.thinkLevel && options.thinkLevel !== "none" ? buildAnthropicThinking(options.model, options.thinkLevel, thinkingMaxTokens) : void 0;
5284
- if (thinkingConfig) {
5285
- effectiveMaxTokens = thinkingMaxTokens;
5286
- }
6085
+ const reasoningWire = buildAnthropicReasoningWire(provider, options);
6086
+ const effectiveMaxTokens = reasoningWire.effectiveMaxTokens;
5287
6087
  logger.debug("Anthropic thinking configuration", {
5288
6088
  thinkLevel: options.thinkLevel,
5289
- thinkingConfig,
6089
+ thinkingConfig: reasoningWire.thinking,
5290
6090
  effectiveMaxTokens
5291
6091
  });
5292
6092
  let anthropicRequest;
@@ -5297,12 +6097,15 @@ async function streamAnthropicCompletion(provider, apiKey, options, messageId, c
5297
6097
  model: options.model,
5298
6098
  max_tokens: effectiveMaxTokens,
5299
6099
  // Omit temperature when thinking is enabled (Anthropic will use default temperature=1)
5300
- ...thinkingConfig ? {} : { temperature: options.temperature },
6100
+ ...reasoningWire.legacyBudgetEnabled ? {} : { temperature: options.temperature },
5301
6101
  ...systemMessages.length > 0 ? { system: systemMessages.map((m) => m.content).join("\n\n") } : {},
5302
6102
  messages: nonSystemMessages.map((m) => convertMessageToAnthropic(m)),
5303
6103
  stream: true,
5304
- ...thinkingConfig ? { thinking: thinkingConfig } : {}
6104
+ ...reasoningWire.thinking ? { thinking: reasoningWire.thinking } : {}
5305
6105
  };
6106
+ if (reasoningWire.outputConfig) {
6107
+ anthropicRequest.output_config = reasoningWire.outputConfig;
6108
+ }
5306
6109
  } else {
5307
6110
  const config = {
5308
6111
  defaultModel: options.model
@@ -5316,12 +6119,17 @@ async function streamAnthropicCompletion(provider, apiKey, options, messageId, c
5316
6119
  // Anthropic requires max_tokens; use adjusted value
5317
6120
  max_tokens: effectiveMaxTokens,
5318
6121
  // Omit temperature when thinking is enabled (Anthropic will use default temperature=1)
5319
- temperature: thinkingConfig ? void 0 : options.temperature,
6122
+ temperature: reasoningWire.legacyBudgetEnabled ? void 0 : options.temperature,
5320
6123
  stream: true
5321
6124
  };
5322
6125
  anthropicRequest = convertOpenAIToAnthropic(openaiRequest, config);
5323
- if (thinkingConfig) {
5324
- anthropicRequest.thinking = thinkingConfig;
6126
+ if (reasoningWire.thinking) {
6127
+ anthropicRequest.thinking = reasoningWire.thinking;
6128
+ }
6129
+ if (reasoningWire.outputConfig) {
6130
+ anthropicRequest.output_config = reasoningWire.outputConfig;
6131
+ }
6132
+ if (reasoningWire.legacyBudgetEnabled) {
5325
6133
  delete anthropicRequest.temperature;
5326
6134
  }
5327
6135
  }
@@ -5387,11 +6195,13 @@ async function streamGeminiCompletion(provider, apiKey, options, messageId, call
5387
6195
  contents.push(convertMessageToGemini(msg));
5388
6196
  }
5389
6197
  }
6198
+ const thinkingConfig = buildGeminiThinkingConfig(provider, options);
5390
6199
  const request = {
5391
6200
  contents,
5392
6201
  generationConfig: {
5393
6202
  ...options.maxTokens !== void 0 ? { maxOutputTokens: options.maxTokens } : {},
5394
- ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
6203
+ ...options.temperature !== void 0 ? { temperature: options.temperature } : {},
6204
+ ...thinkingConfig ? { thinkingConfig } : {}
5395
6205
  }
5396
6206
  };
5397
6207
  if (systemInstruction) {
@@ -5457,11 +6267,9 @@ async function streamOpenAIResponseCompletion(provider, apiKey, options, message
5457
6267
  ...options.maxTokens !== void 0 ? { max_output_tokens: options.maxTokens } : {},
5458
6268
  ...options.temperature !== void 0 ? { temperature: options.temperature } : {}
5459
6269
  };
5460
- if (options.thinkLevel && options.thinkLevel !== "none") {
5461
- const effort = getOpenAIReasoningEffort2(options.thinkLevel);
5462
- if (effort) {
5463
- request.reasoning = { effort, summary: "auto" };
5464
- }
6270
+ const responseEffort = resolveOpenAIEffort(provider, options, "openai-responses");
6271
+ if (responseEffort) {
6272
+ request.reasoning = { effort: responseEffort, summary: "auto" };
5465
6273
  }
5466
6274
  const apiUrl = buildProviderApiUrl(provider, { model: options.model, stream: true });
5467
6275
  const headers = getProviderHeaders(provider, apiKey);
@@ -5505,7 +6313,7 @@ async function streamOpenAIResponseCompletion(provider, apiKey, options, message
5505
6313
 
5506
6314
  // src/completion/ThinkingResolver.ts
5507
6315
  import {
5508
- buildAnthropicThinking as buildAnthropicThinking2,
6316
+ buildAnthropicThinking,
5509
6317
  calculateThinkingBudget,
5510
6318
  DEFAULT_MAX_TOKENS,
5511
6319
  getClaudeMaxTokens,
@@ -5549,7 +6357,7 @@ async function resolveThinkingBudget(getProvider, logger, providerId, modelId, m
5549
6357
  const providerName = provider?.name?.toLowerCase() || "";
5550
6358
  const apiFormat = provider ? resolveApiFormat(provider) : "openai";
5551
6359
  if (apiFormat === "anthropic" || providerName === "anthropic" || providerName.includes("claude")) {
5552
- const thinkingConfig = buildAnthropicThinking2(modelId, thinkLevel, maxTokens);
6360
+ const thinkingConfig = buildAnthropicThinking(modelId, thinkLevel, maxTokens);
5553
6361
  const adjustedMaxTokens = getClaudeMaxTokens(maxTokens, thinkingBudget) || maxTokens;
5554
6362
  logger.debug("Claude model thinking budget", {
5555
6363
  thinkingBudget,
@@ -6204,7 +7012,8 @@ async function completeWithTransformers(options, llmConfig, getProvider, resolve
6204
7012
  name: provider.name,
6205
7013
  baseUrl: provider.api_base_url,
6206
7014
  apiKey,
6207
- models: provider.models || []
7015
+ models: provider.models || [],
7016
+ modelConfigs: provider.modelConfigs
6208
7017
  };
6209
7018
  const executor = getSharedExecutor2();
6210
7019
  const { response } = await executeProviderCall({
@@ -6334,7 +7143,8 @@ async function completeStreamWithTransformers(options, callbacks, llmConfig, get
6334
7143
  name: provider.name,
6335
7144
  baseUrl: provider.api_base_url,
6336
7145
  apiKey,
6337
- models: provider.models || []
7146
+ models: provider.models || [],
7147
+ modelConfigs: provider.modelConfigs
6338
7148
  };
6339
7149
  const executor = getSharedExecutor2();
6340
7150
  const { response } = await executeProviderCall({
@@ -6985,6 +7795,37 @@ export {
6985
7795
  extractRouteToken,
6986
7796
  isLoopbackAddress,
6987
7797
  ProviderProxy,
7798
+ ROUTE_LEASE_REQUEST_SCHEMA,
7799
+ ROUTE_LEASE_RESULT_SCHEMA,
7800
+ ROUTE_LEASE_CAPABILITIES_SCHEMA,
7801
+ ROUTE_LEASE_API_VERSION,
7802
+ ROUTE_LEASE_DEFAULT_TTL_SECONDS,
7803
+ ROUTE_LEASE_MAX_TTL_SECONDS,
7804
+ ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES,
7805
+ ROUTE_LEASE_MAX_CONSUMER_BYTES,
7806
+ ROUTE_LEASE_MAX_EXECUTION_ID_BYTES,
7807
+ ROUTE_LEASE_MAX_SESSION_ID_BYTES,
7808
+ ROUTE_LEASE_MAX_MODEL_BYTES,
7809
+ ROUTE_LEASE_SESSION_HASH_DOMAIN,
7810
+ ROUTE_LEASE_CODEX_TOKEN_ENV,
7811
+ ROUTE_LEASE_RUNTIMES,
7812
+ ROUTE_LEASE_CAPABILITIES,
7813
+ ROUTE_LEASE_RUNTIME_TABLE,
7814
+ RouteLeaseError,
7815
+ normalizeRouteLeaseIdempotencyKey,
7816
+ normalizeRouteLeaseTtl,
7817
+ hashRouteLeaseSessionId,
7818
+ canonicalizeRouteLeasePayload,
7819
+ hashRouteLeasePayload,
7820
+ parseRouteLeaseCreate,
7821
+ routeLeaseRuntime,
7822
+ SYSTEM_ROUTE_LEASE_CLOCK,
7823
+ RouteLeaseManager,
7824
+ gatewayBindingToEndpointConfig,
7825
+ candidateGatewayBindings,
7826
+ candidateBackgroundModelIds,
7827
+ resolveGatewayBinding,
7828
+ RouteLeaseTargetResolver,
6988
7829
  getProviderProxy,
6989
7830
  __resetProviderProxyForTests,
6990
7831
  KeyedMutex,
@@ -7013,10 +7854,6 @@ export {
7013
7854
  loadServerConfig,
7014
7855
  saveServerConfig,
7015
7856
  mergeServerConfig,
7016
- gatewayBindingToEndpointConfig,
7017
- candidateGatewayBindings,
7018
- candidateBackgroundModelIds,
7019
- resolveGatewayBinding,
7020
7857
  computeKeyExpiry,
7021
7858
  checkKeyQuota,
7022
7859
  randomBase62,