@omnicross/core 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{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/{chunk-4BB343BI.cjs → chunk-26K6T2YS.cjs} +1209 -472
- package/dist/{chunk-OJGQKHN5.cjs → chunk-EPN67EGP.cjs} +3 -0
- package/dist/{chunk-E3COOKUP.js → chunk-QYC54PMG.js} +924 -187
- package/dist/{chunk-SOO562NM.js → chunk-UAPBLNN2.js} +3 -0
- 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 +3 -3
- package/dist/completion/CompletionService.js +2 -2
- package/dist/completion.cjs +3 -3
- package/dist/completion.js +2 -2
- package/dist/index.cjs +59 -3
- package/dist/index.d.cts +5 -5
- package/dist/index.d.ts +5 -5
- package/dist/index.js +58 -2
- package/dist/outbound-api/routeResolver.d.cts +3 -3
- package/dist/outbound-api/routeResolver.d.ts +3 -3
- package/dist/outbound-api/types.d.cts +2 -2
- package/dist/outbound-api/types.d.ts +2 -2
- package/dist/outbound-api.cjs +3 -3
- package/dist/outbound-api.d.cts +4 -4
- package/dist/outbound-api.d.ts +4 -4
- package/dist/outbound-api.js +2 -2
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +2 -2
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +2 -2
- package/dist/ports.d.cts +2 -2
- package/dist/ports.d.ts +2 -2
- package/dist/provider-proxy/ProviderProxy.cjs +3 -3
- package/dist/provider-proxy/ProviderProxy.d.cts +1 -1
- package/dist/provider-proxy/ProviderProxy.d.ts +1 -1
- package/dist/provider-proxy/ProviderProxy.js +2 -2
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +3 -3
- package/dist/provider-proxy/ingress/providerProxyShared.js +2 -2
- package/dist/provider-proxy/types.d.cts +15 -1
- package/dist/provider-proxy/types.d.ts +15 -1
- package/dist/provider-proxy.cjs +59 -3
- package/dist/provider-proxy.d.cts +254 -8
- package/dist/provider-proxy.d.ts +254 -8
- package/dist/provider-proxy.js +60 -4
- 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/{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
|
@@ -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,
|
|
@@ -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
|
};
|
|
@@ -1571,6 +1607,10 @@ function emitGeminiUsageRecord(recorder, rawUsageMetadata, attribution) {
|
|
|
1571
1607
|
providerId: attribution.providerId,
|
|
1572
1608
|
model: attribution.model,
|
|
1573
1609
|
apiKeyId: attribution.apiKeyId,
|
|
1610
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
1611
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
1612
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
1613
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
1574
1614
|
auditResponse: attribution.auditResponse,
|
|
1575
1615
|
engineOrigin: "codex-ingress",
|
|
1576
1616
|
usage: tapped,
|
|
@@ -1636,6 +1676,7 @@ async function handleGeminiGenerateContentRequest(res, rawBody, url, route, deps
|
|
|
1636
1676
|
providerId: route.providerId ?? "gemini",
|
|
1637
1677
|
model: resolvedModel,
|
|
1638
1678
|
apiKeyId: route.apiKeyId ?? null,
|
|
1679
|
+
routeLease: route.routeLease,
|
|
1639
1680
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1640
1681
|
auditResponse: res
|
|
1641
1682
|
};
|
|
@@ -1792,6 +1833,10 @@ function emitChatCompletionsUsageRecord(recorder, rawUsage, attribution) {
|
|
|
1792
1833
|
providerId: attribution.providerId,
|
|
1793
1834
|
model: attribution.model,
|
|
1794
1835
|
apiKeyId: attribution.apiKeyId,
|
|
1836
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
1837
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
1838
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
1839
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
1795
1840
|
auditResponse: attribution.auditResponse,
|
|
1796
1841
|
engineOrigin: "codex-ingress",
|
|
1797
1842
|
usage: tapped,
|
|
@@ -1844,6 +1889,7 @@ async function handleOpenAIChatRequest(res, rawBody, route, deps) {
|
|
|
1844
1889
|
providerId: route.providerId ?? "openai",
|
|
1845
1890
|
model: resolvedModel,
|
|
1846
1891
|
apiKeyId: route.apiKeyId ?? null,
|
|
1892
|
+
routeLease: route.routeLease,
|
|
1847
1893
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
1848
1894
|
auditResponse: res
|
|
1849
1895
|
};
|
|
@@ -2072,6 +2118,10 @@ function emitResponsesUsageRecord(recorder, rawUsage, attribution) {
|
|
|
2072
2118
|
providerId: attribution.providerId,
|
|
2073
2119
|
model: attribution.model,
|
|
2074
2120
|
apiKeyId: attribution.apiKeyId,
|
|
2121
|
+
runId: attribution.routeLease?.runId ?? null,
|
|
2122
|
+
routeLeaseId: attribution.routeLease?.leaseId ?? null,
|
|
2123
|
+
routeLeaseConsumer: attribution.routeLease?.consumer ?? null,
|
|
2124
|
+
routeLeaseStageId: attribution.routeLease?.stageId ?? null,
|
|
2075
2125
|
cacheKeySource: attribution.cacheKeySource,
|
|
2076
2126
|
cacheKeyInjected: attribution.cacheKeyInjected,
|
|
2077
2127
|
auditResponse: attribution.auditResponse,
|
|
@@ -2205,8 +2255,9 @@ async function handleOpenAIResponsesRequest(res, rawBody, route, deps, requestHe
|
|
|
2205
2255
|
const usageAttribution = {
|
|
2206
2256
|
sessionId: route.sessionId,
|
|
2207
2257
|
providerId: route.providerId ?? "codex",
|
|
2208
|
-
model: resolvedModel,
|
|
2258
|
+
model: providerResponse.actualModel ?? resolvedModel,
|
|
2209
2259
|
apiKeyId: route.apiKeyId ?? null,
|
|
2260
|
+
routeLease: route.routeLease,
|
|
2210
2261
|
cacheKeySource: cacheKeyAttribution.cacheKeySource,
|
|
2211
2262
|
cacheKeyInjected: cacheKeyAttribution.cacheKeyInjected,
|
|
2212
2263
|
// request-audit-log: correlate this request's tokens/cost to its audit record.
|
|
@@ -2391,6 +2442,7 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2391
2442
|
const endpointTransformer = getResponsesEndpointTransformer();
|
|
2392
2443
|
const { auth, sessionKey, chain, transformerProvider, resolvedModel, isStream, resolveUrl, upstreamUrl } = plan;
|
|
2393
2444
|
let proxyAccountId;
|
|
2445
|
+
let actualModel = resolvedModel;
|
|
2394
2446
|
let activityRecordId;
|
|
2395
2447
|
const authHeaders = {};
|
|
2396
2448
|
await auth.applyHeaders(authHeaders, {
|
|
@@ -2400,10 +2452,13 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2400
2452
|
preferredAccountId: plan.preferredAccountId,
|
|
2401
2453
|
preferredAccountGroup: plan.preferredAccountGroup,
|
|
2402
2454
|
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
2403
|
-
reportSelection: (accountId) => {
|
|
2455
|
+
reportSelection: (accountId, _isActive, remappedModel) => {
|
|
2404
2456
|
proxyAccountId = accountId;
|
|
2457
|
+
if (remappedModel) actualModel = remappedModel;
|
|
2405
2458
|
}
|
|
2406
2459
|
});
|
|
2460
|
+
responsesBody.model = actualModel;
|
|
2461
|
+
transformerProvider.models = [actualModel];
|
|
2407
2462
|
let rawStatus = null;
|
|
2408
2463
|
const { response } = await executeProviderCall({
|
|
2409
2464
|
executor,
|
|
@@ -2427,7 +2482,7 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2427
2482
|
return headers;
|
|
2428
2483
|
},
|
|
2429
2484
|
fetchFn: (url, headers, body) => {
|
|
2430
|
-
console.log(`[ProviderProxy:responses] -> ${url} model=${
|
|
2485
|
+
console.log(`[ProviderProxy:responses] -> ${url} model=${actualModel} stream=${isStream}`);
|
|
2431
2486
|
return fetchUpstream(
|
|
2432
2487
|
url,
|
|
2433
2488
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
@@ -2438,7 +2493,7 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2438
2493
|
endpoint: "responses",
|
|
2439
2494
|
sessionKey,
|
|
2440
2495
|
sessionSource: plan.sessionSource ?? "none",
|
|
2441
|
-
model:
|
|
2496
|
+
model: actualModel,
|
|
2442
2497
|
onRecorded: (record) => {
|
|
2443
2498
|
activityRecordId = record.id;
|
|
2444
2499
|
}
|
|
@@ -2451,7 +2506,7 @@ async function runPipeline4(responsesBody, plan) {
|
|
|
2451
2506
|
},
|
|
2452
2507
|
runResponseChain: true
|
|
2453
2508
|
});
|
|
2454
|
-
return { response, rawStatus, accountId: proxyAccountId, activityRecordId };
|
|
2509
|
+
return { response, rawStatus, accountId: proxyAccountId, actualModel, activityRecordId };
|
|
2455
2510
|
}
|
|
2456
2511
|
async function runPipelineWithPoolReporting4(responsesBody, plan) {
|
|
2457
2512
|
const first = await runPipeline4(responsesBody, plan);
|
|
@@ -2555,6 +2610,7 @@ var ProviderProxy = class {
|
|
|
2555
2610
|
server = null;
|
|
2556
2611
|
port = 0;
|
|
2557
2612
|
routes;
|
|
2613
|
+
beforeStop = /* @__PURE__ */ new Set();
|
|
2558
2614
|
/**
|
|
2559
2615
|
* Start the resident listener on a stable port on 127.0.0.1. Idempotent —
|
|
2560
2616
|
* a second `start()` returns the already-bound port.
|
|
@@ -2595,6 +2651,12 @@ var ProviderProxy = class {
|
|
|
2595
2651
|
}
|
|
2596
2652
|
/** Stop the listener, clear all routes, and release the port. */
|
|
2597
2653
|
async stop() {
|
|
2654
|
+
for (const cleanup of [...this.beforeStop]) {
|
|
2655
|
+
try {
|
|
2656
|
+
await cleanup();
|
|
2657
|
+
} catch {
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2598
2660
|
this.routes.clear();
|
|
2599
2661
|
const server = this.server;
|
|
2600
2662
|
if (!server) return;
|
|
@@ -2611,6 +2673,15 @@ var ProviderProxy = class {
|
|
|
2611
2673
|
getBaseUrl() {
|
|
2612
2674
|
return `http://127.0.0.1:${this.port}`;
|
|
2613
2675
|
}
|
|
2676
|
+
/** Register daemon-owned cleanup that must run before route-map teardown. */
|
|
2677
|
+
registerBeforeStop(cleanup) {
|
|
2678
|
+
this.beforeStop.add(cleanup);
|
|
2679
|
+
return () => this.beforeStop.delete(cleanup);
|
|
2680
|
+
}
|
|
2681
|
+
/** Whether the resident listener has completed binding. */
|
|
2682
|
+
isReady() {
|
|
2683
|
+
return this.server !== null && this.port > 0;
|
|
2684
|
+
}
|
|
2614
2685
|
/**
|
|
2615
2686
|
* The SHARED route map. Exposed so the outbound API server
|
|
2616
2687
|
* (`outbound-api-server`) can mint per-request routes on the SAME map and
|
|
@@ -2628,8 +2699,12 @@ var ProviderProxy = class {
|
|
|
2628
2699
|
return this.deps;
|
|
2629
2700
|
}
|
|
2630
2701
|
/** Register a route for one run; returns the crypto route token (task 2.2). */
|
|
2631
|
-
addRoute(context,
|
|
2632
|
-
return this.routes.addRoute(context,
|
|
2702
|
+
addRoute(context, idleMsOrOptions) {
|
|
2703
|
+
return this.routes.addRoute(context, idleMsOrOptions);
|
|
2704
|
+
}
|
|
2705
|
+
/** Re-arm one route while retaining the exact same bearer token. */
|
|
2706
|
+
renewRoute(token, idleMs) {
|
|
2707
|
+
return this.routes.renewRoute(token, idleMs);
|
|
2633
2708
|
}
|
|
2634
2709
|
/** Remove a route at run end. Returns true if an entry existed. */
|
|
2635
2710
|
removeRoute(token) {
|
|
@@ -2641,6 +2716,802 @@ var ProviderProxy = class {
|
|
|
2641
2716
|
}
|
|
2642
2717
|
};
|
|
2643
2718
|
|
|
2719
|
+
// src/provider-proxy/RouteLeaseManager.ts
|
|
2720
|
+
import { randomBytes as randomBytes3, randomUUID } from "crypto";
|
|
2721
|
+
|
|
2722
|
+
// src/provider-proxy/routeLeaseSchema.ts
|
|
2723
|
+
import { createHash, createHmac, randomBytes as randomBytes2 } from "crypto";
|
|
2724
|
+
var ROUTE_LEASE_REQUEST_SCHEMA = "omnicross.route-lease.request/1";
|
|
2725
|
+
var ROUTE_LEASE_RESULT_SCHEMA = "omnicross.route-lease/1";
|
|
2726
|
+
var ROUTE_LEASE_CAPABILITIES_SCHEMA = "omnicross.route-lease.capabilities/1";
|
|
2727
|
+
var ROUTE_LEASE_API_VERSION = 1;
|
|
2728
|
+
var ROUTE_LEASE_DEFAULT_TTL_SECONDS = 600;
|
|
2729
|
+
var ROUTE_LEASE_MAX_TTL_SECONDS = 3600;
|
|
2730
|
+
var ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES = 256;
|
|
2731
|
+
var ROUTE_LEASE_MAX_CONSUMER_BYTES = 64;
|
|
2732
|
+
var ROUTE_LEASE_MAX_EXECUTION_ID_BYTES = 128;
|
|
2733
|
+
var ROUTE_LEASE_MAX_SESSION_ID_BYTES = 512;
|
|
2734
|
+
var ROUTE_LEASE_MAX_MODEL_BYTES = 256;
|
|
2735
|
+
var ROUTE_LEASE_SESSION_HASH_DOMAIN = "omnicross.route-lease.session/v1";
|
|
2736
|
+
var ROUTE_LEASE_CODEX_TOKEN_ENV = "OMNICROSS_CODEX_ROUTE_TOKEN";
|
|
2737
|
+
var ROUTE_LEASE_RUNTIMES = ["claude", "codex"];
|
|
2738
|
+
var ROUTE_LEASE_CAPABILITIES = Object.freeze({
|
|
2739
|
+
schemaVersion: ROUTE_LEASE_CAPABILITIES_SCHEMA,
|
|
2740
|
+
runtimes: Object.freeze(["claude", "codex"]),
|
|
2741
|
+
upstreamKinds: Object.freeze(["provider", "account", "account-group", "account-pool"]),
|
|
2742
|
+
leaseApiVersion: ROUTE_LEASE_API_VERSION,
|
|
2743
|
+
codexAuthMode: "env_key",
|
|
2744
|
+
maxTtlSeconds: ROUTE_LEASE_MAX_TTL_SECONDS
|
|
2745
|
+
});
|
|
2746
|
+
var ROUTE_LEASE_RUNTIME_TABLE = Object.freeze({
|
|
2747
|
+
claude: Object.freeze({ endpoint: "messages", ingressFormat: "anthropic-messages", wirePath: "/v1/messages" }),
|
|
2748
|
+
codex: Object.freeze({ endpoint: "responses", ingressFormat: "openai-responses", wirePath: "/openai/responses" })
|
|
2749
|
+
});
|
|
2750
|
+
var ERROR_DEFAULTS = {
|
|
2751
|
+
invalid_request: { status: 400, retryable: false },
|
|
2752
|
+
runtime_unsupported: { status: 400, retryable: false },
|
|
2753
|
+
model_not_configured: { status: 400, retryable: false },
|
|
2754
|
+
format_unsupported: { status: 400, retryable: false },
|
|
2755
|
+
control_unauthorized: { status: 403, retryable: false },
|
|
2756
|
+
upstream_not_found: { status: 404, retryable: false },
|
|
2757
|
+
lease_not_found: { status: 404, retryable: false },
|
|
2758
|
+
idempotency_conflict: { status: 409, retryable: false },
|
|
2759
|
+
upstream_unavailable: { status: 409, retryable: true },
|
|
2760
|
+
lease_expired: { status: 410, retryable: false },
|
|
2761
|
+
upstream_exhausted: { status: 429, retryable: true },
|
|
2762
|
+
daemon_not_ready: { status: 503, retryable: true }
|
|
2763
|
+
};
|
|
2764
|
+
var RouteLeaseError = class extends Error {
|
|
2765
|
+
constructor(code, message, options = {}) {
|
|
2766
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
2767
|
+
this.code = code;
|
|
2768
|
+
this.name = "RouteLeaseError";
|
|
2769
|
+
const defaults = ERROR_DEFAULTS[code];
|
|
2770
|
+
this.status = options.status ?? defaults.status;
|
|
2771
|
+
this.retryable = options.retryable ?? defaults.retryable;
|
|
2772
|
+
if (options.retryAfterSeconds !== void 0) {
|
|
2773
|
+
this.retryAfterSeconds = Math.max(1, Math.min(3600, Math.floor(options.retryAfterSeconds)));
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
code;
|
|
2777
|
+
type = "route_lease_error";
|
|
2778
|
+
status;
|
|
2779
|
+
retryable;
|
|
2780
|
+
retryAfterSeconds;
|
|
2781
|
+
toResponse() {
|
|
2782
|
+
return { error: { type: this.type, code: this.code, message: this.message, retryable: this.retryable } };
|
|
2783
|
+
}
|
|
2784
|
+
};
|
|
2785
|
+
var IDEMPOTENCY_RE = /^[A-Za-z0-9._:-]+$/u;
|
|
2786
|
+
var CONTROL_RE = /[\u0000-\u001f\u007f-\u009f]/u;
|
|
2787
|
+
var PROCESS_SESSION_HMAC_KEY = randomBytes2(32);
|
|
2788
|
+
function isRecord(value) {
|
|
2789
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
2790
|
+
}
|
|
2791
|
+
function byteLength(value) {
|
|
2792
|
+
return Buffer.byteLength(value, "utf8");
|
|
2793
|
+
}
|
|
2794
|
+
function normalizedString(value, field, maxBytes, options = {}) {
|
|
2795
|
+
if (typeof value !== "string") throw invalid(`${field} must be a string`);
|
|
2796
|
+
const normalized = value.trim();
|
|
2797
|
+
if (!normalized || byteLength(normalized) > maxBytes || CONTROL_RE.test(normalized)) {
|
|
2798
|
+
throw invalid(`${field} is invalid`);
|
|
2799
|
+
}
|
|
2800
|
+
if (options.asciiPattern && !options.asciiPattern.test(normalized)) {
|
|
2801
|
+
throw invalid(`${field} is invalid`);
|
|
2802
|
+
}
|
|
2803
|
+
return normalized;
|
|
2804
|
+
}
|
|
2805
|
+
function optionalExecutionId(value, field) {
|
|
2806
|
+
return value === void 0 ? void 0 : normalizedString(value, field, ROUTE_LEASE_MAX_EXECUTION_ID_BYTES);
|
|
2807
|
+
}
|
|
2808
|
+
function invalid(message) {
|
|
2809
|
+
return new RouteLeaseError("invalid_request", message);
|
|
2810
|
+
}
|
|
2811
|
+
function normalizeRouteLeaseIdempotencyKey(value) {
|
|
2812
|
+
return normalizedString(value, "Idempotency-Key", ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES, {
|
|
2813
|
+
asciiPattern: IDEMPOTENCY_RE
|
|
2814
|
+
});
|
|
2815
|
+
}
|
|
2816
|
+
function normalizeRouteLeaseTtl(value) {
|
|
2817
|
+
if (value === void 0) return ROUTE_LEASE_DEFAULT_TTL_SECONDS;
|
|
2818
|
+
if (!Number.isInteger(value) || value < 1 || value > ROUTE_LEASE_MAX_TTL_SECONDS) {
|
|
2819
|
+
throw invalid(`ttlSeconds must be an integer from 1 to ${ROUTE_LEASE_MAX_TTL_SECONDS}`);
|
|
2820
|
+
}
|
|
2821
|
+
return value;
|
|
2822
|
+
}
|
|
2823
|
+
function normalizeUpstream(value) {
|
|
2824
|
+
if (!isRecord(value) || typeof value.kind !== "string") throw invalid("upstream is invalid");
|
|
2825
|
+
const providerId = normalizedString(value.providerId, "upstream.providerId", 128);
|
|
2826
|
+
switch (value.kind) {
|
|
2827
|
+
case "provider": {
|
|
2828
|
+
const keyId = value.keyId === void 0 ? void 0 : normalizedString(value.keyId, "upstream.keyId", 128);
|
|
2829
|
+
return keyId ? { kind: "provider", providerId, keyId } : { kind: "provider", providerId };
|
|
2830
|
+
}
|
|
2831
|
+
case "account":
|
|
2832
|
+
return {
|
|
2833
|
+
kind: "account",
|
|
2834
|
+
providerId,
|
|
2835
|
+
accountId: normalizedString(value.accountId, "upstream.accountId", 128)
|
|
2836
|
+
};
|
|
2837
|
+
case "account-group":
|
|
2838
|
+
return {
|
|
2839
|
+
kind: "account-group",
|
|
2840
|
+
providerId,
|
|
2841
|
+
group: normalizedString(value.group, "upstream.group", 128)
|
|
2842
|
+
};
|
|
2843
|
+
case "account-pool":
|
|
2844
|
+
return { kind: "account-pool", providerId };
|
|
2845
|
+
default:
|
|
2846
|
+
throw invalid("upstream kind is unsupported");
|
|
2847
|
+
}
|
|
2848
|
+
}
|
|
2849
|
+
function hashRouteLeaseSessionId(sessionId, hmacKey = PROCESS_SESSION_HMAC_KEY) {
|
|
2850
|
+
return createHmac("sha256", hmacKey).update(ROUTE_LEASE_SESSION_HASH_DOMAIN, "utf8").update("\0", "utf8").update(sessionId, "utf8").digest("hex");
|
|
2851
|
+
}
|
|
2852
|
+
function canonicalizeRouteLeasePayload(value) {
|
|
2853
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
2854
|
+
if (Array.isArray(value)) {
|
|
2855
|
+
return `[${value.map((entry) => canonicalizeRouteLeasePayload(entry)).join(",")}]`;
|
|
2856
|
+
}
|
|
2857
|
+
const record = value;
|
|
2858
|
+
const fields = Object.keys(record).filter((key) => record[key] !== void 0).sort().map((key) => `${JSON.stringify(key)}:${canonicalizeRouteLeasePayload(record[key])}`);
|
|
2859
|
+
return `{${fields.join(",")}}`;
|
|
2860
|
+
}
|
|
2861
|
+
function hashRouteLeasePayload(value) {
|
|
2862
|
+
return createHash("sha256").update(canonicalizeRouteLeasePayload(value), "utf8").digest("hex");
|
|
2863
|
+
}
|
|
2864
|
+
function parseRouteLeaseCreate(raw, idempotencyHeader, hmacKey = PROCESS_SESSION_HMAC_KEY) {
|
|
2865
|
+
if (!isRecord(raw)) throw invalid("request body must be a JSON object");
|
|
2866
|
+
if (raw.schemaVersion !== ROUTE_LEASE_REQUEST_SCHEMA) {
|
|
2867
|
+
throw invalid(`schemaVersion must be ${ROUTE_LEASE_REQUEST_SCHEMA}`);
|
|
2868
|
+
}
|
|
2869
|
+
const consumer = normalizedString(raw.consumer, "consumer", ROUTE_LEASE_MAX_CONSUMER_BYTES);
|
|
2870
|
+
const runtime = raw.runtime;
|
|
2871
|
+
if (runtime !== "claude" && runtime !== "codex") {
|
|
2872
|
+
throw new RouteLeaseError("runtime_unsupported", "runtime is unsupported");
|
|
2873
|
+
}
|
|
2874
|
+
const upstream = normalizeUpstream(raw.upstream);
|
|
2875
|
+
if (typeof raw.model !== "string" || raw.model.trim().length === 0) {
|
|
2876
|
+
throw new RouteLeaseError("model_not_configured", "model is not configured");
|
|
2877
|
+
}
|
|
2878
|
+
const model = normalizedString(raw.model, "model", ROUTE_LEASE_MAX_MODEL_BYTES);
|
|
2879
|
+
const ttlSeconds = normalizeRouteLeaseTtl(raw.ttlSeconds);
|
|
2880
|
+
const idempotencyKey = normalizeRouteLeaseIdempotencyKey(idempotencyHeader);
|
|
2881
|
+
let execution;
|
|
2882
|
+
let semanticExecution;
|
|
2883
|
+
if (raw.execution !== void 0) {
|
|
2884
|
+
if (!isRecord(raw.execution)) throw invalid("execution must be a JSON object");
|
|
2885
|
+
const runId = optionalExecutionId(raw.execution.runId, "execution.runId");
|
|
2886
|
+
const stageId = optionalExecutionId(raw.execution.stageId, "execution.stageId");
|
|
2887
|
+
let attempt;
|
|
2888
|
+
if (raw.execution.attempt !== void 0) {
|
|
2889
|
+
if (!Number.isSafeInteger(raw.execution.attempt) || raw.execution.attempt < 1) {
|
|
2890
|
+
throw invalid("execution.attempt must be a positive integer");
|
|
2891
|
+
}
|
|
2892
|
+
attempt = raw.execution.attempt;
|
|
2893
|
+
}
|
|
2894
|
+
let sessionId;
|
|
2895
|
+
let sessionIdHash;
|
|
2896
|
+
if (raw.execution.sessionId !== void 0) {
|
|
2897
|
+
sessionId = normalizedString(raw.execution.sessionId, "execution.sessionId", ROUTE_LEASE_MAX_SESSION_ID_BYTES);
|
|
2898
|
+
sessionIdHash = hashRouteLeaseSessionId(sessionId, hmacKey);
|
|
2899
|
+
}
|
|
2900
|
+
if (runId !== void 0 || stageId !== void 0 || attempt !== void 0 || sessionIdHash !== void 0) {
|
|
2901
|
+
execution = { runId, stageId, attempt, sessionIdHash };
|
|
2902
|
+
semanticExecution = { runId, stageId, attempt, sessionId };
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
const request = {
|
|
2906
|
+
schemaVersion: ROUTE_LEASE_REQUEST_SCHEMA,
|
|
2907
|
+
consumer,
|
|
2908
|
+
runtime,
|
|
2909
|
+
upstream,
|
|
2910
|
+
model,
|
|
2911
|
+
...execution ? { execution } : {},
|
|
2912
|
+
ttlSeconds
|
|
2913
|
+
};
|
|
2914
|
+
const semanticPayload = {
|
|
2915
|
+
schemaVersion: request.schemaVersion,
|
|
2916
|
+
consumer,
|
|
2917
|
+
runtime,
|
|
2918
|
+
upstream,
|
|
2919
|
+
model,
|
|
2920
|
+
...semanticExecution ? { execution: semanticExecution } : {},
|
|
2921
|
+
ttlSeconds
|
|
2922
|
+
};
|
|
2923
|
+
return { request, idempotencyKey, payloadHash: hashRouteLeasePayload(semanticPayload) };
|
|
2924
|
+
}
|
|
2925
|
+
function routeLeaseRuntime(runtime) {
|
|
2926
|
+
switch (runtime) {
|
|
2927
|
+
case "claude":
|
|
2928
|
+
return ROUTE_LEASE_RUNTIME_TABLE.claude;
|
|
2929
|
+
case "codex":
|
|
2930
|
+
return ROUTE_LEASE_RUNTIME_TABLE.codex;
|
|
2931
|
+
default: {
|
|
2932
|
+
const exhaustive = runtime;
|
|
2933
|
+
throw new RouteLeaseError("runtime_unsupported", `runtime is unsupported: ${String(exhaustive)}`);
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
}
|
|
2937
|
+
|
|
2938
|
+
// src/provider-proxy/RouteLeaseManager.ts
|
|
2939
|
+
var SYSTEM_ROUTE_LEASE_CLOCK = {
|
|
2940
|
+
now: () => Date.now(),
|
|
2941
|
+
setTimeout(callback, delayMs) {
|
|
2942
|
+
const timer = setTimeout(callback, delayMs);
|
|
2943
|
+
timer.unref?.();
|
|
2944
|
+
return timer;
|
|
2945
|
+
},
|
|
2946
|
+
clearTimeout(handle) {
|
|
2947
|
+
clearTimeout(handle);
|
|
2948
|
+
}
|
|
2949
|
+
};
|
|
2950
|
+
function cloneUpstream(upstream) {
|
|
2951
|
+
return { ...upstream };
|
|
2952
|
+
}
|
|
2953
|
+
function metadataCopy(metadata) {
|
|
2954
|
+
return {
|
|
2955
|
+
...metadata,
|
|
2956
|
+
upstream: cloneUpstream(metadata.upstream),
|
|
2957
|
+
...metadata.execution ? { execution: { ...metadata.execution } } : {}
|
|
2958
|
+
};
|
|
2959
|
+
}
|
|
2960
|
+
var RouteLeaseManager = class {
|
|
2961
|
+
constructor(routePort, targetResolver, descriptors, options = {}) {
|
|
2962
|
+
this.routePort = routePort;
|
|
2963
|
+
this.targetResolver = targetResolver;
|
|
2964
|
+
this.descriptors = descriptors;
|
|
2965
|
+
this.clock = options.clock ?? SYSTEM_ROUTE_LEASE_CLOCK;
|
|
2966
|
+
this.hmacKey = options.hmacKey ?? randomBytes3(32);
|
|
2967
|
+
this.tombstoneMaxCount = Math.max(1, options.tombstoneMaxCount ?? 512);
|
|
2968
|
+
this.tombstoneMaxAgeMs = Math.max(1, options.tombstoneMaxAgeMs ?? 60 * 60 * 1e3);
|
|
2969
|
+
this.logger = options.logger;
|
|
2970
|
+
this.afterPublish = options.afterPublish;
|
|
2971
|
+
}
|
|
2972
|
+
routePort;
|
|
2973
|
+
targetResolver;
|
|
2974
|
+
descriptors;
|
|
2975
|
+
active = /* @__PURE__ */ new Map();
|
|
2976
|
+
idempotency = /* @__PURE__ */ new Map();
|
|
2977
|
+
pendingCreates = /* @__PURE__ */ new Map();
|
|
2978
|
+
tombstones = /* @__PURE__ */ new Map();
|
|
2979
|
+
clock;
|
|
2980
|
+
hmacKey;
|
|
2981
|
+
tombstoneMaxCount;
|
|
2982
|
+
tombstoneMaxAgeMs;
|
|
2983
|
+
logger;
|
|
2984
|
+
afterPublish;
|
|
2985
|
+
closed = false;
|
|
2986
|
+
parse(raw, idempotencyKey) {
|
|
2987
|
+
return parseRouteLeaseCreate(raw, idempotencyKey, this.hmacKey);
|
|
2988
|
+
}
|
|
2989
|
+
async createFromRequest(raw, idempotencyKey) {
|
|
2990
|
+
return this.create(this.parse(raw, idempotencyKey));
|
|
2991
|
+
}
|
|
2992
|
+
async create(input) {
|
|
2993
|
+
if (this.closed) throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
|
|
2994
|
+
const request = input.request;
|
|
2995
|
+
const scope = `${request.consumer}\0${input.idempotencyKey}`;
|
|
2996
|
+
const existing = this.idempotency.get(scope);
|
|
2997
|
+
if (existing) {
|
|
2998
|
+
const lease = this.active.get(existing.leaseId);
|
|
2999
|
+
if (!lease) {
|
|
3000
|
+
this.idempotency.delete(scope);
|
|
3001
|
+
} else if (existing.payloadHash !== input.payloadHash) {
|
|
3002
|
+
throw new RouteLeaseError("idempotency_conflict", "idempotency key is already used for a different live request");
|
|
3003
|
+
} else {
|
|
3004
|
+
return { created: false, result: lease.result };
|
|
3005
|
+
}
|
|
3006
|
+
}
|
|
3007
|
+
const pending = this.pendingCreates.get(scope);
|
|
3008
|
+
if (pending) {
|
|
3009
|
+
if (pending.payloadHash !== input.payloadHash) {
|
|
3010
|
+
throw new RouteLeaseError("idempotency_conflict", "idempotency key is already used for a different live request");
|
|
3011
|
+
}
|
|
3012
|
+
const outcome2 = await pending.outcome;
|
|
3013
|
+
return { created: false, result: outcome2.result };
|
|
3014
|
+
}
|
|
3015
|
+
const outcome = this.createNew(input, scope);
|
|
3016
|
+
const pendingCreate = { payloadHash: input.payloadHash, outcome };
|
|
3017
|
+
this.pendingCreates.set(scope, pendingCreate);
|
|
3018
|
+
try {
|
|
3019
|
+
return await outcome;
|
|
3020
|
+
} finally {
|
|
3021
|
+
if (this.pendingCreates.get(scope) === pendingCreate) this.pendingCreates.delete(scope);
|
|
3022
|
+
}
|
|
3023
|
+
}
|
|
3024
|
+
async createNew(input, scope) {
|
|
3025
|
+
const request = input.request;
|
|
3026
|
+
if (!this.routePort.isReady()) {
|
|
3027
|
+
throw new RouteLeaseError("daemon_not_ready", "resident provider proxy is not ready");
|
|
3028
|
+
}
|
|
3029
|
+
if (!this.descriptors.has(request.runtime)) {
|
|
3030
|
+
throw new RouteLeaseError("runtime_unsupported", "runtime launch adapter is unavailable");
|
|
3031
|
+
}
|
|
3032
|
+
const leaseId = randomUUID();
|
|
3033
|
+
const resolved = await this.targetResolver.resolve(request);
|
|
3034
|
+
this.assertOpen();
|
|
3035
|
+
const route = {
|
|
3036
|
+
...resolved,
|
|
3037
|
+
sessionId: request.execution?.sessionIdHash ?? null,
|
|
3038
|
+
routeLease: {
|
|
3039
|
+
leaseId,
|
|
3040
|
+
consumer: request.consumer,
|
|
3041
|
+
...request.execution?.runId ? { runId: request.execution.runId } : {},
|
|
3042
|
+
...request.execution?.stageId ? { stageId: request.execution.stageId } : {}
|
|
3043
|
+
}
|
|
3044
|
+
};
|
|
3045
|
+
const now = this.clock.now();
|
|
3046
|
+
const ttlMs = request.ttlSeconds * 1e3;
|
|
3047
|
+
const expiresAtMs = now + ttlMs;
|
|
3048
|
+
let routeToken;
|
|
3049
|
+
let published = false;
|
|
3050
|
+
let activeLease;
|
|
3051
|
+
try {
|
|
3052
|
+
routeToken = this.routePort.addRoute(route, {
|
|
3053
|
+
idleMs: ttlMs,
|
|
3054
|
+
onActivity: (at) => this.noteActivity(leaseId, at),
|
|
3055
|
+
onEvicted: () => this.onRouteEvicted(leaseId)
|
|
3056
|
+
});
|
|
3057
|
+
const launch = this.descriptors.build(request.runtime, {
|
|
3058
|
+
proxyBaseUrl: this.routePort.getBaseUrl(),
|
|
3059
|
+
model: request.model,
|
|
3060
|
+
routeToken
|
|
3061
|
+
});
|
|
3062
|
+
this.assertOpen();
|
|
3063
|
+
const metadata = {
|
|
3064
|
+
leaseId,
|
|
3065
|
+
consumer: request.consumer,
|
|
3066
|
+
runtime: request.runtime,
|
|
3067
|
+
upstream: cloneUpstream(request.upstream),
|
|
3068
|
+
model: request.model,
|
|
3069
|
+
createdAt: new Date(now).toISOString(),
|
|
3070
|
+
expiresAt: new Date(expiresAtMs).toISOString(),
|
|
3071
|
+
status: "active",
|
|
3072
|
+
...request.execution ? { execution: { ...request.execution } } : {}
|
|
3073
|
+
};
|
|
3074
|
+
const result = {
|
|
3075
|
+
schemaVersion: ROUTE_LEASE_RESULT_SCHEMA,
|
|
3076
|
+
...metadata,
|
|
3077
|
+
status: "active",
|
|
3078
|
+
launch: { env: { ...launch.env }, extraArgs: [...launch.extraArgs] }
|
|
3079
|
+
};
|
|
3080
|
+
const timer = this.clock.setTimeout(() => this.expire(leaseId), ttlMs);
|
|
3081
|
+
activeLease = {
|
|
3082
|
+
leaseId,
|
|
3083
|
+
routeToken,
|
|
3084
|
+
idempotencyScope: scope,
|
|
3085
|
+
payloadHash: input.payloadHash,
|
|
3086
|
+
request,
|
|
3087
|
+
route,
|
|
3088
|
+
result,
|
|
3089
|
+
metadata,
|
|
3090
|
+
expiresAtMs,
|
|
3091
|
+
timer
|
|
3092
|
+
};
|
|
3093
|
+
this.assertOpen();
|
|
3094
|
+
this.active.set(leaseId, activeLease);
|
|
3095
|
+
this.idempotency.set(scope, { leaseId, payloadHash: input.payloadHash });
|
|
3096
|
+
published = true;
|
|
3097
|
+
this.afterPublish?.();
|
|
3098
|
+
if (this.closed) {
|
|
3099
|
+
this.finalize(activeLease, "released");
|
|
3100
|
+
throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
|
|
3101
|
+
}
|
|
3102
|
+
return { created: true, result };
|
|
3103
|
+
} catch (error) {
|
|
3104
|
+
if (published && activeLease) {
|
|
3105
|
+
this.active.delete(leaseId);
|
|
3106
|
+
this.idempotency.delete(scope);
|
|
3107
|
+
this.clock.clearTimeout(activeLease.timer);
|
|
3108
|
+
}
|
|
3109
|
+
if (routeToken) this.safeRemoveRoute(routeToken, leaseId);
|
|
3110
|
+
if (error instanceof RouteLeaseError) throw error;
|
|
3111
|
+
throw new RouteLeaseError("upstream_unavailable", "route lease creation failed safely", { cause: error });
|
|
3112
|
+
}
|
|
3113
|
+
}
|
|
3114
|
+
list() {
|
|
3115
|
+
this.pruneTombstones();
|
|
3116
|
+
return [
|
|
3117
|
+
...[...this.active.values()].map((lease) => metadataCopy(lease.metadata)),
|
|
3118
|
+
...[...this.tombstones.values()].map((entry) => metadataCopy(entry.metadata))
|
|
3119
|
+
].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
|
|
3120
|
+
}
|
|
3121
|
+
get(leaseId) {
|
|
3122
|
+
const active = this.active.get(leaseId);
|
|
3123
|
+
if (active) return metadataCopy(active.metadata);
|
|
3124
|
+
this.pruneTombstones();
|
|
3125
|
+
const tombstone = this.tombstones.get(leaseId);
|
|
3126
|
+
if (tombstone) return metadataCopy(tombstone.metadata);
|
|
3127
|
+
throw new RouteLeaseError("lease_not_found", "route lease was not found");
|
|
3128
|
+
}
|
|
3129
|
+
renew(leaseId, ttlSeconds) {
|
|
3130
|
+
const lease = this.active.get(leaseId);
|
|
3131
|
+
if (!lease) {
|
|
3132
|
+
this.pruneTombstones();
|
|
3133
|
+
const known = this.tombstones.get(leaseId);
|
|
3134
|
+
if (known?.metadata.status === "expired") {
|
|
3135
|
+
throw new RouteLeaseError("lease_expired", "route lease has expired");
|
|
3136
|
+
}
|
|
3137
|
+
throw new RouteLeaseError("lease_not_found", "route lease was not found");
|
|
3138
|
+
}
|
|
3139
|
+
const now = this.clock.now();
|
|
3140
|
+
if (now >= lease.expiresAtMs) {
|
|
3141
|
+
this.finalize(lease, "expired");
|
|
3142
|
+
throw new RouteLeaseError("lease_expired", "route lease has expired");
|
|
3143
|
+
}
|
|
3144
|
+
const ttlMs = ttlSeconds * 1e3;
|
|
3145
|
+
const expiresAtMs = now + ttlMs;
|
|
3146
|
+
const nextTimer = this.clock.setTimeout(() => this.expire(leaseId), ttlMs);
|
|
3147
|
+
const previousTimer = lease.timer;
|
|
3148
|
+
lease.timer = nextTimer;
|
|
3149
|
+
lease.expiresAtMs = expiresAtMs;
|
|
3150
|
+
lease.metadata = { ...lease.metadata, expiresAt: new Date(expiresAtMs).toISOString() };
|
|
3151
|
+
this.clock.clearTimeout(previousTimer);
|
|
3152
|
+
if (!this.routePort.renewRoute(lease.routeToken, ttlMs)) {
|
|
3153
|
+
this.finalize(lease, "expired", true);
|
|
3154
|
+
throw new RouteLeaseError("lease_expired", "route lease route has expired");
|
|
3155
|
+
}
|
|
3156
|
+
return { leaseId, expiresAt: lease.metadata.expiresAt, status: "active" };
|
|
3157
|
+
}
|
|
3158
|
+
release(leaseId) {
|
|
3159
|
+
const lease = this.active.get(leaseId);
|
|
3160
|
+
if (!lease) return { leaseId, released: false };
|
|
3161
|
+
this.finalize(lease, "released");
|
|
3162
|
+
return { leaseId, released: true };
|
|
3163
|
+
}
|
|
3164
|
+
shutdown() {
|
|
3165
|
+
if (this.closed) return;
|
|
3166
|
+
this.closed = true;
|
|
3167
|
+
for (const lease of [...this.active.values()]) {
|
|
3168
|
+
try {
|
|
3169
|
+
this.finalize(lease, "released");
|
|
3170
|
+
} catch (error) {
|
|
3171
|
+
this.logger?.warn("route lease cleanup failed during shutdown", { leaseId: lease.leaseId, error: safeErrorName(error) });
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
}
|
|
3175
|
+
activeCount() {
|
|
3176
|
+
return this.active.size;
|
|
3177
|
+
}
|
|
3178
|
+
assertOpen() {
|
|
3179
|
+
if (this.closed) {
|
|
3180
|
+
throw new RouteLeaseError("daemon_not_ready", "route lease manager is shutting down");
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
noteActivity(leaseId, at) {
|
|
3184
|
+
const lease = this.active.get(leaseId);
|
|
3185
|
+
if (!lease || at >= lease.expiresAtMs) return;
|
|
3186
|
+
lease.metadata = { ...lease.metadata, lastActivityAt: new Date(at).toISOString() };
|
|
3187
|
+
}
|
|
3188
|
+
onRouteEvicted(leaseId) {
|
|
3189
|
+
const lease = this.active.get(leaseId);
|
|
3190
|
+
if (lease) this.finalize(lease, "expired", true);
|
|
3191
|
+
}
|
|
3192
|
+
expire(leaseId) {
|
|
3193
|
+
const lease = this.active.get(leaseId);
|
|
3194
|
+
if (!lease) return;
|
|
3195
|
+
const remaining = lease.expiresAtMs - this.clock.now();
|
|
3196
|
+
if (remaining > 0) {
|
|
3197
|
+
lease.timer = this.clock.setTimeout(() => this.expire(leaseId), remaining);
|
|
3198
|
+
return;
|
|
3199
|
+
}
|
|
3200
|
+
this.finalize(lease, "expired");
|
|
3201
|
+
}
|
|
3202
|
+
finalize(lease, status, routeAlreadyGone = false) {
|
|
3203
|
+
if (this.active.get(lease.leaseId) !== lease) return;
|
|
3204
|
+
this.active.delete(lease.leaseId);
|
|
3205
|
+
this.idempotency.delete(lease.idempotencyScope);
|
|
3206
|
+
this.clock.clearTimeout(lease.timer);
|
|
3207
|
+
if (!routeAlreadyGone) this.safeRemoveRoute(lease.routeToken, lease.leaseId);
|
|
3208
|
+
const metadata = { ...lease.metadata, status };
|
|
3209
|
+
this.tombstones.delete(lease.leaseId);
|
|
3210
|
+
this.tombstones.set(lease.leaseId, { metadata, terminalAtMs: this.clock.now() });
|
|
3211
|
+
this.pruneTombstones();
|
|
3212
|
+
}
|
|
3213
|
+
safeRemoveRoute(routeToken, leaseId) {
|
|
3214
|
+
try {
|
|
3215
|
+
this.routePort.removeRoute(routeToken);
|
|
3216
|
+
} catch (error) {
|
|
3217
|
+
this.logger?.warn("route lease route cleanup failed", { leaseId, error: safeErrorName(error) });
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
pruneTombstones() {
|
|
3221
|
+
const cutoff = this.clock.now() - this.tombstoneMaxAgeMs;
|
|
3222
|
+
for (const [leaseId, tombstone] of this.tombstones) {
|
|
3223
|
+
if (tombstone.terminalAtMs < cutoff) this.tombstones.delete(leaseId);
|
|
3224
|
+
}
|
|
3225
|
+
while (this.tombstones.size > this.tombstoneMaxCount) {
|
|
3226
|
+
const oldest = this.tombstones.keys().next().value;
|
|
3227
|
+
if (!oldest) break;
|
|
3228
|
+
this.tombstones.delete(oldest);
|
|
3229
|
+
}
|
|
3230
|
+
}
|
|
3231
|
+
};
|
|
3232
|
+
function safeErrorName(error) {
|
|
3233
|
+
return error instanceof Error ? error.name : "unknown";
|
|
3234
|
+
}
|
|
3235
|
+
|
|
3236
|
+
// src/outbound-api/gatewayBindingResolver.ts
|
|
3237
|
+
var MESSAGE_FALLBACK_KINDS = ["sonnet", "opus", "haiku", "fable"];
|
|
3238
|
+
function nonBlank(value) {
|
|
3239
|
+
return typeof value === "string" && value.trim() !== "";
|
|
3240
|
+
}
|
|
3241
|
+
function gatewayBindingAllowsKey(binding, apiKeyId) {
|
|
3242
|
+
const scope = binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3243
|
+
return scope === "all" || Boolean(binding.apiKeyIds?.includes(apiKeyId));
|
|
3244
|
+
}
|
|
3245
|
+
function wildcardMatches(pattern, value) {
|
|
3246
|
+
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
|
|
3247
|
+
return new RegExp(`^${escaped}$`, "iu").test(value);
|
|
3248
|
+
}
|
|
3249
|
+
function resolveGatewayModelMapping(mappings, requestedModel) {
|
|
3250
|
+
if (!nonBlank(requestedModel)) return void 0;
|
|
3251
|
+
const wanted = requestedModel.trim();
|
|
3252
|
+
const exact = (mappings ?? []).find(
|
|
3253
|
+
(mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
|
|
3254
|
+
);
|
|
3255
|
+
if (exact && nonBlank(exact.target)) return exact.target.trim();
|
|
3256
|
+
const wildcard = (mappings ?? []).find(
|
|
3257
|
+
(mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
|
|
3258
|
+
);
|
|
3259
|
+
return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
|
|
3260
|
+
}
|
|
3261
|
+
function routeCanServe(binding, requestedModel, role) {
|
|
3262
|
+
if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
|
|
3263
|
+
if (binding.modelMappings?.length) {
|
|
3264
|
+
return nonBlank(resolveGatewayModelMapping(binding.modelMappings, requestedModel));
|
|
3265
|
+
}
|
|
3266
|
+
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3267
|
+
const map = binding.modelMap ?? {};
|
|
3268
|
+
const kind = detectModelKind(binding.endpoint, requestedModel);
|
|
3269
|
+
if (kind && nonBlank(map[kind])) return true;
|
|
3270
|
+
if (binding.endpoint === "messages") {
|
|
3271
|
+
return MESSAGE_FALLBACK_KINDS.some((candidate) => nonBlank(map[candidate]));
|
|
3272
|
+
}
|
|
3273
|
+
return nonBlank(map.codex);
|
|
3274
|
+
}
|
|
3275
|
+
if (binding.endpoint === "chat" && binding.dispatchMode === "prefix") {
|
|
3276
|
+
return resolvePrefixTarget(binding.prefixTargets, requestedModel) !== null;
|
|
3277
|
+
}
|
|
3278
|
+
if (binding.endpoint === "chat") {
|
|
3279
|
+
if (!requestedModel) return false;
|
|
3280
|
+
const wanted = requestedModel.trim().toLowerCase();
|
|
3281
|
+
return (binding.models ?? []).some((ref) => modelIdOf(ref)?.toLowerCase() === wanted);
|
|
3282
|
+
}
|
|
3283
|
+
return role === "background" ? nonBlank(binding.backgroundModel) : nonBlank(binding.defaultModel);
|
|
3284
|
+
}
|
|
3285
|
+
function modelIdOf(ref) {
|
|
3286
|
+
if (!nonBlank(ref)) return void 0;
|
|
3287
|
+
const trimmed = ref.trim();
|
|
3288
|
+
const comma = trimmed.indexOf(",");
|
|
3289
|
+
const modelId = comma >= 0 ? trimmed.slice(comma + 1).trim() : trimmed;
|
|
3290
|
+
return modelId || void 0;
|
|
3291
|
+
}
|
|
3292
|
+
function targetProviderId(target) {
|
|
3293
|
+
return target.providerId.trim();
|
|
3294
|
+
}
|
|
3295
|
+
function targetRef(target, ref) {
|
|
3296
|
+
const modelId = modelIdOf(ref) ?? "";
|
|
3297
|
+
return modelId ? `${targetProviderId(target)},${modelId}` : "";
|
|
3298
|
+
}
|
|
3299
|
+
function targetRefs(target, refs) {
|
|
3300
|
+
return (refs ?? []).map((ref) => targetRef(target, ref)).filter(nonBlank);
|
|
3301
|
+
}
|
|
3302
|
+
function targetPrefixRefs(target, refs) {
|
|
3303
|
+
if (!refs) return void 0;
|
|
3304
|
+
const mapped = {};
|
|
3305
|
+
for (const kind of ["claude", "gpt", "gemini"]) {
|
|
3306
|
+
const ref = targetRef(target, refs[kind]);
|
|
3307
|
+
if (ref) mapped[kind] = ref;
|
|
3308
|
+
}
|
|
3309
|
+
return mapped.claude || mapped.gpt || mapped.gemini ? mapped : void 0;
|
|
3310
|
+
}
|
|
3311
|
+
function applySingleModel(config, binding, targetModel) {
|
|
3312
|
+
const ref = targetRef(binding.target, targetModel);
|
|
3313
|
+
if (!ref) return;
|
|
3314
|
+
if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3315
|
+
config.modelMap = Object.fromEntries(
|
|
3316
|
+
modelKindsForEndpoint(binding.endpoint).map((kind) => [kind, ref])
|
|
3317
|
+
);
|
|
3318
|
+
} else if (binding.endpoint === "chat") {
|
|
3319
|
+
config.models = [ref];
|
|
3320
|
+
config.dispatchMode = "list";
|
|
3321
|
+
} else {
|
|
3322
|
+
config.defaultModel = ref;
|
|
3323
|
+
config.backgroundModel = ref;
|
|
3324
|
+
}
|
|
3325
|
+
}
|
|
3326
|
+
function gatewayBindingToEndpointConfig(binding, requestedModel) {
|
|
3327
|
+
const target = binding.target;
|
|
3328
|
+
const config = {
|
|
3329
|
+
endpoint: binding.endpoint,
|
|
3330
|
+
useSubscription: target.kind !== "provider"
|
|
3331
|
+
};
|
|
3332
|
+
const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
|
|
3333
|
+
const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : resolveGatewayModelMapping(binding.modelMappings, requestedModel);
|
|
3334
|
+
if (usesGenericModelHandling) {
|
|
3335
|
+
if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
|
|
3336
|
+
} else if (isKindMappedEndpoint(binding.endpoint)) {
|
|
3337
|
+
config.modelMap = Object.fromEntries(
|
|
3338
|
+
Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
|
|
3339
|
+
);
|
|
3340
|
+
} else if (binding.endpoint === "chat") {
|
|
3341
|
+
config.models = targetRefs(target, binding.models);
|
|
3342
|
+
if (binding.dispatchMode === "prefix") {
|
|
3343
|
+
config.dispatchMode = "prefix";
|
|
3344
|
+
config.prefixTargets = targetPrefixRefs(target, binding.prefixTargets);
|
|
3345
|
+
}
|
|
3346
|
+
} else {
|
|
3347
|
+
config.defaultModel = targetRef(target, binding.defaultModel);
|
|
3348
|
+
config.backgroundModel = targetRef(target, binding.backgroundModel);
|
|
3349
|
+
if (binding.backgroundModelIds) config.backgroundModelIds = [...binding.backgroundModelIds];
|
|
3350
|
+
}
|
|
3351
|
+
if (target.kind === "account") {
|
|
3352
|
+
config.boundAccountId = target.accountId;
|
|
3353
|
+
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3354
|
+
} else if (target.kind === "account-group") {
|
|
3355
|
+
config.boundAccountGroup = target.group;
|
|
3356
|
+
config.boundAccountFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3357
|
+
} else if (target.kind === "provider" && target.keyId) {
|
|
3358
|
+
config.boundKeyId = target.keyId;
|
|
3359
|
+
config.boundKeyFallbackPolicy = binding.fallback === "next" ? "pool" : "strict";
|
|
3360
|
+
}
|
|
3361
|
+
return config;
|
|
3362
|
+
}
|
|
3363
|
+
function candidateGatewayBindings(bindings, apiKeyId, endpoint) {
|
|
3364
|
+
const candidates = (bindings ?? []).filter(
|
|
3365
|
+
(binding) => binding.enabled && binding.endpoint === endpoint && gatewayBindingAllowsKey(binding, apiKeyId)
|
|
3366
|
+
);
|
|
3367
|
+
const scopeOf = (binding) => binding.keyScope ?? (binding.apiKeyIds?.length ? "selected" : "all");
|
|
3368
|
+
const scoped = candidates.filter((binding) => scopeOf(binding) === "selected");
|
|
3369
|
+
const pool = scoped.length > 0 ? scoped : candidates.filter((binding) => scopeOf(binding) === "all");
|
|
3370
|
+
return pool.sort(
|
|
3371
|
+
(left, right) => (left.priority ?? 100) - (right.priority ?? 100) || left.id.localeCompare(right.id)
|
|
3372
|
+
);
|
|
3373
|
+
}
|
|
3374
|
+
function candidateBackgroundModelIds(bindings, apiKeyId, endpoint) {
|
|
3375
|
+
const ids = [
|
|
3376
|
+
...new Set(
|
|
3377
|
+
candidateGatewayBindings(bindings, apiKeyId, endpoint).flatMap(
|
|
3378
|
+
(binding) => binding.backgroundModelIds ?? []
|
|
3379
|
+
)
|
|
3380
|
+
)
|
|
3381
|
+
];
|
|
3382
|
+
return ids.length > 0 ? ids : void 0;
|
|
3383
|
+
}
|
|
3384
|
+
function resolveGatewayBinding(input) {
|
|
3385
|
+
const candidates = candidateGatewayBindings(input.bindings, input.apiKeyId, input.endpoint);
|
|
3386
|
+
const binding = candidates.find(
|
|
3387
|
+
(candidate) => candidate.fallback === "fail" || routeCanServe(candidate, input.requestedModel, input.role)
|
|
3388
|
+
) ?? candidates[0];
|
|
3389
|
+
if (!binding) return { source: "none" };
|
|
3390
|
+
return {
|
|
3391
|
+
source: "binding",
|
|
3392
|
+
binding,
|
|
3393
|
+
config: gatewayBindingToEndpointConfig(binding, input.requestedModel)
|
|
3394
|
+
};
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// src/provider-proxy/RouteLeaseTargetResolver.ts
|
|
3398
|
+
function resolveEnvKey(raw) {
|
|
3399
|
+
if (!raw) return "";
|
|
3400
|
+
return raw.startsWith("$") ? process.env[raw.slice(1)] ?? "" : raw;
|
|
3401
|
+
}
|
|
3402
|
+
function providerSupportsModel(provider, model) {
|
|
3403
|
+
const flat = provider.models ?? [];
|
|
3404
|
+
const configured = provider.modelConfigs ?? [];
|
|
3405
|
+
if (flat.length === 0 && configured.length === 0) return false;
|
|
3406
|
+
return flat.includes(model) || configured.some((entry) => entry.id === model && entry.enabled !== false);
|
|
3407
|
+
}
|
|
3408
|
+
function syntheticBinding(request) {
|
|
3409
|
+
const runtime = routeLeaseRuntime(request.runtime);
|
|
3410
|
+
return {
|
|
3411
|
+
id: "route-lease-ephemeral",
|
|
3412
|
+
name: "Route Lease ephemeral target",
|
|
3413
|
+
enabled: true,
|
|
3414
|
+
endpoint: runtime.endpoint,
|
|
3415
|
+
target: request.upstream,
|
|
3416
|
+
fallback: "fail",
|
|
3417
|
+
modelMode: "passthrough"
|
|
3418
|
+
};
|
|
3419
|
+
}
|
|
3420
|
+
function transformerRegistered(llmConfig, name) {
|
|
3421
|
+
return Boolean(llmConfig.getTransformerService()?.getTransformer(name));
|
|
3422
|
+
}
|
|
3423
|
+
var RouteLeaseTargetResolver = class {
|
|
3424
|
+
constructor(llmConfig, options = {}) {
|
|
3425
|
+
this.llmConfig = llmConfig;
|
|
3426
|
+
this.options = options;
|
|
3427
|
+
}
|
|
3428
|
+
llmConfig;
|
|
3429
|
+
options;
|
|
3430
|
+
async resolve(request) {
|
|
3431
|
+
const runtime = routeLeaseRuntime(request.runtime);
|
|
3432
|
+
await this.preflightTarget(request);
|
|
3433
|
+
const binding = syntheticBinding(request);
|
|
3434
|
+
const config = gatewayBindingToEndpointConfig(binding, request.model);
|
|
3435
|
+
const result = await resolveRoute({
|
|
3436
|
+
config,
|
|
3437
|
+
ingressFormat: runtime.ingressFormat,
|
|
3438
|
+
llmConfig: this.llmConfig,
|
|
3439
|
+
requestedModel: request.model,
|
|
3440
|
+
sessionId: request.execution?.sessionIdHash ?? null
|
|
3441
|
+
});
|
|
3442
|
+
if (!result.ok) {
|
|
3443
|
+
throw new RouteLeaseError(
|
|
3444
|
+
result.error.status === 404 ? "upstream_not_found" : "upstream_unavailable",
|
|
3445
|
+
"the selected upstream could not be resolved"
|
|
3446
|
+
);
|
|
3447
|
+
}
|
|
3448
|
+
return { ...result.route, requestedModel: void 0 };
|
|
3449
|
+
}
|
|
3450
|
+
async preflightTarget(request) {
|
|
3451
|
+
const target = request.upstream;
|
|
3452
|
+
if (target.kind !== "provider") {
|
|
3453
|
+
await this.preflightSubscription(target, request.model);
|
|
3454
|
+
return;
|
|
3455
|
+
}
|
|
3456
|
+
const provider = await this.llmConfig.getProvider(target.providerId);
|
|
3457
|
+
if (!provider) throw new RouteLeaseError("upstream_not_found", "provider was not found");
|
|
3458
|
+
if (provider.enabled === false) throw new RouteLeaseError("upstream_unavailable", "provider is disabled");
|
|
3459
|
+
if (!providerSupportsModel(provider, request.model)) {
|
|
3460
|
+
throw new RouteLeaseError("model_not_configured", "model is not configured for the provider");
|
|
3461
|
+
}
|
|
3462
|
+
if (target.keyId) {
|
|
3463
|
+
const availability = await this.options.providerKeys?.getKeyAvailability?.(
|
|
3464
|
+
target.providerId,
|
|
3465
|
+
target.keyId
|
|
3466
|
+
);
|
|
3467
|
+
if (availability === "not-found") {
|
|
3468
|
+
throw new RouteLeaseError("upstream_not_found", "the selected provider key was not found");
|
|
3469
|
+
}
|
|
3470
|
+
const usable = availability === "usable" || availability === void 0 && Boolean(this.options.providerKeys) && await this.options.providerKeys.hasUsableKey(target.providerId, target.keyId);
|
|
3471
|
+
if (!usable) {
|
|
3472
|
+
throw new RouteLeaseError("upstream_unavailable", "the selected provider key is unavailable");
|
|
3473
|
+
}
|
|
3474
|
+
} else {
|
|
3475
|
+
const directKey = resolveEnvKey(provider.api_key);
|
|
3476
|
+
const poolAvailability = await this.options.providerKeys?.getPoolAvailability?.(target.providerId);
|
|
3477
|
+
const poolReady = poolAvailability ? poolAvailability.outcome === "usable" : this.options.providerKeys ? await this.options.providerKeys.hasUsableKeys(target.providerId) : false;
|
|
3478
|
+
if (!directKey && poolAvailability?.outcome === "exhausted") {
|
|
3479
|
+
throw new RouteLeaseError("upstream_exhausted", "provider key pool is temporarily exhausted", {
|
|
3480
|
+
retryAfterSeconds: poolAvailability.retryAfterSeconds
|
|
3481
|
+
});
|
|
3482
|
+
}
|
|
3483
|
+
if (!directKey && !poolReady) {
|
|
3484
|
+
throw new RouteLeaseError("upstream_unavailable", "provider has no usable credential");
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
const sameFormat = request.runtime === "claude" && provider.apiFormat === "anthropic" || request.runtime === "codex" && provider.apiFormat === "openai-response";
|
|
3488
|
+
if (!sameFormat) {
|
|
3489
|
+
try {
|
|
3490
|
+
const chain = await resolveProviderChain(this.llmConfig, provider.id, request.model);
|
|
3491
|
+
if (!chain.hasTransformers) {
|
|
3492
|
+
throw new RouteLeaseError("format_unsupported", "no transformer chain supports this runtime and upstream");
|
|
3493
|
+
}
|
|
3494
|
+
} catch (error) {
|
|
3495
|
+
if (error instanceof RouteLeaseError) throw error;
|
|
3496
|
+
throw new RouteLeaseError("format_unsupported", "the transformer chain could not be resolved", { cause: error });
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
}
|
|
3500
|
+
async preflightSubscription(target, model) {
|
|
3501
|
+
const registry = getSubscriptionRegistryForOutbound();
|
|
3502
|
+
const profile = registry?.getProfile(target.providerId) ?? null;
|
|
3503
|
+
if (!profile) throw new RouteLeaseError("upstream_not_found", "subscription provider was not found");
|
|
3504
|
+
await this.options.subscriptions?.assertAvailable(target, model);
|
|
3505
|
+
const names = profile.resolveProviderTransformerNames?.(model) ?? profile.providerTransformerNames ?? [];
|
|
3506
|
+
if (names.some((name) => !transformerRegistered(this.llmConfig, name))) {
|
|
3507
|
+
throw new RouteLeaseError("format_unsupported", "a required transformer is unavailable");
|
|
3508
|
+
}
|
|
3509
|
+
if (profile.modelTransformerNames?.some((name) => !transformerRegistered(this.llmConfig, name))) {
|
|
3510
|
+
throw new RouteLeaseError("format_unsupported", "a required model transformer is unavailable");
|
|
3511
|
+
}
|
|
3512
|
+
}
|
|
3513
|
+
};
|
|
3514
|
+
|
|
2644
3515
|
// src/provider-proxy/index.ts
|
|
2645
3516
|
var instance = null;
|
|
2646
3517
|
function getProviderProxy(deps) {
|
|
@@ -3255,169 +4126,8 @@ function mergeServerConfig(current, patch) {
|
|
|
3255
4126
|
});
|
|
3256
4127
|
}
|
|
3257
4128
|
|
|
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
4129
|
// src/outbound-api/outboundApiKeyAuth.ts
|
|
3420
|
-
import { createHash, randomBytes as
|
|
4130
|
+
import { createHash as createHash2, randomBytes as randomBytes4 } from "crypto";
|
|
3421
4131
|
|
|
3422
4132
|
// src/outbound-api/keyPolicy.ts
|
|
3423
4133
|
var DAY_MS = 864e5;
|
|
@@ -3469,7 +4179,7 @@ var BASE62_REJECT_THRESHOLD = 248;
|
|
|
3469
4179
|
function randomBase62(count) {
|
|
3470
4180
|
let out = "";
|
|
3471
4181
|
while (out.length < count) {
|
|
3472
|
-
const buf =
|
|
4182
|
+
const buf = randomBytes4(count - out.length + 8);
|
|
3473
4183
|
for (const byte of buf) {
|
|
3474
4184
|
if (out.length >= count) break;
|
|
3475
4185
|
if (byte >= BASE62_REJECT_THRESHOLD) continue;
|
|
@@ -3482,13 +4192,13 @@ function generateSecret() {
|
|
|
3482
4192
|
return KEY_PREFIX + randomBase62(SECRET_BYTES);
|
|
3483
4193
|
}
|
|
3484
4194
|
function hashKey(secret) {
|
|
3485
|
-
return
|
|
4195
|
+
return createHash2("sha256").update(secret, "utf8").digest("hex");
|
|
3486
4196
|
}
|
|
3487
4197
|
function keyPrefix(secret) {
|
|
3488
4198
|
return secret.slice(0, PREFIX_LEN);
|
|
3489
4199
|
}
|
|
3490
4200
|
function newKeyId() {
|
|
3491
|
-
return `oak_${Date.now()}_${
|
|
4201
|
+
return `oak_${Date.now()}_${randomBytes4(6).toString("hex")}`;
|
|
3492
4202
|
}
|
|
3493
4203
|
async function createNamedKey(db, name) {
|
|
3494
4204
|
const secret = generateSecret();
|
|
@@ -4008,7 +4718,7 @@ var UserMessageSerialQueue = class {
|
|
|
4008
4718
|
};
|
|
4009
4719
|
|
|
4010
4720
|
// src/outbound-api/voucher.ts
|
|
4011
|
-
import { createHash as
|
|
4721
|
+
import { createHash as createHash3 } from "crypto";
|
|
4012
4722
|
var CODE_ENTROPY_CHARS = 32;
|
|
4013
4723
|
var CODE_PREFIX = "CC_";
|
|
4014
4724
|
var DISPLAY_PREFIX_LEN = 8;
|
|
@@ -4017,7 +4727,7 @@ function generateVoucherCode() {
|
|
|
4017
4727
|
return CODE_PREFIX + randomBase62(CODE_ENTROPY_CHARS);
|
|
4018
4728
|
}
|
|
4019
4729
|
function hashVoucherCode(code) {
|
|
4020
|
-
return
|
|
4730
|
+
return createHash3("sha256").update(code, "utf8").digest("hex");
|
|
4021
4731
|
}
|
|
4022
4732
|
function voucherCodePrefix(code) {
|
|
4023
4733
|
return code.slice(0, DISPLAY_PREFIX_LEN);
|
|
@@ -6985,6 +7695,37 @@ export {
|
|
|
6985
7695
|
extractRouteToken,
|
|
6986
7696
|
isLoopbackAddress,
|
|
6987
7697
|
ProviderProxy,
|
|
7698
|
+
ROUTE_LEASE_REQUEST_SCHEMA,
|
|
7699
|
+
ROUTE_LEASE_RESULT_SCHEMA,
|
|
7700
|
+
ROUTE_LEASE_CAPABILITIES_SCHEMA,
|
|
7701
|
+
ROUTE_LEASE_API_VERSION,
|
|
7702
|
+
ROUTE_LEASE_DEFAULT_TTL_SECONDS,
|
|
7703
|
+
ROUTE_LEASE_MAX_TTL_SECONDS,
|
|
7704
|
+
ROUTE_LEASE_MAX_IDEMPOTENCY_BYTES,
|
|
7705
|
+
ROUTE_LEASE_MAX_CONSUMER_BYTES,
|
|
7706
|
+
ROUTE_LEASE_MAX_EXECUTION_ID_BYTES,
|
|
7707
|
+
ROUTE_LEASE_MAX_SESSION_ID_BYTES,
|
|
7708
|
+
ROUTE_LEASE_MAX_MODEL_BYTES,
|
|
7709
|
+
ROUTE_LEASE_SESSION_HASH_DOMAIN,
|
|
7710
|
+
ROUTE_LEASE_CODEX_TOKEN_ENV,
|
|
7711
|
+
ROUTE_LEASE_RUNTIMES,
|
|
7712
|
+
ROUTE_LEASE_CAPABILITIES,
|
|
7713
|
+
ROUTE_LEASE_RUNTIME_TABLE,
|
|
7714
|
+
RouteLeaseError,
|
|
7715
|
+
normalizeRouteLeaseIdempotencyKey,
|
|
7716
|
+
normalizeRouteLeaseTtl,
|
|
7717
|
+
hashRouteLeaseSessionId,
|
|
7718
|
+
canonicalizeRouteLeasePayload,
|
|
7719
|
+
hashRouteLeasePayload,
|
|
7720
|
+
parseRouteLeaseCreate,
|
|
7721
|
+
routeLeaseRuntime,
|
|
7722
|
+
SYSTEM_ROUTE_LEASE_CLOCK,
|
|
7723
|
+
RouteLeaseManager,
|
|
7724
|
+
gatewayBindingToEndpointConfig,
|
|
7725
|
+
candidateGatewayBindings,
|
|
7726
|
+
candidateBackgroundModelIds,
|
|
7727
|
+
resolveGatewayBinding,
|
|
7728
|
+
RouteLeaseTargetResolver,
|
|
6988
7729
|
getProviderProxy,
|
|
6989
7730
|
__resetProviderProxyForTests,
|
|
6990
7731
|
KeyedMutex,
|
|
@@ -7013,10 +7754,6 @@ export {
|
|
|
7013
7754
|
loadServerConfig,
|
|
7014
7755
|
saveServerConfig,
|
|
7015
7756
|
mergeServerConfig,
|
|
7016
|
-
gatewayBindingToEndpointConfig,
|
|
7017
|
-
candidateGatewayBindings,
|
|
7018
|
-
candidateBackgroundModelIds,
|
|
7019
|
-
resolveGatewayBinding,
|
|
7020
7757
|
computeKeyExpiry,
|
|
7021
7758
|
checkKeyQuota,
|
|
7022
7759
|
randomBase62,
|