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.
Files changed (42) hide show
  1. package/bin/omni.js +527 -228
  2. package/gateway.js +763 -401
  3. package/package.json +1 -1
  4. package/public/assets/{Chip-BB_5C1Zp.js → Chip-C4X8tf5z.js} +1 -1
  5. package/public/assets/Confirm-BnmH8Gn0.js +4 -0
  6. package/public/assets/{CopyValue-CRQDLo7k.js → CopyValue-B-uR4Js-.js} +5 -5
  7. package/public/assets/{Field-uHZxl4fI.js → Field-Ct4SeDZD.js} +2 -2
  8. package/public/assets/{Lamp-B-5SjXbG.js → Lamp-CBOwqG5K.js} +7 -7
  9. package/public/assets/{Meter-DI_BRUKt.js → Meter-COdz0dwY.js} +1 -1
  10. package/public/assets/{Modal-CI6jk2D4.js → Modal-DMFUHQ-A.js} +8 -8
  11. package/public/assets/{Rack-D1WJswv3.js → Rack-DAbY9x1I.js} +18 -18
  12. package/public/assets/{Readout-BocZ2HXP.js → Readout-Bl2kD9Tl.js} +6 -2
  13. package/public/assets/{States-Bbiu5cHE.js → States-D5Memn8D.js} +4 -4
  14. package/public/assets/{Table-CdPWxYaz.js → Table-DObRatbW.js} +1 -1
  15. package/public/assets/{Toggle-CiLC67Dw.js → Toggle-Dkjlvck5.js} +1 -1
  16. package/public/assets/{TokenBreakdown-B96iPBm9.js → TokenBreakdown-oXP49ZTa.js} +5 -2
  17. package/public/assets/_app-Brc2wQZm.js +1 -0
  18. package/public/assets/_app.accounts-I9RXUiyi.js +54 -0
  19. package/public/assets/{_app.console-Daz4aKhf.js → _app.console-BVVpryIB.js} +10 -10
  20. package/public/assets/_app.index-83Ig4FUL.js +62 -0
  21. package/public/assets/_app.keys-KeP6L4g_.js +39 -0
  22. package/public/assets/_app.logs-B5oh01Ls.js +32 -0
  23. package/public/assets/_app.models-BuOFMZUN.js +144 -0
  24. package/public/assets/{_app.settings-a0FKyQAi.js → _app.settings-BN436vI2.js} +4 -4
  25. package/public/assets/_app.usage-C528VOia.js +166 -0
  26. package/public/assets/catalog-kJ53n_fc.js +1 -0
  27. package/public/assets/{dist-C-IbPRiV.js → dist-CnzT-Ut2.js} +1 -1
  28. package/public/assets/index-CE-KQ-ju.js +172 -0
  29. package/public/assets/{login-CTvH_KAd.js → login-2LpCZOtB.js} +8 -8
  30. package/public/assets/{queries-D2o-X8Pj.js → queries-1zRLkX-Q.js} +27 -27
  31. package/public/assets/{trash-2-BcZb-sCT.js → trash-2-Da6cLgPn.js} +1 -1
  32. package/public/index.html +2 -2
  33. package/public/assets/Confirm-B6aAiVbT.js +0 -4
  34. package/public/assets/_app-BBOF6A0T.js +0 -1
  35. package/public/assets/_app.accounts-BNkhpvaB.js +0 -54
  36. package/public/assets/_app.index-LO6d38oe.js +0 -62
  37. package/public/assets/_app.keys-kBqLqoFf.js +0 -39
  38. package/public/assets/_app.logs-_wYNS47N.js +0 -32
  39. package/public/assets/_app.models-Cjng8ohC.js +0 -144
  40. package/public/assets/_app.usage-D3KtgLrC.js +0 -166
  41. package/public/assets/catalog-C_OQ0icG.js +0 -1
  42. 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 = { ...body.output_config ?? {}, effort: req.reasoning.effort };
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
- Object.assign(body, req.vendor?.anthropic ?? {});
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?.kimi ?? {});
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 isRecord(value) {
7793
+ function isRecord2(value) {
7663
7794
  return typeof value === "object" && value !== null;
7664
7795
  }
7665
7796
  function recordFrom(value) {
7666
- return isRecord(value) ? value : null;
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(metadata) {
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, ...metadata });
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 modelSchema = exports_external.object({
22349
- id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
22350
- message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
22351
- }),
22352
- strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
22353
- isAlias: exports_external.boolean(),
22354
- targets: exports_external.array(exports_external.object({
22355
- provider: providerIdSchema,
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
- })).min(1, "a virtual model needs at least one target")
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
- recency: exports_external.number()
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 blendedCost(input, output) {
22619
- return input * 0.25 + output * 0.75;
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) => blendedCost(p.target.costPerMTok.input, p.target.costPerMTok.output));
22628
- const minCost = Math.min(...costs);
22629
- const maxCost = Math.max(...costs);
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 h = snapshot.health.get(healthKey(pair.credential.id, pair.target.model));
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
- let health = 1 / (1 + (h?.consecutiveFailures ?? 0));
22646
- if (h?.breakerState === "open" || h?.breakerState === "halfOpen")
22647
- health *= 0.5;
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 cost = maxCost === 0 ? UNKNOWN : lowerIsBetter(costs[i], minCost, maxCost);
22650
- const latency = h?.ewmaTtftMs == null ? UNKNOWN : lowerIsBetter(h.ewmaTtftMs, minLatency, maxLatency);
22651
- const idle = idleTimes[i];
22652
- const recency = Number.isFinite(idle) ? Math.min(1, idle / (maxIdle || 1)) : 1;
22653
- const reasons = { tier, health, quota, cost, latency, recency };
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: { ...DEFAULT_SETTINGS.weights, ...stored.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 isRecord2(value) {
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 (!isRecord2(json5))
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: isRecord2(auth) ? nonBlankStringOrNull(auth.chatgpt_account_id) : null
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 (!isRecord2(value) || typeof value.access_token !== "string")
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.usage === undefined)
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].usage !== undefined && (cooldowns.get(c.id) ?? 0) <= now);
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 isRecord3(value) {
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 isRecord3(body2) ? body2 : null;
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 = 500;
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.cacheRead ?? prices.input * READ_OVER_INPUT;
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
- log.attempts = i + 1;
41410
- log.credentialId = candidate.credential.id;
41411
- log.resolvedProvider = candidate.target.provider;
41412
- log.resolvedModel = candidate.target.model;
41413
- await deps.onRoute?.({
41414
- provider: candidate.target.provider,
41415
- model: candidate.target.model,
41416
- credentialId: candidate.credential.id
41417
- });
41418
- logger2.debug("attempt started", {
41419
- requestId,
41420
- provider: candidate.target.provider,
41421
- model: candidate.target.model,
41422
- credentialId: candidate.credential.id,
41423
- attempt: i + 1
41424
- });
41425
- log.inputTokens = 0;
41426
- log.outputTokens = 0;
41427
- log.cacheReadTokens = 0;
41428
- log.cacheWriteTokens = 0;
41429
- log.ttftMs = null;
41430
- let committed = false;
41431
- let authRefreshRetried = false;
41432
- let retrySecrets;
41433
- const attemptNow = deps.now();
41434
- const preemptiveRefreshRequired = candidate.credential.authType === "oauth" && candidate.credential.expiresAt !== null && candidate.credential.expiresAt - DISPATCH_REFRESH_LEAD_MS <= attemptNow;
41435
- while (true) {
41436
- try {
41437
- const result = await waitForCancellation(attempt({
41438
- candidate,
41439
- request: dispatchRequest,
41440
- adapter: deps.adapters[candidate.target.provider],
41441
- http: deps.http,
41442
- now: attemptNow,
41443
- signal: dispatchSignal,
41444
- refresh: (credential) => waitForCancellation(deps.refresh(credential), dispatchSignal),
41445
- refreshLeadMs: DISPATCH_REFRESH_LEAD_MS,
41446
- ...retrySecrets === undefined ? {} : { secrets: retrySecrets },
41447
- logger: logger2,
41448
- requestId
41449
- }), dispatchSignal);
41450
- for (const d of result.degradations)
41451
- log.degradations.push(d);
41452
- let terminal = false;
41453
- const pending = [];
41454
- for await (const event of result.events) {
41455
- if (event.type === "blockDelta" && !committed) {
41456
- committed = true;
41457
- log.ttftMs = deps.now() - startedAt;
41458
- logger2.debug("stream committed", {
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
- ttftMs: log.ttftMs
41832
+ code: code2
41465
41833
  });
41466
- for (const buffered of pending)
41467
- yield buffered;
41468
- pending.length = 0;
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
- if (event.type === "end") {
41471
- terminal = true;
41472
- log.inputTokens = event.usage.inputTokens;
41473
- log.outputTokens = event.usage.outputTokens;
41474
- log.cacheReadTokens = event.usage.cacheReadTokens;
41475
- log.cacheWriteTokens = event.usage.cacheWriteTokens;
41476
- log.costUsd = priceOf(candidate.target.costPerMTok, event.usage, candidate.target.provider);
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 (event.type === "error") {
41479
- terminal = true;
41480
- if (!committed && RETRYABLE[event.code]) {
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 (!committed && event.type !== "end") {
41497
- pending.push(event);
41498
- } else {
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
- return { events: run(), log: () => log };
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 ONE_M_SUFFIX = "[1m]";
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(ONE_M_SUFFIX)) {
42095
- const stripped = model.slice(0, -ONE_M_SUFFIX.length).trim();
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 (!isRecord3(body2))
42805
+ if (!isRecord4(body2))
42447
42806
  return;
42448
42807
  const outputConfig = body2.output_config;
42449
- if (!isRecord3(outputConfig))
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,