@omnicross/core 0.4.4 → 0.4.6
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/{CompletionService-BniP7j88.d.cts → CompletionService-BJu4AWVc.d.cts} +1 -1
- package/dist/{CompletionService-BqrKPwfA.d.ts → CompletionService-C4W2gBi_.d.ts} +1 -1
- package/dist/{ProviderProxy-Ckr6oTrE.d.ts → ProviderProxy-DxsNi3cO.d.ts} +1 -1
- package/dist/{ProviderProxy-Bz_nkOwq.d.cts → ProviderProxy-OuQPYW2I.d.cts} +1 -1
- package/dist/auth/GeminiCodeAssistProjectResolver.cjs +4 -4
- package/dist/auth/GeminiCodeAssistProjectResolver.js +3 -3
- package/dist/{chunk-NKVZ6Z3Y.cjs → chunk-4HMAS74X.cjs} +8 -3
- package/dist/{chunk-MPFOC7AT.js → chunk-5XG6FR5F.js} +2 -2
- package/dist/{chunk-VFFCV4J6.cjs → chunk-CXRJMNJJ.cjs} +12 -6
- package/dist/{chunk-E5RCCGE7.cjs → chunk-DF5JXK36.cjs} +2 -2
- package/dist/{chunk-NCZDUBC2.js → chunk-DRUT7H5H.js} +1 -1
- package/dist/{chunk-RX3BCX53.cjs → chunk-DV7656NN.cjs} +1208 -527
- package/dist/{chunk-CGEZQC2G.cjs → chunk-F3TT5XPV.cjs} +2 -2
- package/dist/{chunk-EE7VP4O3.js → chunk-HQ7C3VB6.js} +1 -1
- package/dist/{chunk-ZJJBITUN.js → chunk-IAWLTZUP.js} +2 -2
- package/dist/{chunk-57Y2EA2G.cjs → chunk-JGRNVH7C.cjs} +1 -1
- package/dist/{chunk-NTF7TLT7.js → chunk-LBIXTHP7.js} +11 -5
- package/dist/{chunk-GWC7HCZI.cjs → chunk-LWBXQXZ3.cjs} +1 -1
- package/dist/{chunk-7PIUVCKK.js → chunk-MZCJL3QU.js} +1 -1
- package/dist/{chunk-K6J2RIEX.js → chunk-NDJSLJIA.js} +1 -1
- package/dist/{chunk-UOOLU64V.cjs → chunk-QLVRJ5FS.cjs} +4 -4
- package/dist/{chunk-F6P4DIBL.js → chunk-TLYFFUW4.js} +1 -1
- package/dist/{chunk-YV4JSBPA.js → chunk-TU4BB776.js} +789 -108
- package/dist/{chunk-GL6XM527.js → chunk-TX7ZM4OI.js} +8 -3
- package/dist/{chunk-ZMERU7D3.js → chunk-VXHN5IDL.js} +1 -1
- package/dist/{chunk-NDIBVTBZ.cjs → chunk-XYFCNDJK.cjs} +2 -2
- package/dist/{chunk-X6GHTNDL.cjs → chunk-ZG57GAVU.cjs} +7 -7
- package/dist/{chunk-QWNUOR7F.cjs → chunk-ZTN2PVKO.cjs} +1 -1
- package/dist/completion/CompletionService.cjs +12 -12
- package/dist/completion/CompletionService.d.cts +2 -2
- package/dist/completion/CompletionService.d.ts +2 -2
- package/dist/completion/CompletionService.js +11 -11
- package/dist/completion.cjs +12 -12
- package/dist/completion.d.cts +2 -2
- package/dist/completion.d.ts +2 -2
- package/dist/completion.js +11 -11
- package/dist/image-generation.d.cts +4 -4
- package/dist/image-generation.d.ts +4 -4
- package/dist/index.cjs +14 -12
- package/dist/index.d.cts +7 -7
- package/dist/index.d.ts +7 -7
- package/dist/index.js +13 -11
- package/dist/openai-operation.d.cts +1 -1
- package/dist/openai-operation.d.ts +1 -1
- package/dist/outbound-api/routeResolver.cjs +2 -2
- package/dist/outbound-api/routeResolver.d.cts +5 -5
- package/dist/outbound-api/routeResolver.d.ts +5 -5
- package/dist/outbound-api/routeResolver.js +1 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.cts +9 -1
- package/dist/outbound-api/subscriptionRegistryPort.d.ts +9 -1
- package/dist/outbound-api/types.d.cts +5 -5
- package/dist/outbound-api/types.d.ts +5 -5
- package/dist/outbound-api.cjs +14 -12
- package/dist/outbound-api.d.cts +49 -8
- package/dist/outbound-api.d.ts +49 -8
- package/dist/outbound-api.js +13 -11
- package/dist/pipeline/AccountAllowanceScheduling.d.cts +4 -4
- package/dist/pipeline/AccountAllowanceScheduling.d.ts +4 -4
- package/dist/pipeline/AccountRouteActivity.cjs +2 -2
- package/dist/pipeline/AccountRouteActivity.d.cts +33 -8
- package/dist/pipeline/AccountRouteActivity.d.ts +33 -8
- package/dist/pipeline/AccountRouteActivity.js +1 -1
- package/dist/pipeline/SubscriptionAuthSource.d.cts +11 -0
- package/dist/pipeline/SubscriptionAuthSource.d.ts +11 -0
- package/dist/pipeline/upstreamFetch.cjs +3 -3
- package/dist/pipeline/upstreamFetch.d.cts +15 -1
- package/dist/pipeline/upstreamFetch.d.ts +15 -1
- package/dist/pipeline/upstreamFetch.js +2 -2
- package/dist/ports.d.cts +4 -4
- package/dist/ports.d.ts +4 -4
- package/dist/provider-proxy/ProviderProxy.cjs +12 -12
- package/dist/provider-proxy/ProviderProxy.d.cts +2 -2
- package/dist/provider-proxy/ProviderProxy.d.ts +2 -2
- package/dist/provider-proxy/ProviderProxy.js +11 -11
- package/dist/provider-proxy/identity/codexCliHeaders.d.cts +6 -4
- package/dist/provider-proxy/identity/codexCliHeaders.d.ts +6 -4
- package/dist/provider-proxy/identity/openCodeGoHeaders.cjs +2 -2
- package/dist/provider-proxy/identity/openCodeGoHeaders.js +1 -1
- package/dist/provider-proxy/ingress/providerProxyShared.cjs +20 -12
- package/dist/provider-proxy/ingress/providerProxyShared.d.cts +54 -2
- package/dist/provider-proxy/ingress/providerProxyShared.d.ts +54 -2
- package/dist/provider-proxy/ingress/providerProxyShared.js +19 -11
- package/dist/provider-proxy/types.d.cts +1 -1
- package/dist/provider-proxy/types.d.ts +1 -1
- package/dist/provider-proxy.cjs +12 -12
- package/dist/provider-proxy.d.cts +6 -6
- package/dist/provider-proxy.d.ts +6 -6
- package/dist/provider-proxy.js +11 -11
- package/dist/{routeResolver-C2z0pp-q.d.cts → routeResolver-BQYMEXer.d.cts} +2 -2
- package/dist/{routeResolver-BLTleO4H.d.ts → routeResolver-DTfyzaFb.d.ts} +2 -2
- package/dist/transformer/transformers/AntigravityTransformer.cjs +6 -6
- package/dist/transformer/transformers/AntigravityTransformer.js +5 -5
- package/dist/transformer/transformers/OpenAITransformer.cjs +2 -2
- package/dist/transformer/transformers/OpenAITransformer.js +1 -1
- package/dist/transformer/transformers/antigravityFailover.cjs +7 -7
- package/dist/transformer/transformers/antigravityFailover.js +6 -6
- package/dist/transformer/transformers/antigravityIdentity.cjs +4 -4
- package/dist/transformer/transformers/antigravityIdentity.js +3 -3
- package/dist/transformer/transformers.cjs +9 -9
- package/dist/transformer/transformers.js +8 -8
- package/dist/transformer.cjs +9 -9
- package/dist/transformer.js +8 -8
- package/dist/{types-C6m-Db26.d.cts → types-BpSkj2JA.d.cts} +66 -5
- package/dist/{types-BVwJQ9ZS.d.cts → types-BsZ1s_a_.d.cts} +1 -1
- package/dist/{types-By9Tg6-I.d.ts → types-Di8VH1c9.d.ts} +12 -0
- package/dist/{types-BSIJdsyl.d.cts → types-Dpr5IAaP.d.cts} +12 -0
- package/dist/{types-Da735j2d.d.ts → types-EReErShW.d.ts} +66 -5
- package/dist/{types-VAHRysdP.d.ts → types-USaixzVP.d.ts} +1 -1
- package/package.json +2 -2
|
@@ -11,7 +11,7 @@ import {
|
|
|
11
11
|
import {
|
|
12
12
|
fetchWithAntigravityFailover,
|
|
13
13
|
maybeAntigravityFailoverUrl
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-NDJSLJIA.js";
|
|
15
15
|
import {
|
|
16
16
|
GeminiTransformer
|
|
17
17
|
} from "./chunk-OXBGNEYR.js";
|
|
@@ -53,7 +53,7 @@ import {
|
|
|
53
53
|
} from "./chunk-XX6NQJMA.js";
|
|
54
54
|
import {
|
|
55
55
|
extractOpenCodeSessionHeader
|
|
56
|
-
} from "./chunk-
|
|
56
|
+
} from "./chunk-DRUT7H5H.js";
|
|
57
57
|
import {
|
|
58
58
|
NEVER_FORWARD_HEADERS,
|
|
59
59
|
fillMissingHeaders,
|
|
@@ -118,7 +118,7 @@ import {
|
|
|
118
118
|
parseModelRef,
|
|
119
119
|
resolvePrefixTarget,
|
|
120
120
|
resolveRoute
|
|
121
|
-
} from "./chunk-
|
|
121
|
+
} from "./chunk-VXHN5IDL.js";
|
|
122
122
|
import {
|
|
123
123
|
getSubscriptionRegistryForOutbound
|
|
124
124
|
} from "./chunk-AQ5TNQB7.js";
|
|
@@ -147,19 +147,20 @@ import {
|
|
|
147
147
|
} from "./chunk-5NJLQ6T6.js";
|
|
148
148
|
import {
|
|
149
149
|
getAntigravityProjectResolver
|
|
150
|
-
} from "./chunk-
|
|
150
|
+
} from "./chunk-HQ7C3VB6.js";
|
|
151
151
|
import {
|
|
152
|
+
extractReasoningIntent,
|
|
152
153
|
resolveReasoningPlan
|
|
153
154
|
} from "./chunk-ZLNHPUFA.js";
|
|
154
155
|
import {
|
|
155
156
|
fetchUpstream
|
|
156
|
-
} from "./chunk-
|
|
157
|
+
} from "./chunk-LBIXTHP7.js";
|
|
157
158
|
import {
|
|
158
159
|
getSharedAccountAllowanceStore
|
|
159
160
|
} from "./chunk-FYPKKMB3.js";
|
|
160
161
|
import {
|
|
161
162
|
getSharedAccountRouteActivity
|
|
162
|
-
} from "./chunk-
|
|
163
|
+
} from "./chunk-TX7ZM4OI.js";
|
|
163
164
|
import {
|
|
164
165
|
DEFAULT_IMAGE_API_LIMITS,
|
|
165
166
|
ImageGenerationError,
|
|
@@ -896,6 +897,124 @@ async function aggregateAnthropicSseToJsonBody(response, rewriteModel, signal) {
|
|
|
896
897
|
if (rewriteModel) message["model"] = rewriteModel;
|
|
897
898
|
return JSON.stringify(message);
|
|
898
899
|
}
|
|
900
|
+
async function aggregateResponsesSseToJsonBody(response, rewriteModel, signal) {
|
|
901
|
+
const text = await readResponseTextWithSignal(response, signal);
|
|
902
|
+
let terminal = null;
|
|
903
|
+
for (const raw of text.split("\n")) {
|
|
904
|
+
const line = raw.trim();
|
|
905
|
+
if (!line.startsWith("data:")) continue;
|
|
906
|
+
const payload = line.slice(5).trim();
|
|
907
|
+
if (!payload || payload === "[DONE]") continue;
|
|
908
|
+
try {
|
|
909
|
+
const ev = JSON.parse(payload);
|
|
910
|
+
if (ev["type"] === "response.completed" || ev["type"] === "response.failed" || ev["type"] === "response.incomplete") {
|
|
911
|
+
const resp = ev["response"];
|
|
912
|
+
if (resp && typeof resp === "object") terminal = resp;
|
|
913
|
+
}
|
|
914
|
+
} catch {
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
if (!terminal) return null;
|
|
918
|
+
if (rewriteModel && typeof terminal["model"] === "string") terminal["model"] = rewriteModel;
|
|
919
|
+
return JSON.stringify(terminal);
|
|
920
|
+
}
|
|
921
|
+
async function aggregateOpenAIChatChunksToJsonBody(response, rewriteModel, signal) {
|
|
922
|
+
const text = await readResponseTextWithSignal(response, signal);
|
|
923
|
+
let id;
|
|
924
|
+
let created;
|
|
925
|
+
let model;
|
|
926
|
+
let content = "";
|
|
927
|
+
let reasoning = "";
|
|
928
|
+
let finishReason = null;
|
|
929
|
+
let usage;
|
|
930
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
931
|
+
let sawChoice = false;
|
|
932
|
+
for (const raw of text.split("\n")) {
|
|
933
|
+
const line = raw.trim();
|
|
934
|
+
if (!line.startsWith("data:")) continue;
|
|
935
|
+
const payload = line.slice(5).trim();
|
|
936
|
+
if (!payload || payload === "[DONE]") continue;
|
|
937
|
+
let ev;
|
|
938
|
+
try {
|
|
939
|
+
ev = JSON.parse(payload);
|
|
940
|
+
} catch {
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
if (typeof ev["id"] === "string" && id === void 0) id = ev["id"];
|
|
944
|
+
if (typeof ev["created"] === "number" && created === void 0) created = ev["created"];
|
|
945
|
+
if (typeof ev["model"] === "string" && model === void 0) model = ev["model"];
|
|
946
|
+
if (ev["usage"] && typeof ev["usage"] === "object") {
|
|
947
|
+
usage = ev["usage"];
|
|
948
|
+
}
|
|
949
|
+
const choices = ev["choices"];
|
|
950
|
+
if (!Array.isArray(choices) || choices.length === 0) continue;
|
|
951
|
+
const choice = choices[0];
|
|
952
|
+
if (!choice || typeof choice !== "object") continue;
|
|
953
|
+
sawChoice = true;
|
|
954
|
+
if (typeof choice["finish_reason"] === "string") finishReason = choice["finish_reason"];
|
|
955
|
+
const delta = choice["delta"];
|
|
956
|
+
if (!delta || typeof delta !== "object") continue;
|
|
957
|
+
const d = delta;
|
|
958
|
+
if (typeof d["content"] === "string") content += d["content"];
|
|
959
|
+
if (typeof d["reasoning_content"] === "string") reasoning += d["reasoning_content"];
|
|
960
|
+
const thinking = d["thinking"];
|
|
961
|
+
if (thinking && typeof thinking === "object") {
|
|
962
|
+
const tc = thinking["content"];
|
|
963
|
+
if (typeof tc === "string") reasoning += tc;
|
|
964
|
+
} else if (typeof thinking === "string") {
|
|
965
|
+
reasoning += thinking;
|
|
966
|
+
}
|
|
967
|
+
const tcs = d["tool_calls"];
|
|
968
|
+
if (Array.isArray(tcs)) {
|
|
969
|
+
for (const tcRaw of tcs) {
|
|
970
|
+
if (!tcRaw || typeof tcRaw !== "object") continue;
|
|
971
|
+
const tc = tcRaw;
|
|
972
|
+
const idx = typeof tc["index"] === "number" ? tc["index"] : 0;
|
|
973
|
+
const fn = tc["function"] ?? {};
|
|
974
|
+
const slot = toolCalls.get(idx) ?? { id: "", name: "", arguments: "" };
|
|
975
|
+
if (typeof tc["id"] === "string" && tc["id"]) slot.id = tc["id"];
|
|
976
|
+
if (typeof fn["name"] === "string" && fn["name"]) slot.name = fn["name"];
|
|
977
|
+
if (typeof fn["arguments"] === "string") slot.arguments += fn["arguments"];
|
|
978
|
+
toolCalls.set(idx, slot);
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (!sawChoice && !usage) return null;
|
|
983
|
+
const message = { role: "assistant", content };
|
|
984
|
+
if (reasoning) message["reasoning_content"] = reasoning;
|
|
985
|
+
if (toolCalls.size > 0) {
|
|
986
|
+
message["tool_calls"] = [...toolCalls.entries()].sort((a, b) => a[0] - b[0]).map(([index, tc]) => ({
|
|
987
|
+
index,
|
|
988
|
+
id: tc.id,
|
|
989
|
+
type: "function",
|
|
990
|
+
function: { name: tc.name, arguments: tc.arguments || "{}" }
|
|
991
|
+
}));
|
|
992
|
+
}
|
|
993
|
+
const completion = {
|
|
994
|
+
id: id ?? "chatcmpl-aggregated",
|
|
995
|
+
object: "chat.completion",
|
|
996
|
+
created: created ?? Math.floor(Date.now() / 1e3),
|
|
997
|
+
model: rewriteModel ?? model ?? "",
|
|
998
|
+
choices: [{ index: 0, message, finish_reason: finishReason ?? "stop" }]
|
|
999
|
+
};
|
|
1000
|
+
if (usage) completion["usage"] = usage;
|
|
1001
|
+
return JSON.stringify(completion);
|
|
1002
|
+
}
|
|
1003
|
+
async function sniffIsSseResponse(response) {
|
|
1004
|
+
const contentType = response.headers.get("Content-Type") ?? "";
|
|
1005
|
+
if (contentType.includes("text/event-stream")) return true;
|
|
1006
|
+
if (!response.body) return false;
|
|
1007
|
+
const peek = response.clone();
|
|
1008
|
+
try {
|
|
1009
|
+
const reader = peek.body.getReader();
|
|
1010
|
+
const { value } = await reader.read();
|
|
1011
|
+
reader.releaseLock();
|
|
1012
|
+
const head = value ? new TextDecoder().decode(value).trimStart() : "";
|
|
1013
|
+
return head.startsWith("event:") || head.startsWith("data:");
|
|
1014
|
+
} catch {
|
|
1015
|
+
return false;
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
899
1018
|
function writeError(res, status, message) {
|
|
900
1019
|
if (isAnthropicProtocolResponse(res)) {
|
|
901
1020
|
writeAnthropicError(res, status, message);
|
|
@@ -953,6 +1072,21 @@ async function resolvePoolBoundKey(deps, providerId, provider, sessionId, prefer
|
|
|
953
1072
|
}
|
|
954
1073
|
return resolveApiKey(resolveProviderEndpoint(provider).apiKey);
|
|
955
1074
|
}
|
|
1075
|
+
function buildByoRouteActivityMeta(deps, providerId, sessionId) {
|
|
1076
|
+
return {
|
|
1077
|
+
providerId,
|
|
1078
|
+
resolveKeyId: () => {
|
|
1079
|
+
if (!sessionId) return void 0;
|
|
1080
|
+
const pool = deps.apiKeyPool;
|
|
1081
|
+
if (!pool || typeof pool.getKeyIdForSession !== "function") return void 0;
|
|
1082
|
+
try {
|
|
1083
|
+
return pool.getKeyIdForSession(providerId, sessionId) ?? void 0;
|
|
1084
|
+
} catch {
|
|
1085
|
+
return void 0;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
956
1090
|
|
|
957
1091
|
// src/provider-proxy/providerProxyRouteMap.ts
|
|
958
1092
|
import { randomBytes } from "crypto";
|
|
@@ -1666,6 +1800,31 @@ function collectText(value) {
|
|
|
1666
1800
|
function proxyProviderId(plan) {
|
|
1667
1801
|
return plan.isSubscription ? plan.transformerProvider.name : "byo";
|
|
1668
1802
|
}
|
|
1803
|
+
function buildAnthropicRouteActivity(plan, proxyAccountId, model, onRecorded) {
|
|
1804
|
+
if (!plan.isSubscription) {
|
|
1805
|
+
const routeSessionId = plan.byoSessionId || void 0;
|
|
1806
|
+
return {
|
|
1807
|
+
providerId: plan.byoActivity?.providerId ?? plan.transformerProvider.name,
|
|
1808
|
+
credentialKind: "provider-key",
|
|
1809
|
+
keyId: plan.byoActivity?.resolveKeyId(),
|
|
1810
|
+
endpoint: "messages",
|
|
1811
|
+
sessionKey: routeSessionId,
|
|
1812
|
+
sessionSource: routeSessionId ? "route-session-id" : "none",
|
|
1813
|
+
model,
|
|
1814
|
+
onRecorded
|
|
1815
|
+
};
|
|
1816
|
+
}
|
|
1817
|
+
return {
|
|
1818
|
+
providerId: proxyProviderId(plan),
|
|
1819
|
+
credentialKind: "subscription-account",
|
|
1820
|
+
accountId: proxyAccountId,
|
|
1821
|
+
endpoint: "messages",
|
|
1822
|
+
sessionKey: plan.sessionKey,
|
|
1823
|
+
sessionSource: plan.sessionKey ? "content-fingerprint" : "none",
|
|
1824
|
+
model,
|
|
1825
|
+
onRecorded
|
|
1826
|
+
};
|
|
1827
|
+
}
|
|
1669
1828
|
async function runPipeline(anthropicBody, plan, reportSelection, signal, callerOpenCodeSession) {
|
|
1670
1829
|
const executor = getSharedExecutor();
|
|
1671
1830
|
const endpointTransformer = getAnthropicEndpointTransformer();
|
|
@@ -1715,15 +1874,14 @@ async function runPipeline(anthropicBody, plan, reportSelection, signal, callerO
|
|
|
1715
1874
|
{
|
|
1716
1875
|
providerId: proxyProviderId(plan),
|
|
1717
1876
|
accountId: proxyAccountId,
|
|
1718
|
-
routeActivity:
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
onRecorded: (record5) => {
|
|
1877
|
+
routeActivity: buildAnthropicRouteActivity(
|
|
1878
|
+
plan,
|
|
1879
|
+
proxyAccountId,
|
|
1880
|
+
resolvedModel,
|
|
1881
|
+
(record5) => {
|
|
1724
1882
|
activityRecordId = record5.id;
|
|
1725
1883
|
}
|
|
1726
|
-
|
|
1884
|
+
)
|
|
1727
1885
|
}
|
|
1728
1886
|
).then((r) => {
|
|
1729
1887
|
rawStatus = r.status;
|
|
@@ -1786,15 +1944,14 @@ async function runSubscriptionSameFormatFetch(rawBody, plan, reportSelection, op
|
|
|
1786
1944
|
{
|
|
1787
1945
|
providerId: proxyProviderId(plan),
|
|
1788
1946
|
accountId: proxyAccountId,
|
|
1789
|
-
routeActivity:
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
onRecorded: (record5) => {
|
|
1947
|
+
routeActivity: buildAnthropicRouteActivity(
|
|
1948
|
+
plan,
|
|
1949
|
+
proxyAccountId,
|
|
1950
|
+
outboundModel,
|
|
1951
|
+
(record5) => {
|
|
1795
1952
|
activityRecordId = record5.id;
|
|
1796
1953
|
}
|
|
1797
|
-
|
|
1954
|
+
)
|
|
1798
1955
|
}
|
|
1799
1956
|
);
|
|
1800
1957
|
return {
|
|
@@ -2818,7 +2975,7 @@ async function resolveResponsesRouteProfile(route, deps, resolvedModel) {
|
|
|
2818
2975
|
}
|
|
2819
2976
|
async function buildResponsesCallPlan(route, deps, resolved, resolvedModel, isStream, sessionKey, sessionSource, requestHeaders, affinity) {
|
|
2820
2977
|
if (route.authMode === "byo") {
|
|
2821
|
-
return buildByoPlan(route, deps, resolved, resolvedModel, isStream, affinity);
|
|
2978
|
+
return buildByoPlan(route, deps, resolved, resolvedModel, isStream, sessionKey, sessionSource, affinity);
|
|
2822
2979
|
}
|
|
2823
2980
|
return buildSubscriptionPlan2(
|
|
2824
2981
|
route,
|
|
@@ -2832,7 +2989,7 @@ async function buildResponsesCallPlan(route, deps, resolved, resolvedModel, isSt
|
|
|
2832
2989
|
affinity
|
|
2833
2990
|
);
|
|
2834
2991
|
}
|
|
2835
|
-
async function buildByoPlan(route, deps, resolved, resolvedModel, isStream, affinity) {
|
|
2992
|
+
async function buildByoPlan(route, deps, resolved, resolvedModel, isStream, sessionKey, sessionSource, affinity) {
|
|
2836
2993
|
const providerId = route.providerId;
|
|
2837
2994
|
const provider = resolved.provider;
|
|
2838
2995
|
const affinityKeyId = affinity?.credential.kind === "byo-key" ? affinity.credential.id : void 0;
|
|
@@ -2883,7 +3040,11 @@ async function buildByoPlan(route, deps, resolved, resolvedModel, isStream, affi
|
|
|
2883
3040
|
resolveCredential: () => {
|
|
2884
3041
|
const keyId = route.sessionId && typeof deps.apiKeyPool?.getKeyIdForSession === "function" ? deps.apiKeyPool.getKeyIdForSession(providerId, route.sessionId) : null;
|
|
2885
3042
|
return keyId ? { kind: "byo-key", id: keyId } : credential;
|
|
2886
|
-
}
|
|
3043
|
+
},
|
|
3044
|
+
byoActivity: buildByoRouteActivityMeta(deps, providerId, route.sessionId),
|
|
3045
|
+
byoSessionId: route.sessionId ?? null,
|
|
3046
|
+
sessionKey,
|
|
3047
|
+
sessionSource
|
|
2887
3048
|
};
|
|
2888
3049
|
}
|
|
2889
3050
|
async function buildSubscriptionPlan2(route, deps, resolved, resolvedModel, isStream, sessionKey, sessionSource, requestHeaders, affinity) {
|
|
@@ -2954,6 +3115,7 @@ async function resolveGeminiProject(profile, hints) {
|
|
|
2954
3115
|
if (!accessToken) return void 0;
|
|
2955
3116
|
return getGeminiCodeAssistResolver()?.resolveProject(accessToken);
|
|
2956
3117
|
}
|
|
3118
|
+
var resolveGeminiProjectForChatBridge = resolveGeminiProject;
|
|
2957
3119
|
async function resolveAntigravityProjectThreaded(profile, hints) {
|
|
2958
3120
|
const headers = {};
|
|
2959
3121
|
await profile.authStrategy.applyHeaders(headers, hints);
|
|
@@ -2985,6 +3147,32 @@ async function executeResponsesUpstream(body, plan, operation, signal) {
|
|
|
2985
3147
|
}
|
|
2986
3148
|
return result;
|
|
2987
3149
|
}
|
|
3150
|
+
function buildResponsesRouteActivity(plan, accountId, actualModel, onRecorded) {
|
|
3151
|
+
if (plan.proxyProviderId === "byo") {
|
|
3152
|
+
const contentKey = plan.sessionKey?.trim() || void 0;
|
|
3153
|
+
const routeSessionId = plan.byoSessionId || void 0;
|
|
3154
|
+
return {
|
|
3155
|
+
providerId: plan.byoActivity?.providerId ?? plan.providerIdentity,
|
|
3156
|
+
credentialKind: "provider-key",
|
|
3157
|
+
keyId: plan.byoActivity?.resolveKeyId(),
|
|
3158
|
+
endpoint: "responses",
|
|
3159
|
+
sessionKey: contentKey ?? routeSessionId,
|
|
3160
|
+
sessionSource: contentKey ? plan.sessionSource ?? "none" : routeSessionId ? "route-session-id" : "none",
|
|
3161
|
+
model: actualModel,
|
|
3162
|
+
onRecorded
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
return {
|
|
3166
|
+
providerId: plan.proxyProviderId,
|
|
3167
|
+
credentialKind: "subscription-account",
|
|
3168
|
+
accountId,
|
|
3169
|
+
endpoint: "responses",
|
|
3170
|
+
sessionKey: plan.sessionKey,
|
|
3171
|
+
sessionSource: plan.sessionSource ?? "none",
|
|
3172
|
+
model: actualModel,
|
|
3173
|
+
onRecorded
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
2988
3176
|
async function applyPlanAuth(body, plan) {
|
|
2989
3177
|
let accountId;
|
|
2990
3178
|
let actualModel = plan.resolvedModel;
|
|
@@ -3004,12 +3192,16 @@ async function applyPlanAuth(body, plan) {
|
|
|
3004
3192
|
});
|
|
3005
3193
|
body.model = actualModel;
|
|
3006
3194
|
plan.transformerProvider.models = [actualModel];
|
|
3195
|
+
if (plan.proxyProviderId === "codex") {
|
|
3196
|
+
body.store = false;
|
|
3197
|
+
if (!plan.isStream) body.stream = true;
|
|
3198
|
+
}
|
|
3007
3199
|
return { headers, accountId, actualModel };
|
|
3008
3200
|
}
|
|
3009
3201
|
function decorateCodexHeaders(headers, plan) {
|
|
3010
3202
|
if (plan.proxyProviderId !== "codex") return;
|
|
3011
3203
|
fillMissingHeaders(headers, plan.callerClientHeaders ?? {});
|
|
3012
|
-
fillMissingHeaders(headers, { accept: codexAcceptHeader(
|
|
3204
|
+
fillMissingHeaders(headers, { accept: codexAcceptHeader(true) });
|
|
3013
3205
|
fillMissingCodexCliIdentity(headers);
|
|
3014
3206
|
}
|
|
3015
3207
|
async function runNative(body, plan, operation, signal) {
|
|
@@ -3025,15 +3217,9 @@ async function runNative(body, plan, operation, signal) {
|
|
|
3025
3217
|
{
|
|
3026
3218
|
providerId: plan.proxyProviderId,
|
|
3027
3219
|
accountId,
|
|
3028
|
-
routeActivity: plan
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
sessionSource: plan.sessionSource ?? "none",
|
|
3032
|
-
model: actualModel,
|
|
3033
|
-
onRecorded: (record5) => {
|
|
3034
|
-
activityRecordId = record5.id;
|
|
3035
|
-
}
|
|
3036
|
-
}
|
|
3220
|
+
routeActivity: buildResponsesRouteActivity(plan, accountId, actualModel, (record5) => {
|
|
3221
|
+
activityRecordId = record5.id;
|
|
3222
|
+
})
|
|
3037
3223
|
}
|
|
3038
3224
|
);
|
|
3039
3225
|
return {
|
|
@@ -3078,15 +3264,9 @@ async function runReduced(body, plan, signal) {
|
|
|
3078
3264
|
{
|
|
3079
3265
|
providerId: plan.proxyProviderId,
|
|
3080
3266
|
accountId,
|
|
3081
|
-
routeActivity: plan
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
sessionSource: plan.sessionSource ?? "none",
|
|
3085
|
-
model: actualModel,
|
|
3086
|
-
onRecorded: (record5) => {
|
|
3087
|
-
activityRecordId = record5.id;
|
|
3088
|
-
}
|
|
3089
|
-
}
|
|
3267
|
+
routeActivity: buildResponsesRouteActivity(plan, accountId, actualModel, (record5) => {
|
|
3268
|
+
activityRecordId = record5.id;
|
|
3269
|
+
})
|
|
3090
3270
|
}
|
|
3091
3271
|
);
|
|
3092
3272
|
const upstream = plan.proxyProviderId === "antigravity" ? await fetchWithAntigravityFailover(url, send, signal) : await send(url);
|
|
@@ -4070,6 +4250,30 @@ async function handleResponsesOperation(res, responsesBody, route, deps, request
|
|
|
4070
4250
|
})
|
|
4071
4251
|
};
|
|
4072
4252
|
}
|
|
4253
|
+
if (!isStream) {
|
|
4254
|
+
if (await sniffIsSseResponse(providerResponse.response)) {
|
|
4255
|
+
const collapsed = await aggregateResponsesSseToJsonBody(
|
|
4256
|
+
providerResponse.response,
|
|
4257
|
+
route.requestedModel,
|
|
4258
|
+
signal
|
|
4259
|
+
);
|
|
4260
|
+
if (collapsed !== null) {
|
|
4261
|
+
providerResponse = {
|
|
4262
|
+
...providerResponse,
|
|
4263
|
+
response: new Response(collapsed, {
|
|
4264
|
+
status: providerResponse.response.status,
|
|
4265
|
+
headers: { "Content-Type": "application/json" }
|
|
4266
|
+
})
|
|
4267
|
+
};
|
|
4268
|
+
} else {
|
|
4269
|
+
throw new OpenAIOperationError({
|
|
4270
|
+
status: 502,
|
|
4271
|
+
code: "upstream_stream_collapse_failed",
|
|
4272
|
+
message: "upstream streamed no terminal response event"
|
|
4273
|
+
});
|
|
4274
|
+
}
|
|
4275
|
+
}
|
|
4276
|
+
}
|
|
4073
4277
|
let overloadRecorded = false;
|
|
4074
4278
|
let usageLimitRecorded = false;
|
|
4075
4279
|
const observeSse = (event) => {
|
|
@@ -4329,7 +4533,9 @@ async function buildByoPlan2(res, route, deps, resolvedModel, isStream) {
|
|
|
4329
4533
|
isSubscription: false,
|
|
4330
4534
|
provider,
|
|
4331
4535
|
apiKey,
|
|
4332
|
-
extendedContextEnabled: route.anthropicSdkHints?.extendedContext?.enabled ?? false
|
|
4536
|
+
extendedContextEnabled: route.anthropicSdkHints?.extendedContext?.enabled ?? false,
|
|
4537
|
+
byoActivity: buildByoRouteActivityMeta(deps, providerId, route.sessionId),
|
|
4538
|
+
byoSessionId: route.sessionId ?? null
|
|
4333
4539
|
};
|
|
4334
4540
|
}
|
|
4335
4541
|
async function runSameFormatFetch(bodyToSend, plan, options, keyOverride, urlOverride) {
|
|
@@ -4355,12 +4561,30 @@ async function runSameFormatFetch(bodyToSend, plan, options, keyOverride, urlOve
|
|
|
4355
4561
|
headers["anthropic-version"] = callerVersion || DEFAULT_ANTHROPIC_VERSION;
|
|
4356
4562
|
const url = urlOverride ?? buildProviderApiUrl(provider, { model: resolvedModel, stream: isStream });
|
|
4357
4563
|
console.info(`[ProviderProxy:anthropic] (same-format) -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
4564
|
+
let activityRecordId;
|
|
4358
4565
|
const response = await fetchUpstream(
|
|
4359
4566
|
url,
|
|
4360
4567
|
{ method: "POST", headers, body: bodyToSend, signal: options.signal },
|
|
4361
|
-
{
|
|
4568
|
+
{
|
|
4569
|
+
providerId: "byo",
|
|
4570
|
+
routeActivity: {
|
|
4571
|
+
providerId: plan.byoActivity?.providerId ?? provider.name,
|
|
4572
|
+
credentialKind: "provider-key",
|
|
4573
|
+
// LIVE resolution: a pool rebind retry re-invokes this fetch, and the
|
|
4574
|
+
// binding has already rotated, so the retried attempt attributes the
|
|
4575
|
+
// NEW key. `keyOverride` is a key STRING (never recorded).
|
|
4576
|
+
keyId: plan.byoActivity?.resolveKeyId(),
|
|
4577
|
+
endpoint: "messages",
|
|
4578
|
+
sessionKey: plan.byoSessionId || void 0,
|
|
4579
|
+
sessionSource: plan.byoSessionId ? "route-session-id" : "none",
|
|
4580
|
+
model: resolvedModel,
|
|
4581
|
+
onRecorded: (record5) => {
|
|
4582
|
+
activityRecordId = record5.id;
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
}
|
|
4362
4586
|
);
|
|
4363
|
-
return { response, rawStatus: response.status };
|
|
4587
|
+
return { response, rawStatus: response.status, activityRecordId };
|
|
4364
4588
|
}
|
|
4365
4589
|
function resolveSameFormatBody(anthropicBody, rawBody, plan) {
|
|
4366
4590
|
let originalModel;
|
|
@@ -4903,7 +5127,9 @@ async function buildByoPlan3(res, route, deps, resolvedModel, isStream) {
|
|
|
4903
5127
|
resolvedModel,
|
|
4904
5128
|
isStream,
|
|
4905
5129
|
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : byoUrl,
|
|
4906
|
-
upstreamUrl: byoUrl
|
|
5130
|
+
upstreamUrl: byoUrl,
|
|
5131
|
+
byoActivity: buildByoRouteActivityMeta(deps, providerId, route.sessionId),
|
|
5132
|
+
byoSessionId: route.sessionId ?? null
|
|
4907
5133
|
};
|
|
4908
5134
|
}
|
|
4909
5135
|
async function runPipeline2(geminiBody, plan) {
|
|
@@ -4934,7 +5160,18 @@ async function runPipeline2(geminiBody, plan) {
|
|
|
4934
5160
|
return fetchUpstream(
|
|
4935
5161
|
url,
|
|
4936
5162
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
4937
|
-
{
|
|
5163
|
+
{
|
|
5164
|
+
providerId: "byo",
|
|
5165
|
+
routeActivity: {
|
|
5166
|
+
providerId: plan.byoActivity?.providerId ?? transformerProvider.name,
|
|
5167
|
+
credentialKind: "provider-key",
|
|
5168
|
+
keyId: plan.byoActivity?.resolveKeyId(),
|
|
5169
|
+
endpoint: "generateContent",
|
|
5170
|
+
sessionKey: plan.byoSessionId || void 0,
|
|
5171
|
+
sessionSource: plan.byoSessionId ? "route-session-id" : "none",
|
|
5172
|
+
model: resolvedModel
|
|
5173
|
+
}
|
|
5174
|
+
}
|
|
4938
5175
|
).then((r) => {
|
|
4939
5176
|
rawStatus = r.status;
|
|
4940
5177
|
return r;
|
|
@@ -5015,7 +5252,16 @@ function recordChatCompletionsStreamUsage(recorder, rawUsage, attribution) {
|
|
|
5015
5252
|
}
|
|
5016
5253
|
|
|
5017
5254
|
// src/provider-proxy/ingress/openaiChatIngress.ts
|
|
5018
|
-
var CHAT_BRIDGE_SUBSCRIPTION_PROVIDERS = /* @__PURE__ */ new Set([
|
|
5255
|
+
var CHAT_BRIDGE_SUBSCRIPTION_PROVIDERS = /* @__PURE__ */ new Set([
|
|
5256
|
+
"claude",
|
|
5257
|
+
"kimi",
|
|
5258
|
+
"grok",
|
|
5259
|
+
"opencodego",
|
|
5260
|
+
"codex",
|
|
5261
|
+
"gemini",
|
|
5262
|
+
"copilot",
|
|
5263
|
+
"antigravity"
|
|
5264
|
+
]);
|
|
5019
5265
|
var IDENTITY_ENDPOINT_TRANSFORMER = { name: "identity-openai-chat" };
|
|
5020
5266
|
async function handleOpenAIChatRequest(res, rawBody, route, deps) {
|
|
5021
5267
|
let chatBody;
|
|
@@ -5029,9 +5275,26 @@ async function handleOpenAIChatRequest(res, rawBody, route, deps) {
|
|
|
5029
5275
|
const resolvedModel = route.model;
|
|
5030
5276
|
chatBody.model = resolvedModel;
|
|
5031
5277
|
try {
|
|
5032
|
-
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream) : await buildByoPlan4(res, route, deps, resolvedModel, isStream);
|
|
5278
|
+
const plan = route.authMode === "subscription" ? await buildSubscriptionPlan3(res, route, deps, chatBody, resolvedModel, isStream) : await buildByoPlan4(res, route, deps, resolvedModel, isStream);
|
|
5033
5279
|
if (!plan) return;
|
|
5034
|
-
|
|
5280
|
+
let providerResponse = plan.isSubscription ? await runPipelineWithSubscriptionRetry2(chatBody, plan) : await runPipelineWithPoolReporting3(chatBody, plan);
|
|
5281
|
+
if (!isStream) {
|
|
5282
|
+
if (await sniffIsSseResponse(providerResponse.response)) {
|
|
5283
|
+
const collapsed = await aggregateOpenAIChatChunksToJsonBody(providerResponse.response);
|
|
5284
|
+
if (collapsed !== null) {
|
|
5285
|
+
providerResponse = {
|
|
5286
|
+
response: new Response(collapsed, {
|
|
5287
|
+
status: providerResponse.response.status,
|
|
5288
|
+
headers: { "Content-Type": "application/json" }
|
|
5289
|
+
}),
|
|
5290
|
+
rawStatus: providerResponse.rawStatus
|
|
5291
|
+
};
|
|
5292
|
+
} else {
|
|
5293
|
+
writeError(res, 502, "upstream streamed no completable chat chunks");
|
|
5294
|
+
return;
|
|
5295
|
+
}
|
|
5296
|
+
}
|
|
5297
|
+
}
|
|
5035
5298
|
const usageAttribution = {
|
|
5036
5299
|
sessionId: route.sessionId,
|
|
5037
5300
|
providerId: route.providerId ?? "openai",
|
|
@@ -5118,10 +5381,12 @@ async function buildByoPlan4(res, route, deps, resolvedModel, isStream) {
|
|
|
5118
5381
|
resolveUrl: (config) => config.url instanceof URL ? config.url.toString() : byoUrl,
|
|
5119
5382
|
upstreamUrl: byoUrl,
|
|
5120
5383
|
proxyProviderId: "byo",
|
|
5121
|
-
isSubscription: false
|
|
5384
|
+
isSubscription: false,
|
|
5385
|
+
byoActivity: buildByoRouteActivityMeta(deps, providerId, route.sessionId),
|
|
5386
|
+
routeSessionId: route.sessionId ?? null
|
|
5122
5387
|
};
|
|
5123
5388
|
}
|
|
5124
|
-
async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream) {
|
|
5389
|
+
async function buildSubscriptionPlan3(res, route, deps, chatBody, resolvedModel, isStream) {
|
|
5125
5390
|
const profile = route.subscriptionProfile;
|
|
5126
5391
|
if (!profile) {
|
|
5127
5392
|
writeError(res, 502, "Subscription mode requires a subscription profile");
|
|
@@ -5132,27 +5397,56 @@ async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream)
|
|
|
5132
5397
|
writeError(
|
|
5133
5398
|
res,
|
|
5134
5399
|
501,
|
|
5135
|
-
`the OpenAI-chat\u2192subscription bridge
|
|
5400
|
+
`the OpenAI-chat\u2192subscription bridge does not implement subscription provider '${providerId}' on this endpoint yet (use the /v1/responses endpoint, a Claude/Kimi/Grok/OpenCodeGo subscription, or a BYO provider)`
|
|
5136
5401
|
);
|
|
5137
5402
|
return null;
|
|
5138
5403
|
}
|
|
5139
|
-
|
|
5404
|
+
let mappedModel = resolvedModel;
|
|
5405
|
+
if (profile.modelMapper) {
|
|
5406
|
+
const summary = buildSubscriptionRequestSummary(chatBody);
|
|
5407
|
+
const mapped = profile.modelMapper(resolvedModel, summary, route.subscriptionConfig);
|
|
5408
|
+
mappedModel = mapped.resolvedModel;
|
|
5409
|
+
}
|
|
5410
|
+
const upstreamUrl = profile.resolveUpstreamUrl?.(mappedModel, route.subscriptionConfig);
|
|
5140
5411
|
if (!upstreamUrl) {
|
|
5141
5412
|
writeError(res, 502, "Subscription profile is missing resolveUpstreamUrl");
|
|
5142
5413
|
return null;
|
|
5143
5414
|
}
|
|
5144
5415
|
const auth = route.auth ?? new SubscriptionAuthSource(profile);
|
|
5416
|
+
const chatChainNames = profile.chatBridgeTransformerNames?.(mappedModel, route.subscriptionConfig) ?? profile.resolveProviderTransformerNames?.(mappedModel, route.subscriptionConfig);
|
|
5145
5417
|
const chain = resolveSubscriptionChain(
|
|
5146
5418
|
profile,
|
|
5147
5419
|
deps.llmConfig.getTransformerService(),
|
|
5148
|
-
IDENTITY_ENDPOINT_TRANSFORMER
|
|
5420
|
+
IDENTITY_ENDPOINT_TRANSFORMER,
|
|
5421
|
+
chatChainNames
|
|
5149
5422
|
);
|
|
5150
5423
|
const transformerProvider = {
|
|
5151
5424
|
name: providerId,
|
|
5152
5425
|
baseUrl: upstreamUrl,
|
|
5153
5426
|
apiKey: "",
|
|
5154
|
-
models: [
|
|
5427
|
+
models: [mappedModel]
|
|
5155
5428
|
};
|
|
5429
|
+
const sessionKey = deriveSubscriptionSessionKey(chatBody);
|
|
5430
|
+
if (providerId === "gemini") {
|
|
5431
|
+
transformerProvider.geminiProject = await resolveGeminiProjectForChatBridge(profile, {
|
|
5432
|
+
sessionKey,
|
|
5433
|
+
preferredAccountId: route.preferredAccountId,
|
|
5434
|
+
preferredAccountGroup: route.preferredAccountGroup,
|
|
5435
|
+
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy
|
|
5436
|
+
});
|
|
5437
|
+
} else if (providerId === "antigravity") {
|
|
5438
|
+
transformerProvider.geminiProject = await resolveAntigravityProjectThreaded(profile, {
|
|
5439
|
+
resolvedModel: mappedModel,
|
|
5440
|
+
reportSelection: () => {
|
|
5441
|
+
},
|
|
5442
|
+
sessionKey,
|
|
5443
|
+
preferredAccountId: route.preferredAccountId,
|
|
5444
|
+
preferredAccountGroup: route.preferredAccountGroup,
|
|
5445
|
+
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy
|
|
5446
|
+
});
|
|
5447
|
+
}
|
|
5448
|
+
chatBody.model = mappedModel;
|
|
5449
|
+
const resolveUrl = providerId === "antigravity" ? (config) => config.url instanceof URL ? config.url.toString() : typeof config.url === "string" ? config.url : upstreamUrl : () => upstreamUrl;
|
|
5156
5450
|
return {
|
|
5157
5451
|
auth,
|
|
5158
5452
|
preferredAccountId: route.preferredAccountId,
|
|
@@ -5160,12 +5454,14 @@ async function buildSubscriptionPlan3(res, route, deps, resolvedModel, isStream)
|
|
|
5160
5454
|
boundAccountFallbackPolicy: route.boundAccountFallbackPolicy,
|
|
5161
5455
|
chain,
|
|
5162
5456
|
transformerProvider,
|
|
5163
|
-
resolvedModel,
|
|
5457
|
+
resolvedModel: mappedModel,
|
|
5164
5458
|
isStream,
|
|
5165
|
-
resolveUrl
|
|
5459
|
+
resolveUrl,
|
|
5166
5460
|
upstreamUrl,
|
|
5167
5461
|
proxyProviderId: providerId,
|
|
5168
|
-
isSubscription: true
|
|
5462
|
+
isSubscription: true,
|
|
5463
|
+
contentSessionKey: sessionKey,
|
|
5464
|
+
routeSessionId: route.sessionId ?? null
|
|
5169
5465
|
};
|
|
5170
5466
|
}
|
|
5171
5467
|
async function runPipeline3(chatBody, plan) {
|
|
@@ -5176,6 +5472,7 @@ async function runPipeline3(chatBody, plan) {
|
|
|
5176
5472
|
await auth.applyHeaders(authHeaders, {
|
|
5177
5473
|
upstreamUrl,
|
|
5178
5474
|
model: resolvedModel,
|
|
5475
|
+
sessionKey: plan.contentSessionKey,
|
|
5179
5476
|
preferredAccountId: plan.preferredAccountId,
|
|
5180
5477
|
preferredAccountGroup: plan.preferredAccountGroup,
|
|
5181
5478
|
boundAccountFallbackPolicy: plan.boundAccountFallbackPolicy,
|
|
@@ -5197,14 +5494,36 @@ async function runPipeline3(chatBody, plan) {
|
|
|
5197
5494
|
if (value !== void 0 && !(key in headers)) headers[key] = value;
|
|
5198
5495
|
}
|
|
5199
5496
|
}
|
|
5497
|
+
if (plan.proxyProviderId === "codex") fillMissingCodexCliIdentity(headers);
|
|
5200
5498
|
return headers;
|
|
5201
5499
|
},
|
|
5202
5500
|
fetchFn: (url, headers, body) => {
|
|
5203
5501
|
console.log(`[ProviderProxy:chat] -> ${url} model=${resolvedModel} stream=${isStream}`);
|
|
5502
|
+
const sessionKey = plan.routeSessionId || void 0;
|
|
5204
5503
|
return fetchUpstream(
|
|
5205
5504
|
url,
|
|
5206
5505
|
{ method: "POST", headers, body: JSON.stringify(body) },
|
|
5207
|
-
{
|
|
5506
|
+
{
|
|
5507
|
+
providerId: plan.proxyProviderId,
|
|
5508
|
+
accountId: proxyAccountId,
|
|
5509
|
+
routeActivity: plan.isSubscription ? {
|
|
5510
|
+
providerId: plan.proxyProviderId,
|
|
5511
|
+
credentialKind: "subscription-account",
|
|
5512
|
+
accountId: proxyAccountId,
|
|
5513
|
+
endpoint: "chat",
|
|
5514
|
+
sessionKey,
|
|
5515
|
+
sessionSource: sessionKey ? "route-session-id" : "none",
|
|
5516
|
+
model: resolvedModel
|
|
5517
|
+
} : {
|
|
5518
|
+
providerId: plan.byoActivity?.providerId ?? transformerProvider.name,
|
|
5519
|
+
credentialKind: "provider-key",
|
|
5520
|
+
keyId: plan.byoActivity?.resolveKeyId(),
|
|
5521
|
+
endpoint: "chat",
|
|
5522
|
+
sessionKey,
|
|
5523
|
+
sessionSource: sessionKey ? "route-session-id" : "none",
|
|
5524
|
+
model: resolvedModel
|
|
5525
|
+
}
|
|
5526
|
+
}
|
|
5208
5527
|
).then((r) => {
|
|
5209
5528
|
rawStatus = r.status;
|
|
5210
5529
|
return r;
|
|
@@ -5983,17 +6302,22 @@ function wildcardMatches(pattern, value) {
|
|
|
5983
6302
|
const escaped = pattern.split("*").map((part) => part.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")).join(".*");
|
|
5984
6303
|
return new RegExp(`^${escaped}$`, "iu").test(value);
|
|
5985
6304
|
}
|
|
5986
|
-
function
|
|
6305
|
+
function resolveGatewayModelMappingRow(mappings, requestedModel) {
|
|
5987
6306
|
if (!nonBlank(requestedModel)) return void 0;
|
|
5988
6307
|
const wanted = requestedModel.trim();
|
|
5989
6308
|
const exact = (mappings ?? []).find(
|
|
5990
6309
|
(mapping) => nonBlank(mapping.source) && !mapping.source.includes("*") && mapping.source.trim().toLocaleLowerCase() === wanted.toLocaleLowerCase()
|
|
5991
6310
|
);
|
|
5992
|
-
if (exact && nonBlank(exact.target))
|
|
6311
|
+
if (exact && nonBlank(exact.target)) {
|
|
6312
|
+
return { target: exact.target.trim(), effort: exact.effort };
|
|
6313
|
+
}
|
|
5993
6314
|
const wildcard = (mappings ?? []).find(
|
|
5994
6315
|
(mapping) => nonBlank(mapping.source) && mapping.source.includes("*") && wildcardMatches(mapping.source.trim(), wanted)
|
|
5995
6316
|
);
|
|
5996
|
-
return wildcard && nonBlank(wildcard.target) ? wildcard.target.trim() : void 0;
|
|
6317
|
+
return wildcard && nonBlank(wildcard.target) ? { target: wildcard.target.trim(), effort: wildcard.effort } : void 0;
|
|
6318
|
+
}
|
|
6319
|
+
function resolveGatewayModelMapping(mappings, requestedModel) {
|
|
6320
|
+
return resolveGatewayModelMappingRow(mappings, requestedModel)?.target;
|
|
5997
6321
|
}
|
|
5998
6322
|
function routeCanServe(binding, requestedModel, role) {
|
|
5999
6323
|
if (binding.modelMode === "passthrough") return nonBlank(requestedModel);
|
|
@@ -6067,9 +6391,11 @@ function gatewayBindingToEndpointConfig(binding, requestedModel) {
|
|
|
6067
6391
|
useSubscription: target.kind !== "provider"
|
|
6068
6392
|
};
|
|
6069
6393
|
const usesGenericModelHandling = binding.modelMode === "passthrough" || Boolean(binding.modelMappings?.length);
|
|
6070
|
-
const
|
|
6394
|
+
const winningMapping = binding.modelMode === "passthrough" ? void 0 : resolveGatewayModelMappingRow(binding.modelMappings, requestedModel);
|
|
6395
|
+
const dynamicModel = binding.modelMode === "passthrough" ? requestedModel : winningMapping?.target;
|
|
6071
6396
|
if (usesGenericModelHandling) {
|
|
6072
6397
|
if (nonBlank(dynamicModel)) applySingleModel(config, binding, dynamicModel);
|
|
6398
|
+
if (winningMapping?.effort) config.reasoningEffort = winningMapping.effort;
|
|
6073
6399
|
} else if (isKindMappedEndpoint(binding.endpoint)) {
|
|
6074
6400
|
config.modelMap = Object.fromEntries(
|
|
6075
6401
|
Object.entries(binding.modelMap ?? {}).map(([kind, ref]) => [kind, targetRef(target, ref)])
|
|
@@ -8494,6 +8820,7 @@ function normalizeBindingTarget(raw) {
|
|
|
8494
8820
|
}
|
|
8495
8821
|
return null;
|
|
8496
8822
|
}
|
|
8823
|
+
var MAPPING_EFFORT_MAX_LENGTH = 32;
|
|
8497
8824
|
function normalizeGatewayModelMappings(raw) {
|
|
8498
8825
|
if (!Array.isArray(raw)) return [];
|
|
8499
8826
|
const mappings = [];
|
|
@@ -8503,7 +8830,9 @@ function normalizeGatewayModelMappings(raw) {
|
|
|
8503
8830
|
const source = typeof value.source === "string" ? value.source.trim() : "";
|
|
8504
8831
|
const target = typeof value.target === "string" ? value.target.trim() : "";
|
|
8505
8832
|
if (!source || !target) continue;
|
|
8506
|
-
|
|
8833
|
+
const rawEffort = typeof value.effort === "string" ? value.effort.trim() : "";
|
|
8834
|
+
const effort = rawEffort && rawEffort.length <= MAPPING_EFFORT_MAX_LENGTH ? rawEffort : void 0;
|
|
8835
|
+
mappings.push(effort ? { source, target, effort } : { source, target });
|
|
8507
8836
|
}
|
|
8508
8837
|
return mappings;
|
|
8509
8838
|
}
|
|
@@ -8795,6 +9124,61 @@ function deriveAuditSessionKey(body, headers = {}, options = {}) {
|
|
|
8795
9124
|
return gateway.key;
|
|
8796
9125
|
}
|
|
8797
9126
|
|
|
9127
|
+
// src/outbound-api/mappingEffortInjection.ts
|
|
9128
|
+
function isPlainObject(value) {
|
|
9129
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
9130
|
+
}
|
|
9131
|
+
function hasOpenAiWireReasoningIntent(body) {
|
|
9132
|
+
return extractReasoningIntent({
|
|
9133
|
+
model: typeof body["model"] === "string" ? body["model"] : "",
|
|
9134
|
+
reasoning: isPlainObject(body["reasoning"]) ? body["reasoning"] : void 0,
|
|
9135
|
+
reasoning_effort: body["reasoning_effort"]
|
|
9136
|
+
}) !== void 0;
|
|
9137
|
+
}
|
|
9138
|
+
function injectMappingEffortDefault(endpoint, effort, body) {
|
|
9139
|
+
if (!effort) return false;
|
|
9140
|
+
switch (endpoint) {
|
|
9141
|
+
case "chat":
|
|
9142
|
+
if (hasOpenAiWireReasoningIntent(body)) {
|
|
9143
|
+
return false;
|
|
9144
|
+
}
|
|
9145
|
+
body["reasoning_effort"] = effort;
|
|
9146
|
+
return true;
|
|
9147
|
+
case "responses": {
|
|
9148
|
+
if (hasOpenAiWireReasoningIntent(body)) {
|
|
9149
|
+
return false;
|
|
9150
|
+
}
|
|
9151
|
+
const reasoning = body["reasoning"];
|
|
9152
|
+
body["reasoning"] = {
|
|
9153
|
+
...isPlainObject(reasoning) ? reasoning : {},
|
|
9154
|
+
effort
|
|
9155
|
+
};
|
|
9156
|
+
return true;
|
|
9157
|
+
}
|
|
9158
|
+
case "messages": {
|
|
9159
|
+
if (body["thinking"] !== void 0) return false;
|
|
9160
|
+
if (effort === "none") return false;
|
|
9161
|
+
body["thinking"] = { type: "adaptive" };
|
|
9162
|
+
const outputConfig = body["output_config"];
|
|
9163
|
+
body["output_config"] = {
|
|
9164
|
+
...isPlainObject(outputConfig) ? outputConfig : {},
|
|
9165
|
+
effort
|
|
9166
|
+
};
|
|
9167
|
+
return true;
|
|
9168
|
+
}
|
|
9169
|
+
case "gemini": {
|
|
9170
|
+
const generationConfig = body["generationConfig"];
|
|
9171
|
+
const config = isPlainObject(generationConfig) ? generationConfig : {};
|
|
9172
|
+
if (config["thinkingConfig"] !== void 0) return false;
|
|
9173
|
+
body["generationConfig"] = {
|
|
9174
|
+
...config,
|
|
9175
|
+
thinkingConfig: { thinkingLevel: effort }
|
|
9176
|
+
};
|
|
9177
|
+
return true;
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
}
|
|
9181
|
+
|
|
8798
9182
|
// src/outbound-api/outboundApiKeyAuth.ts
|
|
8799
9183
|
import { createHash as createHash3, randomBytes as randomBytes4 } from "crypto";
|
|
8800
9184
|
|
|
@@ -8981,8 +9365,23 @@ function toVerifiedKey(row) {
|
|
|
8981
9365
|
if (rateLimit) key.rateLimit = rateLimit;
|
|
8982
9366
|
const modelRestriction = extractModelRestriction(row);
|
|
8983
9367
|
if (modelRestriction) key.modelRestriction = modelRestriction;
|
|
9368
|
+
const directTarget = extractBoundUpstream(row);
|
|
9369
|
+
if (directTarget) key.boundUpstream = directTarget;
|
|
8984
9370
|
return key;
|
|
8985
9371
|
}
|
|
9372
|
+
function extractBoundUpstream(row) {
|
|
9373
|
+
if (row.boundUpstream && typeof row.boundUpstream === "object") {
|
|
9374
|
+
const target = row.boundUpstream;
|
|
9375
|
+
if (target.kind === "provider" && typeof target.providerId === "string" && target.providerId || target.kind === "account-pool" && typeof target.providerId === "string" && target.providerId || target.kind === "account" && target.providerId && target.accountId || target.kind === "account-group" && target.providerId && target.group) {
|
|
9376
|
+
return target;
|
|
9377
|
+
}
|
|
9378
|
+
return void 0;
|
|
9379
|
+
}
|
|
9380
|
+
if (typeof row.boundUpstreamProviderId === "string" && row.boundUpstreamProviderId) {
|
|
9381
|
+
return { kind: "provider", providerId: row.boundUpstreamProviderId };
|
|
9382
|
+
}
|
|
9383
|
+
return void 0;
|
|
9384
|
+
}
|
|
8986
9385
|
async function verifyKey(db, presentedKey, now = Date.now()) {
|
|
8987
9386
|
if (!presentedKey) return { status: "invalid" };
|
|
8988
9387
|
const trimmed = presentedKey.trim();
|
|
@@ -9908,7 +10307,102 @@ function resolveModelsShape(modelsShape, allowedEndpoints) {
|
|
|
9908
10307
|
if (!allowedEndpoints || allowedEndpoints.length === 0) return "anthropic";
|
|
9909
10308
|
return allowedEndpoints.includes("messages") ? "anthropic" : "openai";
|
|
9910
10309
|
}
|
|
10310
|
+
var UPSTREAM_MODELS_TTL_MS = 6e4;
|
|
10311
|
+
var UPSTREAM_MODELS_NEGATIVE_TTL_MS = 5e3;
|
|
10312
|
+
var UPSTREAM_MODELS_TIMEOUT_MS = 1e4;
|
|
10313
|
+
var upstreamModelsCache = /* @__PURE__ */ new Map();
|
|
10314
|
+
var upstreamModelsInFlight = /* @__PURE__ */ new Map();
|
|
10315
|
+
function resolveProviderKeyLiteral(apiKey) {
|
|
10316
|
+
if (!apiKey) return "";
|
|
10317
|
+
if (apiKey.startsWith("$")) return process.env[apiKey.slice(1)] || "";
|
|
10318
|
+
return apiKey;
|
|
10319
|
+
}
|
|
10320
|
+
function buildProviderModelsUrl(baseUrl, format) {
|
|
10321
|
+
const base = baseUrl.trim().replace(/\/+$/, "");
|
|
10322
|
+
if (!base) return null;
|
|
10323
|
+
if (format === "google") {
|
|
10324
|
+
return /\/models$/.test(base) ? base : `${base}/models`;
|
|
10325
|
+
}
|
|
10326
|
+
if (format === "anthropic") {
|
|
10327
|
+
if (base.endsWith("/messages")) return `${base.slice(0, -"/messages".length)}/models`;
|
|
10328
|
+
if (/\/v\d+$/.test(base)) return `${base}/models`;
|
|
10329
|
+
return `${base}/v1/models`;
|
|
10330
|
+
}
|
|
10331
|
+
const completionSuffix = format === "openai-response" ? "/responses" : "/chat/completions";
|
|
10332
|
+
const root = base.endsWith(completionSuffix) ? base.slice(0, -completionSuffix.length) : base;
|
|
10333
|
+
if (root !== base || /\/v\d+$/.test(base)) return `${root}/models`;
|
|
10334
|
+
return `${base}/v1/models`;
|
|
10335
|
+
}
|
|
10336
|
+
async function fetchUpstreamModelsUncached(provider) {
|
|
10337
|
+
const format = resolveApiFormat(provider);
|
|
10338
|
+
const url = buildProviderModelsUrl(provider.api_base_url ?? "", format);
|
|
10339
|
+
if (!url) return [];
|
|
10340
|
+
const headers = getProviderHeaders(provider, resolveProviderKeyLiteral(provider.api_key));
|
|
10341
|
+
delete headers["Content-Type"];
|
|
10342
|
+
headers["Accept"] = "application/json";
|
|
10343
|
+
const response = await fetchUpstream(
|
|
10344
|
+
url,
|
|
10345
|
+
{ method: "GET", headers, signal: AbortSignal.timeout(UPSTREAM_MODELS_TIMEOUT_MS) },
|
|
10346
|
+
{ providerId: "byo" }
|
|
10347
|
+
);
|
|
10348
|
+
if (!response.ok) return [];
|
|
10349
|
+
const body = await response.json();
|
|
10350
|
+
const fromData = Array.isArray(body?.data) ? body.data.map((entry) => typeof entry?.id === "string" ? entry.id.trim() : "").filter((id) => id !== "") : [];
|
|
10351
|
+
if (fromData.length > 0) return fromData;
|
|
10352
|
+
return Array.isArray(body?.models) ? body.models.map(
|
|
10353
|
+
(entry) => typeof entry?.name === "string" ? entry.name.replace(/^models\//, "").trim() : ""
|
|
10354
|
+
).filter((id) => id !== "") : [];
|
|
10355
|
+
}
|
|
10356
|
+
async function discoverUpstreamModels(provider) {
|
|
10357
|
+
const cached = upstreamModelsCache.get(provider.id);
|
|
10358
|
+
if (cached) {
|
|
10359
|
+
const ttl = cached.ok ? UPSTREAM_MODELS_TTL_MS : UPSTREAM_MODELS_NEGATIVE_TTL_MS;
|
|
10360
|
+
if (Date.now() - cached.at < ttl) return cached.ids;
|
|
10361
|
+
upstreamModelsCache.delete(provider.id);
|
|
10362
|
+
}
|
|
10363
|
+
const existing = upstreamModelsInFlight.get(provider.id);
|
|
10364
|
+
if (existing) return existing;
|
|
10365
|
+
const pending = (async () => {
|
|
10366
|
+
let ids = [];
|
|
10367
|
+
try {
|
|
10368
|
+
ids = await fetchUpstreamModelsUncached(provider);
|
|
10369
|
+
} catch {
|
|
10370
|
+
ids = [];
|
|
10371
|
+
}
|
|
10372
|
+
upstreamModelsCache.set(provider.id, { at: Date.now(), ids, ok: ids.length > 0 });
|
|
10373
|
+
return ids;
|
|
10374
|
+
})();
|
|
10375
|
+
upstreamModelsInFlight.set(provider.id, pending);
|
|
10376
|
+
try {
|
|
10377
|
+
return await pending;
|
|
10378
|
+
} finally {
|
|
10379
|
+
upstreamModelsInFlight.delete(provider.id);
|
|
10380
|
+
}
|
|
10381
|
+
}
|
|
10382
|
+
async function passthroughProviderModelIds(llmConfig, providerId) {
|
|
10383
|
+
const provider = await llmConfig.getProvider(providerId);
|
|
10384
|
+
if (!provider) return [];
|
|
10385
|
+
const configured = provider.models ?? [];
|
|
10386
|
+
return configured.length > 0 ? configured : await discoverUpstreamModels(provider);
|
|
10387
|
+
}
|
|
9911
10388
|
var ANTHROPIC_MODELS_CREATED_AT = (/* @__PURE__ */ new Date()).toISOString();
|
|
10389
|
+
function hasWildcardMapping(binding) {
|
|
10390
|
+
return (binding.modelMappings ?? []).some(
|
|
10391
|
+
(mapping) => mapping.source.includes("*") && mapping.target.trim() !== ""
|
|
10392
|
+
);
|
|
10393
|
+
}
|
|
10394
|
+
async function pushTargetCatalog(llmConfig, target, push) {
|
|
10395
|
+
if (target.kind === "provider") {
|
|
10396
|
+
for (const id of await passthroughProviderModelIds(llmConfig, target.providerId)) {
|
|
10397
|
+
push(id);
|
|
10398
|
+
}
|
|
10399
|
+
} else if (Object.prototype.hasOwnProperty.call(SUBSCRIPTION_MODEL_CATALOG, target.providerId)) {
|
|
10400
|
+
for (const id of SUBSCRIPTION_MODEL_CATALOG[target.providerId]) {
|
|
10401
|
+
push(id);
|
|
10402
|
+
}
|
|
10403
|
+
}
|
|
10404
|
+
}
|
|
10405
|
+
var MODEL_LIST_ENDPOINTS = ["chat", "responses", "messages", "gemini"];
|
|
9912
10406
|
function parseModelsLimit(url) {
|
|
9913
10407
|
const query = url?.split("?")[1];
|
|
9914
10408
|
if (!query) return void 0;
|
|
@@ -9917,8 +10411,7 @@ function parseModelsLimit(url) {
|
|
|
9917
10411
|
const n = Number.parseInt(raw, 10);
|
|
9918
10412
|
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
9919
10413
|
}
|
|
9920
|
-
async function writeModelsListAnthropic(res, llmConfig, config, apiKeyId, allowedEndpoints, limit) {
|
|
9921
|
-
void allowedEndpoints;
|
|
10414
|
+
async function writeModelsListAnthropic(res, llmConfig, config, apiKeyId, allowedEndpoints, limit, directTarget) {
|
|
9922
10415
|
const entries2 = [];
|
|
9923
10416
|
const seen = /* @__PURE__ */ new Set();
|
|
9924
10417
|
const push = (id, displayName) => {
|
|
@@ -9932,29 +10425,37 @@ async function writeModelsListAnthropic(res, llmConfig, config, apiKeyId, allowe
|
|
|
9932
10425
|
const modelId = ref ? parseModelRef(ref)?.modelId : void 0;
|
|
9933
10426
|
return modelId ? `${name} (via ${modelId})` : void 0;
|
|
9934
10427
|
};
|
|
9935
|
-
for (const
|
|
9936
|
-
if (
|
|
9937
|
-
|
|
9938
|
-
|
|
9939
|
-
|
|
9940
|
-
|
|
9941
|
-
|
|
9942
|
-
|
|
10428
|
+
for (const endpoint of MODEL_LIST_ENDPOINTS) {
|
|
10429
|
+
if (allowedEndpoints && !allowedEndpoints.includes(endpoint)) continue;
|
|
10430
|
+
for (const binding of candidateGatewayBindings(config.bindings, apiKeyId, endpoint)) {
|
|
10431
|
+
if (binding.modelMode === "passthrough") {
|
|
10432
|
+
await pushTargetCatalog(llmConfig, binding.target, push);
|
|
10433
|
+
continue;
|
|
10434
|
+
}
|
|
10435
|
+
if (binding.modelMappings?.length) {
|
|
10436
|
+
for (const mapping of binding.modelMappings) {
|
|
10437
|
+
const source = mapping.source.trim();
|
|
10438
|
+
if (source === "" || source.includes("*")) continue;
|
|
10439
|
+
push(source, viaLabel(source, mapping.target));
|
|
10440
|
+
}
|
|
10441
|
+
if (hasWildcardMapping(binding)) {
|
|
10442
|
+
await pushTargetCatalog(llmConfig, binding.target, push);
|
|
10443
|
+
for (const mapping of binding.modelMappings ?? []) {
|
|
10444
|
+
if (!mapping.source.includes("*") || mapping.target.trim() === "") continue;
|
|
10445
|
+
push(parseModelRef(mapping.target)?.modelId ?? mapping.target.trim());
|
|
10446
|
+
}
|
|
9943
10447
|
}
|
|
10448
|
+
continue;
|
|
9944
10449
|
}
|
|
9945
|
-
|
|
9946
|
-
|
|
9947
|
-
|
|
9948
|
-
for (const mapping of binding.modelMappings) {
|
|
9949
|
-
const source = mapping.source.trim();
|
|
9950
|
-
if (source === "" || source.includes("*")) continue;
|
|
9951
|
-
push(source, viaLabel(source, mapping.target));
|
|
10450
|
+
for (const [kind, ref] of Object.entries(binding.modelMap ?? {})) {
|
|
10451
|
+
if (typeof ref !== "string" || ref.trim() === "") continue;
|
|
10452
|
+
push(kind, viaLabel(kind, ref));
|
|
9952
10453
|
}
|
|
9953
|
-
continue;
|
|
9954
10454
|
}
|
|
9955
|
-
|
|
9956
|
-
|
|
9957
|
-
|
|
10455
|
+
}
|
|
10456
|
+
if (directTarget?.kind === "provider") {
|
|
10457
|
+
for (const id of await passthroughProviderModelIds(llmConfig, directTarget.providerId)) {
|
|
10458
|
+
push(id);
|
|
9958
10459
|
}
|
|
9959
10460
|
}
|
|
9960
10461
|
const limited = limit !== void 0 ? entries2.slice(0, limit) : entries2;
|
|
@@ -9996,29 +10497,172 @@ function handleOauthUsageProxy(res, config, apiKeyId) {
|
|
|
9996
10497
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
9997
10498
|
res.end(JSON.stringify(body));
|
|
9998
10499
|
}
|
|
9999
|
-
|
|
10500
|
+
function directUpstreamRoot(baseUrl, format) {
|
|
10501
|
+
const base = baseUrl.trim().replace(/\/+$/, "");
|
|
10502
|
+
if (!base) return null;
|
|
10503
|
+
if (format === "google") return base.replace(/\/models$/, "");
|
|
10504
|
+
if (format === "anthropic") return base.replace(/\/messages$/, "");
|
|
10505
|
+
if (format === "openai-response") return base.replace(/\/responses$/, "");
|
|
10506
|
+
return base.replace(/\/chat\/completions$/, "");
|
|
10507
|
+
}
|
|
10508
|
+
function directUpstreamUrl(provider, requestUrl) {
|
|
10509
|
+
const root = directUpstreamRoot(
|
|
10510
|
+
provider.api_base_url ?? "",
|
|
10511
|
+
resolveApiFormat(provider)
|
|
10512
|
+
);
|
|
10513
|
+
if (!root) return null;
|
|
10514
|
+
const raw = requestUrl ?? "/";
|
|
10515
|
+
const queryIndex = raw.indexOf("?");
|
|
10516
|
+
const path = (queryIndex >= 0 ? raw.slice(0, queryIndex) : raw) || "/";
|
|
10517
|
+
const query = queryIndex >= 0 ? raw.slice(queryIndex) : "";
|
|
10518
|
+
const versionSegment = /\/v\d+[a-z]*$/.exec(root)?.[0];
|
|
10519
|
+
let tail = path;
|
|
10520
|
+
if (versionSegment) {
|
|
10521
|
+
if (path === versionSegment) tail = "";
|
|
10522
|
+
else if (path.startsWith(`${versionSegment}/`)) tail = path.slice(versionSegment.length);
|
|
10523
|
+
}
|
|
10524
|
+
return `${root}${tail}${query}`;
|
|
10525
|
+
}
|
|
10526
|
+
function directUpstreamBinding(target, apiKeyId, priority = 1e3) {
|
|
10527
|
+
return {
|
|
10528
|
+
id: `direct:${apiKeyId}`,
|
|
10529
|
+
name: "direct upstream",
|
|
10530
|
+
enabled: true,
|
|
10531
|
+
keyScope: "selected",
|
|
10532
|
+
apiKeyIds: [apiKeyId],
|
|
10533
|
+
endpoint: "messages",
|
|
10534
|
+
target,
|
|
10535
|
+
priority,
|
|
10536
|
+
fallback: "fail",
|
|
10537
|
+
modelMode: "passthrough"
|
|
10538
|
+
};
|
|
10539
|
+
}
|
|
10540
|
+
async function relayDirectUpstream(req, res, deps, verified, config, concurrencyGate, audit, preReadBody) {
|
|
10541
|
+
const direct = verified.boundUpstream;
|
|
10542
|
+
const providerId = direct?.kind === "provider" ? direct.providerId : "";
|
|
10543
|
+
const provider = await deps.llmConfig.getProvider(providerId);
|
|
10544
|
+
if (!provider) {
|
|
10545
|
+
writeJsonError(
|
|
10546
|
+
res,
|
|
10547
|
+
503,
|
|
10548
|
+
`direct upstream provider '${providerId}' is not configured for this key`
|
|
10549
|
+
);
|
|
10550
|
+
return;
|
|
10551
|
+
}
|
|
10552
|
+
const url = directUpstreamUrl(provider, req.url);
|
|
10553
|
+
if (!url) {
|
|
10554
|
+
writeJsonError(res, 503, `direct upstream provider '${providerId}' has no base URL`);
|
|
10555
|
+
return;
|
|
10556
|
+
}
|
|
10557
|
+
const concurrencyLimit = verified.maxConcurrency;
|
|
10558
|
+
let releaseConcurrency = null;
|
|
10559
|
+
if (typeof concurrencyLimit === "number" && concurrencyLimit > 0) {
|
|
10560
|
+
const cq = config.concurrencyQueue ?? DEFAULT_CONCURRENCY_QUEUE;
|
|
10561
|
+
const acquisition = concurrencyGate.acquire(verified.id, concurrencyLimit, {
|
|
10562
|
+
maxQueueSizeFactor: cq.maxQueueSizeFactor,
|
|
10563
|
+
minQueueSize: cq.minQueueSize,
|
|
10564
|
+
waitTimeoutMs: cq.waitTimeoutMs
|
|
10565
|
+
});
|
|
10566
|
+
const cancelOnClose = () => acquisition.cancel();
|
|
10567
|
+
res.once("close", cancelOnClose);
|
|
10568
|
+
let slot;
|
|
10569
|
+
try {
|
|
10570
|
+
slot = await acquisition.granted;
|
|
10571
|
+
} catch (err) {
|
|
10572
|
+
res.removeListener("close", cancelOnClose);
|
|
10573
|
+
if (isConcurrencyRejection(err)) {
|
|
10574
|
+
writeJsonError(res, 429, "Concurrency limit exceeded", { "Retry-After": "5" });
|
|
10575
|
+
return;
|
|
10576
|
+
}
|
|
10577
|
+
throw err;
|
|
10578
|
+
}
|
|
10579
|
+
res.removeListener("close", cancelOnClose);
|
|
10580
|
+
const release2 = () => slot.release();
|
|
10581
|
+
releaseConcurrency = release2;
|
|
10582
|
+
res.once("close", release2);
|
|
10583
|
+
}
|
|
10584
|
+
try {
|
|
10585
|
+
const rawBody = preReadBody !== void 0 ? preReadBody : await readBody3(req);
|
|
10586
|
+
if (audit) {
|
|
10587
|
+
audit.setRequestBody(rawBody);
|
|
10588
|
+
try {
|
|
10589
|
+
audit.sessionKey = deriveAuditSessionKey(
|
|
10590
|
+
rawBody ? JSON.parse(rawBody) : {},
|
|
10591
|
+
req.headers,
|
|
10592
|
+
{ fallbackKey: verified.id, endpoint: "direct" }
|
|
10593
|
+
);
|
|
10594
|
+
} catch {
|
|
10595
|
+
}
|
|
10596
|
+
}
|
|
10597
|
+
const headers = getProviderHeaders(provider, resolveProviderKeyLiteral(provider.api_key));
|
|
10598
|
+
delete headers["Content-Type"];
|
|
10599
|
+
const clientContentType = req.headers["content-type"];
|
|
10600
|
+
if (typeof clientContentType === "string" && clientContentType) {
|
|
10601
|
+
headers["Content-Type"] = clientContentType;
|
|
10602
|
+
}
|
|
10603
|
+
const clientAccept = req.headers["accept"];
|
|
10604
|
+
if (typeof clientAccept === "string" && clientAccept) headers["Accept"] = clientAccept;
|
|
10605
|
+
const hasRequestBody = req.method !== "GET" && req.method !== "HEAD";
|
|
10606
|
+
const upstream = await fetchUpstream(
|
|
10607
|
+
url,
|
|
10608
|
+
{
|
|
10609
|
+
method: req.method ?? "GET",
|
|
10610
|
+
headers,
|
|
10611
|
+
...hasRequestBody ? { body: rawBody } : {},
|
|
10612
|
+
// Client hang-up aborts the upstream call (no egress after cancel).
|
|
10613
|
+
signal: requestLifecycleSignal(req, res)
|
|
10614
|
+
},
|
|
10615
|
+
{ providerId: "byo" }
|
|
10616
|
+
);
|
|
10617
|
+
if (audit) audit.provider = provider.id;
|
|
10618
|
+
const responseHeaders = {};
|
|
10619
|
+
const contentType = upstream.headers.get("content-type");
|
|
10620
|
+
if (contentType) responseHeaders["Content-Type"] = contentType;
|
|
10621
|
+
res.writeHead(upstream.status, responseHeaders);
|
|
10622
|
+
if (!upstream.body) {
|
|
10623
|
+
res.end();
|
|
10624
|
+
return;
|
|
10625
|
+
}
|
|
10626
|
+
const bodyStream = Readable2.fromWeb(
|
|
10627
|
+
upstream.body
|
|
10628
|
+
);
|
|
10629
|
+
bodyStream.on("error", () => {
|
|
10630
|
+
res.destroy();
|
|
10631
|
+
});
|
|
10632
|
+
bodyStream.pipe(res);
|
|
10633
|
+
await new Promise((resolve) => {
|
|
10634
|
+
res.once("close", resolve);
|
|
10635
|
+
});
|
|
10636
|
+
} catch (err) {
|
|
10637
|
+
const message = serializeError(err);
|
|
10638
|
+
if (deps.logger) deps.logger.error("[OutboundApi] direct relay error:", message);
|
|
10639
|
+
else console.error("[OutboundApi] direct relay error:", message);
|
|
10640
|
+
emitWebhookEvent({ kind: "server.error", at: Date.now(), message });
|
|
10641
|
+
if (audit) audit.error = message;
|
|
10642
|
+
if (!res.headersSent) writeJsonError(res, 502, message);
|
|
10643
|
+
else res.destroy();
|
|
10644
|
+
}
|
|
10645
|
+
}
|
|
10646
|
+
async function writeModelsList(res, llmConfig, config, apiKeyId, allowedEndpoints, imageModels = [], directTarget) {
|
|
10000
10647
|
const modelIds = [];
|
|
10001
|
-
for (const endpoint of
|
|
10648
|
+
for (const endpoint of MODEL_LIST_ENDPOINTS) {
|
|
10002
10649
|
if (allowedEndpoints && !allowedEndpoints.includes(endpoint)) continue;
|
|
10003
10650
|
for (const binding of candidateGatewayBindings(config.bindings, apiKeyId, endpoint)) {
|
|
10004
10651
|
if (binding.modelMode === "passthrough") {
|
|
10005
|
-
|
|
10006
|
-
const provider = await llmConfig.getProvider(binding.target.providerId);
|
|
10007
|
-
modelIds.push(...provider?.models ?? []);
|
|
10008
|
-
} else if (Object.prototype.hasOwnProperty.call(
|
|
10009
|
-
SUBSCRIPTION_MODEL_CATALOG,
|
|
10010
|
-
binding.target.providerId
|
|
10011
|
-
)) {
|
|
10012
|
-
modelIds.push(
|
|
10013
|
-
...SUBSCRIPTION_MODEL_CATALOG[binding.target.providerId]
|
|
10014
|
-
);
|
|
10015
|
-
}
|
|
10652
|
+
await pushTargetCatalog(llmConfig, binding.target, (id) => modelIds.push(id));
|
|
10016
10653
|
continue;
|
|
10017
10654
|
}
|
|
10018
10655
|
if (binding.modelMappings?.length) {
|
|
10019
10656
|
modelIds.push(
|
|
10020
10657
|
...binding.modelMappings.map((mapping) => mapping.source.trim()).filter((source) => source !== "" && !source.includes("*"))
|
|
10021
10658
|
);
|
|
10659
|
+
if (hasWildcardMapping(binding)) {
|
|
10660
|
+
await pushTargetCatalog(llmConfig, binding.target, (id) => modelIds.push(id));
|
|
10661
|
+
for (const mapping of binding.modelMappings ?? []) {
|
|
10662
|
+
if (!mapping.source.includes("*") || mapping.target.trim() === "") continue;
|
|
10663
|
+
modelIds.push(parseModelRef(mapping.target)?.modelId ?? mapping.target.trim());
|
|
10664
|
+
}
|
|
10665
|
+
}
|
|
10022
10666
|
continue;
|
|
10023
10667
|
}
|
|
10024
10668
|
const endpointConfig = gatewayBindingToEndpointConfig(binding);
|
|
@@ -10035,6 +10679,9 @@ async function writeModelsList(res, llmConfig, config, apiKeyId, allowedEndpoint
|
|
|
10035
10679
|
);
|
|
10036
10680
|
}
|
|
10037
10681
|
}
|
|
10682
|
+
if (directTarget?.kind === "provider") {
|
|
10683
|
+
modelIds.push(...await passthroughProviderModelIds(llmConfig, directTarget.providerId));
|
|
10684
|
+
}
|
|
10038
10685
|
modelIds.push(...imageModels);
|
|
10039
10686
|
const seen = /* @__PURE__ */ new Set();
|
|
10040
10687
|
const data = modelIds.filter((modelId) => {
|
|
@@ -10129,6 +10776,16 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
10129
10776
|
});
|
|
10130
10777
|
return;
|
|
10131
10778
|
}
|
|
10779
|
+
const directProvider = verified.boundUpstream?.kind === "provider" ? verified.boundUpstream : null;
|
|
10780
|
+
if (verified.boundUpstream && !directProvider) {
|
|
10781
|
+
config = {
|
|
10782
|
+
...config,
|
|
10783
|
+
bindings: [
|
|
10784
|
+
...config.bindings ?? [],
|
|
10785
|
+
directUpstreamBinding(verified.boundUpstream, verified.id, 1e3)
|
|
10786
|
+
]
|
|
10787
|
+
};
|
|
10788
|
+
}
|
|
10132
10789
|
if (isOauthUsagePath) {
|
|
10133
10790
|
if (req.method !== "GET") {
|
|
10134
10791
|
writeJsonError(res, 404, `Unsupported: ${req.method} ${req.url}`);
|
|
@@ -10192,7 +10849,8 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
10192
10849
|
config,
|
|
10193
10850
|
verified.id,
|
|
10194
10851
|
verified.allowedEndpoints,
|
|
10195
|
-
parseModelsLimit(req.url)
|
|
10852
|
+
parseModelsLimit(req.url),
|
|
10853
|
+
verified.boundUpstream
|
|
10196
10854
|
);
|
|
10197
10855
|
} else {
|
|
10198
10856
|
let imageModels = [];
|
|
@@ -10208,21 +10866,26 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
10208
10866
|
config,
|
|
10209
10867
|
verified.id,
|
|
10210
10868
|
verified.allowedEndpoints,
|
|
10211
|
-
imageModels
|
|
10869
|
+
imageModels,
|
|
10870
|
+
verified.boundUpstream
|
|
10212
10871
|
);
|
|
10213
10872
|
}
|
|
10214
10873
|
return;
|
|
10215
10874
|
}
|
|
10216
10875
|
const endpoint = selectEndpoint(req.method, req.url);
|
|
10217
10876
|
if (!endpoint) {
|
|
10877
|
+
if (directProvider) {
|
|
10878
|
+
await relayDirectUpstream(req, res, deps, verified, config, concurrencyGate, audit);
|
|
10879
|
+
return;
|
|
10880
|
+
}
|
|
10218
10881
|
writeJsonError(res, 404, `Unsupported: ${req.method} ${req.url}`);
|
|
10219
10882
|
return;
|
|
10220
10883
|
}
|
|
10221
|
-
if (verified.allowedEndpoints && !verified.allowedEndpoints.includes(endpoint)) {
|
|
10884
|
+
if (verified.allowedEndpoints && !verified.boundUpstream && !verified.allowedEndpoints.includes(endpoint)) {
|
|
10222
10885
|
writeJsonError(res, 403, "API key is not allowed to access this endpoint");
|
|
10223
10886
|
return;
|
|
10224
10887
|
}
|
|
10225
|
-
if (candidateGatewayBindings(config.bindings, verified.id, endpoint).length === 0) {
|
|
10888
|
+
if (candidateGatewayBindings(config.bindings, verified.id, endpoint).length === 0 && !directProvider) {
|
|
10226
10889
|
writeJsonError(res, 503, `endpoint '${endpoint}' has no downstream route for this key`);
|
|
10227
10890
|
return;
|
|
10228
10891
|
}
|
|
@@ -10316,22 +10979,31 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
10316
10979
|
role
|
|
10317
10980
|
});
|
|
10318
10981
|
if (bindingResolution.source === "none") {
|
|
10982
|
+
if (directProvider) {
|
|
10983
|
+
await relayDirectUpstream(req, res, deps, verified, config, concurrencyGate, audit, rawBody);
|
|
10984
|
+
return;
|
|
10985
|
+
}
|
|
10319
10986
|
writeJsonError(res, 503, `endpoint '${endpoint}' has no downstream route for this key`);
|
|
10320
10987
|
return;
|
|
10321
10988
|
}
|
|
10322
10989
|
const effectiveEndpointConfig = bindingResolution.config;
|
|
10323
10990
|
const bindingAffinitySuffix = `:${bindingResolution.binding.id}`;
|
|
10324
10991
|
const sessionId = deps.proxyDeps.apiKeyPool ? `outbound:${verified.id}${bindingAffinitySuffix}` : null;
|
|
10992
|
+
let replayBody = rawBody;
|
|
10993
|
+
if (!isCountTokens && effectiveEndpointConfig.reasoningEffort && injectMappingEffortDefault(endpoint, effectiveEndpointConfig.reasoningEffort, parsedBody)) {
|
|
10994
|
+
replayBody = JSON.stringify(parsedBody);
|
|
10995
|
+
}
|
|
10325
10996
|
const hostedImageGenerationAllowed = verified.allowedEndpoints.includes("images");
|
|
10997
|
+
const listResolvedModel = endpoint === "chat" && bindingResolution.binding.modelMode === "mapped" ? resolveGatewayModelMappingRow(bindingResolution.binding.modelMappings, requestedModel)?.target ?? requestedModel : requestedModel;
|
|
10326
10998
|
const resolved = isKindMappedEndpoint(endpoint) || endpoint === "chat" ? await resolveRoute({
|
|
10327
10999
|
config: effectiveEndpointConfig,
|
|
10328
11000
|
ingressFormat,
|
|
10329
11001
|
llmConfig: deps.llmConfig,
|
|
10330
11002
|
sessionId,
|
|
10331
|
-
//
|
|
10332
|
-
// kind-mapped endpoints it selects the kind AND is stamped onto
|
|
10333
|
-
// `route.requestedModel
|
|
10334
|
-
requestedModel,
|
|
11003
|
+
// The id the route resolves against: the client's ORIGINAL id for
|
|
11004
|
+
// kind-mapped endpoints (it selects the kind AND is stamped onto
|
|
11005
|
+
// `route.requestedModel`), the mapping-resolved target for chat.
|
|
11006
|
+
requestedModel: listResolvedModel,
|
|
10335
11007
|
// Attribution: stamp the verified named-key id onto the route.
|
|
10336
11008
|
apiKeyId: verified.id,
|
|
10337
11009
|
hostedImageGenerationAllowed
|
|
@@ -10346,6 +11018,10 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
10346
11018
|
hostedImageGenerationAllowed
|
|
10347
11019
|
});
|
|
10348
11020
|
if (!resolved.ok) {
|
|
11021
|
+
if (directProvider) {
|
|
11022
|
+
await relayDirectUpstream(req, res, deps, verified, config, concurrencyGate, audit, rawBody);
|
|
11023
|
+
return;
|
|
11024
|
+
}
|
|
10349
11025
|
writeJsonError(res, resolved.error.status, resolved.error.message);
|
|
10350
11026
|
return;
|
|
10351
11027
|
}
|
|
@@ -10407,7 +11083,7 @@ async function handleOutboundRequest(req, res, deps, config, rateLimiter, serial
|
|
|
10407
11083
|
);
|
|
10408
11084
|
try {
|
|
10409
11085
|
shimAuthHeader(req, token);
|
|
10410
|
-
const replay = makeReplayRequest(req,
|
|
11086
|
+
const replay = makeReplayRequest(req, replayBody);
|
|
10411
11087
|
await routeRequest(replay, res, routeMap, deps.proxyDeps);
|
|
10412
11088
|
} catch (err) {
|
|
10413
11089
|
if (isBoundAccountSelectionError(err)) {
|
|
@@ -13105,9 +13781,13 @@ export {
|
|
|
13105
13781
|
projectUpstreamResponseHeaders,
|
|
13106
13782
|
relayResponse,
|
|
13107
13783
|
aggregateAnthropicSseToJsonBody,
|
|
13784
|
+
aggregateResponsesSseToJsonBody,
|
|
13785
|
+
aggregateOpenAIChatChunksToJsonBody,
|
|
13786
|
+
sniffIsSseResponse,
|
|
13108
13787
|
writeError,
|
|
13109
13788
|
writeBoundAccountError,
|
|
13110
13789
|
resolvePoolBoundKey,
|
|
13790
|
+
buildByoRouteActivityMeta,
|
|
13111
13791
|
DEFAULT_ROUTE_IDLE_MS,
|
|
13112
13792
|
ProviderProxyRouteMap,
|
|
13113
13793
|
previousResponseNotFound,
|
|
@@ -13187,6 +13867,7 @@ export {
|
|
|
13187
13867
|
loadServerConfig,
|
|
13188
13868
|
saveServerConfig,
|
|
13189
13869
|
mergeServerConfig,
|
|
13870
|
+
injectMappingEffortDefault,
|
|
13190
13871
|
computeKeyExpiry,
|
|
13191
13872
|
checkKeyQuota,
|
|
13192
13873
|
randomBase62,
|