omnigateway 0.2.0 → 0.2.1
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/bin/omni.js +527 -228
- package/gateway.js +763 -401
- package/package.json +1 -1
- package/public/assets/{Chip-BB_5C1Zp.js → Chip-C4X8tf5z.js} +1 -1
- package/public/assets/Confirm-BnmH8Gn0.js +4 -0
- package/public/assets/{CopyValue-CRQDLo7k.js → CopyValue-B-uR4Js-.js} +5 -5
- package/public/assets/{Field-uHZxl4fI.js → Field-Ct4SeDZD.js} +2 -2
- package/public/assets/{Lamp-B-5SjXbG.js → Lamp-CBOwqG5K.js} +7 -7
- package/public/assets/{Meter-DI_BRUKt.js → Meter-COdz0dwY.js} +1 -1
- package/public/assets/{Modal-CI6jk2D4.js → Modal-DMFUHQ-A.js} +8 -8
- package/public/assets/{Rack-D1WJswv3.js → Rack-DAbY9x1I.js} +18 -18
- package/public/assets/{Readout-BocZ2HXP.js → Readout-Bl2kD9Tl.js} +6 -2
- package/public/assets/{States-Bbiu5cHE.js → States-D5Memn8D.js} +4 -4
- package/public/assets/{Table-CdPWxYaz.js → Table-DObRatbW.js} +1 -1
- package/public/assets/{Toggle-CiLC67Dw.js → Toggle-Dkjlvck5.js} +1 -1
- package/public/assets/{TokenBreakdown-B96iPBm9.js → TokenBreakdown-oXP49ZTa.js} +5 -2
- package/public/assets/_app-Brc2wQZm.js +1 -0
- package/public/assets/_app.accounts-I9RXUiyi.js +54 -0
- package/public/assets/{_app.console-Daz4aKhf.js → _app.console-BVVpryIB.js} +10 -10
- package/public/assets/_app.index-83Ig4FUL.js +62 -0
- package/public/assets/_app.keys-KeP6L4g_.js +39 -0
- package/public/assets/_app.logs-B5oh01Ls.js +32 -0
- package/public/assets/_app.models-BuOFMZUN.js +144 -0
- package/public/assets/{_app.settings-a0FKyQAi.js → _app.settings-BN436vI2.js} +4 -4
- package/public/assets/_app.usage-C528VOia.js +166 -0
- package/public/assets/catalog-kJ53n_fc.js +1 -0
- package/public/assets/{dist-C-IbPRiV.js → dist-CnzT-Ut2.js} +1 -1
- package/public/assets/index-CE-KQ-ju.js +172 -0
- package/public/assets/{login-CTvH_KAd.js → login-2LpCZOtB.js} +8 -8
- package/public/assets/{queries-D2o-X8Pj.js → queries-1zRLkX-Q.js} +27 -27
- package/public/assets/{trash-2-BcZb-sCT.js → trash-2-Da6cLgPn.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/Confirm-B6aAiVbT.js +0 -4
- package/public/assets/_app-BBOF6A0T.js +0 -1
- package/public/assets/_app.accounts-BNkhpvaB.js +0 -54
- package/public/assets/_app.index-LO6d38oe.js +0 -62
- package/public/assets/_app.keys-kBqLqoFf.js +0 -39
- package/public/assets/_app.logs-_wYNS47N.js +0 -32
- package/public/assets/_app.models-Cjng8ohC.js +0 -144
- package/public/assets/_app.usage-D3KtgLrC.js +0 -166
- package/public/assets/catalog-C_OQ0icG.js +0 -1
- package/public/assets/index-PW6EvVh5.js +0 -170
package/gateway.js
CHANGED
|
@@ -5299,12 +5299,14 @@ function createAdminAuth(store, opts) {
|
|
|
5299
5299
|
var ANTHROPIC_NATIVE_TOOLS = {
|
|
5300
5300
|
anthropic: true,
|
|
5301
5301
|
openai: false,
|
|
5302
|
-
kimi: false
|
|
5302
|
+
kimi: false,
|
|
5303
|
+
custom: false
|
|
5303
5304
|
};
|
|
5304
5305
|
var PROVIDER_CAPABILITIES = {
|
|
5305
5306
|
anthropic: { tools: true, images: true, reasoning: true },
|
|
5306
5307
|
openai: { tools: true, images: true, reasoning: true },
|
|
5307
|
-
kimi: { tools: true, images: false, reasoning: false }
|
|
5308
|
+
kimi: { tools: true, images: false, reasoning: false },
|
|
5309
|
+
custom: { tools: true, images: true, reasoning: true }
|
|
5308
5310
|
};
|
|
5309
5311
|
// packages/ir/src/errors.ts
|
|
5310
5312
|
var RETRYABLE = {
|
|
@@ -5318,6 +5320,7 @@ var RETRYABLE = {
|
|
|
5318
5320
|
TIMEOUT: true,
|
|
5319
5321
|
NETWORK: true,
|
|
5320
5322
|
BAD_REQUEST: false,
|
|
5323
|
+
CONFLICT: false,
|
|
5321
5324
|
CONTENT_FILTER: false,
|
|
5322
5325
|
NO_CANDIDATES: false,
|
|
5323
5326
|
ALL_CANDIDATES_FAILED: false,
|
|
@@ -5329,6 +5332,7 @@ var HTTP_STATUS = {
|
|
|
5329
5332
|
QUOTA_EXHAUSTED: 429,
|
|
5330
5333
|
OVERLOADED: 503,
|
|
5331
5334
|
BAD_REQUEST: 400,
|
|
5335
|
+
CONFLICT: 409,
|
|
5332
5336
|
CONTENT_FILTER: 400,
|
|
5333
5337
|
CAPABILITY_MISMATCH: 400,
|
|
5334
5338
|
MODEL_UNAVAILABLE: 404,
|
|
@@ -5627,6 +5631,29 @@ function estimateInputTokens(request) {
|
|
|
5627
5631
|
total += toolTokens(tool);
|
|
5628
5632
|
return total;
|
|
5629
5633
|
}
|
|
5634
|
+
function estimateCachedInputTokens(request) {
|
|
5635
|
+
let running = 0;
|
|
5636
|
+
let cached = 0;
|
|
5637
|
+
for (const tool of request.tools ?? []) {
|
|
5638
|
+
running += toolTokens(tool);
|
|
5639
|
+
if (tool.cacheControl !== undefined)
|
|
5640
|
+
cached = running;
|
|
5641
|
+
}
|
|
5642
|
+
for (const block of request.system ?? []) {
|
|
5643
|
+
running += blockTokens(block);
|
|
5644
|
+
if (cacheControlOf(block) !== undefined)
|
|
5645
|
+
cached = running;
|
|
5646
|
+
}
|
|
5647
|
+
for (const message of request.messages) {
|
|
5648
|
+
running += MESSAGE_OVERHEAD;
|
|
5649
|
+
for (const block of message.content) {
|
|
5650
|
+
running += blockTokens(block);
|
|
5651
|
+
if (cacheControlOf(block) !== undefined)
|
|
5652
|
+
cached = running;
|
|
5653
|
+
}
|
|
5654
|
+
}
|
|
5655
|
+
return cached;
|
|
5656
|
+
}
|
|
5630
5657
|
// packages/ir/src/validate.ts
|
|
5631
5658
|
function validateRequest(req) {
|
|
5632
5659
|
const seenToolUseIds = new Set;
|
|
@@ -5729,7 +5756,8 @@ var BODY_ORDER = {
|
|
|
5729
5756
|
"parallel_tool_calls",
|
|
5730
5757
|
"metadata"
|
|
5731
5758
|
],
|
|
5732
|
-
kimi: ["model", "messages", "tools", "tool_choice", "max_tokens", "temperature", "stream"]
|
|
5759
|
+
kimi: ["model", "messages", "tools", "tool_choice", "max_tokens", "temperature", "stream"],
|
|
5760
|
+
custom: []
|
|
5733
5761
|
};
|
|
5734
5762
|
function orderFields(obj, order) {
|
|
5735
5763
|
const out = {};
|
|
@@ -5830,10 +5858,20 @@ var ANTHROPIC_MODELS = {
|
|
|
5830
5858
|
id: "claude-haiku-4-5",
|
|
5831
5859
|
label: "Claude Haiku 4.5",
|
|
5832
5860
|
pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
|
|
5833
|
-
limits: { contextWindow: 200000, maxOutputTokens: 64000 }
|
|
5861
|
+
limits: { contextWindow: 200000, maxOutputTokens: 64000 },
|
|
5862
|
+
reasoningForm: "budget"
|
|
5834
5863
|
}
|
|
5835
5864
|
]
|
|
5836
5865
|
};
|
|
5866
|
+
var ONE_M_SUFFIX = "[1m]";
|
|
5867
|
+
var DATED_SUFFIX = /-\d{8}$/;
|
|
5868
|
+
function anthropicReasoningForm(model) {
|
|
5869
|
+
let id = model.trim();
|
|
5870
|
+
if (id.toLowerCase().endsWith(ONE_M_SUFFIX))
|
|
5871
|
+
id = id.slice(0, -ONE_M_SUFFIX.length).trim();
|
|
5872
|
+
id = id.replace(DATED_SUFFIX, "");
|
|
5873
|
+
return ANTHROPIC_MODELS.models.find((m) => m.id === id)?.reasoningForm ?? "adaptive";
|
|
5874
|
+
}
|
|
5837
5875
|
|
|
5838
5876
|
// packages/providers/src/kimi/models.ts
|
|
5839
5877
|
var KIMI_MODELS = {
|
|
@@ -5905,7 +5943,8 @@ var OPENAI_MODELS = {
|
|
|
5905
5943
|
var PROVIDER_MODEL_CATALOG = {
|
|
5906
5944
|
anthropic: ANTHROPIC_MODELS,
|
|
5907
5945
|
openai: OPENAI_MODELS,
|
|
5908
|
-
kimi: KIMI_MODELS
|
|
5946
|
+
kimi: KIMI_MODELS,
|
|
5947
|
+
custom: { defaultModel: "", models: [] }
|
|
5909
5948
|
};
|
|
5910
5949
|
function catalogPricing(provider, model) {
|
|
5911
5950
|
return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
|
|
@@ -6118,7 +6157,8 @@ var kimi = {
|
|
|
6118
6157
|
var PROFILES = {
|
|
6119
6158
|
anthropic: { ...anthropic, order: envOrder("OMNI_ORDER_ANTHROPIC", anthropic.order) },
|
|
6120
6159
|
openai: { ...openai, order: envOrder("OMNI_ORDER_OPENAI", openai.order) },
|
|
6121
|
-
kimi: { ...kimi, order: envOrder("OMNI_ORDER_KIMI", kimi.order) }
|
|
6160
|
+
kimi: { ...kimi, order: envOrder("OMNI_ORDER_KIMI", kimi.order) },
|
|
6161
|
+
custom: { headers: [], order: [] }
|
|
6122
6162
|
};
|
|
6123
6163
|
|
|
6124
6164
|
// packages/providers/src/sse.ts
|
|
@@ -6696,6 +6736,45 @@ function encodeToolChoice(c) {
|
|
|
6696
6736
|
return { type: "tool", name: c.name };
|
|
6697
6737
|
}
|
|
6698
6738
|
}
|
|
6739
|
+
function isRecord(value) {
|
|
6740
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6741
|
+
}
|
|
6742
|
+
function withoutEffort(vendor, note) {
|
|
6743
|
+
const config = vendor.output_config;
|
|
6744
|
+
if (!isRecord(config))
|
|
6745
|
+
return vendor;
|
|
6746
|
+
const entries = Object.entries(config);
|
|
6747
|
+
const kept = entries.filter(([key]) => key !== "effort");
|
|
6748
|
+
if (kept.length === entries.length)
|
|
6749
|
+
return vendor;
|
|
6750
|
+
note("anthropic:effort-unsupported");
|
|
6751
|
+
if (kept.length === 0) {
|
|
6752
|
+
return Object.fromEntries(Object.entries(vendor).filter(([key]) => key !== "output_config"));
|
|
6753
|
+
}
|
|
6754
|
+
return { ...vendor, output_config: Object.fromEntries(kept) };
|
|
6755
|
+
}
|
|
6756
|
+
var THINKING_ONLY_EDITS = new Set(["clear_thinking_20251015"]);
|
|
6757
|
+
function thinkingIsOff(thinking) {
|
|
6758
|
+
if (!isRecord(thinking))
|
|
6759
|
+
return thinking !== undefined;
|
|
6760
|
+
return thinking.type !== "adaptive" && thinking.type !== "enabled";
|
|
6761
|
+
}
|
|
6762
|
+
function stripUnsupportedEdits(body, note) {
|
|
6763
|
+
if (!thinkingIsOff(body.thinking))
|
|
6764
|
+
return;
|
|
6765
|
+
const config = body.context_management;
|
|
6766
|
+
if (!isRecord(config) || !Array.isArray(config.edits))
|
|
6767
|
+
return;
|
|
6768
|
+
const kept = config.edits.filter((e) => !(isRecord(e) && THINKING_ONLY_EDITS.has(String(e.type))));
|
|
6769
|
+
if (kept.length === config.edits.length)
|
|
6770
|
+
return;
|
|
6771
|
+
note("anthropic:clear-thinking-unsupported");
|
|
6772
|
+
if (kept.length === 0 && Object.keys(config).length === 1) {
|
|
6773
|
+
delete body.context_management;
|
|
6774
|
+
return;
|
|
6775
|
+
}
|
|
6776
|
+
body.context_management = { ...config, edits: kept };
|
|
6777
|
+
}
|
|
6699
6778
|
function toWire(req, model, opts) {
|
|
6700
6779
|
const degradations = [];
|
|
6701
6780
|
const note = (d) => {
|
|
@@ -6745,12 +6824,20 @@ function toWire(req, model, opts) {
|
|
|
6745
6824
|
if (req.reasoning !== undefined) {
|
|
6746
6825
|
switch (req.reasoning.mode) {
|
|
6747
6826
|
case "adaptive":
|
|
6827
|
+
if (anthropicReasoningForm(model) === "budget") {
|
|
6828
|
+
body.thinking = { type: "disabled" };
|
|
6829
|
+
note("anthropic:adaptive-thinking-unsupported");
|
|
6830
|
+
break;
|
|
6831
|
+
}
|
|
6748
6832
|
body.thinking = {
|
|
6749
6833
|
type: "adaptive",
|
|
6750
6834
|
...req.reasoning.display === undefined ? {} : { display: req.reasoning.display }
|
|
6751
6835
|
};
|
|
6752
6836
|
if (req.reasoning.effort !== undefined) {
|
|
6753
|
-
body.output_config = {
|
|
6837
|
+
body.output_config = {
|
|
6838
|
+
...isRecord(body.output_config) ? body.output_config : {},
|
|
6839
|
+
effort: req.reasoning.effort
|
|
6840
|
+
};
|
|
6754
6841
|
}
|
|
6755
6842
|
break;
|
|
6756
6843
|
case "budget":
|
|
@@ -6761,7 +6848,9 @@ function toWire(req, model, opts) {
|
|
|
6761
6848
|
break;
|
|
6762
6849
|
}
|
|
6763
6850
|
}
|
|
6764
|
-
|
|
6851
|
+
const vendor = req.vendor?.anthropic ?? {};
|
|
6852
|
+
Object.assign(body, anthropicReasoningForm(model) === "budget" ? withoutEffort(vendor, note) : vendor);
|
|
6853
|
+
stripUnsupportedEdits(body, note);
|
|
6765
6854
|
return { body, degradations };
|
|
6766
6855
|
}
|
|
6767
6856
|
|
|
@@ -6825,115 +6914,6 @@ var anthropicAdapter = {
|
|
|
6825
6914
|
return { events: decodeAnthropic(parseSse(res.body)), degradations: notes };
|
|
6826
6915
|
}
|
|
6827
6916
|
};
|
|
6828
|
-
// packages/providers/src/http-client.ts
|
|
6829
|
-
import { request as httpRequest } from "http";
|
|
6830
|
-
import { request as httpsRequest } from "https";
|
|
6831
|
-
import { Readable } from "stream";
|
|
6832
|
-
function nodeHttpClient(options = {}) {
|
|
6833
|
-
const logger2 = options.logger ?? noopLogger;
|
|
6834
|
-
const now = options.now ?? (() => Date.now());
|
|
6835
|
-
return (req) => new Promise((resolve, reject) => {
|
|
6836
|
-
const url = new URL(req.url);
|
|
6837
|
-
const startedAt = now();
|
|
6838
|
-
let traced = false;
|
|
6839
|
-
const trace = (status, failed = false) => {
|
|
6840
|
-
if (traced || !logger2.enabled("debug"))
|
|
6841
|
-
return;
|
|
6842
|
-
traced = true;
|
|
6843
|
-
logger2.debug("upstream http", {
|
|
6844
|
-
provider: req.provider,
|
|
6845
|
-
status,
|
|
6846
|
-
host: url.host,
|
|
6847
|
-
path: url.pathname,
|
|
6848
|
-
durationMs: now() - startedAt,
|
|
6849
|
-
reason: failed ? "transport error" : undefined
|
|
6850
|
-
});
|
|
6851
|
-
};
|
|
6852
|
-
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
6853
|
-
const bodyBytes = Buffer.from(req.body, "utf8");
|
|
6854
|
-
const headers = {};
|
|
6855
|
-
for (const [name, value] of req.headers)
|
|
6856
|
-
headers[name] = value;
|
|
6857
|
-
if (req.body.length > 0 && !hasHeader(req, "content-length")) {
|
|
6858
|
-
headers["Content-Length"] = bodyBytes.byteLength;
|
|
6859
|
-
}
|
|
6860
|
-
const outgoing = send({
|
|
6861
|
-
protocol: url.protocol,
|
|
6862
|
-
hostname: url.hostname,
|
|
6863
|
-
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
6864
|
-
path: `${url.pathname}${url.search}`,
|
|
6865
|
-
method: req.method,
|
|
6866
|
-
headers,
|
|
6867
|
-
setHost: !hasHeader(req, "host")
|
|
6868
|
-
}, (incoming) => {
|
|
6869
|
-
const chunks = [];
|
|
6870
|
-
let buffered = null;
|
|
6871
|
-
const responseHeaders = new Headers;
|
|
6872
|
-
for (const [k, v] of Object.entries(incoming.headers)) {
|
|
6873
|
-
if (Array.isArray(v))
|
|
6874
|
-
for (const one of v)
|
|
6875
|
-
responseHeaders.append(k, one);
|
|
6876
|
-
else if (typeof v === "string")
|
|
6877
|
-
responseHeaders.set(k, v);
|
|
6878
|
-
}
|
|
6879
|
-
trace(incoming.statusCode);
|
|
6880
|
-
resolve({
|
|
6881
|
-
status: incoming.statusCode ?? 0,
|
|
6882
|
-
headers: responseHeaders,
|
|
6883
|
-
body: Readable.toWeb(incoming),
|
|
6884
|
-
text: () => {
|
|
6885
|
-
buffered ??= new Promise((res, rej) => {
|
|
6886
|
-
incoming.on("data", (c) => chunks.push(c));
|
|
6887
|
-
incoming.on("end", () => res(Buffer.concat(chunks).toString("utf8")));
|
|
6888
|
-
incoming.on("error", rej);
|
|
6889
|
-
});
|
|
6890
|
-
return buffered;
|
|
6891
|
-
}
|
|
6892
|
-
});
|
|
6893
|
-
});
|
|
6894
|
-
const onAbort = () => outgoing.destroy(new Error("aborted"));
|
|
6895
|
-
outgoing.on("error", (err) => {
|
|
6896
|
-
req.signal.removeEventListener("abort", onAbort);
|
|
6897
|
-
trace(undefined, true);
|
|
6898
|
-
reject(err);
|
|
6899
|
-
});
|
|
6900
|
-
outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
|
|
6901
|
-
if (req.signal.aborted) {
|
|
6902
|
-
outgoing.destroy(new Error("aborted"));
|
|
6903
|
-
return;
|
|
6904
|
-
}
|
|
6905
|
-
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
6906
|
-
if (bodyBytes.byteLength > 0)
|
|
6907
|
-
outgoing.write(bodyBytes);
|
|
6908
|
-
outgoing.end();
|
|
6909
|
-
});
|
|
6910
|
-
}
|
|
6911
|
-
function hasHeader(req, lowerName) {
|
|
6912
|
-
return req.headers.some(([name]) => name.toLowerCase() === lowerName);
|
|
6913
|
-
}
|
|
6914
|
-
// packages/providers/src/kimi-device.ts
|
|
6915
|
-
import { randomUUID } from "crypto";
|
|
6916
|
-
function mintKimiDevice() {
|
|
6917
|
-
return {
|
|
6918
|
-
deviceId: randomUUID(),
|
|
6919
|
-
deviceName: "MacBook-Pro",
|
|
6920
|
-
deviceModel: "MacBookPro18,3",
|
|
6921
|
-
osVersion: "15.3.1"
|
|
6922
|
-
};
|
|
6923
|
-
}
|
|
6924
|
-
function kimiDeviceHeaders(providerData) {
|
|
6925
|
-
const deviceId = providerData.deviceId;
|
|
6926
|
-
if (typeof deviceId !== "string" || deviceId.length === 0)
|
|
6927
|
-
return [];
|
|
6928
|
-
const str = (v) => typeof v === "string" && v.length > 0 ? v : "unknown";
|
|
6929
|
-
return [
|
|
6930
|
-
["X-Msh-Device-Id", deviceId],
|
|
6931
|
-
["X-Msh-Device-Name", str(providerData.deviceName)],
|
|
6932
|
-
["X-Msh-Device-Model", str(providerData.deviceModel)],
|
|
6933
|
-
["X-Msh-Os-Version", str(providerData.osVersion)]
|
|
6934
|
-
];
|
|
6935
|
-
}
|
|
6936
|
-
|
|
6937
6917
|
// packages/providers/src/kimi/decode.ts
|
|
6938
6918
|
var FINISH = {
|
|
6939
6919
|
stop: "endTurn",
|
|
@@ -7044,7 +7024,7 @@ function encodeToolChoice2(c) {
|
|
|
7044
7024
|
return { type: "function", function: { name: c.name } };
|
|
7045
7025
|
}
|
|
7046
7026
|
}
|
|
7047
|
-
function toChatWire(req, model) {
|
|
7027
|
+
function toChatWire(req, model, vendor = "kimi") {
|
|
7048
7028
|
const degradations = [];
|
|
7049
7029
|
const note = (d) => {
|
|
7050
7030
|
if (!degradations.includes(d))
|
|
@@ -7128,44 +7108,10 @@ function toChatWire(req, model) {
|
|
|
7128
7108
|
body.tool_choice = encodeToolChoice2(req.toolChoice);
|
|
7129
7109
|
if (req.reasoning !== undefined)
|
|
7130
7110
|
note("kimi:reasoning-dropped");
|
|
7131
|
-
Object.assign(body, req.vendor?.
|
|
7111
|
+
Object.assign(body, req.vendor?.[vendor] ?? {});
|
|
7132
7112
|
return { body, degradations };
|
|
7133
7113
|
}
|
|
7134
7114
|
|
|
7135
|
-
// packages/providers/src/kimi/index.ts
|
|
7136
|
-
var BASE_URL2 = "https://api.kimi.com/coding/v1/chat/completions";
|
|
7137
|
-
var kimiAdapter = {
|
|
7138
|
-
id: "kimi",
|
|
7139
|
-
capabilities: PROVIDER_CAPABILITIES.kimi,
|
|
7140
|
-
async send(req) {
|
|
7141
|
-
const { body, degradations } = toChatWire(req.request, req.model);
|
|
7142
|
-
const token = req.credentials.accessToken ?? req.credentials.apiKey;
|
|
7143
|
-
if (token === null) {
|
|
7144
|
-
throw new GatewayError("AUTH", "kimi credential has no token", { provider: "kimi" });
|
|
7145
|
-
}
|
|
7146
|
-
const protocol = [
|
|
7147
|
-
["Content-Type", "application/json"],
|
|
7148
|
-
["Accept", "text/event-stream"],
|
|
7149
|
-
["Authorization", `Bearer ${token}`],
|
|
7150
|
-
...kimiDeviceHeaders(req.credentials.providerData)
|
|
7151
|
-
];
|
|
7152
|
-
const profile = PROFILES.kimi;
|
|
7153
|
-
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
7154
|
-
const res = await req.http({
|
|
7155
|
-
provider: "kimi",
|
|
7156
|
-
url: BASE_URL2,
|
|
7157
|
-
method: "POST",
|
|
7158
|
-
headers,
|
|
7159
|
-
body: JSON.stringify(orderFields({ ...body, stream: true }, BODY_ORDER.kimi)),
|
|
7160
|
-
signal: req.signal
|
|
7161
|
-
});
|
|
7162
|
-
if (res.status < 200 || res.status >= 300)
|
|
7163
|
-
throw await httpError(res, "kimi");
|
|
7164
|
-
if (res.body === null)
|
|
7165
|
-
throw new GatewayError("UPSTREAM", "empty response body", { provider: "kimi" });
|
|
7166
|
-
return { events: decodeChat(parseSse(res.body)), degradations };
|
|
7167
|
-
}
|
|
7168
|
-
};
|
|
7169
7115
|
// packages/providers/src/openai/decode.ts
|
|
7170
7116
|
var ERROR_CODE = {
|
|
7171
7117
|
rate_limit_exceeded: "RATE_LIMIT",
|
|
@@ -7441,6 +7387,190 @@ ${block.text}
|
|
|
7441
7387
|
return { body, degradations };
|
|
7442
7388
|
}
|
|
7443
7389
|
|
|
7390
|
+
// packages/providers/src/custom/index.ts
|
|
7391
|
+
function metadata(data) {
|
|
7392
|
+
const { origin, protocol } = data;
|
|
7393
|
+
if (typeof origin !== "string" || protocol !== "chat_completions" && protocol !== "responses") {
|
|
7394
|
+
throw new GatewayError("BAD_REQUEST", "custom credential has invalid endpoint metadata");
|
|
7395
|
+
}
|
|
7396
|
+
return { origin, protocol };
|
|
7397
|
+
}
|
|
7398
|
+
var customAdapter = {
|
|
7399
|
+
id: "custom",
|
|
7400
|
+
capabilities: PROVIDER_CAPABILITIES.custom,
|
|
7401
|
+
async send(req) {
|
|
7402
|
+
const apiKey = req.credentials.apiKey;
|
|
7403
|
+
if (apiKey === null) {
|
|
7404
|
+
throw new GatewayError("AUTH", "custom credential has no API key", { provider: "custom" });
|
|
7405
|
+
}
|
|
7406
|
+
const { origin, protocol } = metadata(req.credentials.providerData);
|
|
7407
|
+
const encoded = protocol === "chat_completions" ? toChatWire(req.request, req.model, "openai") : toResponsesWire(req.request, req.model);
|
|
7408
|
+
const headers = [
|
|
7409
|
+
["Content-Type", "application/json"],
|
|
7410
|
+
["Authorization", `Bearer ${apiKey}`]
|
|
7411
|
+
];
|
|
7412
|
+
const res = await req.http({
|
|
7413
|
+
provider: "custom",
|
|
7414
|
+
url: `${origin}/v1/${protocol === "chat_completions" ? "chat/completions" : "responses"}`,
|
|
7415
|
+
method: "POST",
|
|
7416
|
+
headers,
|
|
7417
|
+
body: JSON.stringify({ ...encoded.body, stream: true }),
|
|
7418
|
+
signal: req.signal
|
|
7419
|
+
});
|
|
7420
|
+
if (res.status < 200 || res.status >= 300)
|
|
7421
|
+
throw await httpError(res, "custom");
|
|
7422
|
+
if (res.body === null) {
|
|
7423
|
+
throw new GatewayError("UPSTREAM", "empty response body", { provider: "custom" });
|
|
7424
|
+
}
|
|
7425
|
+
return {
|
|
7426
|
+
events: protocol === "chat_completions" ? decodeChat(parseSse(res.body)) : decodeResponses(parseSse(res.body)),
|
|
7427
|
+
degradations: encoded.degradations.map((value) => value.replace(protocol === "chat_completions" ? /^kimi:/ : /^openai:/, "custom:"))
|
|
7428
|
+
};
|
|
7429
|
+
}
|
|
7430
|
+
};
|
|
7431
|
+
// packages/providers/src/http-client.ts
|
|
7432
|
+
import { request as httpRequest } from "http";
|
|
7433
|
+
import { request as httpsRequest } from "https";
|
|
7434
|
+
import { Readable } from "stream";
|
|
7435
|
+
function nodeHttpClient(options = {}) {
|
|
7436
|
+
const logger2 = options.logger ?? noopLogger;
|
|
7437
|
+
const now = options.now ?? (() => Date.now());
|
|
7438
|
+
return (req) => new Promise((resolve, reject) => {
|
|
7439
|
+
const url = new URL(req.url);
|
|
7440
|
+
const startedAt = now();
|
|
7441
|
+
let traced = false;
|
|
7442
|
+
const trace = (status, failed = false) => {
|
|
7443
|
+
if (traced || !logger2.enabled("debug"))
|
|
7444
|
+
return;
|
|
7445
|
+
traced = true;
|
|
7446
|
+
logger2.debug("upstream http", {
|
|
7447
|
+
provider: req.provider,
|
|
7448
|
+
status,
|
|
7449
|
+
host: url.host,
|
|
7450
|
+
path: url.pathname,
|
|
7451
|
+
durationMs: now() - startedAt,
|
|
7452
|
+
reason: failed ? "transport error" : undefined
|
|
7453
|
+
});
|
|
7454
|
+
};
|
|
7455
|
+
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
7456
|
+
const bodyBytes = Buffer.from(req.body, "utf8");
|
|
7457
|
+
const headers = {};
|
|
7458
|
+
for (const [name, value] of req.headers)
|
|
7459
|
+
headers[name] = value;
|
|
7460
|
+
if (req.body.length > 0 && !hasHeader(req, "content-length")) {
|
|
7461
|
+
headers["Content-Length"] = bodyBytes.byteLength;
|
|
7462
|
+
}
|
|
7463
|
+
const outgoing = send({
|
|
7464
|
+
protocol: url.protocol,
|
|
7465
|
+
hostname: url.hostname,
|
|
7466
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
7467
|
+
path: `${url.pathname}${url.search}`,
|
|
7468
|
+
method: req.method,
|
|
7469
|
+
headers,
|
|
7470
|
+
setHost: !hasHeader(req, "host")
|
|
7471
|
+
}, (incoming) => {
|
|
7472
|
+
const chunks = [];
|
|
7473
|
+
let buffered = null;
|
|
7474
|
+
const responseHeaders = new Headers;
|
|
7475
|
+
for (const [k, v] of Object.entries(incoming.headers)) {
|
|
7476
|
+
if (Array.isArray(v))
|
|
7477
|
+
for (const one of v)
|
|
7478
|
+
responseHeaders.append(k, one);
|
|
7479
|
+
else if (typeof v === "string")
|
|
7480
|
+
responseHeaders.set(k, v);
|
|
7481
|
+
}
|
|
7482
|
+
trace(incoming.statusCode);
|
|
7483
|
+
resolve({
|
|
7484
|
+
status: incoming.statusCode ?? 0,
|
|
7485
|
+
headers: responseHeaders,
|
|
7486
|
+
body: Readable.toWeb(incoming),
|
|
7487
|
+
text: () => {
|
|
7488
|
+
buffered ??= new Promise((res, rej) => {
|
|
7489
|
+
incoming.on("data", (c) => chunks.push(c));
|
|
7490
|
+
incoming.on("end", () => res(Buffer.concat(chunks).toString("utf8")));
|
|
7491
|
+
incoming.on("error", rej);
|
|
7492
|
+
});
|
|
7493
|
+
return buffered;
|
|
7494
|
+
}
|
|
7495
|
+
});
|
|
7496
|
+
});
|
|
7497
|
+
const onAbort = () => outgoing.destroy(new Error("aborted"));
|
|
7498
|
+
outgoing.on("error", (err) => {
|
|
7499
|
+
req.signal.removeEventListener("abort", onAbort);
|
|
7500
|
+
trace(undefined, true);
|
|
7501
|
+
reject(err);
|
|
7502
|
+
});
|
|
7503
|
+
outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
|
|
7504
|
+
if (req.signal.aborted) {
|
|
7505
|
+
outgoing.destroy(new Error("aborted"));
|
|
7506
|
+
return;
|
|
7507
|
+
}
|
|
7508
|
+
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
7509
|
+
if (bodyBytes.byteLength > 0)
|
|
7510
|
+
outgoing.write(bodyBytes);
|
|
7511
|
+
outgoing.end();
|
|
7512
|
+
});
|
|
7513
|
+
}
|
|
7514
|
+
function hasHeader(req, lowerName) {
|
|
7515
|
+
return req.headers.some(([name]) => name.toLowerCase() === lowerName);
|
|
7516
|
+
}
|
|
7517
|
+
// packages/providers/src/kimi-device.ts
|
|
7518
|
+
import { randomUUID } from "crypto";
|
|
7519
|
+
function mintKimiDevice() {
|
|
7520
|
+
return {
|
|
7521
|
+
deviceId: randomUUID(),
|
|
7522
|
+
deviceName: "MacBook-Pro",
|
|
7523
|
+
deviceModel: "MacBookPro18,3",
|
|
7524
|
+
osVersion: "15.3.1"
|
|
7525
|
+
};
|
|
7526
|
+
}
|
|
7527
|
+
function kimiDeviceHeaders(providerData) {
|
|
7528
|
+
const deviceId = providerData.deviceId;
|
|
7529
|
+
if (typeof deviceId !== "string" || deviceId.length === 0)
|
|
7530
|
+
return [];
|
|
7531
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : "unknown";
|
|
7532
|
+
return [
|
|
7533
|
+
["X-Msh-Device-Id", deviceId],
|
|
7534
|
+
["X-Msh-Device-Name", str(providerData.deviceName)],
|
|
7535
|
+
["X-Msh-Device-Model", str(providerData.deviceModel)],
|
|
7536
|
+
["X-Msh-Os-Version", str(providerData.osVersion)]
|
|
7537
|
+
];
|
|
7538
|
+
}
|
|
7539
|
+
|
|
7540
|
+
// packages/providers/src/kimi/index.ts
|
|
7541
|
+
var BASE_URL2 = "https://api.kimi.com/coding/v1/chat/completions";
|
|
7542
|
+
var kimiAdapter = {
|
|
7543
|
+
id: "kimi",
|
|
7544
|
+
capabilities: PROVIDER_CAPABILITIES.kimi,
|
|
7545
|
+
async send(req) {
|
|
7546
|
+
const { body, degradations } = toChatWire(req.request, req.model);
|
|
7547
|
+
const token = req.credentials.accessToken ?? req.credentials.apiKey;
|
|
7548
|
+
if (token === null) {
|
|
7549
|
+
throw new GatewayError("AUTH", "kimi credential has no token", { provider: "kimi" });
|
|
7550
|
+
}
|
|
7551
|
+
const protocol = [
|
|
7552
|
+
["Content-Type", "application/json"],
|
|
7553
|
+
["Accept", "text/event-stream"],
|
|
7554
|
+
["Authorization", `Bearer ${token}`],
|
|
7555
|
+
...kimiDeviceHeaders(req.credentials.providerData)
|
|
7556
|
+
];
|
|
7557
|
+
const profile = PROFILES.kimi;
|
|
7558
|
+
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
7559
|
+
const res = await req.http({
|
|
7560
|
+
provider: "kimi",
|
|
7561
|
+
url: BASE_URL2,
|
|
7562
|
+
method: "POST",
|
|
7563
|
+
headers,
|
|
7564
|
+
body: JSON.stringify(orderFields({ ...body, stream: true }, BODY_ORDER.kimi)),
|
|
7565
|
+
signal: req.signal
|
|
7566
|
+
});
|
|
7567
|
+
if (res.status < 200 || res.status >= 300)
|
|
7568
|
+
throw await httpError(res, "kimi");
|
|
7569
|
+
if (res.body === null)
|
|
7570
|
+
throw new GatewayError("UPSTREAM", "empty response body", { provider: "kimi" });
|
|
7571
|
+
return { events: decodeChat(parseSse(res.body)), degradations };
|
|
7572
|
+
}
|
|
7573
|
+
};
|
|
7444
7574
|
// packages/providers/src/openai/index.ts
|
|
7445
7575
|
var OAUTH_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
7446
7576
|
var API_URL = "https://api.openai.com/v1/responses";
|
|
@@ -7482,7 +7612,8 @@ var openaiAdapter = {
|
|
|
7482
7612
|
var ADAPTERS = {
|
|
7483
7613
|
anthropic: anthropicAdapter,
|
|
7484
7614
|
openai: openaiAdapter,
|
|
7485
|
-
kimi: kimiAdapter
|
|
7615
|
+
kimi: kimiAdapter,
|
|
7616
|
+
custom: customAdapter
|
|
7486
7617
|
};
|
|
7487
7618
|
// packages/control/src/oauth/types.ts
|
|
7488
7619
|
async function postJson(deps, provider, url, profile2, opts) {
|
|
@@ -7659,11 +7790,11 @@ function pendingError(code) {
|
|
|
7659
7790
|
error[PENDING_MARKER] = true;
|
|
7660
7791
|
return error;
|
|
7661
7792
|
}
|
|
7662
|
-
function
|
|
7793
|
+
function isRecord2(value) {
|
|
7663
7794
|
return typeof value === "object" && value !== null;
|
|
7664
7795
|
}
|
|
7665
7796
|
function recordFrom(value) {
|
|
7666
|
-
return
|
|
7797
|
+
return isRecord2(value) ? value : null;
|
|
7667
7798
|
}
|
|
7668
7799
|
function stringFrom(value, field) {
|
|
7669
7800
|
const candidate = value[field];
|
|
@@ -7866,7 +7997,7 @@ function createPendingFlows(opts) {
|
|
|
7866
7997
|
}
|
|
7867
7998
|
|
|
7868
7999
|
// packages/control/src/connect.ts
|
|
7869
|
-
var PROVIDER_IDS = ["anthropic", "openai", "kimi"];
|
|
8000
|
+
var PROVIDER_IDS = ["anthropic", "openai", "kimi", "custom"];
|
|
7870
8001
|
var FLOW_TTL_MS = 600000;
|
|
7871
8002
|
function isProviderId(value) {
|
|
7872
8003
|
return typeof value === "string" && PROVIDER_IDS.includes(value);
|
|
@@ -7907,6 +8038,8 @@ function createConnectFlows(deps) {
|
|
|
7907
8038
|
}
|
|
7908
8039
|
async function complete(flow, code) {
|
|
7909
8040
|
const provider = deps.providers[flow.provider];
|
|
8041
|
+
if (provider === undefined)
|
|
8042
|
+
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth");
|
|
7910
8043
|
const result = await provider.exchange({ code, pending: flow.pending }, { http: deps.http, now: deps.now });
|
|
7911
8044
|
const id = crypto.randomUUID();
|
|
7912
8045
|
await deps.store.credentials.create({
|
|
@@ -7950,6 +8083,9 @@ function createConnectFlows(deps) {
|
|
|
7950
8083
|
}
|
|
7951
8084
|
const label = typeof labelInput === "string" && labelInput.trim().length > 0 ? labelInput.trim() : providerInput;
|
|
7952
8085
|
const provider = deps.providers[providerInput];
|
|
8086
|
+
if (provider === undefined) {
|
|
8087
|
+
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth");
|
|
8088
|
+
}
|
|
7953
8089
|
const redirectUri = callbackUri(providerInput);
|
|
7954
8090
|
const start = provider.begin === undefined ? provider.start({ redirectUri }) : await (async () => {
|
|
7955
8091
|
const initial = provider.start({ redirectUri });
|
|
@@ -19202,12 +19338,12 @@ function describe(description) {
|
|
|
19202
19338
|
ch._zod.check = () => {};
|
|
19203
19339
|
return ch;
|
|
19204
19340
|
}
|
|
19205
|
-
function meta(
|
|
19341
|
+
function meta(metadata2) {
|
|
19206
19342
|
const ch = new $ZodCheck({ check: "meta" });
|
|
19207
19343
|
ch._zod.onattach = [
|
|
19208
19344
|
(inst) => {
|
|
19209
19345
|
const existing = globalRegistry.get(inst) ?? {};
|
|
19210
|
-
globalRegistry.add(inst, { ...existing, ...
|
|
19346
|
+
globalRegistry.add(inst, { ...existing, ...metadata2 });
|
|
19211
19347
|
}
|
|
19212
19348
|
];
|
|
19213
19349
|
ch._zod.check = () => {};
|
|
@@ -22339,20 +22475,36 @@ function parseOrThrow(schema, body2) {
|
|
|
22339
22475
|
const path = issue2?.path.join(".") ?? "(root)";
|
|
22340
22476
|
throw new GatewayError("BAD_REQUEST", `${path}: ${issue2?.message ?? "invalid request"}`);
|
|
22341
22477
|
}
|
|
22342
|
-
var providerIdSchema = exports_external.enum(["anthropic", "openai", "kimi"]);
|
|
22478
|
+
var providerIdSchema = exports_external.enum(["anthropic", "openai", "kimi", "custom"]);
|
|
22343
22479
|
var dryRunSchema = exports_external.object({
|
|
22344
22480
|
tools: exports_external.boolean().default(false),
|
|
22345
22481
|
images: exports_external.boolean().default(false),
|
|
22346
22482
|
reasoning: exports_external.boolean().default(false)
|
|
22347
22483
|
}).strict();
|
|
22348
|
-
var
|
|
22349
|
-
|
|
22350
|
-
|
|
22351
|
-
|
|
22352
|
-
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22484
|
+
var targetSchema = exports_external.discriminatedUnion("provider", [
|
|
22485
|
+
exports_external.object({
|
|
22486
|
+
provider: exports_external.enum(["anthropic", "openai", "kimi"]),
|
|
22487
|
+
model: exports_external.string().min(1),
|
|
22488
|
+
tier: exports_external.number().int().min(1),
|
|
22489
|
+
weight: exports_external.number().positive(),
|
|
22490
|
+
costPerMTok: exports_external.object({
|
|
22491
|
+
input: exports_external.number().min(0),
|
|
22492
|
+
output: exports_external.number().min(0),
|
|
22493
|
+
cacheRead: exports_external.number().min(0).optional(),
|
|
22494
|
+
cacheWrite5m: exports_external.number().min(0).optional(),
|
|
22495
|
+
cacheWrite1h: exports_external.number().min(0).optional()
|
|
22496
|
+
}),
|
|
22497
|
+
contextWindow: exports_external.number().int().positive().optional(),
|
|
22498
|
+
maxOutputTokens: exports_external.number().int().positive().optional(),
|
|
22499
|
+
capabilities: exports_external.object({
|
|
22500
|
+
tools: exports_external.boolean(),
|
|
22501
|
+
images: exports_external.boolean(),
|
|
22502
|
+
reasoning: exports_external.boolean()
|
|
22503
|
+
})
|
|
22504
|
+
}).strict(),
|
|
22505
|
+
exports_external.object({
|
|
22506
|
+
provider: exports_external.literal("custom"),
|
|
22507
|
+
endpointId: exports_external.string().trim().min(1),
|
|
22356
22508
|
model: exports_external.string().min(1),
|
|
22357
22509
|
tier: exports_external.number().int().min(1),
|
|
22358
22510
|
weight: exports_external.number().positive(),
|
|
@@ -22370,7 +22522,15 @@ var modelSchema = exports_external.object({
|
|
|
22370
22522
|
images: exports_external.boolean(),
|
|
22371
22523
|
reasoning: exports_external.boolean()
|
|
22372
22524
|
})
|
|
22373
|
-
})
|
|
22525
|
+
}).strict()
|
|
22526
|
+
]);
|
|
22527
|
+
var modelSchema = exports_external.object({
|
|
22528
|
+
id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
|
|
22529
|
+
message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
|
|
22530
|
+
}),
|
|
22531
|
+
strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
|
|
22532
|
+
isAlias: exports_external.boolean(),
|
|
22533
|
+
targets: exports_external.array(targetSchema).min(1, "a virtual model needs at least one target")
|
|
22374
22534
|
});
|
|
22375
22535
|
var keyCreateSchema = exports_external.object({
|
|
22376
22536
|
label: exports_external.string().min(1).default("api key"),
|
|
@@ -22384,7 +22544,7 @@ var settingsSchema = exports_external.object({
|
|
|
22384
22544
|
quota: exports_external.number(),
|
|
22385
22545
|
cost: exports_external.number(),
|
|
22386
22546
|
latency: exports_external.number(),
|
|
22387
|
-
|
|
22547
|
+
load: exports_external.number()
|
|
22388
22548
|
}).strict(),
|
|
22389
22549
|
maxAttempts: exports_external.number().int().min(1).max(10),
|
|
22390
22550
|
requestDeadlineMs: exports_external.number().int().min(0),
|
|
@@ -22443,6 +22603,70 @@ function summarizeCredential(credential) {
|
|
|
22443
22603
|
async function listCredentials(store) {
|
|
22444
22604
|
return (await store.credentials.list()).map(summarizeCredential);
|
|
22445
22605
|
}
|
|
22606
|
+
function requiredString(value, field) {
|
|
22607
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
22608
|
+
throw new GatewayError("BAD_REQUEST", `${field}: must not be empty`);
|
|
22609
|
+
}
|
|
22610
|
+
return value.trim();
|
|
22611
|
+
}
|
|
22612
|
+
function customProviderData(input) {
|
|
22613
|
+
const endpointId = requiredString(input.endpointId, "endpointId");
|
|
22614
|
+
const endpointLabel = requiredString(input.endpointLabel, "endpointLabel");
|
|
22615
|
+
const originInput = requiredString(input.origin, "origin");
|
|
22616
|
+
if (input.protocol !== "chat_completions" && input.protocol !== "responses") {
|
|
22617
|
+
throw new GatewayError("BAD_REQUEST", "protocol: unsupported protocol");
|
|
22618
|
+
}
|
|
22619
|
+
let url2;
|
|
22620
|
+
try {
|
|
22621
|
+
url2 = new URL(originInput);
|
|
22622
|
+
} catch {
|
|
22623
|
+
throw new GatewayError("BAD_REQUEST", "origin: must be a valid URL");
|
|
22624
|
+
}
|
|
22625
|
+
if (url2.protocol !== "http:" && url2.protocol !== "https:" || url2.hostname.length === 0 || url2.username.length > 0 || url2.password.length > 0 || url2.pathname !== "" && url2.pathname !== "/" || url2.search.length > 0 || url2.hash.length > 0) {
|
|
22626
|
+
throw new GatewayError("BAD_REQUEST", "origin: must be an HTTP(S) server origin");
|
|
22627
|
+
}
|
|
22628
|
+
return { endpointId, endpointLabel, origin: url2.origin, protocol: input.protocol };
|
|
22629
|
+
}
|
|
22630
|
+
function sameCustomEndpoint(a, b) {
|
|
22631
|
+
return a.endpointId === b.endpointId && a.endpointLabel === b.endpointLabel && a.origin === b.origin && a.protocol === b.protocol;
|
|
22632
|
+
}
|
|
22633
|
+
async function createApiKeyCredential(store, input, logger2 = noopLogger) {
|
|
22634
|
+
const provider = parseOrThrow(providerIdSchema, input.provider);
|
|
22635
|
+
const apiKey = requiredString(input.apiKey, "apiKey");
|
|
22636
|
+
if (input.label !== undefined && typeof input.label !== "string") {
|
|
22637
|
+
throw new GatewayError("BAD_REQUEST", "label: must be a string");
|
|
22638
|
+
}
|
|
22639
|
+
let providerData = {};
|
|
22640
|
+
if (provider === "custom") {
|
|
22641
|
+
const custom2 = customProviderData(input);
|
|
22642
|
+
const existing = (await store.credentials.list()).filter((credential) => credential.provider === "custom" && credential.providerData.endpointId === custom2.endpointId);
|
|
22643
|
+
if (existing.some((credential) => !sameCustomEndpoint(credential.providerData, custom2))) {
|
|
22644
|
+
throw new GatewayError("CONFLICT", `endpointId: metadata conflicts with existing endpoint`);
|
|
22645
|
+
}
|
|
22646
|
+
providerData = custom2;
|
|
22647
|
+
}
|
|
22648
|
+
const label = input.label?.trim() || `${provider} api key`;
|
|
22649
|
+
const created = await store.credentials.create({
|
|
22650
|
+
id: crypto.randomUUID(),
|
|
22651
|
+
provider,
|
|
22652
|
+
label,
|
|
22653
|
+
authType: "apiKey",
|
|
22654
|
+
enabled: true,
|
|
22655
|
+
tier: 1,
|
|
22656
|
+
weight: 1,
|
|
22657
|
+
expiresAt: null,
|
|
22658
|
+
accountEmail: null,
|
|
22659
|
+
providerData,
|
|
22660
|
+
disabledReason: null,
|
|
22661
|
+
disabledAt: null,
|
|
22662
|
+
accessToken: null,
|
|
22663
|
+
refreshToken: null,
|
|
22664
|
+
apiKey,
|
|
22665
|
+
idToken: null
|
|
22666
|
+
});
|
|
22667
|
+
logger2.info("credential added", { credentialId: created.id, provider: created.provider });
|
|
22668
|
+
return summarizeCredential(created);
|
|
22669
|
+
}
|
|
22446
22670
|
async function credentialHealth(store) {
|
|
22447
22671
|
const [health, quota] = await Promise.all([
|
|
22448
22672
|
store.credentials.listHealth(),
|
|
@@ -22537,6 +22761,9 @@ function eligible(input) {
|
|
|
22537
22761
|
for (const credential of snapshot.credentials) {
|
|
22538
22762
|
if (credential.provider !== target.provider)
|
|
22539
22763
|
continue;
|
|
22764
|
+
if (target.provider === "custom" && credential.providerData.endpointId !== target.endpointId) {
|
|
22765
|
+
continue;
|
|
22766
|
+
}
|
|
22540
22767
|
const drop = (reason) => {
|
|
22541
22768
|
excluded.push({ credentialId: credential.id, model: target.model, reason });
|
|
22542
22769
|
};
|
|
@@ -22608,6 +22835,22 @@ function quotaHeadroom(credential, windows, now, pollIntervalMs) {
|
|
|
22608
22835
|
return Math.min(...usable.map((w) => paceAdjusted(w, now)));
|
|
22609
22836
|
}
|
|
22610
22837
|
|
|
22838
|
+
// packages/store/src/types.ts
|
|
22839
|
+
var READ_OVER_INPUT = 0.1;
|
|
22840
|
+
function cacheReadRate(prices) {
|
|
22841
|
+
return prices.cacheRead ?? prices.input * READ_OVER_INPUT;
|
|
22842
|
+
}
|
|
22843
|
+
var DEFAULT_SETTINGS = {
|
|
22844
|
+
weights: { tier: 10, health: 3, quota: 2, load: 2, cost: 1, latency: 1 },
|
|
22845
|
+
maxAttempts: 3,
|
|
22846
|
+
requestDeadlineMs: 120000,
|
|
22847
|
+
breakerThreshold: 3,
|
|
22848
|
+
breakerCooldownMs: 30000,
|
|
22849
|
+
logRetentionDays: 30,
|
|
22850
|
+
quotaPollIntervalMs: 300000,
|
|
22851
|
+
rtkEnabled: false
|
|
22852
|
+
};
|
|
22853
|
+
|
|
22611
22854
|
// packages/router/src/score.ts
|
|
22612
22855
|
var UNKNOWN = 0.5;
|
|
22613
22856
|
function lowerIsBetter(value, min, max) {
|
|
@@ -22615,43 +22858,49 @@ function lowerIsBetter(value, min, max) {
|
|
|
22615
22858
|
return 1;
|
|
22616
22859
|
return (max - value) / (max - min);
|
|
22617
22860
|
}
|
|
22618
|
-
function
|
|
22619
|
-
return
|
|
22861
|
+
function ratio(value, min) {
|
|
22862
|
+
return Math.min(1, min / value);
|
|
22863
|
+
}
|
|
22864
|
+
function bestPositive(values) {
|
|
22865
|
+
const positive = values.filter((v) => v > 0);
|
|
22866
|
+
return positive.length > 0 ? Math.min(...positive) : null;
|
|
22867
|
+
}
|
|
22868
|
+
var EXPECTED_OUTPUT_TOKENS = 1000;
|
|
22869
|
+
function requestCost(target, request2) {
|
|
22870
|
+
const cachedTok = estimateCachedInputTokens(request2);
|
|
22871
|
+
const freshTok = Math.max(0, estimateInputTokens(request2) - cachedTok);
|
|
22872
|
+
const outTok = Math.min(request2.maxTokens ?? EXPECTED_OUTPUT_TOKENS, EXPECTED_OUTPUT_TOKENS);
|
|
22873
|
+
return freshTok * target.costPerMTok.input + cachedTok * cacheReadRate(target.costPerMTok) + outTok * target.costPerMTok.output;
|
|
22874
|
+
}
|
|
22875
|
+
function healthScore(h) {
|
|
22876
|
+
const base = 1 / (1 + (h?.consecutiveFailures ?? 0));
|
|
22877
|
+
return h?.breakerState === "open" || h?.breakerState === "halfOpen" ? base * 0.5 : base;
|
|
22620
22878
|
}
|
|
22621
22879
|
function score(pairs, input) {
|
|
22622
|
-
const { snapshot, now } = input;
|
|
22880
|
+
const { snapshot, now, load } = input;
|
|
22623
22881
|
const w = snapshot.settings.weights;
|
|
22624
22882
|
const tiers = pairs.map((p) => p.target.tier);
|
|
22625
22883
|
const minTier = Math.min(...tiers);
|
|
22626
22884
|
const maxTier = Math.max(...tiers);
|
|
22627
|
-
const costs = pairs.map((p) =>
|
|
22628
|
-
const
|
|
22629
|
-
const
|
|
22630
|
-
const latencies = pairs.flatMap((p) => {
|
|
22885
|
+
const costs = pairs.map((p) => requestCost(p.target, input.request));
|
|
22886
|
+
const bestCost = bestPositive(costs);
|
|
22887
|
+
const bestLatency = bestPositive(pairs.flatMap((p) => {
|
|
22631
22888
|
const h = snapshot.health.get(healthKey(p.credential.id, p.target.model));
|
|
22632
22889
|
return h?.ewmaTtftMs != null ? [h.ewmaTtftMs] : [];
|
|
22633
|
-
});
|
|
22634
|
-
const minLatency = latencies.length > 0 ? Math.min(...latencies) : 0;
|
|
22635
|
-
const maxLatency = latencies.length > 0 ? Math.max(...latencies) : 0;
|
|
22636
|
-
const idleTimes = pairs.map((p) => {
|
|
22637
|
-
const h = snapshot.health.get(healthKey(p.credential.id, p.target.model));
|
|
22638
|
-
return h?.lastUsedAt == null ? Number.POSITIVE_INFINITY : now - h.lastUsedAt;
|
|
22639
|
-
});
|
|
22640
|
-
const finiteIdle = idleTimes.filter(Number.isFinite);
|
|
22641
|
-
const maxIdle = finiteIdle.length > 0 ? Math.max(...finiteIdle) : 1;
|
|
22890
|
+
}));
|
|
22642
22891
|
return pairs.map((pair, i) => {
|
|
22643
|
-
const
|
|
22892
|
+
const key = healthKey(pair.credential.id, pair.target.model);
|
|
22893
|
+
const h = snapshot.health.get(key);
|
|
22644
22894
|
const tier = lowerIsBetter(pair.target.tier, minTier, maxTier);
|
|
22645
|
-
|
|
22646
|
-
|
|
22647
|
-
|
|
22895
|
+
const inflight = load.get(key) ?? 0;
|
|
22896
|
+
const loadTerm = 1 / (1 + inflight);
|
|
22897
|
+
const health = healthScore(h);
|
|
22648
22898
|
const quota = quotaHeadroom(pair.credential, snapshot.quota.get(pair.credential.id) ?? [], now, snapshot.settings.quotaPollIntervalMs);
|
|
22649
|
-
const
|
|
22650
|
-
const
|
|
22651
|
-
const
|
|
22652
|
-
const
|
|
22653
|
-
const
|
|
22654
|
-
const base = tier * w.tier + health * w.health + quota * w.quota + cost * w.cost + latency * w.latency + recency * w.recency;
|
|
22899
|
+
const ownCost = costs[i];
|
|
22900
|
+
const cost = ownCost <= 0 || bestCost === null ? UNKNOWN : ratio(ownCost, bestCost);
|
|
22901
|
+
const latency = h?.ewmaTtftMs == null || h.ewmaTtftMs <= 0 || bestLatency === null ? UNKNOWN : ratio(h.ewmaTtftMs, bestLatency);
|
|
22902
|
+
const reasons = { tier, health, quota, cost, latency, load: loadTerm };
|
|
22903
|
+
const base = tier * w.tier + health * w.health + quota * w.quota + cost * w.cost + latency * w.latency + loadTerm * w.load;
|
|
22655
22904
|
return {
|
|
22656
22905
|
credential: pair.credential,
|
|
22657
22906
|
target: pair.target,
|
|
@@ -22672,6 +22921,7 @@ var PENALTY = {
|
|
|
22672
22921
|
QUOTA_EXHAUSTED: "soft",
|
|
22673
22922
|
OVERLOADED: "soft",
|
|
22674
22923
|
BAD_REQUEST: "none",
|
|
22924
|
+
CONFLICT: "none",
|
|
22675
22925
|
CONTENT_FILTER: "none",
|
|
22676
22926
|
CAPABILITY_MISMATCH: "none",
|
|
22677
22927
|
NO_CANDIDATES: "none",
|
|
@@ -22764,7 +23014,7 @@ function resolveModel(name, snapshot) {
|
|
|
22764
23014
|
if (sep > 0) {
|
|
22765
23015
|
const prefix = name.slice(0, sep);
|
|
22766
23016
|
const rest = name.slice(sep + 1);
|
|
22767
|
-
if (PROVIDERS.has(prefix) && rest.length > 0) {
|
|
23017
|
+
if (PROVIDERS.has(prefix) && prefix !== "custom" && rest.length > 0) {
|
|
22768
23018
|
return synthesize(prefix, rest);
|
|
22769
23019
|
}
|
|
22770
23020
|
throw new GatewayError("NO_CANDIDATES", `unknown provider "${prefix}" in model "${name}"`);
|
|
@@ -22777,8 +23027,8 @@ function resolveModel(name, snapshot) {
|
|
|
22777
23027
|
}
|
|
22778
23028
|
|
|
22779
23029
|
// packages/router/src/index.ts
|
|
22780
|
-
function weightedShuffle(candidates, rand, headroom) {
|
|
22781
|
-
const drawWeight = (c) => c.credential.weight * c.target.weight * headroom(c);
|
|
23030
|
+
function weightedShuffle(candidates, rand, headroom, health) {
|
|
23031
|
+
const drawWeight = (c) => c.credential.weight * c.target.weight * headroom(c) * health(c);
|
|
22782
23032
|
const total = candidates.reduce((sum, c) => sum + drawWeight(c), 0);
|
|
22783
23033
|
if (total <= 0)
|
|
22784
23034
|
return [...candidates].sort((a, b) => b.score - a.score);
|
|
@@ -22801,6 +23051,8 @@ function rank(input) {
|
|
|
22801
23051
|
return { candidates: [], excluded };
|
|
22802
23052
|
const scored = score(pairs, input);
|
|
22803
23053
|
const headroom = (c) => quotaHeadroom(c.credential, input.snapshot.quota.get(c.credential.id) ?? [], input.now, input.snapshot.settings.quotaPollIntervalMs);
|
|
23054
|
+
const health = (c) => healthScore(input.snapshot.health.get(healthKey(c.credential.id, c.target.model)));
|
|
23055
|
+
const inflight = (c) => input.load.get(healthKey(c.credential.id, c.target.model)) ?? 0;
|
|
22804
23056
|
switch (input.model.strategy) {
|
|
22805
23057
|
case "priority":
|
|
22806
23058
|
scored.sort((a, b) => a.target.tier - b.target.tier || b.score - a.score);
|
|
@@ -22811,11 +23063,11 @@ function rank(input) {
|
|
|
22811
23063
|
return h?.lastUsedAt == null ? Number.POSITIVE_INFINITY : input.now - h.lastUsedAt;
|
|
22812
23064
|
};
|
|
22813
23065
|
const spent = (c) => headroom(c) < QUOTA_FLOOR ? 1 : 0;
|
|
22814
|
-
scored.sort((a, b) => spent(a) - spent(b) || idle(b) - idle(a));
|
|
23066
|
+
scored.sort((a, b) => spent(a) - spent(b) || inflight(a) - inflight(b) || idle(b) - idle(a));
|
|
22815
23067
|
break;
|
|
22816
23068
|
}
|
|
22817
23069
|
case "weighted":
|
|
22818
|
-
return { candidates: weightedShuffle(scored, input.rand, headroom), excluded };
|
|
23070
|
+
return { candidates: weightedShuffle(scored, input.rand, headroom, health), excluded };
|
|
22819
23071
|
case "score":
|
|
22820
23072
|
scored.sort((a, b) => b.score - a.score);
|
|
22821
23073
|
break;
|
|
@@ -22848,7 +23100,7 @@ async function dryRun(deps, modelId, input) {
|
|
|
22848
23100
|
} : {},
|
|
22849
23101
|
...need.reasoning ? { reasoning: { mode: "adaptive" } } : {}
|
|
22850
23102
|
};
|
|
22851
|
-
const result = rank({ request: probe, model, snapshot, now, rand: 0 });
|
|
23103
|
+
const result = rank({ request: probe, model, snapshot, now, rand: 0, load: new Map });
|
|
22852
23104
|
return {
|
|
22853
23105
|
modelId: model.id,
|
|
22854
23106
|
strategy: model.strategy,
|
|
@@ -22905,21 +23157,24 @@ function unhex(s) {
|
|
|
22905
23157
|
out[i] = Number.parseInt(s.slice(i * 2, i * 2 + 2), 16);
|
|
22906
23158
|
return out;
|
|
22907
23159
|
}
|
|
22908
|
-
// packages/store/src/types.ts
|
|
22909
|
-
var DEFAULT_SETTINGS = {
|
|
22910
|
-
weights: { tier: 10, health: 3, quota: 2, cost: 1, latency: 1, recency: 0.5 },
|
|
22911
|
-
maxAttempts: 3,
|
|
22912
|
-
requestDeadlineMs: 120000,
|
|
22913
|
-
breakerThreshold: 3,
|
|
22914
|
-
breakerCooldownMs: 30000,
|
|
22915
|
-
logRetentionDays: 30,
|
|
22916
|
-
quotaPollIntervalMs: 300000,
|
|
22917
|
-
rtkEnabled: false
|
|
22918
|
-
};
|
|
22919
|
-
|
|
22920
23160
|
// packages/store/src/sqlite/config.ts
|
|
22921
23161
|
var SETTINGS_KEY = "settings";
|
|
22922
23162
|
var ADMIN_HASH_KEY = "adminPasswordHash";
|
|
23163
|
+
function knownWeights(stored) {
|
|
23164
|
+
const d = DEFAULT_SETTINGS.weights;
|
|
23165
|
+
const pick2 = (key) => {
|
|
23166
|
+
const value = stored?.[key];
|
|
23167
|
+
return typeof value === "number" && Number.isFinite(value) ? value : d[key];
|
|
23168
|
+
};
|
|
23169
|
+
return {
|
|
23170
|
+
tier: pick2("tier"),
|
|
23171
|
+
health: pick2("health"),
|
|
23172
|
+
quota: pick2("quota"),
|
|
23173
|
+
load: pick2("load"),
|
|
23174
|
+
cost: pick2("cost"),
|
|
23175
|
+
latency: pick2("latency")
|
|
23176
|
+
};
|
|
23177
|
+
}
|
|
22923
23178
|
function createConfigRepo(db, emit = () => {}) {
|
|
22924
23179
|
const readRaw = (key) => db.query("SELECT value FROM settings WHERE key = ?").get(key)?.value ?? null;
|
|
22925
23180
|
const writeRaw = (key, value) => {
|
|
@@ -22957,7 +23212,7 @@ function createConfigRepo(db, emit = () => {}) {
|
|
|
22957
23212
|
...DEFAULT_SETTINGS,
|
|
22958
23213
|
...stored,
|
|
22959
23214
|
rtkEnabled: stored.rtkEnabled === true,
|
|
22960
|
-
weights:
|
|
23215
|
+
weights: knownWeights(stored.weights)
|
|
22961
23216
|
};
|
|
22962
23217
|
} catch {
|
|
22963
23218
|
return DEFAULT_SETTINGS;
|
|
@@ -23988,6 +24243,11 @@ async function putModel(store, id, input) {
|
|
|
23988
24243
|
if (model.id !== id) {
|
|
23989
24244
|
throw new GatewayError("BAD_REQUEST", "model id in the path and body must match");
|
|
23990
24245
|
}
|
|
24246
|
+
const customEndpointIds = new Set((await store.credentials.list()).filter((credential) => credential.provider === "custom").map((credential) => credential.providerData.endpointId).filter((endpointId) => typeof endpointId === "string"));
|
|
24247
|
+
const missing = model.targets.find((target) => target.provider === "custom" && (target.endpointId === undefined || !customEndpointIds.has(target.endpointId)));
|
|
24248
|
+
if (missing !== undefined) {
|
|
24249
|
+
throw new GatewayError("BAD_REQUEST", `custom endpoint "${missing.endpointId}" has no credential`);
|
|
24250
|
+
}
|
|
23991
24251
|
await store.config.putModel(model);
|
|
23992
24252
|
}
|
|
23993
24253
|
async function removeModel(store, id) {
|
|
@@ -24110,7 +24370,7 @@ var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
|
|
|
24110
24370
|
var TOKEN_URL3 = "https://auth.openai.com/oauth/token";
|
|
24111
24371
|
var SCOPES2 = "openid profile email offline_access";
|
|
24112
24372
|
var USAGE_URL3 = "https://chatgpt.com/backend-api/wham/usage";
|
|
24113
|
-
function
|
|
24373
|
+
function isRecord3(value) {
|
|
24114
24374
|
return typeof value === "object" && value !== null;
|
|
24115
24375
|
}
|
|
24116
24376
|
function nonBlankStringOrNull(value) {
|
|
@@ -24127,19 +24387,19 @@ function decodeClaims(idToken) {
|
|
|
24127
24387
|
}
|
|
24128
24388
|
try {
|
|
24129
24389
|
const json5 = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
24130
|
-
if (!
|
|
24390
|
+
if (!isRecord3(json5))
|
|
24131
24391
|
return { email: null, accountId: null };
|
|
24132
24392
|
const auth = json5["https://api.openai.com/auth"];
|
|
24133
24393
|
return {
|
|
24134
24394
|
email: typeof json5.email === "string" ? json5.email : null,
|
|
24135
|
-
accountId:
|
|
24395
|
+
accountId: isRecord3(auth) ? nonBlankStringOrNull(auth.chatgpt_account_id) : null
|
|
24136
24396
|
};
|
|
24137
24397
|
} catch {
|
|
24138
24398
|
return { email: null, accountId: null };
|
|
24139
24399
|
}
|
|
24140
24400
|
}
|
|
24141
24401
|
function parseTokenResponse(value) {
|
|
24142
|
-
if (!
|
|
24402
|
+
if (!isRecord3(value) || typeof value.access_token !== "string")
|
|
24143
24403
|
return null;
|
|
24144
24404
|
return {
|
|
24145
24405
|
accessToken: value.access_token,
|
|
@@ -24277,6 +24537,9 @@ function createRefresher(deps) {
|
|
|
24277
24537
|
throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
|
|
24278
24538
|
}
|
|
24279
24539
|
const provider = deps.providers[credential.provider];
|
|
24540
|
+
if (provider === undefined) {
|
|
24541
|
+
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth refresh");
|
|
24542
|
+
}
|
|
24280
24543
|
logger2.debug("refreshing credential", {
|
|
24281
24544
|
provider: credential.provider,
|
|
24282
24545
|
credentialId: credential.id
|
|
@@ -24329,7 +24592,7 @@ async function probe(deps, credential) {
|
|
|
24329
24592
|
if (credential.authType !== "oauth")
|
|
24330
24593
|
return null;
|
|
24331
24594
|
const provider = deps.providers[credential.provider];
|
|
24332
|
-
if (provider
|
|
24595
|
+
if (provider?.usage === undefined)
|
|
24333
24596
|
return null;
|
|
24334
24597
|
const refreshed = credential.hasRefreshToken && credential.expiresAt !== null && credential.expiresAt - SCHEDULER_REFRESH_LEAD_MS <= deps.now() ? await deps.refresh(credential) : null;
|
|
24335
24598
|
const secrets = refreshed === null ? await credential.openForUsage() : { accessToken: refreshed.accessToken };
|
|
@@ -24357,7 +24620,7 @@ async function probe(deps, credential) {
|
|
|
24357
24620
|
async function poll(deps) {
|
|
24358
24621
|
const logger2 = deps.logger ?? noopLogger;
|
|
24359
24622
|
const now = deps.now();
|
|
24360
|
-
const credentials = (await deps.store.credentials.list()).filter((c) => c.enabled && c.authType === "oauth" && deps.providers[c.provider]
|
|
24623
|
+
const credentials = (await deps.store.credentials.list()).filter((c) => c.enabled && c.authType === "oauth" && deps.providers[c.provider]?.usage !== undefined && (cooldowns.get(c.id) ?? 0) <= now);
|
|
24361
24624
|
let written = 0;
|
|
24362
24625
|
let next = 0;
|
|
24363
24626
|
const worker = async () => {
|
|
@@ -39077,7 +39340,7 @@ function parseOrThrow2(schema, body2) {
|
|
|
39077
39340
|
const path = issue2?.path.join(".") ?? "(root)";
|
|
39078
39341
|
throw new GatewayError("BAD_REQUEST", `${path}: ${issue2?.message ?? "invalid request"}`);
|
|
39079
39342
|
}
|
|
39080
|
-
function
|
|
39343
|
+
function isRecord4(value) {
|
|
39081
39344
|
return typeof value === "object" && value !== null;
|
|
39082
39345
|
}
|
|
39083
39346
|
function extraFields(body2, known) {
|
|
@@ -39136,7 +39399,7 @@ async function readJson(request2) {
|
|
|
39136
39399
|
}
|
|
39137
39400
|
async function readJsonRecord(request2) {
|
|
39138
39401
|
const body2 = await readJson(request2);
|
|
39139
|
-
return
|
|
39402
|
+
return isRecord4(body2) ? body2 : null;
|
|
39140
39403
|
}
|
|
39141
39404
|
function apiErrorResponse(error51) {
|
|
39142
39405
|
const gatewayError = error51 instanceof GatewayError ? error51 : new GatewayError("INTERNAL", "internal error");
|
|
@@ -39203,6 +39466,14 @@ function adminRoutes(deps) {
|
|
|
39203
39466
|
}).get("/api/credentials", async ({ request: request2 }) => {
|
|
39204
39467
|
await requireAdmin(request2, deps.admin);
|
|
39205
39468
|
return { credentials: await listCredentials(deps.store) };
|
|
39469
|
+
}).post("/api/credentials", async ({ request: request2 }) => {
|
|
39470
|
+
await requireAdmin(request2, deps.admin);
|
|
39471
|
+
const body2 = await readJsonRecord(request2);
|
|
39472
|
+
if (body2 === null)
|
|
39473
|
+
throw new GatewayError("BAD_REQUEST", "credential body is required");
|
|
39474
|
+
return {
|
|
39475
|
+
credential: await createApiKeyCredential(deps.store, { provider: body2.provider, apiKey: body2.apiKey, ...body2 }, logger2)
|
|
39476
|
+
};
|
|
39206
39477
|
}).get("/api/credentials/health", async ({ request: request2 }) => {
|
|
39207
39478
|
await requireAdmin(request2, deps.admin);
|
|
39208
39479
|
return credentialHealth(deps.store);
|
|
@@ -40863,7 +41134,7 @@ ${rendered}`;
|
|
|
40863
41134
|
}
|
|
40864
41135
|
|
|
40865
41136
|
// packages/rtk/src/index.ts
|
|
40866
|
-
var MIN_INPUT =
|
|
41137
|
+
var MIN_INPUT = 0;
|
|
40867
41138
|
var MAX_INPUT = 1e6;
|
|
40868
41139
|
var MAX_OUTPUT2 = 250000;
|
|
40869
41140
|
var SHELL = new Set(["bash", "shell", "terminal", "exec", "run_command", "execute_command"]);
|
|
@@ -41279,9 +41550,9 @@ function classify(error51) {
|
|
|
41279
41550
|
var WRITE_OVER_INPUT = {
|
|
41280
41551
|
anthropic: { fiveMinute: 1.25, oneHour: 2 },
|
|
41281
41552
|
openai: { fiveMinute: 0, oneHour: 0 },
|
|
41282
|
-
kimi: { fiveMinute: 0, oneHour: 0 }
|
|
41553
|
+
kimi: { fiveMinute: 0, oneHour: 0 },
|
|
41554
|
+
custom: { fiveMinute: 0, oneHour: 0 }
|
|
41283
41555
|
};
|
|
41284
|
-
var READ_OVER_INPUT = 0.1;
|
|
41285
41556
|
function splitWrites(usage) {
|
|
41286
41557
|
const { cacheWrite5mTokens: five, cacheWrite1hTokens: hour, cacheWriteTokens: total } = usage;
|
|
41287
41558
|
if (five !== undefined && hour !== undefined)
|
|
@@ -41294,7 +41565,7 @@ function splitWrites(usage) {
|
|
|
41294
41565
|
}
|
|
41295
41566
|
function priceOf(prices, usage, provider) {
|
|
41296
41567
|
const fallback = WRITE_OVER_INPUT[provider];
|
|
41297
|
-
const readRate = prices
|
|
41568
|
+
const readRate = cacheReadRate(prices);
|
|
41298
41569
|
const write5mRate = prices.cacheWrite5m ?? prices.input * fallback.fiveMinute;
|
|
41299
41570
|
const write1hRate = prices.cacheWrite1h ?? prices.input * fallback.oneHour;
|
|
41300
41571
|
const writes = splitWrites(usage);
|
|
@@ -41331,10 +41602,13 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
41331
41602
|
signal.addEventListener("abort", abortFromClient, { once: true });
|
|
41332
41603
|
const deadlineTimer = deadlineAt === null ? null : setTimeout(() => dispatchController.abort(new GatewayError("TIMEOUT", "request deadline exceeded")), Math.max(0, deadlineAt - deps.now()));
|
|
41333
41604
|
const dispatchSignal = dispatchController.signal;
|
|
41605
|
+
let releaseOnAbort = null;
|
|
41334
41606
|
const clearDeadline = () => {
|
|
41335
41607
|
if (deadlineTimer !== null)
|
|
41336
41608
|
clearTimeout(deadlineTimer);
|
|
41337
41609
|
signal.removeEventListener("abort", abortFromClient);
|
|
41610
|
+
if (releaseOnAbort !== null)
|
|
41611
|
+
signal.removeEventListener("abort", releaseOnAbort);
|
|
41338
41612
|
};
|
|
41339
41613
|
const checkCancellation = () => {
|
|
41340
41614
|
if (signal.aborted)
|
|
@@ -41365,7 +41639,8 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
41365
41639
|
model,
|
|
41366
41640
|
snapshot,
|
|
41367
41641
|
now: startedAt,
|
|
41368
|
-
rand: deps.rand()
|
|
41642
|
+
rand: deps.rand(),
|
|
41643
|
+
load: deps.loadRegistry.counts()
|
|
41369
41644
|
});
|
|
41370
41645
|
logger2.debug("routing candidates ranked", {
|
|
41371
41646
|
requestId,
|
|
@@ -41388,6 +41663,18 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
41388
41663
|
return fail("NO_CANDIDATES", `no eligible credential for model "${request2.model}"`);
|
|
41389
41664
|
}
|
|
41390
41665
|
const maxAttempts = Math.min(snapshot.settings.maxAttempts, candidates.length);
|
|
41666
|
+
const head = candidates[0];
|
|
41667
|
+
const eager = {
|
|
41668
|
+
credentialId: head.credential.id,
|
|
41669
|
+
model: head.target.model,
|
|
41670
|
+
release: deps.loadRegistry.acquire(head.credential.id, head.target.model)
|
|
41671
|
+
};
|
|
41672
|
+
let eagerHeld = true;
|
|
41673
|
+
releaseOnAbort = () => eager.release();
|
|
41674
|
+
if (signal.aborted)
|
|
41675
|
+
eager.release();
|
|
41676
|
+
else
|
|
41677
|
+
signal.addEventListener("abort", releaseOnAbort, { once: true });
|
|
41391
41678
|
const persistHealth = async (next) => {
|
|
41392
41679
|
await deps.store.credentials.saveHealth([next]);
|
|
41393
41680
|
};
|
|
@@ -41406,184 +41693,196 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
41406
41693
|
break;
|
|
41407
41694
|
}
|
|
41408
41695
|
const candidate = candidates[i];
|
|
41409
|
-
|
|
41410
|
-
|
|
41411
|
-
|
|
41412
|
-
|
|
41413
|
-
|
|
41414
|
-
|
|
41415
|
-
|
|
41416
|
-
|
|
41417
|
-
|
|
41418
|
-
|
|
41419
|
-
|
|
41420
|
-
|
|
41421
|
-
|
|
41422
|
-
|
|
41423
|
-
attempt
|
|
41424
|
-
|
|
41425
|
-
|
|
41426
|
-
|
|
41427
|
-
|
|
41428
|
-
|
|
41429
|
-
|
|
41430
|
-
|
|
41431
|
-
|
|
41432
|
-
|
|
41433
|
-
|
|
41434
|
-
|
|
41435
|
-
|
|
41436
|
-
|
|
41437
|
-
|
|
41438
|
-
|
|
41439
|
-
|
|
41440
|
-
|
|
41441
|
-
|
|
41442
|
-
|
|
41443
|
-
|
|
41444
|
-
|
|
41445
|
-
|
|
41446
|
-
|
|
41447
|
-
|
|
41448
|
-
|
|
41449
|
-
|
|
41450
|
-
|
|
41451
|
-
|
|
41452
|
-
|
|
41453
|
-
|
|
41454
|
-
|
|
41455
|
-
|
|
41456
|
-
|
|
41457
|
-
|
|
41458
|
-
|
|
41696
|
+
const adoptable = eagerHeld && eager.credentialId === candidate.credential.id && eager.model === candidate.target.model;
|
|
41697
|
+
if (adoptable)
|
|
41698
|
+
eagerHeld = false;
|
|
41699
|
+
const releaseSlot = adoptable ? eager.release : deps.loadRegistry.acquire(candidate.credential.id, candidate.target.model);
|
|
41700
|
+
try {
|
|
41701
|
+
log.attempts = i + 1;
|
|
41702
|
+
log.credentialId = candidate.credential.id;
|
|
41703
|
+
log.resolvedProvider = candidate.target.provider;
|
|
41704
|
+
log.resolvedModel = candidate.target.model;
|
|
41705
|
+
await deps.onRoute?.({
|
|
41706
|
+
provider: candidate.target.provider,
|
|
41707
|
+
model: candidate.target.model,
|
|
41708
|
+
credentialId: candidate.credential.id
|
|
41709
|
+
});
|
|
41710
|
+
logger2.debug("attempt started", {
|
|
41711
|
+
requestId,
|
|
41712
|
+
provider: candidate.target.provider,
|
|
41713
|
+
model: candidate.target.model,
|
|
41714
|
+
credentialId: candidate.credential.id,
|
|
41715
|
+
attempt: i + 1
|
|
41716
|
+
});
|
|
41717
|
+
log.inputTokens = 0;
|
|
41718
|
+
log.outputTokens = 0;
|
|
41719
|
+
log.cacheReadTokens = 0;
|
|
41720
|
+
log.cacheWriteTokens = 0;
|
|
41721
|
+
log.ttftMs = null;
|
|
41722
|
+
let committed = false;
|
|
41723
|
+
let authRefreshRetried = false;
|
|
41724
|
+
let retrySecrets;
|
|
41725
|
+
const attemptNow = deps.now();
|
|
41726
|
+
const preemptiveRefreshRequired = candidate.credential.authType === "oauth" && candidate.credential.expiresAt !== null && candidate.credential.expiresAt - DISPATCH_REFRESH_LEAD_MS <= attemptNow;
|
|
41727
|
+
const adapter = deps.adapters[candidate.target.provider];
|
|
41728
|
+
if (adapter === undefined) {
|
|
41729
|
+
throw new GatewayError("INTERNAL", `no adapter for provider ${candidate.target.provider}`);
|
|
41730
|
+
}
|
|
41731
|
+
while (true) {
|
|
41732
|
+
try {
|
|
41733
|
+
const result = await waitForCancellation(attempt({
|
|
41734
|
+
candidate,
|
|
41735
|
+
request: dispatchRequest,
|
|
41736
|
+
adapter,
|
|
41737
|
+
http: deps.http,
|
|
41738
|
+
now: attemptNow,
|
|
41739
|
+
signal: dispatchSignal,
|
|
41740
|
+
refresh: (credential) => waitForCancellation(deps.refresh(credential), dispatchSignal),
|
|
41741
|
+
refreshLeadMs: DISPATCH_REFRESH_LEAD_MS,
|
|
41742
|
+
...retrySecrets === undefined ? {} : { secrets: retrySecrets },
|
|
41743
|
+
logger: logger2,
|
|
41744
|
+
requestId
|
|
41745
|
+
}), dispatchSignal);
|
|
41746
|
+
for (const d of result.degradations)
|
|
41747
|
+
log.degradations.push(d);
|
|
41748
|
+
let terminal = false;
|
|
41749
|
+
const pending = [];
|
|
41750
|
+
for await (const event of result.events) {
|
|
41751
|
+
if (event.type === "blockDelta" && !committed) {
|
|
41752
|
+
committed = true;
|
|
41753
|
+
log.ttftMs = deps.now() - startedAt;
|
|
41754
|
+
logger2.debug("stream committed", {
|
|
41755
|
+
requestId,
|
|
41756
|
+
provider: candidate.target.provider,
|
|
41757
|
+
model: candidate.target.model,
|
|
41758
|
+
credentialId: candidate.credential.id,
|
|
41759
|
+
attempt: i + 1,
|
|
41760
|
+
ttftMs: log.ttftMs
|
|
41761
|
+
});
|
|
41762
|
+
for (const buffered of pending)
|
|
41763
|
+
yield buffered;
|
|
41764
|
+
pending.length = 0;
|
|
41765
|
+
}
|
|
41766
|
+
if (event.type === "end") {
|
|
41767
|
+
terminal = true;
|
|
41768
|
+
log.inputTokens = event.usage.inputTokens;
|
|
41769
|
+
log.outputTokens = event.usage.outputTokens;
|
|
41770
|
+
log.cacheReadTokens = event.usage.cacheReadTokens;
|
|
41771
|
+
log.cacheWriteTokens = event.usage.cacheWriteTokens;
|
|
41772
|
+
log.costUsd = priceOf(candidate.target.costPerMTok, event.usage, candidate.target.provider);
|
|
41773
|
+
}
|
|
41774
|
+
if (event.type === "error") {
|
|
41775
|
+
terminal = true;
|
|
41776
|
+
if (!committed && RETRYABLE[event.code]) {
|
|
41777
|
+
throw new GatewayError(event.code, event.message);
|
|
41778
|
+
}
|
|
41779
|
+
committed = true;
|
|
41780
|
+
yield event;
|
|
41781
|
+
await persistHealth(recordFailure(healthFor(candidate), {
|
|
41782
|
+
settings: snapshot.settings,
|
|
41783
|
+
now: deps.now(),
|
|
41784
|
+
code: event.code,
|
|
41785
|
+
jitter: deps.rand()
|
|
41786
|
+
}));
|
|
41787
|
+
log.status = HTTP_STATUS[event.code];
|
|
41788
|
+
log.errorCode = event.code;
|
|
41789
|
+
log.durationMs = deps.now() - startedAt;
|
|
41790
|
+
return;
|
|
41791
|
+
}
|
|
41792
|
+
if (!committed && event.type !== "end") {
|
|
41793
|
+
pending.push(event);
|
|
41794
|
+
} else {
|
|
41795
|
+
if (!committed) {
|
|
41796
|
+
for (const buffered of pending)
|
|
41797
|
+
yield buffered;
|
|
41798
|
+
pending.length = 0;
|
|
41799
|
+
}
|
|
41800
|
+
yield event;
|
|
41801
|
+
}
|
|
41802
|
+
if (event.type === "end")
|
|
41803
|
+
break;
|
|
41804
|
+
}
|
|
41805
|
+
if (!terminal) {
|
|
41806
|
+
throw new GatewayError("UPSTREAM", "upstream stream ended without a terminal event");
|
|
41807
|
+
}
|
|
41808
|
+
await persistHealth(recordSuccess(healthFor(candidate), {
|
|
41809
|
+
settings: snapshot.settings,
|
|
41810
|
+
now: deps.now(),
|
|
41811
|
+
ttftMs: log.ttftMs
|
|
41812
|
+
}));
|
|
41813
|
+
log.status = 200;
|
|
41814
|
+
log.errorCode = null;
|
|
41815
|
+
log.durationMs = deps.now() - startedAt;
|
|
41816
|
+
return;
|
|
41817
|
+
} catch (error51) {
|
|
41818
|
+
if (signal.aborted)
|
|
41819
|
+
throw signal.reason;
|
|
41820
|
+
const classifiedError = deadlineAt !== null && dispatchSignal.aborted ? { code: "TIMEOUT" } : classify(error51);
|
|
41821
|
+
const { code: code2, retryAfterMs } = classifiedError;
|
|
41822
|
+
const message = error51 instanceof Error ? error51.message : "attempt failed";
|
|
41823
|
+
lastError = retryAfterMs === undefined ? new GatewayError(code2, message) : new GatewayError(code2, message, { retryAfterMs });
|
|
41824
|
+
if (code2 === "AUTH" && !committed && !authRefreshRetried && !preemptiveRefreshRequired && candidate.credential.authType === "oauth" && candidate.credential.hasRefreshToken) {
|
|
41825
|
+
authRefreshRetried = true;
|
|
41826
|
+
logger2.warn("attempt authentication failed; refreshing credential", {
|
|
41459
41827
|
requestId,
|
|
41460
41828
|
provider: candidate.target.provider,
|
|
41461
41829
|
model: candidate.target.model,
|
|
41462
41830
|
credentialId: candidate.credential.id,
|
|
41463
41831
|
attempt: i + 1,
|
|
41464
|
-
|
|
41832
|
+
code: code2
|
|
41465
41833
|
});
|
|
41466
|
-
|
|
41467
|
-
|
|
41468
|
-
|
|
41834
|
+
try {
|
|
41835
|
+
retrySecrets = await waitForCancellation(deps.refresh(candidate.credential), dispatchSignal);
|
|
41836
|
+
continue;
|
|
41837
|
+
} catch (refreshError) {
|
|
41838
|
+
if (signal.aborted)
|
|
41839
|
+
throw signal.reason;
|
|
41840
|
+
const classified = deadlineAt !== null && dispatchSignal.aborted ? { code: "TIMEOUT" } : classify(refreshError);
|
|
41841
|
+
const refreshMessage = refreshError instanceof Error ? refreshError.message : "credential refresh failed";
|
|
41842
|
+
lastError = classified.retryAfterMs === undefined ? new GatewayError(classified.code, refreshMessage) : new GatewayError(classified.code, refreshMessage, {
|
|
41843
|
+
retryAfterMs: classified.retryAfterMs
|
|
41844
|
+
});
|
|
41845
|
+
}
|
|
41469
41846
|
}
|
|
41470
|
-
|
|
41471
|
-
|
|
41472
|
-
|
|
41473
|
-
|
|
41474
|
-
|
|
41475
|
-
|
|
41476
|
-
|
|
41847
|
+
const failure = lastError;
|
|
41848
|
+
await persistHealth(recordFailure(healthFor(candidate), {
|
|
41849
|
+
settings: snapshot.settings,
|
|
41850
|
+
now: deps.now(),
|
|
41851
|
+
code: failure.code,
|
|
41852
|
+
...failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs },
|
|
41853
|
+
jitter: deps.rand()
|
|
41854
|
+
}));
|
|
41855
|
+
if (!committed && RETRYABLE[failure.code] && i + 1 < maxAttempts) {
|
|
41856
|
+
logger2.warn("attempt failed; retrying", {
|
|
41857
|
+
requestId,
|
|
41858
|
+
provider: candidate.target.provider,
|
|
41859
|
+
model: candidate.target.model,
|
|
41860
|
+
credentialId: candidate.credential.id,
|
|
41861
|
+
attempt: i + 1,
|
|
41862
|
+
code: failure.code,
|
|
41863
|
+
retryable: true,
|
|
41864
|
+
retryAfterMs: failure.retryAfterMs
|
|
41865
|
+
});
|
|
41477
41866
|
}
|
|
41478
|
-
if (
|
|
41479
|
-
|
|
41480
|
-
|
|
41481
|
-
throw new GatewayError(event.code, event.message);
|
|
41482
|
-
}
|
|
41483
|
-
committed = true;
|
|
41484
|
-
yield event;
|
|
41485
|
-
await persistHealth(recordFailure(healthFor(candidate), {
|
|
41486
|
-
settings: snapshot.settings,
|
|
41487
|
-
now: deps.now(),
|
|
41488
|
-
code: event.code,
|
|
41489
|
-
jitter: deps.rand()
|
|
41490
|
-
}));
|
|
41491
|
-
log.status = HTTP_STATUS[event.code];
|
|
41492
|
-
log.errorCode = event.code;
|
|
41867
|
+
if (committed) {
|
|
41868
|
+
log.status = HTTP_STATUS[failure.code];
|
|
41869
|
+
log.errorCode = failure.code;
|
|
41493
41870
|
log.durationMs = deps.now() - startedAt;
|
|
41871
|
+
yield {
|
|
41872
|
+
type: "error",
|
|
41873
|
+
code: failure.code,
|
|
41874
|
+
message: failure.message,
|
|
41875
|
+
retryable: false
|
|
41876
|
+
};
|
|
41494
41877
|
return;
|
|
41495
41878
|
}
|
|
41496
|
-
if (!
|
|
41497
|
-
|
|
41498
|
-
|
|
41499
|
-
if (!committed) {
|
|
41500
|
-
for (const buffered of pending)
|
|
41501
|
-
yield buffered;
|
|
41502
|
-
pending.length = 0;
|
|
41503
|
-
}
|
|
41504
|
-
yield event;
|
|
41505
|
-
}
|
|
41506
|
-
if (event.type === "end")
|
|
41507
|
-
break;
|
|
41508
|
-
}
|
|
41509
|
-
if (!terminal) {
|
|
41510
|
-
throw new GatewayError("UPSTREAM", "upstream stream ended without a terminal event");
|
|
41511
|
-
}
|
|
41512
|
-
await persistHealth(recordSuccess(healthFor(candidate), {
|
|
41513
|
-
settings: snapshot.settings,
|
|
41514
|
-
now: deps.now(),
|
|
41515
|
-
ttftMs: log.ttftMs
|
|
41516
|
-
}));
|
|
41517
|
-
log.status = 200;
|
|
41518
|
-
log.errorCode = null;
|
|
41519
|
-
log.durationMs = deps.now() - startedAt;
|
|
41520
|
-
return;
|
|
41521
|
-
} catch (error51) {
|
|
41522
|
-
if (signal.aborted)
|
|
41523
|
-
throw signal.reason;
|
|
41524
|
-
const classifiedError = deadlineAt !== null && dispatchSignal.aborted ? { code: "TIMEOUT" } : classify(error51);
|
|
41525
|
-
const { code: code2, retryAfterMs } = classifiedError;
|
|
41526
|
-
const message = error51 instanceof Error ? error51.message : "attempt failed";
|
|
41527
|
-
lastError = retryAfterMs === undefined ? new GatewayError(code2, message) : new GatewayError(code2, message, { retryAfterMs });
|
|
41528
|
-
if (code2 === "AUTH" && !committed && !authRefreshRetried && !preemptiveRefreshRequired && candidate.credential.authType === "oauth" && candidate.credential.hasRefreshToken) {
|
|
41529
|
-
authRefreshRetried = true;
|
|
41530
|
-
logger2.warn("attempt authentication failed; refreshing credential", {
|
|
41531
|
-
requestId,
|
|
41532
|
-
provider: candidate.target.provider,
|
|
41533
|
-
model: candidate.target.model,
|
|
41534
|
-
credentialId: candidate.credential.id,
|
|
41535
|
-
attempt: i + 1,
|
|
41536
|
-
code: code2
|
|
41537
|
-
});
|
|
41538
|
-
try {
|
|
41539
|
-
retrySecrets = await waitForCancellation(deps.refresh(candidate.credential), dispatchSignal);
|
|
41540
|
-
continue;
|
|
41541
|
-
} catch (refreshError) {
|
|
41542
|
-
if (signal.aborted)
|
|
41543
|
-
throw signal.reason;
|
|
41544
|
-
const classified = deadlineAt !== null && dispatchSignal.aborted ? { code: "TIMEOUT" } : classify(refreshError);
|
|
41545
|
-
const refreshMessage = refreshError instanceof Error ? refreshError.message : "credential refresh failed";
|
|
41546
|
-
lastError = classified.retryAfterMs === undefined ? new GatewayError(classified.code, refreshMessage) : new GatewayError(classified.code, refreshMessage, {
|
|
41547
|
-
retryAfterMs: classified.retryAfterMs
|
|
41548
|
-
});
|
|
41549
|
-
}
|
|
41550
|
-
}
|
|
41551
|
-
const failure = lastError;
|
|
41552
|
-
await persistHealth(recordFailure(healthFor(candidate), {
|
|
41553
|
-
settings: snapshot.settings,
|
|
41554
|
-
now: deps.now(),
|
|
41555
|
-
code: failure.code,
|
|
41556
|
-
...failure.retryAfterMs === undefined ? {} : { retryAfterMs: failure.retryAfterMs },
|
|
41557
|
-
jitter: deps.rand()
|
|
41558
|
-
}));
|
|
41559
|
-
if (!committed && RETRYABLE[failure.code] && i + 1 < maxAttempts) {
|
|
41560
|
-
logger2.warn("attempt failed; retrying", {
|
|
41561
|
-
requestId,
|
|
41562
|
-
provider: candidate.target.provider,
|
|
41563
|
-
model: candidate.target.model,
|
|
41564
|
-
credentialId: candidate.credential.id,
|
|
41565
|
-
attempt: i + 1,
|
|
41566
|
-
code: failure.code,
|
|
41567
|
-
retryable: true,
|
|
41568
|
-
retryAfterMs: failure.retryAfterMs
|
|
41569
|
-
});
|
|
41570
|
-
}
|
|
41571
|
-
if (committed) {
|
|
41572
|
-
log.status = HTTP_STATUS[failure.code];
|
|
41573
|
-
log.errorCode = failure.code;
|
|
41574
|
-
log.durationMs = deps.now() - startedAt;
|
|
41575
|
-
yield {
|
|
41576
|
-
type: "error",
|
|
41577
|
-
code: failure.code,
|
|
41578
|
-
message: failure.message,
|
|
41579
|
-
retryable: false
|
|
41580
|
-
};
|
|
41581
|
-
return;
|
|
41879
|
+
if (!RETRYABLE[failure.code])
|
|
41880
|
+
break candidateLoop;
|
|
41881
|
+
continue candidateLoop;
|
|
41582
41882
|
}
|
|
41583
|
-
if (!RETRYABLE[failure.code])
|
|
41584
|
-
break candidateLoop;
|
|
41585
|
-
continue candidateLoop;
|
|
41586
41883
|
}
|
|
41884
|
+
} finally {
|
|
41885
|
+
releaseSlot();
|
|
41587
41886
|
}
|
|
41588
41887
|
}
|
|
41589
41888
|
const code = lastError?.code === "TIMEOUT" ? "TIMEOUT" : lastError !== null && !RETRYABLE[lastError.code] ? lastError.code : "ALL_CANDIDATES_FAILED";
|
|
@@ -41598,11 +41897,40 @@ async function dispatch(request2, deps, signal, requestId) {
|
|
|
41598
41897
|
};
|
|
41599
41898
|
} finally {
|
|
41600
41899
|
clearDeadline();
|
|
41900
|
+
eager.release();
|
|
41601
41901
|
if (!dispatchController.signal.aborted)
|
|
41602
41902
|
dispatchController.abort();
|
|
41603
41903
|
}
|
|
41604
41904
|
}
|
|
41605
|
-
|
|
41905
|
+
const inner = run();
|
|
41906
|
+
const events = {
|
|
41907
|
+
next: (...args) => inner.next(...args),
|
|
41908
|
+
return: async (value) => {
|
|
41909
|
+
try {
|
|
41910
|
+
return await inner.return(value);
|
|
41911
|
+
} finally {
|
|
41912
|
+
eager.release();
|
|
41913
|
+
}
|
|
41914
|
+
},
|
|
41915
|
+
throw: async (error51) => {
|
|
41916
|
+
try {
|
|
41917
|
+
return await inner.throw(error51);
|
|
41918
|
+
} finally {
|
|
41919
|
+
eager.release();
|
|
41920
|
+
}
|
|
41921
|
+
},
|
|
41922
|
+
[Symbol.asyncIterator]() {
|
|
41923
|
+
return this;
|
|
41924
|
+
},
|
|
41925
|
+
async[Symbol.asyncDispose]() {
|
|
41926
|
+
try {
|
|
41927
|
+
await inner[Symbol.asyncDispose]();
|
|
41928
|
+
} finally {
|
|
41929
|
+
eager.release();
|
|
41930
|
+
}
|
|
41931
|
+
}
|
|
41932
|
+
};
|
|
41933
|
+
return { events, log: () => log };
|
|
41606
41934
|
}
|
|
41607
41935
|
function waitForCancellation(promise2, signal) {
|
|
41608
41936
|
if (signal.aborted)
|
|
@@ -41620,6 +41948,35 @@ function waitForCancellation(promise2, signal) {
|
|
|
41620
41948
|
});
|
|
41621
41949
|
}
|
|
41622
41950
|
|
|
41951
|
+
// apps/gateway/src/dispatch/loadRegistry.ts
|
|
41952
|
+
function createLoadRegistry(logger2 = noopLogger) {
|
|
41953
|
+
const counts = new Map;
|
|
41954
|
+
return {
|
|
41955
|
+
acquire(credentialId, model) {
|
|
41956
|
+
const key = healthKey(credentialId, model);
|
|
41957
|
+
counts.set(key, (counts.get(key) ?? 0) + 1);
|
|
41958
|
+
let released = false;
|
|
41959
|
+
return () => {
|
|
41960
|
+
if (released)
|
|
41961
|
+
return;
|
|
41962
|
+
released = true;
|
|
41963
|
+
const next = (counts.get(key) ?? 0) - 1;
|
|
41964
|
+
if (next > 0) {
|
|
41965
|
+
counts.set(key, next);
|
|
41966
|
+
return;
|
|
41967
|
+
}
|
|
41968
|
+
if (next < 0) {
|
|
41969
|
+
logger2.warn("load registry released more than it acquired", { credentialId });
|
|
41970
|
+
}
|
|
41971
|
+
counts.delete(key);
|
|
41972
|
+
};
|
|
41973
|
+
},
|
|
41974
|
+
counts() {
|
|
41975
|
+
return new Map(counts);
|
|
41976
|
+
}
|
|
41977
|
+
};
|
|
41978
|
+
}
|
|
41979
|
+
|
|
41623
41980
|
// apps/gateway/src/egress/anthropic.ts
|
|
41624
41981
|
var STOP_REASON2 = {
|
|
41625
41982
|
endTurn: "end_turn",
|
|
@@ -41635,6 +41992,7 @@ var ERROR_TYPE2 = {
|
|
|
41635
41992
|
QUOTA_EXHAUSTED: "rate_limit_error",
|
|
41636
41993
|
OVERLOADED: "overloaded_error",
|
|
41637
41994
|
BAD_REQUEST: "invalid_request_error",
|
|
41995
|
+
CONFLICT: "invalid_request_error",
|
|
41638
41996
|
CONTENT_FILTER: "invalid_request_error",
|
|
41639
41997
|
CAPABILITY_MISMATCH: "invalid_request_error",
|
|
41640
41998
|
MODEL_UNAVAILABLE: "not_found_error",
|
|
@@ -41797,6 +42155,7 @@ var ERROR_TYPE3 = {
|
|
|
41797
42155
|
QUOTA_EXHAUSTED: { type: "insufficient_quota", code: "insufficient_quota" },
|
|
41798
42156
|
OVERLOADED: { type: "server_error", code: "server_error" },
|
|
41799
42157
|
BAD_REQUEST: { type: "invalid_request_error", code: "invalid_request" },
|
|
42158
|
+
CONFLICT: { type: "invalid_request_error", code: "conflict" },
|
|
41800
42159
|
CONTENT_FILTER: { type: "invalid_request_error", code: "content_policy_violation" },
|
|
41801
42160
|
CAPABILITY_MISMATCH: { type: "invalid_request_error", code: "invalid_request" },
|
|
41802
42161
|
MODEL_UNAVAILABLE: { type: "invalid_request_error", code: "model_not_found" },
|
|
@@ -42087,12 +42446,12 @@ function mcpServerNames(body2) {
|
|
|
42087
42446
|
|
|
42088
42447
|
// apps/gateway/src/ingress/model.ts
|
|
42089
42448
|
var DISCOVERY_PREFIX = "claude/";
|
|
42090
|
-
var
|
|
42449
|
+
var ONE_M_SUFFIX2 = "[1m]";
|
|
42091
42450
|
function normalizeClientModel(raw, betas2 = []) {
|
|
42092
42451
|
let model = raw.trim();
|
|
42093
42452
|
let wantsOneM = false;
|
|
42094
|
-
if (model.toLowerCase().endsWith(
|
|
42095
|
-
const stripped = model.slice(0, -
|
|
42453
|
+
if (model.toLowerCase().endsWith(ONE_M_SUFFIX2)) {
|
|
42454
|
+
const stripped = model.slice(0, -ONE_M_SUFFIX2.length).trim();
|
|
42096
42455
|
if (stripped.length > 0) {
|
|
42097
42456
|
model = stripped;
|
|
42098
42457
|
wantsOneM = true;
|
|
@@ -42443,10 +42802,10 @@ function toIrToolChoice(c) {
|
|
|
42443
42802
|
}
|
|
42444
42803
|
var EFFORTS = ["low", "medium", "high", "xhigh", "max"];
|
|
42445
42804
|
function readEffort(body2) {
|
|
42446
|
-
if (!
|
|
42805
|
+
if (!isRecord4(body2))
|
|
42447
42806
|
return;
|
|
42448
42807
|
const outputConfig = body2.output_config;
|
|
42449
|
-
if (!
|
|
42808
|
+
if (!isRecord4(outputConfig))
|
|
42450
42809
|
return;
|
|
42451
42810
|
const effort = outputConfig.effort;
|
|
42452
42811
|
return EFFORTS.find((level) => level === effort);
|
|
@@ -42780,7 +43139,7 @@ function asGatewayError(error51) {
|
|
|
42780
43139
|
return error51;
|
|
42781
43140
|
return new GatewayError("INTERNAL", error51 instanceof Error ? error51.message : "internal error");
|
|
42782
43141
|
}
|
|
42783
|
-
function sseResponse(frames, onDone, keepaliveMs) {
|
|
43142
|
+
function sseResponse(frames, onDone, keepaliveMs, source) {
|
|
42784
43143
|
const encoder2 = new TextEncoder;
|
|
42785
43144
|
let done = null;
|
|
42786
43145
|
const runOnce = (cancelled = false) => {
|
|
@@ -42818,6 +43177,7 @@ data: ${data}
|
|
|
42818
43177
|
},
|
|
42819
43178
|
async cancel() {
|
|
42820
43179
|
await frames.return(undefined);
|
|
43180
|
+
await source.return(undefined);
|
|
42821
43181
|
await runOnce(true);
|
|
42822
43182
|
}
|
|
42823
43183
|
});
|
|
@@ -42895,7 +43255,7 @@ async function handle(deps, rateLimiter, surface, request2) {
|
|
|
42895
43255
|
};
|
|
42896
43256
|
if (chatRequest.stream) {
|
|
42897
43257
|
const frames = surface === "anthropic" ? anthropicStream(outcome.events, requestId) : openaiStream(outcome.events, requestId, Math.floor(deps.now() / 1000));
|
|
42898
|
-
return sseResponse(frames, log, deps.keepaliveMs);
|
|
43258
|
+
return sseResponse(frames, log, deps.keepaliveMs, outcome.events);
|
|
42899
43259
|
}
|
|
42900
43260
|
const events = [];
|
|
42901
43261
|
for await (const event of outcome.events)
|
|
@@ -42936,6 +43296,7 @@ function proxyRoutes(deps) {
|
|
|
42936
43296
|
...deps,
|
|
42937
43297
|
logger: logger2,
|
|
42938
43298
|
snapshots: deps.snapshots ?? createRoutingSnapshotCache(deps.store, logger2),
|
|
43299
|
+
loadRegistry: deps.loadRegistry ?? createLoadRegistry(logger2),
|
|
42939
43300
|
keepaliveMs: deps.keepaliveMs ?? KEEPALIVE_MS
|
|
42940
43301
|
};
|
|
42941
43302
|
return new Elysia().post("/v1/messages", ({ request: request2, server }) => {
|
|
@@ -43029,6 +43390,7 @@ function createApp(deps) {
|
|
|
43029
43390
|
requestId,
|
|
43030
43391
|
rateLimiter,
|
|
43031
43392
|
logger: logger2,
|
|
43393
|
+
...deps.loadRegistry === undefined ? {} : { loadRegistry: deps.loadRegistry },
|
|
43032
43394
|
discoveryMirrors: deps.discoveryMirrors === true
|
|
43033
43395
|
})).use(adminRoutes({
|
|
43034
43396
|
store: deps.store,
|