omnigateway 0.1.8 → 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/README.md +27 -33
- package/bin/omni.js +1133 -305
- package/gateway.js +3632 -543
- package/package.json +1 -1
- package/public/assets/{Chip-CM9ZMdr9.js → Chip-C4X8tf5z.js} +1 -1
- package/public/assets/Confirm-BnmH8Gn0.js +4 -0
- package/public/assets/{CopyValue-WrOTcHwq.js → CopyValue-B-uR4Js-.js} +5 -5
- package/public/assets/{Field-Br6jKPz8.js → Field-Ct4SeDZD.js} +2 -2
- package/public/assets/Lamp-CBOwqG5K.js +25 -0
- package/public/assets/{Meter-BzMaum0C.js → Meter-COdz0dwY.js} +1 -1
- package/public/assets/Modal-DMFUHQ-A.js +82 -0
- package/public/assets/{Rack-BFWC50ex.js → Rack-DAbY9x1I.js} +23 -19
- package/public/assets/{Readout-DMJs4gYD.js → Readout-Bl2kD9Tl.js} +6 -2
- package/public/assets/{States-BNeCLhZn.js → States-D5Memn8D.js} +4 -4
- package/public/assets/{Table-nOg_lgEJ.js → Table-DObRatbW.js} +1 -1
- package/public/assets/Toggle-Dkjlvck5.js +39 -0
- package/public/assets/TokenBreakdown-oXP49ZTa.js +31 -0
- package/public/assets/_app-Brc2wQZm.js +1 -0
- package/public/assets/_app.accounts-I9RXUiyi.js +54 -0
- package/public/assets/_app.console-BVVpryIB.js +44 -0
- 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-BN436vI2.js +38 -0
- package/public/assets/_app.usage-C528VOia.js +166 -0
- package/public/assets/catalog-kJ53n_fc.js +1 -0
- package/public/assets/dist-CnzT-Ut2.js +1 -0
- package/public/assets/index-CE-KQ-ju.js +172 -0
- package/public/assets/{login-BpUl6C5b.js → login-2LpCZOtB.js} +8 -8
- package/public/assets/queries-1zRLkX-Q.js +144 -0
- package/public/assets/{trash-2-DjWP2u19.js → trash-2-Da6cLgPn.js} +1 -1
- package/public/index.html +2 -2
- package/public/assets/Lamp-DO-sRDg2.js +0 -25
- package/public/assets/Modal-lhjr949H.js +0 -82
- package/public/assets/Toggle-DYoCVD37.js +0 -42
- package/public/assets/_app-lXr06Xli.js +0 -1
- package/public/assets/_app.accounts-BTb5vHI_.js +0 -51
- package/public/assets/_app.console-DHUClMsI.js +0 -25
- package/public/assets/_app.index-DZ--9mKU.js +0 -61
- package/public/assets/_app.keys-B-cy-2DS.js +0 -39
- package/public/assets/_app.logs-BQjAIT2P.js +0 -33
- package/public/assets/_app.models-BAmXHlf2.js +0 -144
- package/public/assets/_app.settings-CG8bv_rm.js +0 -34
- package/public/assets/_app.usage-BLrQ2hB8.js +0 -163
- package/public/assets/catalog-C_OQ0icG.js +0 -1
- package/public/assets/index-UAZ0O5y0.js +0 -170
- package/public/assets/queries-D_-Jj9vt.js +0 -144
package/bin/omni.js
CHANGED
|
@@ -91,9 +91,8 @@ function formatUsd(value) {
|
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// apps/cli/src/run.ts
|
|
94
|
-
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
94
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
95
95
|
import { homedir as homedir3 } from "os";
|
|
96
|
-
import { dirname as dirname3 } from "path";
|
|
97
96
|
|
|
98
97
|
// packages/control/src/adminAuth.ts
|
|
99
98
|
import { hash, verify } from "@node-rs/argon2";
|
|
@@ -153,10 +152,17 @@ function createAdminAuth(store, opts) {
|
|
|
153
152
|
};
|
|
154
153
|
}
|
|
155
154
|
// packages/ir/src/capabilities.ts
|
|
155
|
+
var ANTHROPIC_NATIVE_TOOLS = {
|
|
156
|
+
anthropic: true,
|
|
157
|
+
openai: false,
|
|
158
|
+
kimi: false,
|
|
159
|
+
custom: false
|
|
160
|
+
};
|
|
156
161
|
var PROVIDER_CAPABILITIES = {
|
|
157
162
|
anthropic: { tools: true, images: true, reasoning: true },
|
|
158
163
|
openai: { tools: true, images: true, reasoning: true },
|
|
159
|
-
kimi: { tools: true, images: false, reasoning: false }
|
|
164
|
+
kimi: { tools: true, images: false, reasoning: false },
|
|
165
|
+
custom: { tools: true, images: true, reasoning: true }
|
|
160
166
|
};
|
|
161
167
|
// packages/ir/src/errors.ts
|
|
162
168
|
var RETRYABLE = {
|
|
@@ -170,6 +176,7 @@ var RETRYABLE = {
|
|
|
170
176
|
TIMEOUT: true,
|
|
171
177
|
NETWORK: true,
|
|
172
178
|
BAD_REQUEST: false,
|
|
179
|
+
CONFLICT: false,
|
|
173
180
|
CONTENT_FILTER: false,
|
|
174
181
|
NO_CANDIDATES: false,
|
|
175
182
|
ALL_CANDIDATES_FAILED: false,
|
|
@@ -236,6 +243,82 @@ function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cache
|
|
|
236
243
|
cacheWriteTokens
|
|
237
244
|
};
|
|
238
245
|
}
|
|
246
|
+
// packages/ir/src/tokens.ts
|
|
247
|
+
var CHARS_PER_TOKEN = 4;
|
|
248
|
+
var IMAGE_TOKENS = 1600;
|
|
249
|
+
var BLOCK_OVERHEAD = 4;
|
|
250
|
+
var MESSAGE_OVERHEAD = 4;
|
|
251
|
+
function fromText(text) {
|
|
252
|
+
return Math.ceil(text.length / CHARS_PER_TOKEN);
|
|
253
|
+
}
|
|
254
|
+
function blockTokens(block) {
|
|
255
|
+
switch (block.type) {
|
|
256
|
+
case "text":
|
|
257
|
+
return BLOCK_OVERHEAD + fromText(block.text);
|
|
258
|
+
case "image":
|
|
259
|
+
return BLOCK_OVERHEAD + IMAGE_TOKENS;
|
|
260
|
+
case "thinking":
|
|
261
|
+
return BLOCK_OVERHEAD + fromText(block.text);
|
|
262
|
+
case "toolUse":
|
|
263
|
+
return BLOCK_OVERHEAD + fromText(block.name) + fromText(safeJson(block.input));
|
|
264
|
+
case "toolResult":
|
|
265
|
+
return BLOCK_OVERHEAD + fromText(block.toolUseId) + fromText(block.content);
|
|
266
|
+
case "anthropicNative":
|
|
267
|
+
return BLOCK_OVERHEAD + fromText(block.blockType) + fromText(safeJson(block.data));
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
function messageTokens(message) {
|
|
271
|
+
let total = MESSAGE_OVERHEAD;
|
|
272
|
+
for (const block of message.content)
|
|
273
|
+
total += blockTokens(block);
|
|
274
|
+
return total;
|
|
275
|
+
}
|
|
276
|
+
function toolTokens(tool) {
|
|
277
|
+
if (tool.provider === "anthropic") {
|
|
278
|
+
return BLOCK_OVERHEAD + fromText(tool.name) + fromText(tool.type) + fromText(safeJson(tool.wire));
|
|
279
|
+
}
|
|
280
|
+
return BLOCK_OVERHEAD + fromText(tool.name) + fromText(tool.description ?? "") + fromText(safeJson(tool.inputSchema));
|
|
281
|
+
}
|
|
282
|
+
function safeJson(value) {
|
|
283
|
+
try {
|
|
284
|
+
return JSON.stringify(value) ?? "";
|
|
285
|
+
} catch {
|
|
286
|
+
return "";
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
function estimateInputTokens(request) {
|
|
290
|
+
let total = 0;
|
|
291
|
+
for (const block of request.system ?? [])
|
|
292
|
+
total += blockTokens(block);
|
|
293
|
+
for (const message of request.messages)
|
|
294
|
+
total += messageTokens(message);
|
|
295
|
+
for (const tool of request.tools ?? [])
|
|
296
|
+
total += toolTokens(tool);
|
|
297
|
+
return total;
|
|
298
|
+
}
|
|
299
|
+
function estimateCachedInputTokens(request) {
|
|
300
|
+
let running = 0;
|
|
301
|
+
let cached = 0;
|
|
302
|
+
for (const tool of request.tools ?? []) {
|
|
303
|
+
running += toolTokens(tool);
|
|
304
|
+
if (tool.cacheControl !== undefined)
|
|
305
|
+
cached = running;
|
|
306
|
+
}
|
|
307
|
+
for (const block of request.system ?? []) {
|
|
308
|
+
running += blockTokens(block);
|
|
309
|
+
if (cacheControlOf(block) !== undefined)
|
|
310
|
+
cached = running;
|
|
311
|
+
}
|
|
312
|
+
for (const message of request.messages) {
|
|
313
|
+
running += MESSAGE_OVERHEAD;
|
|
314
|
+
for (const block of message.content) {
|
|
315
|
+
running += blockTokens(block);
|
|
316
|
+
if (cacheControlOf(block) !== undefined)
|
|
317
|
+
cached = running;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return cached;
|
|
321
|
+
}
|
|
239
322
|
// packages/control/src/config.ts
|
|
240
323
|
var MIN_KEY_LENGTH = 16;
|
|
241
324
|
var TRUTHY = new Set(["1", "true", "yes", "on"]);
|
|
@@ -310,7 +393,8 @@ var BODY_ORDER = {
|
|
|
310
393
|
"parallel_tool_calls",
|
|
311
394
|
"metadata"
|
|
312
395
|
],
|
|
313
|
-
kimi: ["model", "messages", "tools", "tool_choice", "max_tokens", "temperature", "stream"]
|
|
396
|
+
kimi: ["model", "messages", "tools", "tool_choice", "max_tokens", "temperature", "stream"],
|
|
397
|
+
custom: []
|
|
314
398
|
};
|
|
315
399
|
function orderFields(obj, order) {
|
|
316
400
|
const out = {};
|
|
@@ -411,10 +495,20 @@ var ANTHROPIC_MODELS = {
|
|
|
411
495
|
id: "claude-haiku-4-5",
|
|
412
496
|
label: "Claude Haiku 4.5",
|
|
413
497
|
pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
|
|
414
|
-
limits: { contextWindow: 200000, maxOutputTokens: 64000 }
|
|
498
|
+
limits: { contextWindow: 200000, maxOutputTokens: 64000 },
|
|
499
|
+
reasoningForm: "budget"
|
|
415
500
|
}
|
|
416
501
|
]
|
|
417
502
|
};
|
|
503
|
+
var ONE_M_SUFFIX = "[1m]";
|
|
504
|
+
var DATED_SUFFIX = /-\d{8}$/;
|
|
505
|
+
function anthropicReasoningForm(model) {
|
|
506
|
+
let id = model.trim();
|
|
507
|
+
if (id.toLowerCase().endsWith(ONE_M_SUFFIX))
|
|
508
|
+
id = id.slice(0, -ONE_M_SUFFIX.length).trim();
|
|
509
|
+
id = id.replace(DATED_SUFFIX, "");
|
|
510
|
+
return ANTHROPIC_MODELS.models.find((m) => m.id === id)?.reasoningForm ?? "adaptive";
|
|
511
|
+
}
|
|
418
512
|
|
|
419
513
|
// packages/providers/src/kimi/models.ts
|
|
420
514
|
var KIMI_MODELS = {
|
|
@@ -486,7 +580,8 @@ var OPENAI_MODELS = {
|
|
|
486
580
|
var PROVIDER_MODEL_CATALOG = {
|
|
487
581
|
anthropic: ANTHROPIC_MODELS,
|
|
488
582
|
openai: OPENAI_MODELS,
|
|
489
|
-
kimi: KIMI_MODELS
|
|
583
|
+
kimi: KIMI_MODELS,
|
|
584
|
+
custom: { defaultModel: "", models: [] }
|
|
490
585
|
};
|
|
491
586
|
function catalogPricing(provider, model) {
|
|
492
587
|
return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
|
|
@@ -699,7 +794,8 @@ var kimi = {
|
|
|
699
794
|
var PROFILES = {
|
|
700
795
|
anthropic: { ...anthropic, order: envOrder("OMNI_ORDER_ANTHROPIC", anthropic.order) },
|
|
701
796
|
openai: { ...openai, order: envOrder("OMNI_ORDER_OPENAI", openai.order) },
|
|
702
|
-
kimi: { ...kimi, order: envOrder("OMNI_ORDER_KIMI", kimi.order) }
|
|
797
|
+
kimi: { ...kimi, order: envOrder("OMNI_ORDER_KIMI", kimi.order) },
|
|
798
|
+
custom: { headers: [], order: [] }
|
|
703
799
|
};
|
|
704
800
|
|
|
705
801
|
// packages/providers/src/sse.ts
|
|
@@ -759,14 +855,232 @@ function parseRecord(record) {
|
|
|
759
855
|
`) };
|
|
760
856
|
}
|
|
761
857
|
|
|
858
|
+
// packages/providers/src/anthropic/tools.ts
|
|
859
|
+
var COMMON = ["cache_control", "strict", "defer_loading", "allowed_callers"];
|
|
860
|
+
var COMMON_WITH_EXAMPLES = [...COMMON, "input_examples"];
|
|
861
|
+
var searchDomains = ["max_uses", "allowed_domains", "blocked_domains"];
|
|
862
|
+
var ANTHROPIC_TOOL_SPECS = {
|
|
863
|
+
web_search_20250305: {
|
|
864
|
+
family: "webSearch",
|
|
865
|
+
name: "web_search",
|
|
866
|
+
required: [],
|
|
867
|
+
optional: [...COMMON, ...searchDomains, "user_location"]
|
|
868
|
+
},
|
|
869
|
+
web_search_20260209: {
|
|
870
|
+
family: "webSearch",
|
|
871
|
+
name: "web_search",
|
|
872
|
+
required: [],
|
|
873
|
+
optional: [...COMMON, ...searchDomains, "user_location"]
|
|
874
|
+
},
|
|
875
|
+
web_search_20260318: {
|
|
876
|
+
family: "webSearch",
|
|
877
|
+
name: "web_search",
|
|
878
|
+
required: [],
|
|
879
|
+
optional: [...COMMON, ...searchDomains, "user_location", "response_inclusion"]
|
|
880
|
+
},
|
|
881
|
+
web_fetch_20250910: {
|
|
882
|
+
family: "webFetch",
|
|
883
|
+
name: "web_fetch",
|
|
884
|
+
required: [],
|
|
885
|
+
optional: [...COMMON, ...searchDomains, "citations", "max_content_tokens"]
|
|
886
|
+
},
|
|
887
|
+
web_fetch_20260209: {
|
|
888
|
+
family: "webFetch",
|
|
889
|
+
name: "web_fetch",
|
|
890
|
+
required: [],
|
|
891
|
+
optional: [...COMMON, ...searchDomains, "citations", "max_content_tokens"]
|
|
892
|
+
},
|
|
893
|
+
web_fetch_20260309: {
|
|
894
|
+
family: "webFetch",
|
|
895
|
+
name: "web_fetch",
|
|
896
|
+
required: [],
|
|
897
|
+
optional: [...COMMON, ...searchDomains, "citations", "max_content_tokens", "use_cache"]
|
|
898
|
+
},
|
|
899
|
+
web_fetch_20260318: {
|
|
900
|
+
family: "webFetch",
|
|
901
|
+
name: "web_fetch",
|
|
902
|
+
required: [],
|
|
903
|
+
optional: [
|
|
904
|
+
...COMMON,
|
|
905
|
+
...searchDomains,
|
|
906
|
+
"citations",
|
|
907
|
+
"max_content_tokens",
|
|
908
|
+
"use_cache",
|
|
909
|
+
"response_inclusion"
|
|
910
|
+
]
|
|
911
|
+
},
|
|
912
|
+
code_execution_20250522: {
|
|
913
|
+
family: "codeExecution",
|
|
914
|
+
name: "code_execution",
|
|
915
|
+
required: [],
|
|
916
|
+
optional: [...COMMON]
|
|
917
|
+
},
|
|
918
|
+
code_execution_20250825: {
|
|
919
|
+
family: "codeExecution",
|
|
920
|
+
name: "code_execution",
|
|
921
|
+
required: [],
|
|
922
|
+
optional: [...COMMON]
|
|
923
|
+
},
|
|
924
|
+
code_execution_20260120: {
|
|
925
|
+
family: "codeExecution",
|
|
926
|
+
name: "code_execution",
|
|
927
|
+
required: [],
|
|
928
|
+
optional: [...COMMON]
|
|
929
|
+
},
|
|
930
|
+
code_execution_20260521: {
|
|
931
|
+
family: "codeExecution",
|
|
932
|
+
name: "code_execution",
|
|
933
|
+
required: [],
|
|
934
|
+
optional: [...COMMON]
|
|
935
|
+
},
|
|
936
|
+
bash_20241022: {
|
|
937
|
+
family: "bash",
|
|
938
|
+
name: "bash",
|
|
939
|
+
required: [],
|
|
940
|
+
optional: [...COMMON_WITH_EXAMPLES]
|
|
941
|
+
},
|
|
942
|
+
bash_20250124: {
|
|
943
|
+
family: "bash",
|
|
944
|
+
name: "bash",
|
|
945
|
+
required: [],
|
|
946
|
+
optional: [...COMMON_WITH_EXAMPLES]
|
|
947
|
+
},
|
|
948
|
+
text_editor_20241022: {
|
|
949
|
+
family: "textEditor",
|
|
950
|
+
name: "str_replace_editor",
|
|
951
|
+
required: [],
|
|
952
|
+
optional: [...COMMON_WITH_EXAMPLES]
|
|
953
|
+
},
|
|
954
|
+
text_editor_20250124: {
|
|
955
|
+
family: "textEditor",
|
|
956
|
+
name: "str_replace_editor",
|
|
957
|
+
required: [],
|
|
958
|
+
optional: [...COMMON_WITH_EXAMPLES]
|
|
959
|
+
},
|
|
960
|
+
text_editor_20250429: {
|
|
961
|
+
family: "textEditor",
|
|
962
|
+
name: "str_replace_based_edit_tool",
|
|
963
|
+
required: [],
|
|
964
|
+
optional: [...COMMON_WITH_EXAMPLES]
|
|
965
|
+
},
|
|
966
|
+
text_editor_20250728: {
|
|
967
|
+
family: "textEditor",
|
|
968
|
+
name: "str_replace_based_edit_tool",
|
|
969
|
+
required: [],
|
|
970
|
+
optional: [...COMMON_WITH_EXAMPLES, "max_characters"]
|
|
971
|
+
},
|
|
972
|
+
computer_20241022: {
|
|
973
|
+
family: "computer",
|
|
974
|
+
name: "computer",
|
|
975
|
+
required: ["display_width_px", "display_height_px"],
|
|
976
|
+
optional: [...COMMON_WITH_EXAMPLES, "display_number"]
|
|
977
|
+
},
|
|
978
|
+
computer_20250124: {
|
|
979
|
+
family: "computer",
|
|
980
|
+
name: "computer",
|
|
981
|
+
required: ["display_width_px", "display_height_px"],
|
|
982
|
+
optional: [...COMMON_WITH_EXAMPLES, "display_number"]
|
|
983
|
+
},
|
|
984
|
+
computer_20251124: {
|
|
985
|
+
family: "computer",
|
|
986
|
+
name: "computer",
|
|
987
|
+
required: ["display_width_px", "display_height_px"],
|
|
988
|
+
optional: [...COMMON_WITH_EXAMPLES, "display_number", "enable_zoom"]
|
|
989
|
+
},
|
|
990
|
+
memory_20250818: {
|
|
991
|
+
family: "memory",
|
|
992
|
+
name: "memory",
|
|
993
|
+
required: [],
|
|
994
|
+
optional: [...COMMON_WITH_EXAMPLES]
|
|
995
|
+
},
|
|
996
|
+
tool_search_tool_regex_20251119: {
|
|
997
|
+
family: "toolSearchRegex",
|
|
998
|
+
name: "tool_search_tool_regex",
|
|
999
|
+
required: [],
|
|
1000
|
+
optional: [...COMMON]
|
|
1001
|
+
},
|
|
1002
|
+
tool_search_tool_regex: {
|
|
1003
|
+
family: "toolSearchRegex",
|
|
1004
|
+
name: "tool_search_tool_regex",
|
|
1005
|
+
required: [],
|
|
1006
|
+
optional: [...COMMON]
|
|
1007
|
+
},
|
|
1008
|
+
tool_search_tool_bm25_20251119: {
|
|
1009
|
+
family: "toolSearchBm25",
|
|
1010
|
+
name: "tool_search_tool_bm25",
|
|
1011
|
+
required: [],
|
|
1012
|
+
optional: [...COMMON]
|
|
1013
|
+
},
|
|
1014
|
+
tool_search_tool_bm25: {
|
|
1015
|
+
family: "toolSearchBm25",
|
|
1016
|
+
name: "tool_search_tool_bm25",
|
|
1017
|
+
required: [],
|
|
1018
|
+
optional: [...COMMON]
|
|
1019
|
+
},
|
|
1020
|
+
advisor_20260301: {
|
|
1021
|
+
family: "advisor",
|
|
1022
|
+
name: "advisor",
|
|
1023
|
+
required: ["model"],
|
|
1024
|
+
optional: [...COMMON, "caching", "max_uses", "max_tokens"]
|
|
1025
|
+
},
|
|
1026
|
+
mcp_toolset: {
|
|
1027
|
+
family: "mcpToolset",
|
|
1028
|
+
required: ["mcp_server_name"],
|
|
1029
|
+
optional: ["cache_control", "configs", "default_config"]
|
|
1030
|
+
}
|
|
1031
|
+
};
|
|
1032
|
+
var ANTHROPIC_NATIVE_BLOCK_TYPES = new Set([
|
|
1033
|
+
"server_tool_use",
|
|
1034
|
+
"web_search_tool_result",
|
|
1035
|
+
"web_fetch_tool_result",
|
|
1036
|
+
"code_execution_tool_result",
|
|
1037
|
+
"bash_code_execution_tool_result",
|
|
1038
|
+
"text_editor_code_execution_tool_result",
|
|
1039
|
+
"mcp_tool_use",
|
|
1040
|
+
"mcp_tool_result",
|
|
1041
|
+
"tool_search_tool_result",
|
|
1042
|
+
"tool_reference",
|
|
1043
|
+
"advisor_tool_result",
|
|
1044
|
+
"advisor_result",
|
|
1045
|
+
"advisor_redacted_result",
|
|
1046
|
+
"container_upload",
|
|
1047
|
+
"compaction",
|
|
1048
|
+
"search_result",
|
|
1049
|
+
"document",
|
|
1050
|
+
"mid_conv_system",
|
|
1051
|
+
"tool_addition",
|
|
1052
|
+
"tool_removal",
|
|
1053
|
+
"fallback",
|
|
1054
|
+
"redacted_thinking"
|
|
1055
|
+
]);
|
|
1056
|
+
|
|
762
1057
|
// packages/providers/src/anthropic/decode.ts
|
|
763
1058
|
var STOP_REASON = {
|
|
764
1059
|
end_turn: "endTurn",
|
|
765
1060
|
max_tokens: "maxTokens",
|
|
766
1061
|
stop_sequence: "stopSequence",
|
|
767
1062
|
tool_use: "toolUse",
|
|
768
|
-
refusal: "contentFilter"
|
|
1063
|
+
refusal: "contentFilter",
|
|
1064
|
+
pause_turn: "pauseTurn"
|
|
769
1065
|
};
|
|
1066
|
+
var KNOWN_EVENTS = new Set([
|
|
1067
|
+
"message_start",
|
|
1068
|
+
"content_block_start",
|
|
1069
|
+
"content_block_delta",
|
|
1070
|
+
"content_block_stop",
|
|
1071
|
+
"message_delta",
|
|
1072
|
+
"message_stop",
|
|
1073
|
+
"error",
|
|
1074
|
+
"ping"
|
|
1075
|
+
]);
|
|
1076
|
+
var KNOWN_DELTAS = new Set([
|
|
1077
|
+
"text_delta",
|
|
1078
|
+
"thinking_delta",
|
|
1079
|
+
"signature_delta",
|
|
1080
|
+
"input_json_delta",
|
|
1081
|
+
"citations_delta",
|
|
1082
|
+
"compaction_delta"
|
|
1083
|
+
]);
|
|
770
1084
|
var ERROR_TYPE = {
|
|
771
1085
|
overloaded_error: "OVERLOADED",
|
|
772
1086
|
rate_limit_error: "RATE_LIMIT",
|
|
@@ -792,10 +1106,21 @@ async function* decodeAnthropic(messages) {
|
|
|
792
1106
|
let outputTokens = 0;
|
|
793
1107
|
let stopReason = "endTurn";
|
|
794
1108
|
let terminal = false;
|
|
1109
|
+
const nativeBlocks = new Set;
|
|
1110
|
+
const protocolError = (message) => ({
|
|
1111
|
+
type: "error",
|
|
1112
|
+
code: "UPSTREAM",
|
|
1113
|
+
message,
|
|
1114
|
+
retryable: false
|
|
1115
|
+
});
|
|
795
1116
|
for await (const msg of messages) {
|
|
796
1117
|
const d = json(msg.data);
|
|
797
1118
|
if (d === null)
|
|
798
1119
|
continue;
|
|
1120
|
+
if (!KNOWN_EVENTS.has(msg.event)) {
|
|
1121
|
+
yield protocolError(`unrecognized Anthropic stream event "${msg.event}"`);
|
|
1122
|
+
return;
|
|
1123
|
+
}
|
|
799
1124
|
switch (msg.event) {
|
|
800
1125
|
case "message_start": {
|
|
801
1126
|
const m = d.message ?? {};
|
|
@@ -820,6 +1145,18 @@ async function* decodeAnthropic(messages) {
|
|
|
820
1145
|
index,
|
|
821
1146
|
block: { type: "toolUse", id: String(cb.id), name: String(cb.name) }
|
|
822
1147
|
};
|
|
1148
|
+
else if (cb.type !== undefined && ANTHROPIC_NATIVE_BLOCK_TYPES.has(cb.type)) {
|
|
1149
|
+
nativeBlocks.add(index);
|
|
1150
|
+
const { type: _blockType, ...data } = cb;
|
|
1151
|
+
yield {
|
|
1152
|
+
type: "blockStart",
|
|
1153
|
+
index,
|
|
1154
|
+
block: { type: "anthropicNative", blockType: cb.type, data }
|
|
1155
|
+
};
|
|
1156
|
+
} else {
|
|
1157
|
+
yield protocolError(`unrecognized Anthropic content block type "${String(cb.type)}"`);
|
|
1158
|
+
return;
|
|
1159
|
+
}
|
|
823
1160
|
break;
|
|
824
1161
|
}
|
|
825
1162
|
case "content_block_delta": {
|
|
@@ -843,17 +1180,51 @@ async function* decodeAnthropic(messages) {
|
|
|
843
1180
|
yield {
|
|
844
1181
|
type: "blockDelta",
|
|
845
1182
|
index,
|
|
846
|
-
delta: { type: "toolJson", partial: delta.partial_json ?? "" }
|
|
1183
|
+
delta: nativeBlocks.has(index) ? { type: "anthropicNativeJson", partial: delta.partial_json ?? "" } : { type: "toolJson", partial: delta.partial_json ?? "" }
|
|
1184
|
+
};
|
|
1185
|
+
else if (delta.type === "citations_delta")
|
|
1186
|
+
yield {
|
|
1187
|
+
type: "blockDelta",
|
|
1188
|
+
index,
|
|
1189
|
+
delta: {
|
|
1190
|
+
type: "anthropicNative",
|
|
1191
|
+
deltaType: delta.type,
|
|
1192
|
+
data: { citation: delta.citation }
|
|
1193
|
+
}
|
|
847
1194
|
};
|
|
1195
|
+
else if (delta.type === "compaction_delta")
|
|
1196
|
+
yield {
|
|
1197
|
+
type: "blockDelta",
|
|
1198
|
+
index,
|
|
1199
|
+
delta: {
|
|
1200
|
+
type: "anthropicNative",
|
|
1201
|
+
deltaType: delta.type,
|
|
1202
|
+
data: {
|
|
1203
|
+
...delta.content === undefined ? {} : { content: delta.content },
|
|
1204
|
+
...delta.encrypted_content === undefined ? {} : { encrypted_content: delta.encrypted_content }
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
};
|
|
1208
|
+
else if (delta.type === undefined || !KNOWN_DELTAS.has(delta.type)) {
|
|
1209
|
+
yield protocolError(`unrecognized Anthropic content block delta "${String(delta.type)}"`);
|
|
1210
|
+
return;
|
|
1211
|
+
}
|
|
848
1212
|
break;
|
|
849
1213
|
}
|
|
850
1214
|
case "content_block_stop":
|
|
1215
|
+
nativeBlocks.delete(d.index ?? 0);
|
|
851
1216
|
yield { type: "blockEnd", index: d.index ?? 0 };
|
|
852
1217
|
break;
|
|
853
1218
|
case "message_delta": {
|
|
854
1219
|
const reason = d.delta?.stop_reason;
|
|
855
|
-
if (typeof reason === "string")
|
|
856
|
-
|
|
1220
|
+
if (typeof reason === "string") {
|
|
1221
|
+
const mapped = STOP_REASON[reason];
|
|
1222
|
+
if (mapped === undefined) {
|
|
1223
|
+
yield protocolError(`unrecognized Anthropic stop reason "${reason}"`);
|
|
1224
|
+
return;
|
|
1225
|
+
}
|
|
1226
|
+
stopReason = mapped;
|
|
1227
|
+
}
|
|
857
1228
|
inputTokens = d.usage?.input_tokens ?? inputTokens;
|
|
858
1229
|
outputTokens = d.usage?.output_tokens ?? outputTokens;
|
|
859
1230
|
break;
|
|
@@ -912,7 +1283,12 @@ function encodeBlock(b) {
|
|
|
912
1283
|
const cache = wireCacheControl(cacheControlOf(b));
|
|
913
1284
|
switch (b.type) {
|
|
914
1285
|
case "text":
|
|
915
|
-
return {
|
|
1286
|
+
return {
|
|
1287
|
+
type: "text",
|
|
1288
|
+
text: b.text,
|
|
1289
|
+
...b.citations === undefined ? {} : { citations: b.citations },
|
|
1290
|
+
...cache
|
|
1291
|
+
};
|
|
916
1292
|
case "image":
|
|
917
1293
|
return {
|
|
918
1294
|
type: "image",
|
|
@@ -931,11 +1307,16 @@ function encodeBlock(b) {
|
|
|
931
1307
|
is_error: b.isError,
|
|
932
1308
|
...cache
|
|
933
1309
|
};
|
|
1310
|
+
case "anthropicNative":
|
|
1311
|
+
return { ...b.data, type: b.blockType, ...cache };
|
|
934
1312
|
}
|
|
935
1313
|
}
|
|
936
1314
|
function encodeSystemTurn(content) {
|
|
937
|
-
|
|
1315
|
+
if (content.every((block) => block.type === "text")) {
|
|
1316
|
+
return content.map((block) => block.text).join(`
|
|
938
1317
|
`);
|
|
1318
|
+
}
|
|
1319
|
+
return content.map(encodeBlock);
|
|
939
1320
|
}
|
|
940
1321
|
function systemCacheControl(req) {
|
|
941
1322
|
const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
|
|
@@ -947,6 +1328,23 @@ function systemCacheControl(req) {
|
|
|
947
1328
|
lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
|
|
948
1329
|
};
|
|
949
1330
|
}
|
|
1331
|
+
function encodeTool(t) {
|
|
1332
|
+
if (t.provider === "anthropic") {
|
|
1333
|
+
return {
|
|
1334
|
+
type: t.type,
|
|
1335
|
+
...t.name === "" ? {} : { name: t.name },
|
|
1336
|
+
...t.wire,
|
|
1337
|
+
...wireCacheControl(t.cacheControl)
|
|
1338
|
+
};
|
|
1339
|
+
}
|
|
1340
|
+
return {
|
|
1341
|
+
name: t.name,
|
|
1342
|
+
...t.description === undefined ? {} : { description: t.description },
|
|
1343
|
+
input_schema: t.inputSchema,
|
|
1344
|
+
...t.options ?? {},
|
|
1345
|
+
...wireCacheControl(t.cacheControl)
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
950
1348
|
function encodeToolChoice(c) {
|
|
951
1349
|
switch (c.type) {
|
|
952
1350
|
case "auto":
|
|
@@ -959,6 +1357,45 @@ function encodeToolChoice(c) {
|
|
|
959
1357
|
return { type: "tool", name: c.name };
|
|
960
1358
|
}
|
|
961
1359
|
}
|
|
1360
|
+
function isRecord(value) {
|
|
1361
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1362
|
+
}
|
|
1363
|
+
function withoutEffort(vendor, note2) {
|
|
1364
|
+
const config = vendor.output_config;
|
|
1365
|
+
if (!isRecord(config))
|
|
1366
|
+
return vendor;
|
|
1367
|
+
const entries = Object.entries(config);
|
|
1368
|
+
const kept = entries.filter(([key]) => key !== "effort");
|
|
1369
|
+
if (kept.length === entries.length)
|
|
1370
|
+
return vendor;
|
|
1371
|
+
note2("anthropic:effort-unsupported");
|
|
1372
|
+
if (kept.length === 0) {
|
|
1373
|
+
return Object.fromEntries(Object.entries(vendor).filter(([key]) => key !== "output_config"));
|
|
1374
|
+
}
|
|
1375
|
+
return { ...vendor, output_config: Object.fromEntries(kept) };
|
|
1376
|
+
}
|
|
1377
|
+
var THINKING_ONLY_EDITS = new Set(["clear_thinking_20251015"]);
|
|
1378
|
+
function thinkingIsOff(thinking) {
|
|
1379
|
+
if (!isRecord(thinking))
|
|
1380
|
+
return thinking !== undefined;
|
|
1381
|
+
return thinking.type !== "adaptive" && thinking.type !== "enabled";
|
|
1382
|
+
}
|
|
1383
|
+
function stripUnsupportedEdits(body, note2) {
|
|
1384
|
+
if (!thinkingIsOff(body.thinking))
|
|
1385
|
+
return;
|
|
1386
|
+
const config = body.context_management;
|
|
1387
|
+
if (!isRecord(config) || !Array.isArray(config.edits))
|
|
1388
|
+
return;
|
|
1389
|
+
const kept = config.edits.filter((e) => !(isRecord(e) && THINKING_ONLY_EDITS.has(String(e.type))));
|
|
1390
|
+
if (kept.length === config.edits.length)
|
|
1391
|
+
return;
|
|
1392
|
+
note2("anthropic:clear-thinking-unsupported");
|
|
1393
|
+
if (kept.length === 0 && Object.keys(config).length === 1) {
|
|
1394
|
+
delete body.context_management;
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
body.context_management = { ...config, edits: kept };
|
|
1398
|
+
}
|
|
962
1399
|
function toWire(req, model, opts) {
|
|
963
1400
|
const degradations = [];
|
|
964
1401
|
const note2 = (d) => {
|
|
@@ -1001,25 +1438,27 @@ function toWire(req, model, opts) {
|
|
|
1001
1438
|
body.temperature = req.temperature;
|
|
1002
1439
|
if (req.stopSequences !== undefined)
|
|
1003
1440
|
body.stop_sequences = req.stopSequences;
|
|
1004
|
-
if (req.tools !== undefined)
|
|
1005
|
-
body.tools = req.tools.map(
|
|
1006
|
-
name: t.name,
|
|
1007
|
-
...t.description === undefined ? {} : { description: t.description },
|
|
1008
|
-
input_schema: t.inputSchema,
|
|
1009
|
-
...wireCacheControl(t.cacheControl)
|
|
1010
|
-
}));
|
|
1011
|
-
}
|
|
1441
|
+
if (req.tools !== undefined)
|
|
1442
|
+
body.tools = req.tools.map(encodeTool);
|
|
1012
1443
|
if (req.toolChoice !== undefined)
|
|
1013
1444
|
body.tool_choice = encodeToolChoice(req.toolChoice);
|
|
1014
1445
|
if (req.reasoning !== undefined) {
|
|
1015
1446
|
switch (req.reasoning.mode) {
|
|
1016
1447
|
case "adaptive":
|
|
1448
|
+
if (anthropicReasoningForm(model) === "budget") {
|
|
1449
|
+
body.thinking = { type: "disabled" };
|
|
1450
|
+
note2("anthropic:adaptive-thinking-unsupported");
|
|
1451
|
+
break;
|
|
1452
|
+
}
|
|
1017
1453
|
body.thinking = {
|
|
1018
1454
|
type: "adaptive",
|
|
1019
1455
|
...req.reasoning.display === undefined ? {} : { display: req.reasoning.display }
|
|
1020
1456
|
};
|
|
1021
1457
|
if (req.reasoning.effort !== undefined) {
|
|
1022
|
-
body.output_config = {
|
|
1458
|
+
body.output_config = {
|
|
1459
|
+
...isRecord(body.output_config) ? body.output_config : {},
|
|
1460
|
+
effort: req.reasoning.effort
|
|
1461
|
+
};
|
|
1023
1462
|
}
|
|
1024
1463
|
break;
|
|
1025
1464
|
case "budget":
|
|
@@ -1030,7 +1469,9 @@ function toWire(req, model, opts) {
|
|
|
1030
1469
|
break;
|
|
1031
1470
|
}
|
|
1032
1471
|
}
|
|
1033
|
-
|
|
1472
|
+
const vendor = req.vendor?.anthropic ?? {};
|
|
1473
|
+
Object.assign(body, anthropicReasoningForm(model) === "budget" ? withoutEffort(vendor, note2) : vendor);
|
|
1474
|
+
stripUnsupportedEdits(body, note2);
|
|
1034
1475
|
return { body, degradations };
|
|
1035
1476
|
}
|
|
1036
1477
|
|
|
@@ -1094,115 +1535,6 @@ var anthropicAdapter = {
|
|
|
1094
1535
|
return { events: decodeAnthropic(parseSse(res.body)), degradations: notes };
|
|
1095
1536
|
}
|
|
1096
1537
|
};
|
|
1097
|
-
// packages/providers/src/http-client.ts
|
|
1098
|
-
import { request as httpRequest } from "http";
|
|
1099
|
-
import { request as httpsRequest } from "https";
|
|
1100
|
-
import { Readable } from "stream";
|
|
1101
|
-
function nodeHttpClient(options = {}) {
|
|
1102
|
-
const logger2 = options.logger ?? noopLogger;
|
|
1103
|
-
const now = options.now ?? (() => Date.now());
|
|
1104
|
-
return (req) => new Promise((resolve, reject) => {
|
|
1105
|
-
const url = new URL(req.url);
|
|
1106
|
-
const startedAt = now();
|
|
1107
|
-
let traced = false;
|
|
1108
|
-
const trace = (status, failed = false) => {
|
|
1109
|
-
if (traced || !logger2.enabled("debug"))
|
|
1110
|
-
return;
|
|
1111
|
-
traced = true;
|
|
1112
|
-
logger2.debug("upstream http", {
|
|
1113
|
-
provider: req.provider,
|
|
1114
|
-
status,
|
|
1115
|
-
host: url.host,
|
|
1116
|
-
path: url.pathname,
|
|
1117
|
-
durationMs: now() - startedAt,
|
|
1118
|
-
reason: failed ? "transport error" : undefined
|
|
1119
|
-
});
|
|
1120
|
-
};
|
|
1121
|
-
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
1122
|
-
const bodyBytes = Buffer.from(req.body, "utf8");
|
|
1123
|
-
const headers = {};
|
|
1124
|
-
for (const [name, value] of req.headers)
|
|
1125
|
-
headers[name] = value;
|
|
1126
|
-
if (req.body.length > 0 && !hasHeader(req, "content-length")) {
|
|
1127
|
-
headers["Content-Length"] = bodyBytes.byteLength;
|
|
1128
|
-
}
|
|
1129
|
-
const outgoing = send({
|
|
1130
|
-
protocol: url.protocol,
|
|
1131
|
-
hostname: url.hostname,
|
|
1132
|
-
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
1133
|
-
path: `${url.pathname}${url.search}`,
|
|
1134
|
-
method: req.method,
|
|
1135
|
-
headers,
|
|
1136
|
-
setHost: !hasHeader(req, "host")
|
|
1137
|
-
}, (incoming) => {
|
|
1138
|
-
const chunks = [];
|
|
1139
|
-
let buffered = null;
|
|
1140
|
-
const responseHeaders = new Headers;
|
|
1141
|
-
for (const [k, v] of Object.entries(incoming.headers)) {
|
|
1142
|
-
if (Array.isArray(v))
|
|
1143
|
-
for (const one of v)
|
|
1144
|
-
responseHeaders.append(k, one);
|
|
1145
|
-
else if (typeof v === "string")
|
|
1146
|
-
responseHeaders.set(k, v);
|
|
1147
|
-
}
|
|
1148
|
-
trace(incoming.statusCode);
|
|
1149
|
-
resolve({
|
|
1150
|
-
status: incoming.statusCode ?? 0,
|
|
1151
|
-
headers: responseHeaders,
|
|
1152
|
-
body: Readable.toWeb(incoming),
|
|
1153
|
-
text: () => {
|
|
1154
|
-
buffered ??= new Promise((res, rej) => {
|
|
1155
|
-
incoming.on("data", (c) => chunks.push(c));
|
|
1156
|
-
incoming.on("end", () => res(Buffer.concat(chunks).toString("utf8")));
|
|
1157
|
-
incoming.on("error", rej);
|
|
1158
|
-
});
|
|
1159
|
-
return buffered;
|
|
1160
|
-
}
|
|
1161
|
-
});
|
|
1162
|
-
});
|
|
1163
|
-
const onAbort = () => outgoing.destroy(new Error("aborted"));
|
|
1164
|
-
outgoing.on("error", (err) => {
|
|
1165
|
-
req.signal.removeEventListener("abort", onAbort);
|
|
1166
|
-
trace(undefined, true);
|
|
1167
|
-
reject(err);
|
|
1168
|
-
});
|
|
1169
|
-
outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
|
|
1170
|
-
if (req.signal.aborted) {
|
|
1171
|
-
outgoing.destroy(new Error("aborted"));
|
|
1172
|
-
return;
|
|
1173
|
-
}
|
|
1174
|
-
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
1175
|
-
if (bodyBytes.byteLength > 0)
|
|
1176
|
-
outgoing.write(bodyBytes);
|
|
1177
|
-
outgoing.end();
|
|
1178
|
-
});
|
|
1179
|
-
}
|
|
1180
|
-
function hasHeader(req, lowerName) {
|
|
1181
|
-
return req.headers.some(([name]) => name.toLowerCase() === lowerName);
|
|
1182
|
-
}
|
|
1183
|
-
// packages/providers/src/kimi-device.ts
|
|
1184
|
-
import { randomUUID } from "crypto";
|
|
1185
|
-
function mintKimiDevice() {
|
|
1186
|
-
return {
|
|
1187
|
-
deviceId: randomUUID(),
|
|
1188
|
-
deviceName: "MacBook-Pro",
|
|
1189
|
-
deviceModel: "MacBookPro18,3",
|
|
1190
|
-
osVersion: "15.3.1"
|
|
1191
|
-
};
|
|
1192
|
-
}
|
|
1193
|
-
function kimiDeviceHeaders(providerData) {
|
|
1194
|
-
const deviceId = providerData.deviceId;
|
|
1195
|
-
if (typeof deviceId !== "string" || deviceId.length === 0)
|
|
1196
|
-
return [];
|
|
1197
|
-
const str = (v) => typeof v === "string" && v.length > 0 ? v : "unknown";
|
|
1198
|
-
return [
|
|
1199
|
-
["X-Msh-Device-Id", deviceId],
|
|
1200
|
-
["X-Msh-Device-Name", str(providerData.deviceName)],
|
|
1201
|
-
["X-Msh-Device-Model", str(providerData.deviceModel)],
|
|
1202
|
-
["X-Msh-Os-Version", str(providerData.osVersion)]
|
|
1203
|
-
];
|
|
1204
|
-
}
|
|
1205
|
-
|
|
1206
1538
|
// packages/providers/src/kimi/decode.ts
|
|
1207
1539
|
var FINISH = {
|
|
1208
1540
|
stop: "endTurn",
|
|
@@ -1313,7 +1645,7 @@ function encodeToolChoice2(c) {
|
|
|
1313
1645
|
return { type: "function", function: { name: c.name } };
|
|
1314
1646
|
}
|
|
1315
1647
|
}
|
|
1316
|
-
function toChatWire(req, model) {
|
|
1648
|
+
function toChatWire(req, model, vendor = "kimi") {
|
|
1317
1649
|
const degradations = [];
|
|
1318
1650
|
const note2 = (d) => {
|
|
1319
1651
|
if (!degradations.includes(d))
|
|
@@ -1355,6 +1687,9 @@ function toChatWire(req, model) {
|
|
|
1355
1687
|
content: block.content
|
|
1356
1688
|
});
|
|
1357
1689
|
break;
|
|
1690
|
+
case "anthropicNative":
|
|
1691
|
+
note2("kimi:anthropic-native-block-dropped");
|
|
1692
|
+
break;
|
|
1358
1693
|
}
|
|
1359
1694
|
}
|
|
1360
1695
|
if (toolCalls.length > 0) {
|
|
@@ -1382,7 +1717,10 @@ function toChatWire(req, model) {
|
|
|
1382
1717
|
if (req.stopSequences !== undefined)
|
|
1383
1718
|
body.stop = req.stopSequences;
|
|
1384
1719
|
if (req.tools !== undefined) {
|
|
1385
|
-
|
|
1720
|
+
const custom = req.tools.filter((t) => t.provider === "custom");
|
|
1721
|
+
if (custom.length !== req.tools.length)
|
|
1722
|
+
note2("kimi:anthropic-tool-dropped");
|
|
1723
|
+
body.tools = custom.map((t) => ({
|
|
1386
1724
|
type: "function",
|
|
1387
1725
|
function: { name: t.name, description: t.description, parameters: t.inputSchema }
|
|
1388
1726
|
}));
|
|
@@ -1391,44 +1729,10 @@ function toChatWire(req, model) {
|
|
|
1391
1729
|
body.tool_choice = encodeToolChoice2(req.toolChoice);
|
|
1392
1730
|
if (req.reasoning !== undefined)
|
|
1393
1731
|
note2("kimi:reasoning-dropped");
|
|
1394
|
-
Object.assign(body, req.vendor?.
|
|
1732
|
+
Object.assign(body, req.vendor?.[vendor] ?? {});
|
|
1395
1733
|
return { body, degradations };
|
|
1396
1734
|
}
|
|
1397
1735
|
|
|
1398
|
-
// packages/providers/src/kimi/index.ts
|
|
1399
|
-
var BASE_URL2 = "https://api.kimi.com/coding/v1/chat/completions";
|
|
1400
|
-
var kimiAdapter = {
|
|
1401
|
-
id: "kimi",
|
|
1402
|
-
capabilities: PROVIDER_CAPABILITIES.kimi,
|
|
1403
|
-
async send(req) {
|
|
1404
|
-
const { body, degradations } = toChatWire(req.request, req.model);
|
|
1405
|
-
const token = req.credentials.accessToken ?? req.credentials.apiKey;
|
|
1406
|
-
if (token === null) {
|
|
1407
|
-
throw new GatewayError("AUTH", "kimi credential has no token", { provider: "kimi" });
|
|
1408
|
-
}
|
|
1409
|
-
const protocol = [
|
|
1410
|
-
["Content-Type", "application/json"],
|
|
1411
|
-
["Accept", "text/event-stream"],
|
|
1412
|
-
["Authorization", `Bearer ${token}`],
|
|
1413
|
-
...kimiDeviceHeaders(req.credentials.providerData)
|
|
1414
|
-
];
|
|
1415
|
-
const profile = PROFILES.kimi;
|
|
1416
|
-
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
1417
|
-
const res = await req.http({
|
|
1418
|
-
provider: "kimi",
|
|
1419
|
-
url: BASE_URL2,
|
|
1420
|
-
method: "POST",
|
|
1421
|
-
headers,
|
|
1422
|
-
body: JSON.stringify(orderFields({ ...body, stream: true }, BODY_ORDER.kimi)),
|
|
1423
|
-
signal: req.signal
|
|
1424
|
-
});
|
|
1425
|
-
if (res.status < 200 || res.status >= 300)
|
|
1426
|
-
throw await httpError(res, "kimi");
|
|
1427
|
-
if (res.body === null)
|
|
1428
|
-
throw new GatewayError("UPSTREAM", "empty response body", { provider: "kimi" });
|
|
1429
|
-
return { events: decodeChat(parseSse(res.body)), degradations };
|
|
1430
|
-
}
|
|
1431
|
-
};
|
|
1432
1736
|
// packages/providers/src/openai/decode.ts
|
|
1433
1737
|
var ERROR_CODE = {
|
|
1434
1738
|
rate_limit_exceeded: "RATE_LIMIT",
|
|
@@ -1649,6 +1953,9 @@ ${block.text}
|
|
|
1649
1953
|
output: block.content
|
|
1650
1954
|
});
|
|
1651
1955
|
break;
|
|
1956
|
+
case "anthropicNative":
|
|
1957
|
+
note2("openai:anthropic-native-block-dropped");
|
|
1958
|
+
break;
|
|
1652
1959
|
}
|
|
1653
1960
|
}
|
|
1654
1961
|
flush();
|
|
@@ -1672,7 +1979,10 @@ ${block.text}
|
|
|
1672
1979
|
body.temperature = req.temperature;
|
|
1673
1980
|
}
|
|
1674
1981
|
if (req.tools !== undefined) {
|
|
1675
|
-
|
|
1982
|
+
const custom = req.tools.filter((t) => t.provider === "custom");
|
|
1983
|
+
if (custom.length !== req.tools.length)
|
|
1984
|
+
note2("openai:anthropic-tool-dropped");
|
|
1985
|
+
body.tools = custom.map((t) => ({
|
|
1676
1986
|
type: "function",
|
|
1677
1987
|
name: t.name,
|
|
1678
1988
|
description: t.description,
|
|
@@ -1693,11 +2003,195 @@ ${block.text}
|
|
|
1693
2003
|
if (req.reasoning.mode === "budget") {
|
|
1694
2004
|
degradations.push("openai:reasoning-budget-dropped");
|
|
1695
2005
|
}
|
|
1696
|
-
}
|
|
1697
|
-
Object.assign(body, req.vendor?.openai ?? {});
|
|
1698
|
-
return { body, degradations };
|
|
2006
|
+
}
|
|
2007
|
+
Object.assign(body, req.vendor?.openai ?? {});
|
|
2008
|
+
return { body, degradations };
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
// packages/providers/src/custom/index.ts
|
|
2012
|
+
function metadata(data) {
|
|
2013
|
+
const { origin, protocol } = data;
|
|
2014
|
+
if (typeof origin !== "string" || protocol !== "chat_completions" && protocol !== "responses") {
|
|
2015
|
+
throw new GatewayError("BAD_REQUEST", "custom credential has invalid endpoint metadata");
|
|
2016
|
+
}
|
|
2017
|
+
return { origin, protocol };
|
|
2018
|
+
}
|
|
2019
|
+
var customAdapter = {
|
|
2020
|
+
id: "custom",
|
|
2021
|
+
capabilities: PROVIDER_CAPABILITIES.custom,
|
|
2022
|
+
async send(req) {
|
|
2023
|
+
const apiKey = req.credentials.apiKey;
|
|
2024
|
+
if (apiKey === null) {
|
|
2025
|
+
throw new GatewayError("AUTH", "custom credential has no API key", { provider: "custom" });
|
|
2026
|
+
}
|
|
2027
|
+
const { origin, protocol } = metadata(req.credentials.providerData);
|
|
2028
|
+
const encoded = protocol === "chat_completions" ? toChatWire(req.request, req.model, "openai") : toResponsesWire(req.request, req.model);
|
|
2029
|
+
const headers = [
|
|
2030
|
+
["Content-Type", "application/json"],
|
|
2031
|
+
["Authorization", `Bearer ${apiKey}`]
|
|
2032
|
+
];
|
|
2033
|
+
const res = await req.http({
|
|
2034
|
+
provider: "custom",
|
|
2035
|
+
url: `${origin}/v1/${protocol === "chat_completions" ? "chat/completions" : "responses"}`,
|
|
2036
|
+
method: "POST",
|
|
2037
|
+
headers,
|
|
2038
|
+
body: JSON.stringify({ ...encoded.body, stream: true }),
|
|
2039
|
+
signal: req.signal
|
|
2040
|
+
});
|
|
2041
|
+
if (res.status < 200 || res.status >= 300)
|
|
2042
|
+
throw await httpError(res, "custom");
|
|
2043
|
+
if (res.body === null) {
|
|
2044
|
+
throw new GatewayError("UPSTREAM", "empty response body", { provider: "custom" });
|
|
2045
|
+
}
|
|
2046
|
+
return {
|
|
2047
|
+
events: protocol === "chat_completions" ? decodeChat(parseSse(res.body)) : decodeResponses(parseSse(res.body)),
|
|
2048
|
+
degradations: encoded.degradations.map((value) => value.replace(protocol === "chat_completions" ? /^kimi:/ : /^openai:/, "custom:"))
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
};
|
|
2052
|
+
// packages/providers/src/http-client.ts
|
|
2053
|
+
import { request as httpRequest } from "http";
|
|
2054
|
+
import { request as httpsRequest } from "https";
|
|
2055
|
+
import { Readable } from "stream";
|
|
2056
|
+
function nodeHttpClient(options = {}) {
|
|
2057
|
+
const logger2 = options.logger ?? noopLogger;
|
|
2058
|
+
const now = options.now ?? (() => Date.now());
|
|
2059
|
+
return (req) => new Promise((resolve, reject) => {
|
|
2060
|
+
const url = new URL(req.url);
|
|
2061
|
+
const startedAt = now();
|
|
2062
|
+
let traced = false;
|
|
2063
|
+
const trace = (status, failed = false) => {
|
|
2064
|
+
if (traced || !logger2.enabled("debug"))
|
|
2065
|
+
return;
|
|
2066
|
+
traced = true;
|
|
2067
|
+
logger2.debug("upstream http", {
|
|
2068
|
+
provider: req.provider,
|
|
2069
|
+
status,
|
|
2070
|
+
host: url.host,
|
|
2071
|
+
path: url.pathname,
|
|
2072
|
+
durationMs: now() - startedAt,
|
|
2073
|
+
reason: failed ? "transport error" : undefined
|
|
2074
|
+
});
|
|
2075
|
+
};
|
|
2076
|
+
const send = url.protocol === "https:" ? httpsRequest : httpRequest;
|
|
2077
|
+
const bodyBytes = Buffer.from(req.body, "utf8");
|
|
2078
|
+
const headers = {};
|
|
2079
|
+
for (const [name, value] of req.headers)
|
|
2080
|
+
headers[name] = value;
|
|
2081
|
+
if (req.body.length > 0 && !hasHeader(req, "content-length")) {
|
|
2082
|
+
headers["Content-Length"] = bodyBytes.byteLength;
|
|
2083
|
+
}
|
|
2084
|
+
const outgoing = send({
|
|
2085
|
+
protocol: url.protocol,
|
|
2086
|
+
hostname: url.hostname,
|
|
2087
|
+
port: url.port || (url.protocol === "https:" ? 443 : 80),
|
|
2088
|
+
path: `${url.pathname}${url.search}`,
|
|
2089
|
+
method: req.method,
|
|
2090
|
+
headers,
|
|
2091
|
+
setHost: !hasHeader(req, "host")
|
|
2092
|
+
}, (incoming) => {
|
|
2093
|
+
const chunks = [];
|
|
2094
|
+
let buffered = null;
|
|
2095
|
+
const responseHeaders = new Headers;
|
|
2096
|
+
for (const [k, v] of Object.entries(incoming.headers)) {
|
|
2097
|
+
if (Array.isArray(v))
|
|
2098
|
+
for (const one of v)
|
|
2099
|
+
responseHeaders.append(k, one);
|
|
2100
|
+
else if (typeof v === "string")
|
|
2101
|
+
responseHeaders.set(k, v);
|
|
2102
|
+
}
|
|
2103
|
+
trace(incoming.statusCode);
|
|
2104
|
+
resolve({
|
|
2105
|
+
status: incoming.statusCode ?? 0,
|
|
2106
|
+
headers: responseHeaders,
|
|
2107
|
+
body: Readable.toWeb(incoming),
|
|
2108
|
+
text: () => {
|
|
2109
|
+
buffered ??= new Promise((res, rej) => {
|
|
2110
|
+
incoming.on("data", (c) => chunks.push(c));
|
|
2111
|
+
incoming.on("end", () => res(Buffer.concat(chunks).toString("utf8")));
|
|
2112
|
+
incoming.on("error", rej);
|
|
2113
|
+
});
|
|
2114
|
+
return buffered;
|
|
2115
|
+
}
|
|
2116
|
+
});
|
|
2117
|
+
});
|
|
2118
|
+
const onAbort = () => outgoing.destroy(new Error("aborted"));
|
|
2119
|
+
outgoing.on("error", (err) => {
|
|
2120
|
+
req.signal.removeEventListener("abort", onAbort);
|
|
2121
|
+
trace(undefined, true);
|
|
2122
|
+
reject(err);
|
|
2123
|
+
});
|
|
2124
|
+
outgoing.on("close", () => req.signal.removeEventListener("abort", onAbort));
|
|
2125
|
+
if (req.signal.aborted) {
|
|
2126
|
+
outgoing.destroy(new Error("aborted"));
|
|
2127
|
+
return;
|
|
2128
|
+
}
|
|
2129
|
+
req.signal.addEventListener("abort", onAbort, { once: true });
|
|
2130
|
+
if (bodyBytes.byteLength > 0)
|
|
2131
|
+
outgoing.write(bodyBytes);
|
|
2132
|
+
outgoing.end();
|
|
2133
|
+
});
|
|
2134
|
+
}
|
|
2135
|
+
function hasHeader(req, lowerName) {
|
|
2136
|
+
return req.headers.some(([name]) => name.toLowerCase() === lowerName);
|
|
2137
|
+
}
|
|
2138
|
+
// packages/providers/src/kimi-device.ts
|
|
2139
|
+
import { randomUUID } from "crypto";
|
|
2140
|
+
function mintKimiDevice() {
|
|
2141
|
+
return {
|
|
2142
|
+
deviceId: randomUUID(),
|
|
2143
|
+
deviceName: "MacBook-Pro",
|
|
2144
|
+
deviceModel: "MacBookPro18,3",
|
|
2145
|
+
osVersion: "15.3.1"
|
|
2146
|
+
};
|
|
2147
|
+
}
|
|
2148
|
+
function kimiDeviceHeaders(providerData) {
|
|
2149
|
+
const deviceId = providerData.deviceId;
|
|
2150
|
+
if (typeof deviceId !== "string" || deviceId.length === 0)
|
|
2151
|
+
return [];
|
|
2152
|
+
const str = (v) => typeof v === "string" && v.length > 0 ? v : "unknown";
|
|
2153
|
+
return [
|
|
2154
|
+
["X-Msh-Device-Id", deviceId],
|
|
2155
|
+
["X-Msh-Device-Name", str(providerData.deviceName)],
|
|
2156
|
+
["X-Msh-Device-Model", str(providerData.deviceModel)],
|
|
2157
|
+
["X-Msh-Os-Version", str(providerData.osVersion)]
|
|
2158
|
+
];
|
|
1699
2159
|
}
|
|
1700
2160
|
|
|
2161
|
+
// packages/providers/src/kimi/index.ts
|
|
2162
|
+
var BASE_URL2 = "https://api.kimi.com/coding/v1/chat/completions";
|
|
2163
|
+
var kimiAdapter = {
|
|
2164
|
+
id: "kimi",
|
|
2165
|
+
capabilities: PROVIDER_CAPABILITIES.kimi,
|
|
2166
|
+
async send(req) {
|
|
2167
|
+
const { body, degradations } = toChatWire(req.request, req.model);
|
|
2168
|
+
const token = req.credentials.accessToken ?? req.credentials.apiKey;
|
|
2169
|
+
if (token === null) {
|
|
2170
|
+
throw new GatewayError("AUTH", "kimi credential has no token", { provider: "kimi" });
|
|
2171
|
+
}
|
|
2172
|
+
const protocol = [
|
|
2173
|
+
["Content-Type", "application/json"],
|
|
2174
|
+
["Accept", "text/event-stream"],
|
|
2175
|
+
["Authorization", `Bearer ${token}`],
|
|
2176
|
+
...kimiDeviceHeaders(req.credentials.providerData)
|
|
2177
|
+
];
|
|
2178
|
+
const profile = PROFILES.kimi;
|
|
2179
|
+
const headers = orderHeaders(mergeHeaders(profile.headers, protocol), profile.order);
|
|
2180
|
+
const res = await req.http({
|
|
2181
|
+
provider: "kimi",
|
|
2182
|
+
url: BASE_URL2,
|
|
2183
|
+
method: "POST",
|
|
2184
|
+
headers,
|
|
2185
|
+
body: JSON.stringify(orderFields({ ...body, stream: true }, BODY_ORDER.kimi)),
|
|
2186
|
+
signal: req.signal
|
|
2187
|
+
});
|
|
2188
|
+
if (res.status < 200 || res.status >= 300)
|
|
2189
|
+
throw await httpError(res, "kimi");
|
|
2190
|
+
if (res.body === null)
|
|
2191
|
+
throw new GatewayError("UPSTREAM", "empty response body", { provider: "kimi" });
|
|
2192
|
+
return { events: decodeChat(parseSse(res.body)), degradations };
|
|
2193
|
+
}
|
|
2194
|
+
};
|
|
1701
2195
|
// packages/providers/src/openai/index.ts
|
|
1702
2196
|
var OAUTH_URL = "https://chatgpt.com/backend-api/codex/responses";
|
|
1703
2197
|
var API_URL = "https://api.openai.com/v1/responses";
|
|
@@ -1910,11 +2404,11 @@ function pendingError(code) {
|
|
|
1910
2404
|
error[PENDING_MARKER] = true;
|
|
1911
2405
|
return error;
|
|
1912
2406
|
}
|
|
1913
|
-
function
|
|
2407
|
+
function isRecord2(value) {
|
|
1914
2408
|
return typeof value === "object" && value !== null;
|
|
1915
2409
|
}
|
|
1916
2410
|
function recordFrom(value) {
|
|
1917
|
-
return
|
|
2411
|
+
return isRecord2(value) ? value : null;
|
|
1918
2412
|
}
|
|
1919
2413
|
function stringFrom(value, field) {
|
|
1920
2414
|
const candidate = value[field];
|
|
@@ -2117,7 +2611,7 @@ function createPendingFlows(opts) {
|
|
|
2117
2611
|
}
|
|
2118
2612
|
|
|
2119
2613
|
// packages/control/src/connect.ts
|
|
2120
|
-
var PROVIDER_IDS = ["anthropic", "openai", "kimi"];
|
|
2614
|
+
var PROVIDER_IDS = ["anthropic", "openai", "kimi", "custom"];
|
|
2121
2615
|
var FLOW_TTL_MS = 600000;
|
|
2122
2616
|
function isProviderId(value) {
|
|
2123
2617
|
return typeof value === "string" && PROVIDER_IDS.includes(value);
|
|
@@ -2158,6 +2652,8 @@ function createConnectFlows(deps) {
|
|
|
2158
2652
|
}
|
|
2159
2653
|
async function complete(flow, code) {
|
|
2160
2654
|
const provider = deps.providers[flow.provider];
|
|
2655
|
+
if (provider === undefined)
|
|
2656
|
+
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth");
|
|
2161
2657
|
const result = await provider.exchange({ code, pending: flow.pending }, { http: deps.http, now: deps.now });
|
|
2162
2658
|
const id = crypto.randomUUID();
|
|
2163
2659
|
await deps.store.credentials.create({
|
|
@@ -2201,6 +2697,9 @@ function createConnectFlows(deps) {
|
|
|
2201
2697
|
}
|
|
2202
2698
|
const label = typeof labelInput === "string" && labelInput.trim().length > 0 ? labelInput.trim() : providerInput;
|
|
2203
2699
|
const provider = deps.providers[providerInput];
|
|
2700
|
+
if (provider === undefined) {
|
|
2701
|
+
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth");
|
|
2702
|
+
}
|
|
2204
2703
|
const redirectUri = callbackUri(providerInput);
|
|
2205
2704
|
const start = provider.begin === undefined ? provider.start({ redirectUri }) : await (async () => {
|
|
2206
2705
|
const initial = provider.start({ redirectUri });
|
|
@@ -13453,12 +13952,12 @@ function describe(description) {
|
|
|
13453
13952
|
ch._zod.check = () => {};
|
|
13454
13953
|
return ch;
|
|
13455
13954
|
}
|
|
13456
|
-
function meta(
|
|
13955
|
+
function meta(metadata2) {
|
|
13457
13956
|
const ch = new $ZodCheck({ check: "meta" });
|
|
13458
13957
|
ch._zod.onattach = [
|
|
13459
13958
|
(inst) => {
|
|
13460
13959
|
const existing = globalRegistry.get(inst) ?? {};
|
|
13461
|
-
globalRegistry.add(inst, { ...existing, ...
|
|
13960
|
+
globalRegistry.add(inst, { ...existing, ...metadata2 });
|
|
13462
13961
|
}
|
|
13463
13962
|
];
|
|
13464
13963
|
ch._zod.check = () => {};
|
|
@@ -16590,20 +17089,36 @@ function parseOrThrow(schema, body2) {
|
|
|
16590
17089
|
const path = issue2?.path.join(".") ?? "(root)";
|
|
16591
17090
|
throw new GatewayError("BAD_REQUEST", `${path}: ${issue2?.message ?? "invalid request"}`);
|
|
16592
17091
|
}
|
|
16593
|
-
var providerIdSchema = exports_external.enum(["anthropic", "openai", "kimi"]);
|
|
17092
|
+
var providerIdSchema = exports_external.enum(["anthropic", "openai", "kimi", "custom"]);
|
|
16594
17093
|
var dryRunSchema = exports_external.object({
|
|
16595
17094
|
tools: exports_external.boolean().default(false),
|
|
16596
17095
|
images: exports_external.boolean().default(false),
|
|
16597
17096
|
reasoning: exports_external.boolean().default(false)
|
|
16598
17097
|
}).strict();
|
|
16599
|
-
var
|
|
16600
|
-
|
|
16601
|
-
|
|
16602
|
-
|
|
16603
|
-
|
|
16604
|
-
|
|
16605
|
-
|
|
16606
|
-
|
|
17098
|
+
var targetSchema = exports_external.discriminatedUnion("provider", [
|
|
17099
|
+
exports_external.object({
|
|
17100
|
+
provider: exports_external.enum(["anthropic", "openai", "kimi"]),
|
|
17101
|
+
model: exports_external.string().min(1),
|
|
17102
|
+
tier: exports_external.number().int().min(1),
|
|
17103
|
+
weight: exports_external.number().positive(),
|
|
17104
|
+
costPerMTok: exports_external.object({
|
|
17105
|
+
input: exports_external.number().min(0),
|
|
17106
|
+
output: exports_external.number().min(0),
|
|
17107
|
+
cacheRead: exports_external.number().min(0).optional(),
|
|
17108
|
+
cacheWrite5m: exports_external.number().min(0).optional(),
|
|
17109
|
+
cacheWrite1h: exports_external.number().min(0).optional()
|
|
17110
|
+
}),
|
|
17111
|
+
contextWindow: exports_external.number().int().positive().optional(),
|
|
17112
|
+
maxOutputTokens: exports_external.number().int().positive().optional(),
|
|
17113
|
+
capabilities: exports_external.object({
|
|
17114
|
+
tools: exports_external.boolean(),
|
|
17115
|
+
images: exports_external.boolean(),
|
|
17116
|
+
reasoning: exports_external.boolean()
|
|
17117
|
+
})
|
|
17118
|
+
}).strict(),
|
|
17119
|
+
exports_external.object({
|
|
17120
|
+
provider: exports_external.literal("custom"),
|
|
17121
|
+
endpointId: exports_external.string().trim().min(1),
|
|
16607
17122
|
model: exports_external.string().min(1),
|
|
16608
17123
|
tier: exports_external.number().int().min(1),
|
|
16609
17124
|
weight: exports_external.number().positive(),
|
|
@@ -16621,7 +17136,15 @@ var modelSchema = exports_external.object({
|
|
|
16621
17136
|
images: exports_external.boolean(),
|
|
16622
17137
|
reasoning: exports_external.boolean()
|
|
16623
17138
|
})
|
|
16624
|
-
})
|
|
17139
|
+
}).strict()
|
|
17140
|
+
]);
|
|
17141
|
+
var modelSchema = exports_external.object({
|
|
17142
|
+
id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
|
|
17143
|
+
message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
|
|
17144
|
+
}),
|
|
17145
|
+
strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
|
|
17146
|
+
isAlias: exports_external.boolean(),
|
|
17147
|
+
targets: exports_external.array(targetSchema).min(1, "a virtual model needs at least one target")
|
|
16625
17148
|
});
|
|
16626
17149
|
var keyCreateSchema = exports_external.object({
|
|
16627
17150
|
label: exports_external.string().min(1).default("api key"),
|
|
@@ -16635,14 +17158,15 @@ var settingsSchema = exports_external.object({
|
|
|
16635
17158
|
quota: exports_external.number(),
|
|
16636
17159
|
cost: exports_external.number(),
|
|
16637
17160
|
latency: exports_external.number(),
|
|
16638
|
-
|
|
17161
|
+
load: exports_external.number()
|
|
16639
17162
|
}).strict(),
|
|
16640
17163
|
maxAttempts: exports_external.number().int().min(1).max(10),
|
|
16641
|
-
requestDeadlineMs: exports_external.number().int().
|
|
17164
|
+
requestDeadlineMs: exports_external.number().int().min(0),
|
|
16642
17165
|
breakerThreshold: exports_external.number().int().min(1),
|
|
16643
17166
|
breakerCooldownMs: exports_external.number().int().positive(),
|
|
16644
17167
|
logRetentionDays: exports_external.number().int().min(1),
|
|
16645
|
-
quotaPollIntervalMs: exports_external.number().int().min(0)
|
|
17168
|
+
quotaPollIntervalMs: exports_external.number().int().min(0),
|
|
17169
|
+
rtkEnabled: exports_external.boolean()
|
|
16646
17170
|
});
|
|
16647
17171
|
var credentialPatchSchema = exports_external.object({
|
|
16648
17172
|
label: exports_external.string().min(1).optional(),
|
|
@@ -16699,14 +17223,48 @@ async function getCredential(store, id) {
|
|
|
16699
17223
|
throw new GatewayError("BAD_REQUEST", "no such credential");
|
|
16700
17224
|
return summarizeCredential(credential);
|
|
16701
17225
|
}
|
|
17226
|
+
function requiredString(value, field) {
|
|
17227
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
17228
|
+
throw new GatewayError("BAD_REQUEST", `${field}: must not be empty`);
|
|
17229
|
+
}
|
|
17230
|
+
return value.trim();
|
|
17231
|
+
}
|
|
17232
|
+
function customProviderData(input) {
|
|
17233
|
+
const endpointId = requiredString(input.endpointId, "endpointId");
|
|
17234
|
+
const endpointLabel = requiredString(input.endpointLabel, "endpointLabel");
|
|
17235
|
+
const originInput = requiredString(input.origin, "origin");
|
|
17236
|
+
if (input.protocol !== "chat_completions" && input.protocol !== "responses") {
|
|
17237
|
+
throw new GatewayError("BAD_REQUEST", "protocol: unsupported protocol");
|
|
17238
|
+
}
|
|
17239
|
+
let url2;
|
|
17240
|
+
try {
|
|
17241
|
+
url2 = new URL(originInput);
|
|
17242
|
+
} catch {
|
|
17243
|
+
throw new GatewayError("BAD_REQUEST", "origin: must be a valid URL");
|
|
17244
|
+
}
|
|
17245
|
+
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) {
|
|
17246
|
+
throw new GatewayError("BAD_REQUEST", "origin: must be an HTTP(S) server origin");
|
|
17247
|
+
}
|
|
17248
|
+
return { endpointId, endpointLabel, origin: url2.origin, protocol: input.protocol };
|
|
17249
|
+
}
|
|
17250
|
+
function sameCustomEndpoint(a, b) {
|
|
17251
|
+
return a.endpointId === b.endpointId && a.endpointLabel === b.endpointLabel && a.origin === b.origin && a.protocol === b.protocol;
|
|
17252
|
+
}
|
|
16702
17253
|
async function createApiKeyCredential(store, input, logger2 = noopLogger) {
|
|
16703
17254
|
const provider = parseOrThrow(providerIdSchema, input.provider);
|
|
16704
|
-
|
|
16705
|
-
throw new GatewayError("BAD_REQUEST", "apiKey: must not be empty");
|
|
16706
|
-
}
|
|
17255
|
+
const apiKey = requiredString(input.apiKey, "apiKey");
|
|
16707
17256
|
if (input.label !== undefined && typeof input.label !== "string") {
|
|
16708
17257
|
throw new GatewayError("BAD_REQUEST", "label: must be a string");
|
|
16709
17258
|
}
|
|
17259
|
+
let providerData = {};
|
|
17260
|
+
if (provider === "custom") {
|
|
17261
|
+
const custom2 = customProviderData(input);
|
|
17262
|
+
const existing = (await store.credentials.list()).filter((credential) => credential.provider === "custom" && credential.providerData.endpointId === custom2.endpointId);
|
|
17263
|
+
if (existing.some((credential) => !sameCustomEndpoint(credential.providerData, custom2))) {
|
|
17264
|
+
throw new GatewayError("CONFLICT", `endpointId: metadata conflicts with existing endpoint`);
|
|
17265
|
+
}
|
|
17266
|
+
providerData = custom2;
|
|
17267
|
+
}
|
|
16710
17268
|
const label = input.label?.trim() || `${provider} api key`;
|
|
16711
17269
|
const created = await store.credentials.create({
|
|
16712
17270
|
id: crypto.randomUUID(),
|
|
@@ -16718,12 +17276,12 @@ async function createApiKeyCredential(store, input, logger2 = noopLogger) {
|
|
|
16718
17276
|
weight: 1,
|
|
16719
17277
|
expiresAt: null,
|
|
16720
17278
|
accountEmail: null,
|
|
16721
|
-
providerData
|
|
17279
|
+
providerData,
|
|
16722
17280
|
disabledReason: null,
|
|
16723
17281
|
disabledAt: null,
|
|
16724
17282
|
accessToken: null,
|
|
16725
17283
|
refreshToken: null,
|
|
16726
|
-
apiKey
|
|
17284
|
+
apiKey,
|
|
16727
17285
|
idToken: null
|
|
16728
17286
|
});
|
|
16729
17287
|
logger2.info("credential added", { credentialId: created.id, provider: created.provider });
|
|
@@ -16829,6 +17387,11 @@ async function buildSnapshot(store, now) {
|
|
|
16829
17387
|
}
|
|
16830
17388
|
|
|
16831
17389
|
// packages/router/src/filters.ts
|
|
17390
|
+
function needsAnthropicNative(request2) {
|
|
17391
|
+
if (request2.tools?.some((t) => t.provider === "anthropic") === true)
|
|
17392
|
+
return true;
|
|
17393
|
+
return request2.messages.some((m) => m.content.some((b) => b.type === "anthropicNative"));
|
|
17394
|
+
}
|
|
16832
17395
|
function requiredCapabilities(request2) {
|
|
16833
17396
|
const images = request2.messages.some((m) => m.content.some((b) => b.type === "image"));
|
|
16834
17397
|
return {
|
|
@@ -16845,13 +17408,17 @@ function eligible(input) {
|
|
|
16845
17408
|
const { request: request2, model, snapshot, now } = input;
|
|
16846
17409
|
const { breakerThreshold, breakerCooldownMs } = snapshot.settings;
|
|
16847
17410
|
const need = requiredCapabilities(request2);
|
|
17411
|
+
const needNative = needsAnthropicNative(request2);
|
|
16848
17412
|
const pairs = [];
|
|
16849
17413
|
const excluded = [];
|
|
16850
17414
|
for (const target of model.targets) {
|
|
16851
|
-
const missing = ["tools", "images", "reasoning"].find((cap) => need[cap] && !target.capabilities[cap]);
|
|
17415
|
+
const missing = needNative && !ANTHROPIC_NATIVE_TOOLS[target.provider] ? "anthropicTools" : ["tools", "images", "reasoning"].find((cap) => need[cap] && !target.capabilities[cap]);
|
|
16852
17416
|
for (const credential of snapshot.credentials) {
|
|
16853
17417
|
if (credential.provider !== target.provider)
|
|
16854
17418
|
continue;
|
|
17419
|
+
if (target.provider === "custom" && credential.providerData.endpointId !== target.endpointId) {
|
|
17420
|
+
continue;
|
|
17421
|
+
}
|
|
16855
17422
|
const drop = (reason) => {
|
|
16856
17423
|
excluded.push({ credentialId: credential.id, model: target.model, reason });
|
|
16857
17424
|
};
|
|
@@ -16923,6 +17490,22 @@ function quotaHeadroom(credential, windows, now, pollIntervalMs) {
|
|
|
16923
17490
|
return Math.min(...usable.map((w) => paceAdjusted(w, now)));
|
|
16924
17491
|
}
|
|
16925
17492
|
|
|
17493
|
+
// packages/store/src/types.ts
|
|
17494
|
+
var READ_OVER_INPUT = 0.1;
|
|
17495
|
+
function cacheReadRate(prices) {
|
|
17496
|
+
return prices.cacheRead ?? prices.input * READ_OVER_INPUT;
|
|
17497
|
+
}
|
|
17498
|
+
var DEFAULT_SETTINGS = {
|
|
17499
|
+
weights: { tier: 10, health: 3, quota: 2, load: 2, cost: 1, latency: 1 },
|
|
17500
|
+
maxAttempts: 3,
|
|
17501
|
+
requestDeadlineMs: 120000,
|
|
17502
|
+
breakerThreshold: 3,
|
|
17503
|
+
breakerCooldownMs: 30000,
|
|
17504
|
+
logRetentionDays: 30,
|
|
17505
|
+
quotaPollIntervalMs: 300000,
|
|
17506
|
+
rtkEnabled: false
|
|
17507
|
+
};
|
|
17508
|
+
|
|
16926
17509
|
// packages/router/src/score.ts
|
|
16927
17510
|
var UNKNOWN = 0.5;
|
|
16928
17511
|
function lowerIsBetter(value, min, max) {
|
|
@@ -16930,43 +17513,49 @@ function lowerIsBetter(value, min, max) {
|
|
|
16930
17513
|
return 1;
|
|
16931
17514
|
return (max - value) / (max - min);
|
|
16932
17515
|
}
|
|
16933
|
-
function
|
|
16934
|
-
return
|
|
17516
|
+
function ratio(value, min) {
|
|
17517
|
+
return Math.min(1, min / value);
|
|
17518
|
+
}
|
|
17519
|
+
function bestPositive(values) {
|
|
17520
|
+
const positive = values.filter((v) => v > 0);
|
|
17521
|
+
return positive.length > 0 ? Math.min(...positive) : null;
|
|
17522
|
+
}
|
|
17523
|
+
var EXPECTED_OUTPUT_TOKENS = 1000;
|
|
17524
|
+
function requestCost(target, request2) {
|
|
17525
|
+
const cachedTok = estimateCachedInputTokens(request2);
|
|
17526
|
+
const freshTok = Math.max(0, estimateInputTokens(request2) - cachedTok);
|
|
17527
|
+
const outTok = Math.min(request2.maxTokens ?? EXPECTED_OUTPUT_TOKENS, EXPECTED_OUTPUT_TOKENS);
|
|
17528
|
+
return freshTok * target.costPerMTok.input + cachedTok * cacheReadRate(target.costPerMTok) + outTok * target.costPerMTok.output;
|
|
17529
|
+
}
|
|
17530
|
+
function healthScore(h) {
|
|
17531
|
+
const base = 1 / (1 + (h?.consecutiveFailures ?? 0));
|
|
17532
|
+
return h?.breakerState === "open" || h?.breakerState === "halfOpen" ? base * 0.5 : base;
|
|
16935
17533
|
}
|
|
16936
17534
|
function score(pairs, input) {
|
|
16937
|
-
const { snapshot, now } = input;
|
|
17535
|
+
const { snapshot, now, load } = input;
|
|
16938
17536
|
const w = snapshot.settings.weights;
|
|
16939
17537
|
const tiers = pairs.map((p) => p.target.tier);
|
|
16940
17538
|
const minTier = Math.min(...tiers);
|
|
16941
17539
|
const maxTier = Math.max(...tiers);
|
|
16942
|
-
const costs = pairs.map((p) =>
|
|
16943
|
-
const
|
|
16944
|
-
const
|
|
16945
|
-
const latencies = pairs.flatMap((p) => {
|
|
17540
|
+
const costs = pairs.map((p) => requestCost(p.target, input.request));
|
|
17541
|
+
const bestCost = bestPositive(costs);
|
|
17542
|
+
const bestLatency = bestPositive(pairs.flatMap((p) => {
|
|
16946
17543
|
const h = snapshot.health.get(healthKey(p.credential.id, p.target.model));
|
|
16947
17544
|
return h?.ewmaTtftMs != null ? [h.ewmaTtftMs] : [];
|
|
16948
|
-
});
|
|
16949
|
-
const minLatency = latencies.length > 0 ? Math.min(...latencies) : 0;
|
|
16950
|
-
const maxLatency = latencies.length > 0 ? Math.max(...latencies) : 0;
|
|
16951
|
-
const idleTimes = pairs.map((p) => {
|
|
16952
|
-
const h = snapshot.health.get(healthKey(p.credential.id, p.target.model));
|
|
16953
|
-
return h?.lastUsedAt == null ? Number.POSITIVE_INFINITY : now - h.lastUsedAt;
|
|
16954
|
-
});
|
|
16955
|
-
const finiteIdle = idleTimes.filter(Number.isFinite);
|
|
16956
|
-
const maxIdle = finiteIdle.length > 0 ? Math.max(...finiteIdle) : 1;
|
|
17545
|
+
}));
|
|
16957
17546
|
return pairs.map((pair, i) => {
|
|
16958
|
-
const
|
|
17547
|
+
const key = healthKey(pair.credential.id, pair.target.model);
|
|
17548
|
+
const h = snapshot.health.get(key);
|
|
16959
17549
|
const tier = lowerIsBetter(pair.target.tier, minTier, maxTier);
|
|
16960
|
-
|
|
16961
|
-
|
|
16962
|
-
|
|
17550
|
+
const inflight = load.get(key) ?? 0;
|
|
17551
|
+
const loadTerm = 1 / (1 + inflight);
|
|
17552
|
+
const health = healthScore(h);
|
|
16963
17553
|
const quota = quotaHeadroom(pair.credential, snapshot.quota.get(pair.credential.id) ?? [], now, snapshot.settings.quotaPollIntervalMs);
|
|
16964
|
-
const
|
|
16965
|
-
const
|
|
16966
|
-
const
|
|
16967
|
-
const
|
|
16968
|
-
const
|
|
16969
|
-
const base = tier * w.tier + health * w.health + quota * w.quota + cost * w.cost + latency * w.latency + recency * w.recency;
|
|
17554
|
+
const ownCost = costs[i];
|
|
17555
|
+
const cost = ownCost <= 0 || bestCost === null ? UNKNOWN : ratio(ownCost, bestCost);
|
|
17556
|
+
const latency = h?.ewmaTtftMs == null || h.ewmaTtftMs <= 0 || bestLatency === null ? UNKNOWN : ratio(h.ewmaTtftMs, bestLatency);
|
|
17557
|
+
const reasons = { tier, health, quota, cost, latency, load: loadTerm };
|
|
17558
|
+
const base = tier * w.tier + health * w.health + quota * w.quota + cost * w.cost + latency * w.latency + loadTerm * w.load;
|
|
16970
17559
|
return {
|
|
16971
17560
|
credential: pair.credential,
|
|
16972
17561
|
target: pair.target,
|
|
@@ -16979,8 +17568,8 @@ function score(pairs, input) {
|
|
|
16979
17568
|
var PROVIDERS = new Set(Object.keys(PROVIDER_CAPABILITIES));
|
|
16980
17569
|
|
|
16981
17570
|
// packages/router/src/index.ts
|
|
16982
|
-
function weightedShuffle(candidates, rand, headroom) {
|
|
16983
|
-
const drawWeight = (c) => c.credential.weight * c.target.weight * headroom(c);
|
|
17571
|
+
function weightedShuffle(candidates, rand, headroom, health) {
|
|
17572
|
+
const drawWeight = (c) => c.credential.weight * c.target.weight * headroom(c) * health(c);
|
|
16984
17573
|
const total = candidates.reduce((sum, c) => sum + drawWeight(c), 0);
|
|
16985
17574
|
if (total <= 0)
|
|
16986
17575
|
return [...candidates].sort((a, b) => b.score - a.score);
|
|
@@ -17003,6 +17592,8 @@ function rank(input) {
|
|
|
17003
17592
|
return { candidates: [], excluded };
|
|
17004
17593
|
const scored = score(pairs, input);
|
|
17005
17594
|
const headroom = (c) => quotaHeadroom(c.credential, input.snapshot.quota.get(c.credential.id) ?? [], input.now, input.snapshot.settings.quotaPollIntervalMs);
|
|
17595
|
+
const health = (c) => healthScore(input.snapshot.health.get(healthKey(c.credential.id, c.target.model)));
|
|
17596
|
+
const inflight = (c) => input.load.get(healthKey(c.credential.id, c.target.model)) ?? 0;
|
|
17006
17597
|
switch (input.model.strategy) {
|
|
17007
17598
|
case "priority":
|
|
17008
17599
|
scored.sort((a, b) => a.target.tier - b.target.tier || b.score - a.score);
|
|
@@ -17013,11 +17604,11 @@ function rank(input) {
|
|
|
17013
17604
|
return h?.lastUsedAt == null ? Number.POSITIVE_INFINITY : input.now - h.lastUsedAt;
|
|
17014
17605
|
};
|
|
17015
17606
|
const spent = (c) => headroom(c) < QUOTA_FLOOR ? 1 : 0;
|
|
17016
|
-
scored.sort((a, b) => spent(a) - spent(b) || idle(b) - idle(a));
|
|
17607
|
+
scored.sort((a, b) => spent(a) - spent(b) || inflight(a) - inflight(b) || idle(b) - idle(a));
|
|
17017
17608
|
break;
|
|
17018
17609
|
}
|
|
17019
17610
|
case "weighted":
|
|
17020
|
-
return { candidates: weightedShuffle(scored, input.rand, headroom), excluded };
|
|
17611
|
+
return { candidates: weightedShuffle(scored, input.rand, headroom, health), excluded };
|
|
17021
17612
|
case "score":
|
|
17022
17613
|
scored.sort((a, b) => b.score - a.score);
|
|
17023
17614
|
break;
|
|
@@ -17043,10 +17634,14 @@ async function dryRun(deps, modelId, input) {
|
|
|
17043
17634
|
}
|
|
17044
17635
|
],
|
|
17045
17636
|
stream: false,
|
|
17046
|
-
...need.tools ? {
|
|
17637
|
+
...need.tools ? {
|
|
17638
|
+
tools: [
|
|
17639
|
+
{ provider: "custom", name: "probe", description: "", inputSchema: { type: "object" } }
|
|
17640
|
+
]
|
|
17641
|
+
} : {},
|
|
17047
17642
|
...need.reasoning ? { reasoning: { mode: "adaptive" } } : {}
|
|
17048
17643
|
};
|
|
17049
|
-
const result = rank({ request: probe, model, snapshot, now, rand: 0 });
|
|
17644
|
+
const result = rank({ request: probe, model, snapshot, now, rand: 0, load: new Map });
|
|
17050
17645
|
return {
|
|
17051
17646
|
modelId: model.id,
|
|
17052
17647
|
strategy: model.strategy,
|
|
@@ -17103,20 +17698,24 @@ function unhex(s) {
|
|
|
17103
17698
|
out[i] = Number.parseInt(s.slice(i * 2, i * 2 + 2), 16);
|
|
17104
17699
|
return out;
|
|
17105
17700
|
}
|
|
17106
|
-
// packages/store/src/types.ts
|
|
17107
|
-
var DEFAULT_SETTINGS = {
|
|
17108
|
-
weights: { tier: 10, health: 3, quota: 2, cost: 1, latency: 1, recency: 0.5 },
|
|
17109
|
-
maxAttempts: 3,
|
|
17110
|
-
requestDeadlineMs: 120000,
|
|
17111
|
-
breakerThreshold: 3,
|
|
17112
|
-
breakerCooldownMs: 30000,
|
|
17113
|
-
logRetentionDays: 30,
|
|
17114
|
-
quotaPollIntervalMs: 300000
|
|
17115
|
-
};
|
|
17116
|
-
|
|
17117
17701
|
// packages/store/src/sqlite/config.ts
|
|
17118
17702
|
var SETTINGS_KEY = "settings";
|
|
17119
17703
|
var ADMIN_HASH_KEY = "adminPasswordHash";
|
|
17704
|
+
function knownWeights(stored) {
|
|
17705
|
+
const d = DEFAULT_SETTINGS.weights;
|
|
17706
|
+
const pick2 = (key) => {
|
|
17707
|
+
const value = stored?.[key];
|
|
17708
|
+
return typeof value === "number" && Number.isFinite(value) ? value : d[key];
|
|
17709
|
+
};
|
|
17710
|
+
return {
|
|
17711
|
+
tier: pick2("tier"),
|
|
17712
|
+
health: pick2("health"),
|
|
17713
|
+
quota: pick2("quota"),
|
|
17714
|
+
load: pick2("load"),
|
|
17715
|
+
cost: pick2("cost"),
|
|
17716
|
+
latency: pick2("latency")
|
|
17717
|
+
};
|
|
17718
|
+
}
|
|
17120
17719
|
function createConfigRepo(db, emit2 = () => {}) {
|
|
17121
17720
|
const readRaw = (key) => db.query("SELECT value FROM settings WHERE key = ?").get(key)?.value ?? null;
|
|
17122
17721
|
const writeRaw = (key, value) => {
|
|
@@ -17147,12 +17746,18 @@ function createConfigRepo(db, emit2 = () => {}) {
|
|
|
17147
17746
|
const raw = readRaw(SETTINGS_KEY);
|
|
17148
17747
|
if (raw === null)
|
|
17149
17748
|
return DEFAULT_SETTINGS;
|
|
17150
|
-
|
|
17151
|
-
|
|
17152
|
-
|
|
17153
|
-
|
|
17154
|
-
|
|
17155
|
-
|
|
17749
|
+
try {
|
|
17750
|
+
const parsed = JSON.parse(raw);
|
|
17751
|
+
const stored = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
17752
|
+
return {
|
|
17753
|
+
...DEFAULT_SETTINGS,
|
|
17754
|
+
...stored,
|
|
17755
|
+
rtkEnabled: stored.rtkEnabled === true,
|
|
17756
|
+
weights: knownWeights(stored.weights)
|
|
17757
|
+
};
|
|
17758
|
+
} catch {
|
|
17759
|
+
return DEFAULT_SETTINGS;
|
|
17760
|
+
}
|
|
17156
17761
|
},
|
|
17157
17762
|
async putSettings(patch) {
|
|
17158
17763
|
const current = await this.getSettings();
|
|
@@ -17594,6 +18199,20 @@ ALTER TABLE request_logs ADD COLUMN state TEXT NOT NULL DEFAULT 'done';
|
|
|
17594
18199
|
CREATE INDEX idx_request_logs_pending ON request_logs(state) WHERE state = 'pending';
|
|
17595
18200
|
`;
|
|
17596
18201
|
|
|
18202
|
+
// packages/store/src/sqlite/migrations/005_rtk_metrics.sql
|
|
18203
|
+
var _005_rtk_metrics_default = `ALTER TABLE request_logs ADD COLUMN rtk_applied INTEGER NOT NULL DEFAULT 0;
|
|
18204
|
+
ALTER TABLE request_logs ADD COLUMN rtk_filter_hits INTEGER NOT NULL DEFAULT 0;
|
|
18205
|
+
ALTER TABLE request_logs ADD COLUMN rtk_original_code_units INTEGER NOT NULL DEFAULT 0;
|
|
18206
|
+
ALTER TABLE request_logs ADD COLUMN rtk_compressed_code_units INTEGER NOT NULL DEFAULT 0;
|
|
18207
|
+
ALTER TABLE request_logs ADD COLUMN rtk_estimated_tokens_saved INTEGER NOT NULL DEFAULT 0;
|
|
18208
|
+
ALTER TABLE request_logs ADD COLUMN rtk_filters TEXT NOT NULL DEFAULT '[]';
|
|
18209
|
+
`;
|
|
18210
|
+
|
|
18211
|
+
// packages/store/src/sqlite/migrations/006_rtk_usage.sql
|
|
18212
|
+
var _006_rtk_usage_default = `ALTER TABLE usage_daily ADD COLUMN rtk_saved_tokens INTEGER NOT NULL DEFAULT 0;
|
|
18213
|
+
ALTER TABLE usage_daily ADD COLUMN rtk_applied_requests INTEGER NOT NULL DEFAULT 0;
|
|
18214
|
+
`;
|
|
18215
|
+
|
|
17597
18216
|
// packages/store/src/sqlite/rollup.ts
|
|
17598
18217
|
function startOfLocalDay(at) {
|
|
17599
18218
|
const day = new Date(at);
|
|
@@ -17604,8 +18223,8 @@ var UPSERT = `
|
|
|
17604
18223
|
INSERT INTO usage_daily
|
|
17605
18224
|
(day, provider, credential_id, requested_model, resolved_model, api_key_id,
|
|
17606
18225
|
requests, errors, input_tokens, output_tokens, cache_read_tokens,
|
|
17607
|
-
cache_write_tokens, cost_usd, duration_ms_sum)
|
|
17608
|
-
VALUES (
|
|
18226
|
+
cache_write_tokens, rtk_saved_tokens, rtk_applied_requests, cost_usd, duration_ms_sum)
|
|
18227
|
+
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
17609
18228
|
ON CONFLICT (day, provider, credential_id, requested_model, resolved_model, api_key_id)
|
|
17610
18229
|
DO UPDATE SET
|
|
17611
18230
|
requests = requests + excluded.requests,
|
|
@@ -17613,8 +18232,10 @@ var UPSERT = `
|
|
|
17613
18232
|
input_tokens = input_tokens + excluded.input_tokens,
|
|
17614
18233
|
output_tokens = output_tokens + excluded.output_tokens,
|
|
17615
18234
|
cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
|
|
17616
|
-
cache_write_tokens
|
|
17617
|
-
|
|
18235
|
+
cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
|
|
18236
|
+
rtk_saved_tokens = rtk_saved_tokens + excluded.rtk_saved_tokens,
|
|
18237
|
+
rtk_applied_requests = rtk_applied_requests + excluded.rtk_applied_requests,
|
|
18238
|
+
cost_usd = cost_usd + excluded.cost_usd,
|
|
17618
18239
|
duration_ms_sum = duration_ms_sum + excluded.duration_ms_sum`;
|
|
17619
18240
|
function keyOf(log) {
|
|
17620
18241
|
return [
|
|
@@ -17634,6 +18255,8 @@ function countersOf(log) {
|
|
|
17634
18255
|
outputTokens: log.outputTokens,
|
|
17635
18256
|
cacheReadTokens: log.cacheReadTokens,
|
|
17636
18257
|
cacheWriteTokens: log.cacheWriteTokens,
|
|
18258
|
+
rtkSavedTokens: log.rtkEstimatedTokensSaved,
|
|
18259
|
+
rtkAppliedRequests: log.rtkApplied ? 1 : 0,
|
|
17637
18260
|
costUsd: log.costUsd,
|
|
17638
18261
|
durationMsSum: log.durationMs
|
|
17639
18262
|
};
|
|
@@ -17647,6 +18270,8 @@ function upsert(db, key, c) {
|
|
|
17647
18270
|
c.outputTokens,
|
|
17648
18271
|
c.cacheReadTokens,
|
|
17649
18272
|
c.cacheWriteTokens,
|
|
18273
|
+
c.rtkSavedTokens,
|
|
18274
|
+
c.rtkAppliedRequests,
|
|
17650
18275
|
c.costUsd,
|
|
17651
18276
|
c.durationMsSum
|
|
17652
18277
|
]);
|
|
@@ -17677,6 +18302,8 @@ function backfillDaily(db) {
|
|
|
17677
18302
|
outputTokens: 0,
|
|
17678
18303
|
cacheReadTokens: 0,
|
|
17679
18304
|
cacheWriteTokens: 0,
|
|
18305
|
+
rtkSavedTokens: 0,
|
|
18306
|
+
rtkAppliedRequests: 0,
|
|
17680
18307
|
costUsd: 0,
|
|
17681
18308
|
durationMsSum: 0
|
|
17682
18309
|
};
|
|
@@ -17695,13 +18322,42 @@ function backfillDaily(db) {
|
|
|
17695
18322
|
upsert(db, group.key, group.counters);
|
|
17696
18323
|
return groups.size;
|
|
17697
18324
|
}
|
|
18325
|
+
function backfillRtkUsage(db) {
|
|
18326
|
+
const groups = new Map;
|
|
18327
|
+
for (const row of db.query(`SELECT at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
|
|
18328
|
+
rtk_applied, rtk_estimated_tokens_saved
|
|
18329
|
+
FROM request_logs
|
|
18330
|
+
WHERE state = 'done'`).all()) {
|
|
18331
|
+
const key = [
|
|
18332
|
+
startOfLocalDay(row.at),
|
|
18333
|
+
row.resolved_provider ?? "",
|
|
18334
|
+
row.credential_id ?? "",
|
|
18335
|
+
row.requested_model,
|
|
18336
|
+
row.resolved_model ?? "",
|
|
18337
|
+
row.api_key_id ?? ""
|
|
18338
|
+
];
|
|
18339
|
+
const id = key.join("\x00");
|
|
18340
|
+
const group = groups.get(id) ?? { key, saved: 0, applied: 0 };
|
|
18341
|
+
group.saved += row.rtk_estimated_tokens_saved;
|
|
18342
|
+
group.applied += row.rtk_applied === 1 ? 1 : 0;
|
|
18343
|
+
groups.set(id, group);
|
|
18344
|
+
}
|
|
18345
|
+
for (const { key, saved, applied } of groups.values()) {
|
|
18346
|
+
db.run(`UPDATE usage_daily
|
|
18347
|
+
SET rtk_saved_tokens = ?, rtk_applied_requests = ?
|
|
18348
|
+
WHERE day = ? AND provider = ? AND credential_id = ? AND requested_model = ?
|
|
18349
|
+
AND resolved_model = ? AND api_key_id = ?`, [saved, applied, ...key]);
|
|
18350
|
+
}
|
|
18351
|
+
}
|
|
17698
18352
|
|
|
17699
18353
|
// packages/store/src/sqlite/db.ts
|
|
17700
18354
|
var MIGRATIONS = [
|
|
17701
18355
|
{ id: 1, sql: _001_init_default },
|
|
17702
18356
|
{ id: 2, sql: _002_usage_daily_default, after: backfillDaily },
|
|
17703
18357
|
{ id: 3, sql: _003_quota_snapshot_default },
|
|
17704
|
-
{ id: 4, sql: _004_request_state_default }
|
|
18358
|
+
{ id: 4, sql: _004_request_state_default },
|
|
18359
|
+
{ id: 5, sql: _005_rtk_metrics_default },
|
|
18360
|
+
{ id: 6, sql: _006_rtk_usage_default, after: backfillRtkUsage }
|
|
17705
18361
|
];
|
|
17706
18362
|
function openDb(path) {
|
|
17707
18363
|
const db = new Database(path, { create: true });
|
|
@@ -17770,7 +18426,38 @@ function createKeyRepo(db) {
|
|
|
17770
18426
|
}
|
|
17771
18427
|
};
|
|
17772
18428
|
}
|
|
18429
|
+
// packages/rtk/src/catalog.ts
|
|
18430
|
+
var RTK_FILTER_IDS = [
|
|
18431
|
+
"git-diff",
|
|
18432
|
+
"git-status",
|
|
18433
|
+
"git-log",
|
|
18434
|
+
"grep",
|
|
18435
|
+
"path-list",
|
|
18436
|
+
"numbered-read",
|
|
18437
|
+
"build-output",
|
|
18438
|
+
"test-output",
|
|
18439
|
+
"deduplicate-log",
|
|
18440
|
+
"smart-truncate",
|
|
18441
|
+
"lint-output",
|
|
18442
|
+
"package-output",
|
|
18443
|
+
"tree-output",
|
|
18444
|
+
"git-operation",
|
|
18445
|
+
"docker-build"
|
|
18446
|
+
];
|
|
18447
|
+
var FILTER_IDS = new Set(RTK_FILTER_IDS);
|
|
18448
|
+
function isRtkFilterId(value) {
|
|
18449
|
+
return FILTER_IDS.has(value);
|
|
18450
|
+
}
|
|
18451
|
+
|
|
17773
18452
|
// packages/store/src/sqlite/usage.ts
|
|
18453
|
+
function parseRtkFilters(raw) {
|
|
18454
|
+
try {
|
|
18455
|
+
const parsed = JSON.parse(raw);
|
|
18456
|
+
return Array.isArray(parsed) ? parsed.filter(isRtkFilterId) : [];
|
|
18457
|
+
} catch {
|
|
18458
|
+
return [];
|
|
18459
|
+
}
|
|
18460
|
+
}
|
|
17774
18461
|
var toLog = (r) => ({
|
|
17775
18462
|
id: r.id,
|
|
17776
18463
|
state: r.state === "pending" ? "pending" : "done",
|
|
@@ -17790,7 +18477,13 @@ var toLog = (r) => ({
|
|
|
17790
18477
|
ttftMs: r.ttft_ms,
|
|
17791
18478
|
durationMs: r.duration_ms,
|
|
17792
18479
|
costUsd: r.cost_usd,
|
|
17793
|
-
degradations: JSON.parse(r.degradations)
|
|
18480
|
+
degradations: JSON.parse(r.degradations),
|
|
18481
|
+
rtkApplied: r.rtk_applied === 1,
|
|
18482
|
+
rtkFilterHits: r.rtk_filter_hits,
|
|
18483
|
+
rtkOriginalCodeUnits: r.rtk_original_code_units,
|
|
18484
|
+
rtkCompressedCodeUnits: r.rtk_compressed_code_units,
|
|
18485
|
+
rtkEstimatedTokensSaved: r.rtk_estimated_tokens_saved,
|
|
18486
|
+
rtkFilters: parseRtkFilters(r.rtk_filters)
|
|
17794
18487
|
});
|
|
17795
18488
|
var GROUP_COLUMN = {
|
|
17796
18489
|
raw: {
|
|
@@ -17835,8 +18528,9 @@ function label(value) {
|
|
|
17835
18528
|
var COLUMNS = `(id, state, at, api_key_id, requested_model, resolved_provider, resolved_model,
|
|
17836
18529
|
credential_id, attempts, status, error_code, input_tokens, output_tokens,
|
|
17837
18530
|
cache_read_tokens, cache_write_tokens, ttft_ms, duration_ms, cost_usd,
|
|
17838
|
-
degradations
|
|
17839
|
-
|
|
18531
|
+
degradations, rtk_applied, rtk_filter_hits, rtk_original_code_units,
|
|
18532
|
+
rtk_compressed_code_units, rtk_estimated_tokens_saved, rtk_filters)`;
|
|
18533
|
+
var PLACEHOLDERS = "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
|
|
17840
18534
|
function values(log, state) {
|
|
17841
18535
|
return [
|
|
17842
18536
|
log.id,
|
|
@@ -17857,7 +18551,13 @@ function values(log, state) {
|
|
|
17857
18551
|
log.ttftMs,
|
|
17858
18552
|
log.durationMs,
|
|
17859
18553
|
log.costUsd,
|
|
17860
|
-
JSON.stringify(log.degradations)
|
|
18554
|
+
JSON.stringify(log.degradations),
|
|
18555
|
+
log.rtkApplied === true ? 1 : 0,
|
|
18556
|
+
log.rtkFilterHits ?? 0,
|
|
18557
|
+
log.rtkOriginalCodeUnits ?? 0,
|
|
18558
|
+
log.rtkCompressedCodeUnits ?? 0,
|
|
18559
|
+
log.rtkEstimatedTokensSaved ?? 0,
|
|
18560
|
+
JSON.stringify(log.rtkFilters ?? [])
|
|
17861
18561
|
];
|
|
17862
18562
|
}
|
|
17863
18563
|
var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
|
|
@@ -17878,7 +18578,13 @@ var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
|
|
|
17878
18578
|
ttft_ms = excluded.ttft_ms,
|
|
17879
18579
|
duration_ms = excluded.duration_ms,
|
|
17880
18580
|
cost_usd = excluded.cost_usd,
|
|
17881
|
-
degradations = excluded.degradations
|
|
18581
|
+
degradations = excluded.degradations,
|
|
18582
|
+
rtk_applied = excluded.rtk_applied,
|
|
18583
|
+
rtk_filter_hits = excluded.rtk_filter_hits,
|
|
18584
|
+
rtk_original_code_units = excluded.rtk_original_code_units,
|
|
18585
|
+
rtk_compressed_code_units = excluded.rtk_compressed_code_units,
|
|
18586
|
+
rtk_estimated_tokens_saved = excluded.rtk_estimated_tokens_saved,
|
|
18587
|
+
rtk_filters = excluded.rtk_filters`;
|
|
17882
18588
|
function createUsageRepo(db) {
|
|
17883
18589
|
const complete = db.transaction((log) => {
|
|
17884
18590
|
db.run(COMPLETE, values(log, "done"));
|
|
@@ -17923,6 +18629,8 @@ function createUsageRepo(db) {
|
|
|
17923
18629
|
COALESCE(SUM(output_tokens), 0) AS output_tokens,
|
|
17924
18630
|
COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
|
|
17925
18631
|
COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
|
|
18632
|
+
COALESCE(SUM(${daily ? "rtk_saved_tokens" : "rtk_estimated_tokens_saved"}), 0) AS rtk_saved_tokens,
|
|
18633
|
+
COALESCE(SUM(${daily ? "rtk_applied_requests" : "CASE WHEN rtk_applied = 1 THEN 1 ELSE 0 END"}), 0) AS rtk_applied_requests,
|
|
17926
18634
|
COALESCE(SUM(cost_usd), 0) AS cost_usd
|
|
17927
18635
|
FROM ${daily ? "usage_daily" : "request_logs"}
|
|
17928
18636
|
WHERE ${daily ? "" : "state = 'done' AND "}${timeColumn} >= ? AND ${timeColumn} <= ?
|
|
@@ -17936,6 +18644,8 @@ function createUsageRepo(db) {
|
|
|
17936
18644
|
outputTokens: r.output_tokens,
|
|
17937
18645
|
cacheReadTokens: r.cache_read_tokens,
|
|
17938
18646
|
cacheWriteTokens: r.cache_write_tokens,
|
|
18647
|
+
rtkSavedTokens: r.rtk_saved_tokens,
|
|
18648
|
+
rtkAppliedRequests: r.rtk_applied_requests,
|
|
17939
18649
|
costUsd: r.cost_usd,
|
|
17940
18650
|
errors: r.errors,
|
|
17941
18651
|
durationMsSum: r.duration_ms_sum
|
|
@@ -18080,6 +18790,11 @@ async function putModel(store, id, input) {
|
|
|
18080
18790
|
if (model.id !== id) {
|
|
18081
18791
|
throw new GatewayError("BAD_REQUEST", "model id in the path and body must match");
|
|
18082
18792
|
}
|
|
18793
|
+
const customEndpointIds = new Set((await store.credentials.list()).filter((credential) => credential.provider === "custom").map((credential) => credential.providerData.endpointId).filter((endpointId) => typeof endpointId === "string"));
|
|
18794
|
+
const missing = model.targets.find((target) => target.provider === "custom" && (target.endpointId === undefined || !customEndpointIds.has(target.endpointId)));
|
|
18795
|
+
if (missing !== undefined) {
|
|
18796
|
+
throw new GatewayError("BAD_REQUEST", `custom endpoint "${missing.endpointId}" has no credential`);
|
|
18797
|
+
}
|
|
18083
18798
|
await store.config.putModel(model);
|
|
18084
18799
|
}
|
|
18085
18800
|
async function removeModel(store, id) {
|
|
@@ -18202,7 +18917,7 @@ var AUTHORIZE_URL2 = "https://auth.openai.com/oauth/authorize";
|
|
|
18202
18917
|
var TOKEN_URL3 = "https://auth.openai.com/oauth/token";
|
|
18203
18918
|
var SCOPES2 = "openid profile email offline_access";
|
|
18204
18919
|
var USAGE_URL3 = "https://chatgpt.com/backend-api/wham/usage";
|
|
18205
|
-
function
|
|
18920
|
+
function isRecord3(value) {
|
|
18206
18921
|
return typeof value === "object" && value !== null;
|
|
18207
18922
|
}
|
|
18208
18923
|
function nonBlankStringOrNull(value) {
|
|
@@ -18219,19 +18934,19 @@ function decodeClaims(idToken) {
|
|
|
18219
18934
|
}
|
|
18220
18935
|
try {
|
|
18221
18936
|
const json5 = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
18222
|
-
if (!
|
|
18937
|
+
if (!isRecord3(json5))
|
|
18223
18938
|
return { email: null, accountId: null };
|
|
18224
18939
|
const auth = json5["https://api.openai.com/auth"];
|
|
18225
18940
|
return {
|
|
18226
18941
|
email: typeof json5.email === "string" ? json5.email : null,
|
|
18227
|
-
accountId:
|
|
18942
|
+
accountId: isRecord3(auth) ? nonBlankStringOrNull(auth.chatgpt_account_id) : null
|
|
18228
18943
|
};
|
|
18229
18944
|
} catch {
|
|
18230
18945
|
return { email: null, accountId: null };
|
|
18231
18946
|
}
|
|
18232
18947
|
}
|
|
18233
18948
|
function parseTokenResponse(value) {
|
|
18234
|
-
if (!
|
|
18949
|
+
if (!isRecord3(value) || typeof value.access_token !== "string")
|
|
18235
18950
|
return null;
|
|
18236
18951
|
return {
|
|
18237
18952
|
accessToken: value.access_token,
|
|
@@ -18366,6 +19081,9 @@ function createRefresher(deps) {
|
|
|
18366
19081
|
throw new GatewayError("AUTH", `credential ${credential.id} has no refresh token`);
|
|
18367
19082
|
}
|
|
18368
19083
|
const provider = deps.providers[credential.provider];
|
|
19084
|
+
if (provider === undefined) {
|
|
19085
|
+
throw new GatewayError("BAD_REQUEST", "provider does not support OAuth refresh");
|
|
19086
|
+
}
|
|
18369
19087
|
logger2.debug("refreshing credential", {
|
|
18370
19088
|
provider: credential.provider,
|
|
18371
19089
|
credentialId: credential.id
|
|
@@ -18436,33 +19154,77 @@ async function describeModelsForSetup(store) {
|
|
|
18436
19154
|
label: modelDisplayName(model)
|
|
18437
19155
|
}));
|
|
18438
19156
|
}
|
|
18439
|
-
|
|
18440
|
-
|
|
18441
|
-
|
|
18442
|
-
|
|
18443
|
-
|
|
18444
|
-
|
|
18445
|
-
|
|
18446
|
-
|
|
18447
|
-
|
|
18448
|
-
|
|
18449
|
-
|
|
18450
|
-
|
|
18451
|
-
|
|
18452
|
-
|
|
18453
|
-
if (limits.contextWindow !== undefined && !/^claude-/i.test(modelId)) {
|
|
18454
|
-
env2.CLAUDE_CODE_MAX_CONTEXT_TOKENS = String(limits.contextWindow);
|
|
19157
|
+
var CLAUDE_MAPPING_KEYS = {
|
|
19158
|
+
defaultModel: "ANTHROPIC_MODEL",
|
|
19159
|
+
fableModel: "ANTHROPIC_DEFAULT_FABLE_MODEL",
|
|
19160
|
+
opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
|
|
19161
|
+
sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
|
|
19162
|
+
haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL"
|
|
19163
|
+
};
|
|
19164
|
+
function settingsObject(existing) {
|
|
19165
|
+
if (existing === undefined)
|
|
19166
|
+
return {};
|
|
19167
|
+
try {
|
|
19168
|
+
const parsed = JSON.parse(existing);
|
|
19169
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
19170
|
+
throw new Error("settings root is not an object");
|
|
18455
19171
|
}
|
|
18456
|
-
return
|
|
18457
|
-
|
|
18458
|
-
|
|
19172
|
+
return parsed;
|
|
19173
|
+
} catch (error51) {
|
|
19174
|
+
const reason = error51 instanceof Error ? error51.message : "invalid JSON";
|
|
19175
|
+
throw new Error(`cannot parse existing settings.json: ${reason}`);
|
|
19176
|
+
}
|
|
19177
|
+
}
|
|
19178
|
+
function claudeSettings(described, input, mapping, existing) {
|
|
19179
|
+
if (mapping.defaultModel === "")
|
|
19180
|
+
throw new Error("default model is required");
|
|
19181
|
+
const ids = new Set(described.map(({ model }) => model.id));
|
|
19182
|
+
const visibleId = (slot, id) => {
|
|
19183
|
+
if (!ids.has(id))
|
|
19184
|
+
throw new Error(`${slot} names unknown virtual model "${id}"`);
|
|
19185
|
+
const useMirror = input.discoveryMirrors === true && !/^(?:claude|anthropic)/i.test(id);
|
|
19186
|
+
return useMirror ? `claude/${id}` : id;
|
|
19187
|
+
};
|
|
19188
|
+
const settings = settingsObject(existing);
|
|
19189
|
+
const currentEnv = settings.env;
|
|
19190
|
+
const env2 = typeof currentEnv === "object" && currentEnv !== null && !Array.isArray(currentEnv) ? { ...currentEnv } : {};
|
|
19191
|
+
for (const key of Object.values(CLAUDE_MAPPING_KEYS))
|
|
19192
|
+
delete env2[key];
|
|
19193
|
+
delete env2.CLAUDE_CODE_MAX_CONTEXT_TOKENS;
|
|
19194
|
+
env2.ANTHROPIC_BASE_URL = input.baseUrl;
|
|
19195
|
+
env2.ANTHROPIC_AUTH_TOKEN = input.apiKey ?? KEY_PLACEHOLDER;
|
|
19196
|
+
env2.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY = "1";
|
|
19197
|
+
for (const [slot, key] of Object.entries(CLAUDE_MAPPING_KEYS)) {
|
|
19198
|
+
const id = mapping[slot];
|
|
19199
|
+
if (id !== undefined && id !== "")
|
|
19200
|
+
env2[key] = visibleId(slot, id);
|
|
19201
|
+
}
|
|
19202
|
+
return {
|
|
19203
|
+
path: "settings.json",
|
|
19204
|
+
contents: `${JSON.stringify({ ...settings, env: env2 }, null, 2)}
|
|
18459
19205
|
`
|
|
18460
|
-
|
|
18461
|
-
});
|
|
19206
|
+
};
|
|
18462
19207
|
}
|
|
18463
|
-
function opencodeConfig(described, input) {
|
|
19208
|
+
function opencodeConfig(described, input, mapping) {
|
|
19209
|
+
if (mapping.defaultModel === "")
|
|
19210
|
+
throw new Error("default model is required");
|
|
19211
|
+
const byId = new Map(described.map((entry) => [entry.model.id, entry]));
|
|
19212
|
+
const selected = [];
|
|
19213
|
+
const seen = new Set;
|
|
19214
|
+
for (const slot of Object.keys(CLAUDE_MAPPING_KEYS)) {
|
|
19215
|
+
const id = mapping[slot];
|
|
19216
|
+
if (id === undefined || id === "")
|
|
19217
|
+
continue;
|
|
19218
|
+
const entry = byId.get(id);
|
|
19219
|
+
if (entry === undefined)
|
|
19220
|
+
throw new Error(`${slot} names unknown virtual model "${id}"`);
|
|
19221
|
+
if (!seen.has(id)) {
|
|
19222
|
+
seen.add(id);
|
|
19223
|
+
selected.push(entry);
|
|
19224
|
+
}
|
|
19225
|
+
}
|
|
18464
19226
|
const models = {};
|
|
18465
|
-
for (const { model, limits, label: label2 } of
|
|
19227
|
+
for (const { model, limits, label: label2 } of selected) {
|
|
18466
19228
|
const limit = limits.contextWindow === undefined ? undefined : {
|
|
18467
19229
|
context: limits.contextWindow,
|
|
18468
19230
|
...limits.maxOutputTokens === undefined ? {} : { output: limits.maxOutputTokens }
|
|
@@ -18471,6 +19233,7 @@ function opencodeConfig(described, input) {
|
|
|
18471
19233
|
}
|
|
18472
19234
|
const contents = `${JSON.stringify({
|
|
18473
19235
|
$schema: "https://opencode.ai/config.json",
|
|
19236
|
+
model: `omnigateway/${mapping.defaultModel}`,
|
|
18474
19237
|
provider: {
|
|
18475
19238
|
omnigateway: {
|
|
18476
19239
|
npm: "@ai-sdk/openai-compatible",
|
|
@@ -19040,7 +19803,8 @@ var console_ = {
|
|
|
19040
19803
|
var PROVIDER_TONE = {
|
|
19041
19804
|
anthropic: "magenta",
|
|
19042
19805
|
openai: "green",
|
|
19043
|
-
kimi: "blue"
|
|
19806
|
+
kimi: "blue",
|
|
19807
|
+
custom: "cyan"
|
|
19044
19808
|
};
|
|
19045
19809
|
function provider(ctx, id) {
|
|
19046
19810
|
return paint(ctx, PROVIDER_TONE[id], id);
|
|
@@ -19204,21 +19968,36 @@ var credentialsRefresh = {
|
|
|
19204
19968
|
}
|
|
19205
19969
|
};
|
|
19206
19970
|
var credentialsAddKey = {
|
|
19207
|
-
usage: "credentials add-key <provider> [--label L]",
|
|
19971
|
+
usage: "credentials add-key <provider> [--label L] [--endpoint-id ID --endpoint-label L --origin URL --protocol P]",
|
|
19208
19972
|
summary: "Store a provider API key, read from a prompt or stdin",
|
|
19209
|
-
options: {
|
|
19973
|
+
options: {
|
|
19974
|
+
label: { type: "string" },
|
|
19975
|
+
"endpoint-id": { type: "string" },
|
|
19976
|
+
"endpoint-label": { type: "string" },
|
|
19977
|
+
origin: { type: "string" },
|
|
19978
|
+
protocol: { type: "string" }
|
|
19979
|
+
},
|
|
19210
19980
|
async run(args, { ctx, writer, prompt }) {
|
|
19211
19981
|
const providerId = requirePositional(args, 0, "provider");
|
|
19212
19982
|
if (!isProviderId(providerId)) {
|
|
19213
|
-
throw new UsageError("provider must be one of anthropic, openai, kimi");
|
|
19983
|
+
throw new UsageError("provider must be one of anthropic, openai, kimi, custom");
|
|
19214
19984
|
}
|
|
19985
|
+
const protocolFlag = stringFlag(args.values, "protocol");
|
|
19986
|
+
const protocol = protocolFlag === "chat-completions" ? "chat_completions" : protocolFlag === "responses" ? "responses" : protocolFlag;
|
|
19215
19987
|
const key = await prompt.secret(`${providerId} API key: `);
|
|
19216
19988
|
if (key.length === 0)
|
|
19217
19989
|
throw new CliError("no API key given");
|
|
19218
|
-
const
|
|
19990
|
+
const store = await ctx.store();
|
|
19991
|
+
const endpointId = stringFlag(args.values, "endpoint-id");
|
|
19992
|
+
const existingEndpoint = providerId === "custom" && endpointId !== undefined ? (await listCredentials(store)).find((credential) => credential.provider === "custom" && credential.providerData.endpointId === endpointId.trim()) : undefined;
|
|
19993
|
+
const created = await createApiKeyCredential(store, {
|
|
19219
19994
|
provider: providerId,
|
|
19220
19995
|
apiKey: key,
|
|
19221
|
-
label: stringFlag(args.values, "label")
|
|
19996
|
+
label: stringFlag(args.values, "label"),
|
|
19997
|
+
endpointId,
|
|
19998
|
+
endpointLabel: stringFlag(args.values, "endpoint-label") ?? existingEndpoint?.providerData.endpointLabel,
|
|
19999
|
+
origin: stringFlag(args.values, "origin") ?? existingEndpoint?.providerData.origin,
|
|
20000
|
+
protocol: protocol ?? existingEndpoint?.providerData.protocol
|
|
19222
20001
|
});
|
|
19223
20002
|
emit(ctx, writer, { id: created.id, provider: created.provider }, () => `stored ${created.provider} api key as ${created.id}`);
|
|
19224
20003
|
}
|
|
@@ -19867,7 +20646,7 @@ var settingsSet = {
|
|
|
19867
20646
|
};
|
|
19868
20647
|
|
|
19869
20648
|
// apps/cli/src/commands/setup.ts
|
|
19870
|
-
import {
|
|
20649
|
+
import { join as join4 } from "path";
|
|
19871
20650
|
var OPTIONS = {
|
|
19872
20651
|
dir: { type: "string" },
|
|
19873
20652
|
key: { type: "string" },
|
|
@@ -19901,39 +20680,65 @@ ${f.contents}`).join(`
|
|
|
19901
20680
|
function at(dir, file2) {
|
|
19902
20681
|
return { path: join4(dir, file2.path), contents: file2.contents };
|
|
19903
20682
|
}
|
|
20683
|
+
async function promptMapping(models, prompt) {
|
|
20684
|
+
const choices = models.map(({ model }) => model.id).join(", ");
|
|
20685
|
+
if (prompt.input === undefined) {
|
|
20686
|
+
throw new CliError("model mapping requires an interactive terminal");
|
|
20687
|
+
}
|
|
20688
|
+
const ask = async (label2, required2) => {
|
|
20689
|
+
const answer = await prompt.input?.(`${label2} model${required2 ? "" : " (blank to omit)"} [${choices}]: `);
|
|
20690
|
+
if (required2 && answer === "")
|
|
20691
|
+
throw new CliError("default model is required");
|
|
20692
|
+
return answer === "" ? undefined : answer;
|
|
20693
|
+
};
|
|
20694
|
+
const defaultModel = await ask("Default", true);
|
|
20695
|
+
if (defaultModel === undefined)
|
|
20696
|
+
throw new CliError("default model is required");
|
|
20697
|
+
const fableModel = await ask("Fable", false);
|
|
20698
|
+
const opusModel = await ask("Opus", false);
|
|
20699
|
+
const sonnetModel = await ask("Sonnet", false);
|
|
20700
|
+
const haikuModel = await ask("Haiku", false);
|
|
20701
|
+
return {
|
|
20702
|
+
defaultModel,
|
|
20703
|
+
...fableModel === undefined ? {} : { fableModel },
|
|
20704
|
+
...opusModel === undefined ? {} : { opusModel },
|
|
20705
|
+
...sonnetModel === undefined ? {} : { sonnetModel },
|
|
20706
|
+
...haikuModel === undefined ? {} : { haikuModel }
|
|
20707
|
+
};
|
|
20708
|
+
}
|
|
19904
20709
|
var setupClaude = {
|
|
19905
20710
|
usage: "setup claude [--dir <path>] [--key <key>] [--dry-run]",
|
|
19906
|
-
summary: "Write
|
|
20711
|
+
summary: "Write Claude Code settings for this gateway",
|
|
19907
20712
|
options: OPTIONS,
|
|
19908
|
-
async run(args, { ctx, writer, setupFs }) {
|
|
20713
|
+
async run(args, { ctx, writer, prompt, setupFs }) {
|
|
19909
20714
|
const models = await described(ctx);
|
|
19910
|
-
const dir = stringFlag(args.values, "dir") ?? join4(setupFs.homeDir, ".claude"
|
|
20715
|
+
const dir = stringFlag(args.values, "dir") ?? join4(setupFs.homeDir, ".claude");
|
|
19911
20716
|
const key = stringFlag(args.values, "key");
|
|
19912
20717
|
const dryRun2 = boolFlag(args.values, "dry-run");
|
|
19913
|
-
const
|
|
20718
|
+
const mapping = await promptMapping(models, prompt);
|
|
20719
|
+
const path = join4(dir, "settings.json");
|
|
20720
|
+
const file2 = claudeSettings(models, {
|
|
19914
20721
|
baseUrl: baseUrl(ctx),
|
|
19915
20722
|
discoveryMirrors: ctx.config().exposeClaudeCodeAliases,
|
|
19916
20723
|
...key === undefined ? {} : { apiKey: key }
|
|
19917
|
-
}
|
|
19918
|
-
finish(ctx, writer,
|
|
19919
|
-
if (!dryRun2) {
|
|
19920
|
-
note(ctx, writer, `run one with: CLAUDE_CONFIG_DIR=${dirname2(files[0]?.path ?? dir)} claude`);
|
|
19921
|
-
}
|
|
20724
|
+
}, mapping, setupFs.read(path) ?? undefined);
|
|
20725
|
+
finish(ctx, writer, [at(dir, file2)], dryRun2, key, setupFs.write);
|
|
19922
20726
|
}
|
|
19923
20727
|
};
|
|
19924
20728
|
var setupOpencode = {
|
|
19925
20729
|
usage: "setup opencode [--dir <path>] [--key <key>] [--dry-run]",
|
|
19926
20730
|
summary: "Write an opencode.json provider entry for this gateway",
|
|
19927
20731
|
options: OPTIONS,
|
|
19928
|
-
async run(args, { ctx, writer, setupFs }) {
|
|
20732
|
+
async run(args, { ctx, writer, prompt, setupFs }) {
|
|
19929
20733
|
const models = await described(ctx);
|
|
19930
20734
|
const dir = stringFlag(args.values, "dir") ?? setupFs.cwd;
|
|
19931
20735
|
const key = stringFlag(args.values, "key");
|
|
19932
20736
|
const dryRun2 = boolFlag(args.values, "dry-run");
|
|
20737
|
+
const mapping = await promptMapping(models, prompt);
|
|
19933
20738
|
const file2 = opencodeConfig(models, {
|
|
19934
20739
|
baseUrl: baseUrl(ctx),
|
|
19935
20740
|
...key === undefined ? {} : { apiKey: key }
|
|
19936
|
-
});
|
|
20741
|
+
}, mapping);
|
|
19937
20742
|
finish(ctx, writer, [at(dir, file2)], dryRun2, key, setupFs.write);
|
|
19938
20743
|
}
|
|
19939
20744
|
};
|
|
@@ -20316,6 +21121,12 @@ function createPrompt(ctx, writer) {
|
|
|
20316
21121
|
const isTty = process.stdin.isTTY === true;
|
|
20317
21122
|
return {
|
|
20318
21123
|
isTty,
|
|
21124
|
+
async input(question) {
|
|
21125
|
+
if (!isTty)
|
|
21126
|
+
throw new CliError(`${question} \u2014 refusing without a terminal`);
|
|
21127
|
+
writer.err(question);
|
|
21128
|
+
return (await readLine()).trim();
|
|
21129
|
+
},
|
|
20319
21130
|
async secret(question) {
|
|
20320
21131
|
if (!isTty)
|
|
20321
21132
|
return (await readAllStdin()).trim();
|
|
@@ -20336,6 +21147,20 @@ function createPrompt(ctx, writer) {
|
|
|
20336
21147
|
};
|
|
20337
21148
|
}
|
|
20338
21149
|
|
|
21150
|
+
// apps/cli/src/setupFs.ts
|
|
21151
|
+
import { dirname as dirname2 } from "path";
|
|
21152
|
+
function atomicWriteFile(path, contents, ops) {
|
|
21153
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
21154
|
+
ops.mkdir(dirname2(path));
|
|
21155
|
+
try {
|
|
21156
|
+
ops.write(temporary, contents);
|
|
21157
|
+
ops.rename(temporary, path);
|
|
21158
|
+
} catch (error51) {
|
|
21159
|
+
ops.remove(temporary);
|
|
21160
|
+
throw error51;
|
|
21161
|
+
}
|
|
21162
|
+
}
|
|
21163
|
+
|
|
20339
21164
|
// apps/cli/src/run.ts
|
|
20340
21165
|
var VERSION = "0.0.0";
|
|
20341
21166
|
async function run(argv, writer, options = {}) {
|
|
@@ -20377,10 +21202,13 @@ async function run(argv, writer, options = {}) {
|
|
|
20377
21202
|
setupFs: options.setupFs ?? {
|
|
20378
21203
|
homeDir: homedir3(),
|
|
20379
21204
|
cwd: process.cwd(),
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
21205
|
+
read: (path) => existsSync6(path) ? readFileSync3(path, "utf8") : null,
|
|
21206
|
+
write: (path, contents) => atomicWriteFile(path, contents, {
|
|
21207
|
+
mkdir: (directory) => mkdirSync2(directory, { recursive: true }),
|
|
21208
|
+
write: (temporary, data) => writeFileSync2(temporary, data),
|
|
21209
|
+
rename: renameSync,
|
|
21210
|
+
remove: (temporary) => rmSync2(temporary, { force: true })
|
|
21211
|
+
})
|
|
20384
21212
|
},
|
|
20385
21213
|
service: () => options.service?.({ root: ctx.root.root, env: ctx.env }) ?? createServiceDeps({ root: ctx.root.root, env: ctx.env, scope, now: ctx.now }),
|
|
20386
21214
|
foreground: options.foreground ?? runForeground,
|