@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.
- package/dist/{ProviderProxy-C-xqrkKi.d.ts → ProviderProxy--ROec6VI.d.ts} +22 -3
- package/dist/{ProviderProxy-CnMQYN59.d.cts → ProviderProxy-QdwNV6l1.d.cts} +22 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +5 -3
- package/dist/auth/GeminiCodeAssistProjectResolver.js +4 -2
- package/dist/{chunk-N5T4M4GU.js → chunk-2UTGX6FM.js} +2 -2
- package/dist/{chunk-32O35CIF.js → chunk-45FHEOKW.js} +22 -7
- package/dist/{chunk-LVUCAWMK.js → chunk-55FMGZ3L.js} +13 -5
- package/dist/{chunk-OF3BECU2.cjs → chunk-56QD3YW6.cjs} +18 -5
- package/dist/{chunk-V4TXMEJS.cjs → chunk-5LNYGCIW.cjs} +15 -7
- package/dist/chunk-77USGNRW.cjs +37 -0
- package/dist/chunk-AOFWNWLT.cjs +203 -0
- package/dist/{chunk-LB6EBS6Y.js → chunk-CCVRRJOX.js} +17 -4
- package/dist/{chunk-OVQBNVSV.cjs → chunk-CMVXX7ON.cjs} +42 -27
- package/dist/{chunk-E3COOKUP.js → chunk-CQIJCPMF.js} +1108 -271
- package/dist/{chunk-OJGQKHN5.cjs → chunk-EPN67EGP.cjs} +3 -0
- package/dist/{chunk-PEGTIPB3.js → chunk-HKB656B4.js} +37 -43
- package/dist/chunk-N2EIM2XJ.js +203 -0
- package/dist/{chunk-MYYR3HZL.js → chunk-NGYOO5TO.js} +5 -5
- package/dist/{chunk-OKMJLEXX.cjs → chunk-OC6DOOFX.cjs} +5 -5
- package/dist/{chunk-2Z4XM3H5.cjs → chunk-P7EM6BND.cjs} +10 -10
- package/dist/{chunk-KBM2W6UY.cjs → chunk-RICPANBY.cjs} +41 -24
- package/dist/{chunk-TOVPMMHM.js → chunk-S3DYFEBE.js} +37 -20
- package/dist/{chunk-SOO562NM.js → chunk-UAPBLNN2.js} +3 -0
- package/dist/chunk-WYVGULTQ.js +37 -0
- package/dist/{chunk-4BB343BI.cjs → chunk-XELFM7KL.cjs} +1381 -544
- package/dist/{chunk-OXHNSZ53.cjs → chunk-ZQQUD66M.cjs} +54 -60
- package/dist/completion/ApiKeyPoolService.cjs +32 -1
- package/dist/completion/ApiKeyPoolService.d.cts +17 -1
- package/dist/completion/ApiKeyPoolService.d.ts +17 -1
- package/dist/completion/ApiKeyPoolService.js +31 -0
- package/dist/completion/CompletionService.cjs +13 -11
- package/dist/completion/CompletionService.js +12 -10
- package/dist/completion.cjs +13 -11
- package/dist/completion.js +12 -10
- package/dist/index.cjs +69 -11
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +68 -10
- package/dist/outbound-api/routeResolver.d.cts +4 -4
- package/dist/outbound-api/routeResolver.d.ts +4 -4
- package/dist/outbound-api/types.d.cts +3 -3
- package/dist/outbound-api/types.d.ts +3 -3
- package/dist/outbound-api.cjs +13 -11
- package/dist/outbound-api.d.cts +5 -5
- package/dist/outbound-api.d.ts +5 -5
- package/dist/outbound-api.js +12 -10
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +3 -3
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +3 -3
- package/dist/pipeline/executeProviderCall.d.cts +2 -0
- package/dist/pipeline/executeProviderCall.d.ts +2 -0
- package/dist/pipeline/resolveProviderChain.d.cts +1 -0
- package/dist/pipeline/resolveProviderChain.d.ts +1 -0
- package/dist/pipeline/resolveSubscriptionChain.d.cts +2 -0
- package/dist/pipeline/resolveSubscriptionChain.d.ts +2 -0
- package/dist/ports/provider-config-source.d.cts +1 -0
- package/dist/ports/provider-config-source.d.ts +1 -0
- package/dist/ports.d.cts +3 -3
- package/dist/ports.d.ts +3 -3
- package/dist/provider-proxy/ProviderProxy.cjs +13 -11
- package/dist/provider-proxy/ProviderProxy.d.cts +1 -1
- package/dist/provider-proxy/ProviderProxy.d.ts +1 -1
- package/dist/provider-proxy/ProviderProxy.js +12 -10
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +13 -11
- package/dist/provider-proxy/ingress/providerProxyShared.js +12 -10
- package/dist/provider-proxy/types.d.cts +15 -1
- package/dist/provider-proxy/types.d.ts +15 -1
- package/dist/provider-proxy.cjs +69 -11
- package/dist/provider-proxy.d.cts +254 -8
- package/dist/provider-proxy.d.ts +254 -8
- package/dist/provider-proxy.js +70 -12
- package/dist/{routeResolver-BxOfunbm.d.ts → routeResolver-C0mkkZaM.d.ts} +1 -1
- package/dist/{routeResolver-D5OzD0iM.d.cts → routeResolver-CVFE8KKe.d.cts} +1 -1
- package/dist/transformer/TransformerChainExecutor.d.cts +2 -0
- package/dist/transformer/TransformerChainExecutor.d.ts +2 -0
- package/dist/transformer/TransformerService.d.cts +2 -0
- package/dist/transformer/TransformerService.d.ts +2 -0
- package/dist/transformer/transformers/AnthropicTransformer.cjs +4 -2
- package/dist/transformer/transformers/AnthropicTransformer.d.cts +7 -2
- package/dist/transformer/transformers/AnthropicTransformer.d.ts +7 -2
- package/dist/transformer/transformers/AnthropicTransformer.js +3 -1
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.cjs +5 -3
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.cts +2 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.d.ts +2 -0
- package/dist/transformer/transformers/GeminiCodeAssistTransformer.js +4 -2
- package/dist/transformer/transformers/GeminiTransformer.cjs +5 -3
- package/dist/transformer/transformers/GeminiTransformer.d.cts +4 -2
- package/dist/transformer/transformers/GeminiTransformer.d.ts +4 -2
- package/dist/transformer/transformers/GeminiTransformer.js +4 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.cjs +3 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.cts +4 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.d.ts +4 -2
- package/dist/transformer/transformers/OpenAIResponseTransformer.js +2 -1
- package/dist/transformer/transformers/OpenAITransformer.cjs +3 -2
- package/dist/transformer/transformers/OpenAITransformer.d.cts +3 -1
- package/dist/transformer/transformers/OpenAITransformer.d.ts +3 -1
- package/dist/transformer/transformers/OpenAITransformer.js +2 -1
- package/dist/transformer/transformers.cjs +10 -8
- package/dist/transformer/transformers.d.cts +2 -0
- package/dist/transformer/transformers.d.ts +2 -0
- package/dist/transformer/transformers.js +9 -7
- package/dist/transformer/types.d.cts +16 -5
- package/dist/transformer/types.d.ts +16 -5
- package/dist/transformer.cjs +28 -9
- package/dist/transformer.d.cts +76 -1
- package/dist/transformer.d.ts +76 -1
- package/dist/transformer.js +28 -9
- package/dist/{types-CqZD77rS.d.ts → types-BB8pxk_3.d.ts} +1 -1
- package/dist/{types-gjJ5lJ5n.d.cts → types-C2MwkLah.d.cts} +1 -1
- package/dist/usage/usage-recorder.cjs +2 -2
- package/dist/usage/usage-recorder.d.cts +6 -0
- package/dist/usage/usage-recorder.d.ts +6 -0
- package/dist/usage/usage-recorder.js +1 -1
- package/dist/usage.cjs +2 -2
- package/dist/usage.js +1 -1
- package/package.json +1 -1
- /package/dist/{chunk-FZ46M34D.cjs → chunk-QVOB2DNX.cjs} +0 -0
- /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-
|
|
7
|
+
} from "./chunk-HKB656B4.js";
|
|
8
8
|
import {
|
|
9
9
|
GeminiTransformer
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-55FMGZ3L.js";
|
|
11
11
|
import {
|
|
12
12
|
OpenAIResponseTransformer
|
|
13
|
-
} from "./chunk-
|
|
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,
|
|
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
|
-
|
|
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=${
|
|
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:
|
|
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,
|
|
2632
|
-
return this.routes.addRoute(context,
|
|
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
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
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
|
-
|
|
2639
|
-
|
|
2640
|
-
|
|
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
|
|
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 =
|
|
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
|
|
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()}_${
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
4286
|
-
|
|
4287
|
-
(binding
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
|
|
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 =
|
|
4303
|
-
if (seen.has(
|
|
4304
|
-
seen.add(
|
|
5035
|
+
const data = modelIds.filter((modelId) => {
|
|
5036
|
+
if (seen.has(modelId)) return false;
|
|
5037
|
+
seen.add(modelId);
|
|
4305
5038
|
return true;
|
|
4306
|
-
}).map((
|
|
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
|
-
|
|
4942
|
-
|
|
4943
|
-
|
|
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:
|
|
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:
|
|
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
|
-
|
|
5161
|
-
|
|
5162
|
-
|
|
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
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
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
|
|
5281
|
-
|
|
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
|
-
...
|
|
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
|
-
...
|
|
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:
|
|
6122
|
+
temperature: reasoningWire.legacyBudgetEnabled ? void 0 : options.temperature,
|
|
5320
6123
|
stream: true
|
|
5321
6124
|
};
|
|
5322
6125
|
anthropicRequest = convertOpenAIToAnthropic(openaiRequest, config);
|
|
5323
|
-
if (
|
|
5324
|
-
anthropicRequest.thinking =
|
|
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
|
-
|
|
5461
|
-
|
|
5462
|
-
|
|
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
|
|
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 =
|
|
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,
|