omnigateway 0.1.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +27 -33
  2. package/bin/omni.js +917 -161
  3. package/gateway.js +3247 -300
  4. package/package.json +1 -1
  5. package/public/assets/{Chip-C69u2U_b.js → Chip-BB_5C1Zp.js} +1 -1
  6. package/public/assets/Confirm-B6aAiVbT.js +4 -0
  7. package/public/assets/{CopyValue-CJy2T0H6.js → CopyValue-CRQDLo7k.js} +4 -4
  8. package/public/assets/{Field-Sc8mDqJv.js → Field-uHZxl4fI.js} +8 -8
  9. package/public/assets/Lamp-B-5SjXbG.js +25 -0
  10. package/public/assets/{Meter-C0nUftAa.js → Meter-DI_BRUKt.js} +1 -1
  11. package/public/assets/Modal-CI6jk2D4.js +82 -0
  12. package/public/assets/{Rack-CMqGBP4I.js → Rack-D1WJswv3.js} +23 -19
  13. package/public/assets/{Readout-Dv2dZWtI.js → Readout-BocZ2HXP.js} +5 -5
  14. package/public/assets/{States-D55MhqMx.js → States-Bbiu5cHE.js} +10 -10
  15. package/public/assets/{Table-DBB3jdgi.js → Table-CdPWxYaz.js} +1 -1
  16. package/public/assets/Toggle-CiLC67Dw.js +39 -0
  17. package/public/assets/TokenBreakdown-B96iPBm9.js +28 -0
  18. package/public/assets/_app-BBOF6A0T.js +1 -0
  19. package/public/assets/_app.accounts-BNkhpvaB.js +54 -0
  20. package/public/assets/_app.console-Daz4aKhf.js +44 -0
  21. package/public/assets/_app.index-LO6d38oe.js +62 -0
  22. package/public/assets/{_app.keys-BX8k8COP.js → _app.keys-kBqLqoFf.js} +8 -8
  23. package/public/assets/_app.logs-_wYNS47N.js +32 -0
  24. package/public/assets/_app.models-Cjng8ohC.js +144 -0
  25. package/public/assets/_app.settings-a0FKyQAi.js +38 -0
  26. package/public/assets/{_app.usage-BRhEvwVr.js → _app.usage-D3KtgLrC.js} +32 -29
  27. package/public/assets/dist-C-IbPRiV.js +1 -0
  28. package/public/assets/index-PW6EvVh5.js +170 -0
  29. package/public/assets/{login-DaC-wVpI.js → login-CTvH_KAd.js} +8 -8
  30. package/public/assets/queries-D2o-X8Pj.js +144 -0
  31. package/public/assets/{trash-2--aH2iKg-.js → trash-2-BcZb-sCT.js} +1 -1
  32. package/public/index.html +2 -2
  33. package/public/assets/Lamp-7jD_9929.js +0 -25
  34. package/public/assets/Modal-CiVHSJmm.js +0 -82
  35. package/public/assets/Toggle-BoGjb5oi.js +0 -42
  36. package/public/assets/_app-p5Mz7sXD.js +0 -1
  37. package/public/assets/_app.accounts-B5wMO9TH.js +0 -51
  38. package/public/assets/_app.console-Bka7AkiL.js +0 -25
  39. package/public/assets/_app.index-MnQuadXb.js +0 -61
  40. package/public/assets/_app.logs-DD-sd4fn.js +0 -33
  41. package/public/assets/_app.models-CezFNL6y.js +0 -144
  42. package/public/assets/_app.settings-D1taAt8B.js +0 -16
  43. package/public/assets/index-vCuK1fRQ.js +0 -170
  44. package/public/assets/queries-2E6-CboE.js +0 -144
package/bin/omni.js CHANGED
@@ -90,6 +90,10 @@ function formatUsd(value) {
90
90
  return `$${value.toFixed(value < 1 ? 4 : 2)}`;
91
91
  }
92
92
 
93
+ // apps/cli/src/run.ts
94
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2, readFileSync as readFileSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
95
+ import { homedir as homedir3 } from "os";
96
+
93
97
  // packages/control/src/adminAuth.ts
94
98
  import { hash, verify } from "@node-rs/argon2";
95
99
  var MIN_PASSWORD_LENGTH = 12;
@@ -148,6 +152,11 @@ function createAdminAuth(store, opts) {
148
152
  };
149
153
  }
150
154
  // packages/ir/src/capabilities.ts
155
+ var ANTHROPIC_NATIVE_TOOLS = {
156
+ anthropic: true,
157
+ openai: false,
158
+ kimi: false
159
+ };
151
160
  var PROVIDER_CAPABILITIES = {
152
161
  anthropic: { tools: true, images: true, reasoning: true },
153
162
  openai: { tools: true, images: true, reasoning: true },
@@ -233,6 +242,7 @@ function usageFromPromptTotal(promptTokens, outputTokens, cacheReadTokens, cache
233
242
  }
234
243
  // packages/control/src/config.ts
235
244
  var MIN_KEY_LENGTH = 16;
245
+ var TRUTHY = new Set(["1", "true", "yes", "on"]);
236
246
  var DECIMAL_INTEGER = /^\d+$/;
237
247
  function optionalText(value, fallback) {
238
248
  return value?.trim() || fallback;
@@ -252,6 +262,7 @@ function loadConfig(env) {
252
262
  const baseUrl = optionalText(env.OMNI_BASE_URL, derivedBaseUrl).replace(/\/+$/, "") || derivedBaseUrl;
253
263
  const staticDir = env.OMNI_STATIC_DIR?.trim();
254
264
  const logFile = env.OMNI_LOG_FILE?.trim();
265
+ const exposeClaudeCodeAliases = TRUTHY.has((env.OMNI_EXPOSE_CLAUDE_CODE_ALIASES ?? "").trim().toLowerCase());
255
266
  const rawLogLevel = env.OMNI_LOG_LEVEL?.trim();
256
267
  const logLevel = parseLogLevel(rawLogLevel);
257
268
  return {
@@ -263,9 +274,14 @@ function loadConfig(env) {
263
274
  encryptionKey,
264
275
  baseUrl,
265
276
  staticDir: staticDir === undefined || staticDir.length === 0 ? null : staticDir,
277
+ exposeClaudeCodeAliases,
266
278
  logFile: logFile === undefined || logFile.length === 0 ? null : logFile
267
279
  };
268
280
  }
281
+ // packages/providers/src/betas.ts
282
+ var CONTEXT_1M_BETA = "context-1m-2025-08-07";
283
+ var CONTEXT_1M_TOKENS = 1e6;
284
+
269
285
  // packages/providers/src/body.ts
270
286
  var BODY_ORDER = {
271
287
  anthropic: [
@@ -373,6 +389,119 @@ function signAnthropicBody(json) {
373
389
  return json.replace(needle, `cch=${computeCch(json)};`);
374
390
  }
375
391
 
392
+ // packages/providers/src/anthropic/models.ts
393
+ var ANTHROPIC_MODELS = {
394
+ defaultModel: "claude-opus-5",
395
+ models: [
396
+ {
397
+ id: "claude-fable-5",
398
+ label: "Claude Fable 5",
399
+ pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
400
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
401
+ },
402
+ {
403
+ id: "claude-opus-5",
404
+ label: "Claude Opus 5",
405
+ pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
406
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
407
+ },
408
+ {
409
+ id: "claude-sonnet-5",
410
+ label: "Claude Sonnet 5",
411
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
412
+ limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
413
+ },
414
+ {
415
+ id: "claude-haiku-4-5",
416
+ label: "Claude Haiku 4.5",
417
+ pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
418
+ limits: { contextWindow: 200000, maxOutputTokens: 64000 }
419
+ }
420
+ ]
421
+ };
422
+
423
+ // packages/providers/src/kimi/models.ts
424
+ var KIMI_MODELS = {
425
+ defaultModel: "k3-256k",
426
+ models: [
427
+ {
428
+ id: "k3-256k",
429
+ label: "Kimi K3 \u2014 256K",
430
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
431
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
432
+ },
433
+ {
434
+ id: "k3",
435
+ label: "Kimi K3 \u2014 up to 1M",
436
+ pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
437
+ limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
438
+ },
439
+ {
440
+ id: "kimi-for-coding",
441
+ label: "Kimi K2.7 Code",
442
+ pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
443
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
444
+ },
445
+ {
446
+ id: "kimi-for-coding-highspeed",
447
+ label: "Kimi K2.7 Code \u2014 High Speed",
448
+ pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
449
+ limits: { contextWindow: 262144, maxOutputTokens: 131072 }
450
+ }
451
+ ]
452
+ };
453
+
454
+ // packages/providers/src/openai/models.ts
455
+ var OPENAI_MODELS = {
456
+ defaultModel: "gpt-5.6",
457
+ models: [
458
+ {
459
+ id: "gpt-5.6",
460
+ label: "GPT-5.6 \u2014 routes to Sol",
461
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
462
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
463
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
464
+ },
465
+ {
466
+ id: "gpt-5.6-sol",
467
+ label: "GPT-5.6 Sol \u2014 deepest reasoning",
468
+ pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
469
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
470
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
471
+ },
472
+ {
473
+ id: "gpt-5.6-terra",
474
+ label: "GPT-5.6 Terra \u2014 balanced",
475
+ pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
476
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
477
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
478
+ },
479
+ {
480
+ id: "gpt-5.6-luna",
481
+ label: "GPT-5.6 Luna \u2014 fastest",
482
+ pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
483
+ limits: { contextWindow: 922000, maxOutputTokens: 128000 },
484
+ oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
485
+ }
486
+ ]
487
+ };
488
+
489
+ // packages/providers/src/catalog.ts
490
+ var PROVIDER_MODEL_CATALOG = {
491
+ anthropic: ANTHROPIC_MODELS,
492
+ openai: OPENAI_MODELS,
493
+ kimi: KIMI_MODELS
494
+ };
495
+ function catalogPricing(provider, model) {
496
+ return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
497
+ }
498
+ function catalogLimits(provider, model, auth = "apiKey") {
499
+ const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
500
+ if (entry === undefined)
501
+ return null;
502
+ return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
503
+ }
504
+
376
505
  // packages/providers/src/http.ts
377
506
  function codeForStatus(status) {
378
507
  if (status === 401 || status === 403)
@@ -634,14 +763,232 @@ function parseRecord(record) {
634
763
  `) };
635
764
  }
636
765
 
766
+ // packages/providers/src/anthropic/tools.ts
767
+ var COMMON = ["cache_control", "strict", "defer_loading", "allowed_callers"];
768
+ var COMMON_WITH_EXAMPLES = [...COMMON, "input_examples"];
769
+ var searchDomains = ["max_uses", "allowed_domains", "blocked_domains"];
770
+ var ANTHROPIC_TOOL_SPECS = {
771
+ web_search_20250305: {
772
+ family: "webSearch",
773
+ name: "web_search",
774
+ required: [],
775
+ optional: [...COMMON, ...searchDomains, "user_location"]
776
+ },
777
+ web_search_20260209: {
778
+ family: "webSearch",
779
+ name: "web_search",
780
+ required: [],
781
+ optional: [...COMMON, ...searchDomains, "user_location"]
782
+ },
783
+ web_search_20260318: {
784
+ family: "webSearch",
785
+ name: "web_search",
786
+ required: [],
787
+ optional: [...COMMON, ...searchDomains, "user_location", "response_inclusion"]
788
+ },
789
+ web_fetch_20250910: {
790
+ family: "webFetch",
791
+ name: "web_fetch",
792
+ required: [],
793
+ optional: [...COMMON, ...searchDomains, "citations", "max_content_tokens"]
794
+ },
795
+ web_fetch_20260209: {
796
+ family: "webFetch",
797
+ name: "web_fetch",
798
+ required: [],
799
+ optional: [...COMMON, ...searchDomains, "citations", "max_content_tokens"]
800
+ },
801
+ web_fetch_20260309: {
802
+ family: "webFetch",
803
+ name: "web_fetch",
804
+ required: [],
805
+ optional: [...COMMON, ...searchDomains, "citations", "max_content_tokens", "use_cache"]
806
+ },
807
+ web_fetch_20260318: {
808
+ family: "webFetch",
809
+ name: "web_fetch",
810
+ required: [],
811
+ optional: [
812
+ ...COMMON,
813
+ ...searchDomains,
814
+ "citations",
815
+ "max_content_tokens",
816
+ "use_cache",
817
+ "response_inclusion"
818
+ ]
819
+ },
820
+ code_execution_20250522: {
821
+ family: "codeExecution",
822
+ name: "code_execution",
823
+ required: [],
824
+ optional: [...COMMON]
825
+ },
826
+ code_execution_20250825: {
827
+ family: "codeExecution",
828
+ name: "code_execution",
829
+ required: [],
830
+ optional: [...COMMON]
831
+ },
832
+ code_execution_20260120: {
833
+ family: "codeExecution",
834
+ name: "code_execution",
835
+ required: [],
836
+ optional: [...COMMON]
837
+ },
838
+ code_execution_20260521: {
839
+ family: "codeExecution",
840
+ name: "code_execution",
841
+ required: [],
842
+ optional: [...COMMON]
843
+ },
844
+ bash_20241022: {
845
+ family: "bash",
846
+ name: "bash",
847
+ required: [],
848
+ optional: [...COMMON_WITH_EXAMPLES]
849
+ },
850
+ bash_20250124: {
851
+ family: "bash",
852
+ name: "bash",
853
+ required: [],
854
+ optional: [...COMMON_WITH_EXAMPLES]
855
+ },
856
+ text_editor_20241022: {
857
+ family: "textEditor",
858
+ name: "str_replace_editor",
859
+ required: [],
860
+ optional: [...COMMON_WITH_EXAMPLES]
861
+ },
862
+ text_editor_20250124: {
863
+ family: "textEditor",
864
+ name: "str_replace_editor",
865
+ required: [],
866
+ optional: [...COMMON_WITH_EXAMPLES]
867
+ },
868
+ text_editor_20250429: {
869
+ family: "textEditor",
870
+ name: "str_replace_based_edit_tool",
871
+ required: [],
872
+ optional: [...COMMON_WITH_EXAMPLES]
873
+ },
874
+ text_editor_20250728: {
875
+ family: "textEditor",
876
+ name: "str_replace_based_edit_tool",
877
+ required: [],
878
+ optional: [...COMMON_WITH_EXAMPLES, "max_characters"]
879
+ },
880
+ computer_20241022: {
881
+ family: "computer",
882
+ name: "computer",
883
+ required: ["display_width_px", "display_height_px"],
884
+ optional: [...COMMON_WITH_EXAMPLES, "display_number"]
885
+ },
886
+ computer_20250124: {
887
+ family: "computer",
888
+ name: "computer",
889
+ required: ["display_width_px", "display_height_px"],
890
+ optional: [...COMMON_WITH_EXAMPLES, "display_number"]
891
+ },
892
+ computer_20251124: {
893
+ family: "computer",
894
+ name: "computer",
895
+ required: ["display_width_px", "display_height_px"],
896
+ optional: [...COMMON_WITH_EXAMPLES, "display_number", "enable_zoom"]
897
+ },
898
+ memory_20250818: {
899
+ family: "memory",
900
+ name: "memory",
901
+ required: [],
902
+ optional: [...COMMON_WITH_EXAMPLES]
903
+ },
904
+ tool_search_tool_regex_20251119: {
905
+ family: "toolSearchRegex",
906
+ name: "tool_search_tool_regex",
907
+ required: [],
908
+ optional: [...COMMON]
909
+ },
910
+ tool_search_tool_regex: {
911
+ family: "toolSearchRegex",
912
+ name: "tool_search_tool_regex",
913
+ required: [],
914
+ optional: [...COMMON]
915
+ },
916
+ tool_search_tool_bm25_20251119: {
917
+ family: "toolSearchBm25",
918
+ name: "tool_search_tool_bm25",
919
+ required: [],
920
+ optional: [...COMMON]
921
+ },
922
+ tool_search_tool_bm25: {
923
+ family: "toolSearchBm25",
924
+ name: "tool_search_tool_bm25",
925
+ required: [],
926
+ optional: [...COMMON]
927
+ },
928
+ advisor_20260301: {
929
+ family: "advisor",
930
+ name: "advisor",
931
+ required: ["model"],
932
+ optional: [...COMMON, "caching", "max_uses", "max_tokens"]
933
+ },
934
+ mcp_toolset: {
935
+ family: "mcpToolset",
936
+ required: ["mcp_server_name"],
937
+ optional: ["cache_control", "configs", "default_config"]
938
+ }
939
+ };
940
+ var ANTHROPIC_NATIVE_BLOCK_TYPES = new Set([
941
+ "server_tool_use",
942
+ "web_search_tool_result",
943
+ "web_fetch_tool_result",
944
+ "code_execution_tool_result",
945
+ "bash_code_execution_tool_result",
946
+ "text_editor_code_execution_tool_result",
947
+ "mcp_tool_use",
948
+ "mcp_tool_result",
949
+ "tool_search_tool_result",
950
+ "tool_reference",
951
+ "advisor_tool_result",
952
+ "advisor_result",
953
+ "advisor_redacted_result",
954
+ "container_upload",
955
+ "compaction",
956
+ "search_result",
957
+ "document",
958
+ "mid_conv_system",
959
+ "tool_addition",
960
+ "tool_removal",
961
+ "fallback",
962
+ "redacted_thinking"
963
+ ]);
964
+
637
965
  // packages/providers/src/anthropic/decode.ts
638
966
  var STOP_REASON = {
639
967
  end_turn: "endTurn",
640
968
  max_tokens: "maxTokens",
641
969
  stop_sequence: "stopSequence",
642
970
  tool_use: "toolUse",
643
- refusal: "contentFilter"
971
+ refusal: "contentFilter",
972
+ pause_turn: "pauseTurn"
644
973
  };
974
+ var KNOWN_EVENTS = new Set([
975
+ "message_start",
976
+ "content_block_start",
977
+ "content_block_delta",
978
+ "content_block_stop",
979
+ "message_delta",
980
+ "message_stop",
981
+ "error",
982
+ "ping"
983
+ ]);
984
+ var KNOWN_DELTAS = new Set([
985
+ "text_delta",
986
+ "thinking_delta",
987
+ "signature_delta",
988
+ "input_json_delta",
989
+ "citations_delta",
990
+ "compaction_delta"
991
+ ]);
645
992
  var ERROR_TYPE = {
646
993
  overloaded_error: "OVERLOADED",
647
994
  rate_limit_error: "RATE_LIMIT",
@@ -667,10 +1014,21 @@ async function* decodeAnthropic(messages) {
667
1014
  let outputTokens = 0;
668
1015
  let stopReason = "endTurn";
669
1016
  let terminal = false;
1017
+ const nativeBlocks = new Set;
1018
+ const protocolError = (message) => ({
1019
+ type: "error",
1020
+ code: "UPSTREAM",
1021
+ message,
1022
+ retryable: false
1023
+ });
670
1024
  for await (const msg of messages) {
671
1025
  const d = json(msg.data);
672
1026
  if (d === null)
673
1027
  continue;
1028
+ if (!KNOWN_EVENTS.has(msg.event)) {
1029
+ yield protocolError(`unrecognized Anthropic stream event "${msg.event}"`);
1030
+ return;
1031
+ }
674
1032
  switch (msg.event) {
675
1033
  case "message_start": {
676
1034
  const m = d.message ?? {};
@@ -695,6 +1053,18 @@ async function* decodeAnthropic(messages) {
695
1053
  index,
696
1054
  block: { type: "toolUse", id: String(cb.id), name: String(cb.name) }
697
1055
  };
1056
+ else if (cb.type !== undefined && ANTHROPIC_NATIVE_BLOCK_TYPES.has(cb.type)) {
1057
+ nativeBlocks.add(index);
1058
+ const { type: _blockType, ...data } = cb;
1059
+ yield {
1060
+ type: "blockStart",
1061
+ index,
1062
+ block: { type: "anthropicNative", blockType: cb.type, data }
1063
+ };
1064
+ } else {
1065
+ yield protocolError(`unrecognized Anthropic content block type "${String(cb.type)}"`);
1066
+ return;
1067
+ }
698
1068
  break;
699
1069
  }
700
1070
  case "content_block_delta": {
@@ -718,17 +1088,51 @@ async function* decodeAnthropic(messages) {
718
1088
  yield {
719
1089
  type: "blockDelta",
720
1090
  index,
721
- delta: { type: "toolJson", partial: delta.partial_json ?? "" }
1091
+ delta: nativeBlocks.has(index) ? { type: "anthropicNativeJson", partial: delta.partial_json ?? "" } : { type: "toolJson", partial: delta.partial_json ?? "" }
1092
+ };
1093
+ else if (delta.type === "citations_delta")
1094
+ yield {
1095
+ type: "blockDelta",
1096
+ index,
1097
+ delta: {
1098
+ type: "anthropicNative",
1099
+ deltaType: delta.type,
1100
+ data: { citation: delta.citation }
1101
+ }
722
1102
  };
1103
+ else if (delta.type === "compaction_delta")
1104
+ yield {
1105
+ type: "blockDelta",
1106
+ index,
1107
+ delta: {
1108
+ type: "anthropicNative",
1109
+ deltaType: delta.type,
1110
+ data: {
1111
+ ...delta.content === undefined ? {} : { content: delta.content },
1112
+ ...delta.encrypted_content === undefined ? {} : { encrypted_content: delta.encrypted_content }
1113
+ }
1114
+ }
1115
+ };
1116
+ else if (delta.type === undefined || !KNOWN_DELTAS.has(delta.type)) {
1117
+ yield protocolError(`unrecognized Anthropic content block delta "${String(delta.type)}"`);
1118
+ return;
1119
+ }
723
1120
  break;
724
1121
  }
725
1122
  case "content_block_stop":
1123
+ nativeBlocks.delete(d.index ?? 0);
726
1124
  yield { type: "blockEnd", index: d.index ?? 0 };
727
1125
  break;
728
1126
  case "message_delta": {
729
1127
  const reason = d.delta?.stop_reason;
730
- if (typeof reason === "string")
731
- stopReason = STOP_REASON[reason] ?? "endTurn";
1128
+ if (typeof reason === "string") {
1129
+ const mapped = STOP_REASON[reason];
1130
+ if (mapped === undefined) {
1131
+ yield protocolError(`unrecognized Anthropic stop reason "${reason}"`);
1132
+ return;
1133
+ }
1134
+ stopReason = mapped;
1135
+ }
732
1136
  inputTokens = d.usage?.input_tokens ?? inputTokens;
733
1137
  outputTokens = d.usage?.output_tokens ?? outputTokens;
734
1138
  break;
@@ -787,7 +1191,12 @@ function encodeBlock(b) {
787
1191
  const cache = wireCacheControl(cacheControlOf(b));
788
1192
  switch (b.type) {
789
1193
  case "text":
790
- return { type: "text", text: b.text, ...cache };
1194
+ return {
1195
+ type: "text",
1196
+ text: b.text,
1197
+ ...b.citations === undefined ? {} : { citations: b.citations },
1198
+ ...cache
1199
+ };
791
1200
  case "image":
792
1201
  return {
793
1202
  type: "image",
@@ -806,11 +1215,16 @@ function encodeBlock(b) {
806
1215
  is_error: b.isError,
807
1216
  ...cache
808
1217
  };
1218
+ case "anthropicNative":
1219
+ return { ...b.data, type: b.blockType, ...cache };
809
1220
  }
810
1221
  }
811
1222
  function encodeSystemTurn(content) {
812
- return content.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
1223
+ if (content.every((block) => block.type === "text")) {
1224
+ return content.map((block) => block.text).join(`
813
1225
  `);
1226
+ }
1227
+ return content.map(encodeBlock);
814
1228
  }
815
1229
  function systemCacheControl(req) {
816
1230
  const cacheable = req.messages.flatMap((message) => message.content.flatMap((block) => block.type === "thinking" ? [] : [{ role: message.role, block }]));
@@ -822,6 +1236,23 @@ function systemCacheControl(req) {
822
1236
  lost: markedSystemBlocks.length > (promoted === undefined ? 0 : 1)
823
1237
  };
824
1238
  }
1239
+ function encodeTool(t) {
1240
+ if (t.provider === "anthropic") {
1241
+ return {
1242
+ type: t.type,
1243
+ ...t.name === "" ? {} : { name: t.name },
1244
+ ...t.wire,
1245
+ ...wireCacheControl(t.cacheControl)
1246
+ };
1247
+ }
1248
+ return {
1249
+ name: t.name,
1250
+ ...t.description === undefined ? {} : { description: t.description },
1251
+ input_schema: t.inputSchema,
1252
+ ...t.options ?? {},
1253
+ ...wireCacheControl(t.cacheControl)
1254
+ };
1255
+ }
825
1256
  function encodeToolChoice(c) {
826
1257
  switch (c.type) {
827
1258
  case "auto":
@@ -876,14 +1307,8 @@ function toWire(req, model, opts) {
876
1307
  body.temperature = req.temperature;
877
1308
  if (req.stopSequences !== undefined)
878
1309
  body.stop_sequences = req.stopSequences;
879
- if (req.tools !== undefined) {
880
- body.tools = req.tools.map((t) => ({
881
- name: t.name,
882
- ...t.description === undefined ? {} : { description: t.description },
883
- input_schema: t.inputSchema,
884
- ...wireCacheControl(t.cacheControl)
885
- }));
886
- }
1310
+ if (req.tools !== undefined)
1311
+ body.tools = req.tools.map(encodeTool);
887
1312
  if (req.toolChoice !== undefined)
888
1313
  body.tool_choice = encodeToolChoice(req.toolChoice);
889
1314
  if (req.reasoning !== undefined) {
@@ -930,6 +1355,15 @@ var anthropicAdapter = {
930
1355
  ["Accept", "text/event-stream"]
931
1356
  ];
932
1357
  const betas = new Set(req.request.betas ?? []);
1358
+ const notes = [...degradations];
1359
+ if (betas.has(CONTEXT_1M_BETA)) {
1360
+ const limits = catalogLimits("anthropic", req.model, oauth ? "oauth" : "apiKey");
1361
+ const window = limits?.contextWindow;
1362
+ if (window !== undefined && window < CONTEXT_1M_TOKENS) {
1363
+ betas.delete(CONTEXT_1M_BETA);
1364
+ notes.push("anthropic:context-1m-dropped");
1365
+ }
1366
+ }
933
1367
  if (oauth) {
934
1368
  protocol.push(["Authorization", `Bearer ${req.credentials.accessToken}`]);
935
1369
  betas.add(OAUTH_BETA);
@@ -957,121 +1391,9 @@ var anthropicAdapter = {
957
1391
  throw await httpError(res, "anthropic");
958
1392
  if (res.body === null)
959
1393
  throw new GatewayError("UPSTREAM", "empty response body", { provider: "anthropic" });
960
- return { events: decodeAnthropic(parseSse(res.body)), degradations };
1394
+ return { events: decodeAnthropic(parseSse(res.body)), degradations: notes };
961
1395
  }
962
1396
  };
963
- // packages/providers/src/anthropic/models.ts
964
- var ANTHROPIC_MODELS = {
965
- defaultModel: "claude-opus-5",
966
- models: [
967
- {
968
- id: "claude-fable-5",
969
- label: "Claude Fable 5",
970
- pricing: { input: 10, output: 50, cacheRead: 1, cacheWrite5m: 12.5, cacheWrite1h: 20 },
971
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
972
- },
973
- {
974
- id: "claude-opus-5",
975
- label: "Claude Opus 5",
976
- pricing: { input: 5, output: 25, cacheRead: 0.5, cacheWrite5m: 6.25, cacheWrite1h: 10 },
977
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
978
- },
979
- {
980
- id: "claude-sonnet-5",
981
- label: "Claude Sonnet 5",
982
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 3.75, cacheWrite1h: 6 },
983
- limits: { contextWindow: 1e6, maxOutputTokens: 128000 }
984
- },
985
- {
986
- id: "claude-haiku-4-5",
987
- label: "Claude Haiku 4.5",
988
- pricing: { input: 1, output: 5, cacheRead: 0.1, cacheWrite5m: 1.25, cacheWrite1h: 2 },
989
- limits: { contextWindow: 200000, maxOutputTokens: 64000 }
990
- }
991
- ]
992
- };
993
-
994
- // packages/providers/src/kimi/models.ts
995
- var KIMI_MODELS = {
996
- defaultModel: "k3-256k",
997
- models: [
998
- {
999
- id: "k3-256k",
1000
- label: "Kimi K3 \u2014 256K",
1001
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
1002
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
1003
- },
1004
- {
1005
- id: "k3",
1006
- label: "Kimi K3 \u2014 up to 1M",
1007
- pricing: { input: 3, output: 15, cacheRead: 0.3, cacheWrite5m: 0, cacheWrite1h: 0 },
1008
- limits: { contextWindow: 1048576, maxOutputTokens: 131072 }
1009
- },
1010
- {
1011
- id: "kimi-for-coding",
1012
- label: "Kimi K2.7 Code",
1013
- pricing: { input: 0.95, output: 4, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
1014
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
1015
- },
1016
- {
1017
- id: "kimi-for-coding-highspeed",
1018
- label: "Kimi K2.7 Code \u2014 High Speed",
1019
- pricing: { input: 0.95, output: 8, cacheRead: 0.19, cacheWrite5m: 0, cacheWrite1h: 0 },
1020
- limits: { contextWindow: 262144, maxOutputTokens: 131072 }
1021
- }
1022
- ]
1023
- };
1024
-
1025
- // packages/providers/src/openai/models.ts
1026
- var OPENAI_MODELS = {
1027
- defaultModel: "gpt-5.6",
1028
- models: [
1029
- {
1030
- id: "gpt-5.6",
1031
- label: "GPT-5.6 \u2014 routes to Sol",
1032
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
1033
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1034
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1035
- },
1036
- {
1037
- id: "gpt-5.6-sol",
1038
- label: "GPT-5.6 Sol \u2014 deepest reasoning",
1039
- pricing: { input: 5, output: 30, cacheRead: 0.5, cacheWrite5m: 0, cacheWrite1h: 0 },
1040
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1041
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1042
- },
1043
- {
1044
- id: "gpt-5.6-terra",
1045
- label: "GPT-5.6 Terra \u2014 balanced",
1046
- pricing: { input: 2, output: 12, cacheRead: 0.2, cacheWrite5m: 0, cacheWrite1h: 0 },
1047
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1048
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1049
- },
1050
- {
1051
- id: "gpt-5.6-luna",
1052
- label: "GPT-5.6 Luna \u2014 fastest",
1053
- pricing: { input: 0.2, output: 1.2, cacheRead: 0.02, cacheWrite5m: 0, cacheWrite1h: 0 },
1054
- limits: { contextWindow: 922000, maxOutputTokens: 128000 },
1055
- oauthLimits: { contextWindow: 272000, maxOutputTokens: 128000 }
1056
- }
1057
- ]
1058
- };
1059
-
1060
- // packages/providers/src/catalog.ts
1061
- var PROVIDER_MODEL_CATALOG = {
1062
- anthropic: ANTHROPIC_MODELS,
1063
- openai: OPENAI_MODELS,
1064
- kimi: KIMI_MODELS
1065
- };
1066
- function catalogPricing(provider, model) {
1067
- return PROVIDER_MODEL_CATALOG[provider]?.models.find((entry) => entry.id === model)?.pricing ?? null;
1068
- }
1069
- function catalogLimits(provider, model, auth = "apiKey") {
1070
- const entry = PROVIDER_MODEL_CATALOG[provider]?.models.find((choice) => choice.id === model);
1071
- if (entry === undefined)
1072
- return null;
1073
- return (auth === "oauth" ? entry.oauthLimits : undefined) ?? entry.limits;
1074
- }
1075
1397
  // packages/providers/src/http-client.ts
1076
1398
  import { request as httpRequest } from "http";
1077
1399
  import { request as httpsRequest } from "https";
@@ -1297,6 +1619,8 @@ function toChatWire(req, model) {
1297
1619
  if (!degradations.includes(d))
1298
1620
  degradations.push(d);
1299
1621
  };
1622
+ if (req.betas?.includes(CONTEXT_1M_BETA))
1623
+ note2("kimi:context-1m-dropped");
1300
1624
  const messages = [];
1301
1625
  const system = req.system?.flatMap((b) => b.type === "text" ? [b.text] : []).join(`
1302
1626
 
@@ -1331,6 +1655,9 @@ function toChatWire(req, model) {
1331
1655
  content: block.content
1332
1656
  });
1333
1657
  break;
1658
+ case "anthropicNative":
1659
+ note2("kimi:anthropic-native-block-dropped");
1660
+ break;
1334
1661
  }
1335
1662
  }
1336
1663
  if (toolCalls.length > 0) {
@@ -1358,7 +1685,10 @@ function toChatWire(req, model) {
1358
1685
  if (req.stopSequences !== undefined)
1359
1686
  body.stop = req.stopSequences;
1360
1687
  if (req.tools !== undefined) {
1361
- body.tools = req.tools.map((t) => ({
1688
+ const custom = req.tools.filter((t) => t.provider === "custom");
1689
+ if (custom.length !== req.tools.length)
1690
+ note2("kimi:anthropic-tool-dropped");
1691
+ body.tools = custom.map((t) => ({
1362
1692
  type: "function",
1363
1693
  function: { name: t.name, description: t.description, parameters: t.inputSchema }
1364
1694
  }));
@@ -1573,6 +1903,8 @@ function toResponsesWire(req, model, opts = { oauth: false }) {
1573
1903
  if (!degradations.includes(d))
1574
1904
  degradations.push(d);
1575
1905
  };
1906
+ if (req.betas?.includes(CONTEXT_1M_BETA))
1907
+ note2("openai:context-1m-dropped");
1576
1908
  for (const message of req.messages) {
1577
1909
  const parts = [];
1578
1910
  const inlined = message.role === "system";
@@ -1623,6 +1955,9 @@ ${block.text}
1623
1955
  output: block.content
1624
1956
  });
1625
1957
  break;
1958
+ case "anthropicNative":
1959
+ note2("openai:anthropic-native-block-dropped");
1960
+ break;
1626
1961
  }
1627
1962
  }
1628
1963
  flush();
@@ -1646,7 +1981,10 @@ ${block.text}
1646
1981
  body.temperature = req.temperature;
1647
1982
  }
1648
1983
  if (req.tools !== undefined) {
1649
- body.tools = req.tools.map((t) => ({
1984
+ const custom = req.tools.filter((t) => t.provider === "custom");
1985
+ if (custom.length !== req.tools.length)
1986
+ note2("openai:anthropic-tool-dropped");
1987
+ body.tools = custom.map((t) => ({
1650
1988
  type: "function",
1651
1989
  name: t.name,
1652
1990
  description: t.description,
@@ -2261,12 +2599,10 @@ async function readSource(deps, source, lines) {
2261
2599
  return "";
2262
2600
  if (source.kind === "file")
2263
2601
  return deps.readFile(source.path, lines) ?? "";
2264
- const scope = source.scope === "system" ? [] : ["--user"];
2602
+ const unit = source.scope === "system" ? ["-u", source.unit] : [`--user-unit=${source.unit}`];
2265
2603
  const result = await deps.run([
2266
2604
  "journalctl",
2267
- ...scope,
2268
- "-u",
2269
- source.unit,
2605
+ ...unit,
2270
2606
  "-n",
2271
2607
  String(lines),
2272
2608
  "--no-pager",
@@ -16573,7 +16909,9 @@ var dryRunSchema = exports_external.object({
16573
16909
  reasoning: exports_external.boolean().default(false)
16574
16910
  }).strict();
16575
16911
  var modelSchema = exports_external.object({
16576
- id: exports_external.string().min(1),
16912
+ id: exports_external.string().min(1).refine((value) => !value.toLowerCase().startsWith("claude/"), {
16913
+ message: 'model id must not start with "claude/": that prefix is reserved for discovery mirrors'
16914
+ }),
16577
16915
  strategy: exports_external.enum(["score", "priority", "roundRobin", "weighted"]),
16578
16916
  isAlias: exports_external.boolean(),
16579
16917
  targets: exports_external.array(exports_external.object({
@@ -16612,11 +16950,12 @@ var settingsSchema = exports_external.object({
16612
16950
  recency: exports_external.number()
16613
16951
  }).strict(),
16614
16952
  maxAttempts: exports_external.number().int().min(1).max(10),
16615
- requestDeadlineMs: exports_external.number().int().positive(),
16953
+ requestDeadlineMs: exports_external.number().int().min(0),
16616
16954
  breakerThreshold: exports_external.number().int().min(1),
16617
16955
  breakerCooldownMs: exports_external.number().int().positive(),
16618
16956
  logRetentionDays: exports_external.number().int().min(1),
16619
- quotaPollIntervalMs: exports_external.number().int().min(0)
16957
+ quotaPollIntervalMs: exports_external.number().int().min(0),
16958
+ rtkEnabled: exports_external.boolean()
16620
16959
  });
16621
16960
  var credentialPatchSchema = exports_external.object({
16622
16961
  label: exports_external.string().min(1).optional(),
@@ -16803,6 +17142,11 @@ async function buildSnapshot(store, now) {
16803
17142
  }
16804
17143
 
16805
17144
  // packages/router/src/filters.ts
17145
+ function needsAnthropicNative(request2) {
17146
+ if (request2.tools?.some((t) => t.provider === "anthropic") === true)
17147
+ return true;
17148
+ return request2.messages.some((m) => m.content.some((b) => b.type === "anthropicNative"));
17149
+ }
16806
17150
  function requiredCapabilities(request2) {
16807
17151
  const images = request2.messages.some((m) => m.content.some((b) => b.type === "image"));
16808
17152
  return {
@@ -16819,10 +17163,11 @@ function eligible(input) {
16819
17163
  const { request: request2, model, snapshot, now } = input;
16820
17164
  const { breakerThreshold, breakerCooldownMs } = snapshot.settings;
16821
17165
  const need = requiredCapabilities(request2);
17166
+ const needNative = needsAnthropicNative(request2);
16822
17167
  const pairs = [];
16823
17168
  const excluded = [];
16824
17169
  for (const target of model.targets) {
16825
- const missing = ["tools", "images", "reasoning"].find((cap) => need[cap] && !target.capabilities[cap]);
17170
+ const missing = needNative && !ANTHROPIC_NATIVE_TOOLS[target.provider] ? "anthropicTools" : ["tools", "images", "reasoning"].find((cap) => need[cap] && !target.capabilities[cap]);
16826
17171
  for (const credential of snapshot.credentials) {
16827
17172
  if (credential.provider !== target.provider)
16828
17173
  continue;
@@ -17017,7 +17362,11 @@ async function dryRun(deps, modelId, input) {
17017
17362
  }
17018
17363
  ],
17019
17364
  stream: false,
17020
- ...need.tools ? { tools: [{ name: "probe", description: "", inputSchema: { type: "object" } }] } : {},
17365
+ ...need.tools ? {
17366
+ tools: [
17367
+ { provider: "custom", name: "probe", description: "", inputSchema: { type: "object" } }
17368
+ ]
17369
+ } : {},
17021
17370
  ...need.reasoning ? { reasoning: { mode: "adaptive" } } : {}
17022
17371
  };
17023
17372
  const result = rank({ request: probe, model, snapshot, now, rand: 0 });
@@ -17085,7 +17434,8 @@ var DEFAULT_SETTINGS = {
17085
17434
  breakerThreshold: 3,
17086
17435
  breakerCooldownMs: 30000,
17087
17436
  logRetentionDays: 30,
17088
- quotaPollIntervalMs: 300000
17437
+ quotaPollIntervalMs: 300000,
17438
+ rtkEnabled: false
17089
17439
  };
17090
17440
 
17091
17441
  // packages/store/src/sqlite/config.ts
@@ -17121,12 +17471,18 @@ function createConfigRepo(db, emit2 = () => {}) {
17121
17471
  const raw = readRaw(SETTINGS_KEY);
17122
17472
  if (raw === null)
17123
17473
  return DEFAULT_SETTINGS;
17124
- const stored = JSON.parse(raw);
17125
- return {
17126
- ...DEFAULT_SETTINGS,
17127
- ...stored,
17128
- weights: { ...DEFAULT_SETTINGS.weights, ...stored.weights }
17129
- };
17474
+ try {
17475
+ const parsed = JSON.parse(raw);
17476
+ const stored = parsed !== null && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
17477
+ return {
17478
+ ...DEFAULT_SETTINGS,
17479
+ ...stored,
17480
+ rtkEnabled: stored.rtkEnabled === true,
17481
+ weights: { ...DEFAULT_SETTINGS.weights, ...stored.weights }
17482
+ };
17483
+ } catch {
17484
+ return DEFAULT_SETTINGS;
17485
+ }
17130
17486
  },
17131
17487
  async putSettings(patch) {
17132
17488
  const current = await this.getSettings();
@@ -17568,6 +17924,20 @@ ALTER TABLE request_logs ADD COLUMN state TEXT NOT NULL DEFAULT 'done';
17568
17924
  CREATE INDEX idx_request_logs_pending ON request_logs(state) WHERE state = 'pending';
17569
17925
  `;
17570
17926
 
17927
+ // packages/store/src/sqlite/migrations/005_rtk_metrics.sql
17928
+ var _005_rtk_metrics_default = `ALTER TABLE request_logs ADD COLUMN rtk_applied INTEGER NOT NULL DEFAULT 0;
17929
+ ALTER TABLE request_logs ADD COLUMN rtk_filter_hits INTEGER NOT NULL DEFAULT 0;
17930
+ ALTER TABLE request_logs ADD COLUMN rtk_original_code_units INTEGER NOT NULL DEFAULT 0;
17931
+ ALTER TABLE request_logs ADD COLUMN rtk_compressed_code_units INTEGER NOT NULL DEFAULT 0;
17932
+ ALTER TABLE request_logs ADD COLUMN rtk_estimated_tokens_saved INTEGER NOT NULL DEFAULT 0;
17933
+ ALTER TABLE request_logs ADD COLUMN rtk_filters TEXT NOT NULL DEFAULT '[]';
17934
+ `;
17935
+
17936
+ // packages/store/src/sqlite/migrations/006_rtk_usage.sql
17937
+ var _006_rtk_usage_default = `ALTER TABLE usage_daily ADD COLUMN rtk_saved_tokens INTEGER NOT NULL DEFAULT 0;
17938
+ ALTER TABLE usage_daily ADD COLUMN rtk_applied_requests INTEGER NOT NULL DEFAULT 0;
17939
+ `;
17940
+
17571
17941
  // packages/store/src/sqlite/rollup.ts
17572
17942
  function startOfLocalDay(at) {
17573
17943
  const day = new Date(at);
@@ -17578,8 +17948,8 @@ var UPSERT = `
17578
17948
  INSERT INTO usage_daily
17579
17949
  (day, provider, credential_id, requested_model, resolved_model, api_key_id,
17580
17950
  requests, errors, input_tokens, output_tokens, cache_read_tokens,
17581
- cache_write_tokens, cost_usd, duration_ms_sum)
17582
- VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
17951
+ cache_write_tokens, rtk_saved_tokens, rtk_applied_requests, cost_usd, duration_ms_sum)
17952
+ VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
17583
17953
  ON CONFLICT (day, provider, credential_id, requested_model, resolved_model, api_key_id)
17584
17954
  DO UPDATE SET
17585
17955
  requests = requests + excluded.requests,
@@ -17587,8 +17957,10 @@ var UPSERT = `
17587
17957
  input_tokens = input_tokens + excluded.input_tokens,
17588
17958
  output_tokens = output_tokens + excluded.output_tokens,
17589
17959
  cache_read_tokens = cache_read_tokens + excluded.cache_read_tokens,
17590
- cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
17591
- cost_usd = cost_usd + excluded.cost_usd,
17960
+ cache_write_tokens = cache_write_tokens + excluded.cache_write_tokens,
17961
+ rtk_saved_tokens = rtk_saved_tokens + excluded.rtk_saved_tokens,
17962
+ rtk_applied_requests = rtk_applied_requests + excluded.rtk_applied_requests,
17963
+ cost_usd = cost_usd + excluded.cost_usd,
17592
17964
  duration_ms_sum = duration_ms_sum + excluded.duration_ms_sum`;
17593
17965
  function keyOf(log) {
17594
17966
  return [
@@ -17608,6 +17980,8 @@ function countersOf(log) {
17608
17980
  outputTokens: log.outputTokens,
17609
17981
  cacheReadTokens: log.cacheReadTokens,
17610
17982
  cacheWriteTokens: log.cacheWriteTokens,
17983
+ rtkSavedTokens: log.rtkEstimatedTokensSaved,
17984
+ rtkAppliedRequests: log.rtkApplied ? 1 : 0,
17611
17985
  costUsd: log.costUsd,
17612
17986
  durationMsSum: log.durationMs
17613
17987
  };
@@ -17621,6 +17995,8 @@ function upsert(db, key, c) {
17621
17995
  c.outputTokens,
17622
17996
  c.cacheReadTokens,
17623
17997
  c.cacheWriteTokens,
17998
+ c.rtkSavedTokens,
17999
+ c.rtkAppliedRequests,
17624
18000
  c.costUsd,
17625
18001
  c.durationMsSum
17626
18002
  ]);
@@ -17651,6 +18027,8 @@ function backfillDaily(db) {
17651
18027
  outputTokens: 0,
17652
18028
  cacheReadTokens: 0,
17653
18029
  cacheWriteTokens: 0,
18030
+ rtkSavedTokens: 0,
18031
+ rtkAppliedRequests: 0,
17654
18032
  costUsd: 0,
17655
18033
  durationMsSum: 0
17656
18034
  };
@@ -17669,13 +18047,42 @@ function backfillDaily(db) {
17669
18047
  upsert(db, group.key, group.counters);
17670
18048
  return groups.size;
17671
18049
  }
18050
+ function backfillRtkUsage(db) {
18051
+ const groups = new Map;
18052
+ for (const row of db.query(`SELECT at, api_key_id, requested_model, resolved_provider, resolved_model, credential_id,
18053
+ rtk_applied, rtk_estimated_tokens_saved
18054
+ FROM request_logs
18055
+ WHERE state = 'done'`).all()) {
18056
+ const key = [
18057
+ startOfLocalDay(row.at),
18058
+ row.resolved_provider ?? "",
18059
+ row.credential_id ?? "",
18060
+ row.requested_model,
18061
+ row.resolved_model ?? "",
18062
+ row.api_key_id ?? ""
18063
+ ];
18064
+ const id = key.join("\x00");
18065
+ const group = groups.get(id) ?? { key, saved: 0, applied: 0 };
18066
+ group.saved += row.rtk_estimated_tokens_saved;
18067
+ group.applied += row.rtk_applied === 1 ? 1 : 0;
18068
+ groups.set(id, group);
18069
+ }
18070
+ for (const { key, saved, applied } of groups.values()) {
18071
+ db.run(`UPDATE usage_daily
18072
+ SET rtk_saved_tokens = ?, rtk_applied_requests = ?
18073
+ WHERE day = ? AND provider = ? AND credential_id = ? AND requested_model = ?
18074
+ AND resolved_model = ? AND api_key_id = ?`, [saved, applied, ...key]);
18075
+ }
18076
+ }
17672
18077
 
17673
18078
  // packages/store/src/sqlite/db.ts
17674
18079
  var MIGRATIONS = [
17675
18080
  { id: 1, sql: _001_init_default },
17676
18081
  { id: 2, sql: _002_usage_daily_default, after: backfillDaily },
17677
18082
  { id: 3, sql: _003_quota_snapshot_default },
17678
- { id: 4, sql: _004_request_state_default }
18083
+ { id: 4, sql: _004_request_state_default },
18084
+ { id: 5, sql: _005_rtk_metrics_default },
18085
+ { id: 6, sql: _006_rtk_usage_default, after: backfillRtkUsage }
17679
18086
  ];
17680
18087
  function openDb(path) {
17681
18088
  const db = new Database(path, { create: true });
@@ -17744,7 +18151,38 @@ function createKeyRepo(db) {
17744
18151
  }
17745
18152
  };
17746
18153
  }
18154
+ // packages/rtk/src/catalog.ts
18155
+ var RTK_FILTER_IDS = [
18156
+ "git-diff",
18157
+ "git-status",
18158
+ "git-log",
18159
+ "grep",
18160
+ "path-list",
18161
+ "numbered-read",
18162
+ "build-output",
18163
+ "test-output",
18164
+ "deduplicate-log",
18165
+ "smart-truncate",
18166
+ "lint-output",
18167
+ "package-output",
18168
+ "tree-output",
18169
+ "git-operation",
18170
+ "docker-build"
18171
+ ];
18172
+ var FILTER_IDS = new Set(RTK_FILTER_IDS);
18173
+ function isRtkFilterId(value) {
18174
+ return FILTER_IDS.has(value);
18175
+ }
18176
+
17747
18177
  // packages/store/src/sqlite/usage.ts
18178
+ function parseRtkFilters(raw) {
18179
+ try {
18180
+ const parsed = JSON.parse(raw);
18181
+ return Array.isArray(parsed) ? parsed.filter(isRtkFilterId) : [];
18182
+ } catch {
18183
+ return [];
18184
+ }
18185
+ }
17748
18186
  var toLog = (r) => ({
17749
18187
  id: r.id,
17750
18188
  state: r.state === "pending" ? "pending" : "done",
@@ -17764,7 +18202,13 @@ var toLog = (r) => ({
17764
18202
  ttftMs: r.ttft_ms,
17765
18203
  durationMs: r.duration_ms,
17766
18204
  costUsd: r.cost_usd,
17767
- degradations: JSON.parse(r.degradations)
18205
+ degradations: JSON.parse(r.degradations),
18206
+ rtkApplied: r.rtk_applied === 1,
18207
+ rtkFilterHits: r.rtk_filter_hits,
18208
+ rtkOriginalCodeUnits: r.rtk_original_code_units,
18209
+ rtkCompressedCodeUnits: r.rtk_compressed_code_units,
18210
+ rtkEstimatedTokensSaved: r.rtk_estimated_tokens_saved,
18211
+ rtkFilters: parseRtkFilters(r.rtk_filters)
17768
18212
  });
17769
18213
  var GROUP_COLUMN = {
17770
18214
  raw: {
@@ -17809,8 +18253,9 @@ function label(value) {
17809
18253
  var COLUMNS = `(id, state, at, api_key_id, requested_model, resolved_provider, resolved_model,
17810
18254
  credential_id, attempts, status, error_code, input_tokens, output_tokens,
17811
18255
  cache_read_tokens, cache_write_tokens, ttft_ms, duration_ms, cost_usd,
17812
- degradations)`;
17813
- var PLACEHOLDERS = "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
18256
+ degradations, rtk_applied, rtk_filter_hits, rtk_original_code_units,
18257
+ rtk_compressed_code_units, rtk_estimated_tokens_saved, rtk_filters)`;
18258
+ var PLACEHOLDERS = "(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)";
17814
18259
  function values(log, state) {
17815
18260
  return [
17816
18261
  log.id,
@@ -17831,7 +18276,13 @@ function values(log, state) {
17831
18276
  log.ttftMs,
17832
18277
  log.durationMs,
17833
18278
  log.costUsd,
17834
- JSON.stringify(log.degradations)
18279
+ JSON.stringify(log.degradations),
18280
+ log.rtkApplied === true ? 1 : 0,
18281
+ log.rtkFilterHits ?? 0,
18282
+ log.rtkOriginalCodeUnits ?? 0,
18283
+ log.rtkCompressedCodeUnits ?? 0,
18284
+ log.rtkEstimatedTokensSaved ?? 0,
18285
+ JSON.stringify(log.rtkFilters ?? [])
17835
18286
  ];
17836
18287
  }
17837
18288
  var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
@@ -17852,7 +18303,13 @@ var COMPLETE = `INSERT INTO request_logs ${COLUMNS} VALUES ${PLACEHOLDERS}
17852
18303
  ttft_ms = excluded.ttft_ms,
17853
18304
  duration_ms = excluded.duration_ms,
17854
18305
  cost_usd = excluded.cost_usd,
17855
- degradations = excluded.degradations`;
18306
+ degradations = excluded.degradations,
18307
+ rtk_applied = excluded.rtk_applied,
18308
+ rtk_filter_hits = excluded.rtk_filter_hits,
18309
+ rtk_original_code_units = excluded.rtk_original_code_units,
18310
+ rtk_compressed_code_units = excluded.rtk_compressed_code_units,
18311
+ rtk_estimated_tokens_saved = excluded.rtk_estimated_tokens_saved,
18312
+ rtk_filters = excluded.rtk_filters`;
17856
18313
  function createUsageRepo(db) {
17857
18314
  const complete = db.transaction((log) => {
17858
18315
  db.run(COMPLETE, values(log, "done"));
@@ -17897,6 +18354,8 @@ function createUsageRepo(db) {
17897
18354
  COALESCE(SUM(output_tokens), 0) AS output_tokens,
17898
18355
  COALESCE(SUM(cache_read_tokens), 0) AS cache_read_tokens,
17899
18356
  COALESCE(SUM(cache_write_tokens), 0) AS cache_write_tokens,
18357
+ COALESCE(SUM(${daily ? "rtk_saved_tokens" : "rtk_estimated_tokens_saved"}), 0) AS rtk_saved_tokens,
18358
+ COALESCE(SUM(${daily ? "rtk_applied_requests" : "CASE WHEN rtk_applied = 1 THEN 1 ELSE 0 END"}), 0) AS rtk_applied_requests,
17900
18359
  COALESCE(SUM(cost_usd), 0) AS cost_usd
17901
18360
  FROM ${daily ? "usage_daily" : "request_logs"}
17902
18361
  WHERE ${daily ? "" : "state = 'done' AND "}${timeColumn} >= ? AND ${timeColumn} <= ?
@@ -17910,6 +18369,8 @@ function createUsageRepo(db) {
17910
18369
  outputTokens: r.output_tokens,
17911
18370
  cacheReadTokens: r.cache_read_tokens,
17912
18371
  cacheWriteTokens: r.cache_write_tokens,
18372
+ rtkSavedTokens: r.rtk_saved_tokens,
18373
+ rtkAppliedRequests: r.rtk_applied_requests,
17913
18374
  costUsd: r.cost_usd,
17914
18375
  errors: r.errors,
17915
18376
  durationMsSum: r.duration_ms_sum
@@ -17985,6 +18446,60 @@ async function createKey(store, input) {
17985
18446
  async function revokeKey(store, id) {
17986
18447
  await store.keys.revoke(id);
17987
18448
  }
18449
+ // packages/control/src/modelLimits.ts
18450
+ function narrower(a, b) {
18451
+ const context = [a.contextWindow, b.contextWindow].filter((n) => n !== undefined);
18452
+ const output = [a.maxOutputTokens, b.maxOutputTokens].filter((n) => n !== undefined);
18453
+ return {
18454
+ ...context.length === 0 ? {} : { contextWindow: Math.min(...context) },
18455
+ ...output.length === 0 ? {} : { maxOutputTokens: Math.min(...output) }
18456
+ };
18457
+ }
18458
+ function targetLimits(target, auths) {
18459
+ const ways = auths.size === 0 ? ["apiKey"] : [...auths];
18460
+ let listed = {};
18461
+ for (const auth of ways) {
18462
+ const entry = catalogLimits(target.provider, target.model, auth);
18463
+ if (entry === null)
18464
+ continue;
18465
+ listed = narrower(listed, {
18466
+ contextWindow: entry.contextWindow,
18467
+ maxOutputTokens: entry.maxOutputTokens
18468
+ });
18469
+ }
18470
+ const contextWindow = target.contextWindow ?? listed.contextWindow;
18471
+ const maxOutputTokens = target.maxOutputTokens ?? listed.maxOutputTokens;
18472
+ return {
18473
+ ...contextWindow === undefined ? {} : { contextWindow },
18474
+ ...maxOutputTokens === undefined ? {} : { maxOutputTokens }
18475
+ };
18476
+ }
18477
+ function servingAuths(credentials) {
18478
+ const byProvider = new Map;
18479
+ for (const credential of credentials) {
18480
+ if (!credential.enabled)
18481
+ continue;
18482
+ const ways = byProvider.get(credential.provider) ?? new Set;
18483
+ ways.add(credential.authType);
18484
+ byProvider.set(credential.provider, ways);
18485
+ }
18486
+ return byProvider;
18487
+ }
18488
+ function resolveModelLimits(model, credentials) {
18489
+ const auths = servingAuths(credentials);
18490
+ let limits = {};
18491
+ for (const target of model.targets) {
18492
+ limits = narrower(limits, targetLimits(target, auths.get(target.provider) ?? new Set));
18493
+ }
18494
+ return limits;
18495
+ }
18496
+ function modelDisplayName(model) {
18497
+ const only = model.targets.length === 1 ? model.targets[0] : undefined;
18498
+ if (only === undefined)
18499
+ return model.id;
18500
+ const labelled = PROVIDER_MODEL_CATALOG[only.provider]?.models.find((choice) => choice.id === only.model);
18501
+ return labelled?.label ?? model.id;
18502
+ }
17988
18503
  // packages/control/src/models.ts
17989
18504
  async function listModels(store) {
17990
18505
  return store.config.listModels();
@@ -18341,6 +18856,116 @@ async function putSettings(store, input) {
18341
18856
  await store.config.putSettings(settings);
18342
18857
  return settings;
18343
18858
  }
18859
+ // packages/control/src/setup.ts
18860
+ var KEY_PLACEHOLDER = "<your OmniGateway key>";
18861
+ async function describeModelsForSetup(store) {
18862
+ const models = await listModels(store);
18863
+ const credentials = (await listCredentials(store)).map((credential) => ({
18864
+ provider: credential.provider,
18865
+ authType: credential.authType,
18866
+ enabled: credential.enabled
18867
+ }));
18868
+ return models.map((model) => ({
18869
+ model,
18870
+ limits: resolveModelLimits(model, credentials),
18871
+ label: modelDisplayName(model)
18872
+ }));
18873
+ }
18874
+ var CLAUDE_MAPPING_KEYS = {
18875
+ defaultModel: "ANTHROPIC_MODEL",
18876
+ fableModel: "ANTHROPIC_DEFAULT_FABLE_MODEL",
18877
+ opusModel: "ANTHROPIC_DEFAULT_OPUS_MODEL",
18878
+ sonnetModel: "ANTHROPIC_DEFAULT_SONNET_MODEL",
18879
+ haikuModel: "ANTHROPIC_DEFAULT_HAIKU_MODEL"
18880
+ };
18881
+ function settingsObject(existing) {
18882
+ if (existing === undefined)
18883
+ return {};
18884
+ try {
18885
+ const parsed = JSON.parse(existing);
18886
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
18887
+ throw new Error("settings root is not an object");
18888
+ }
18889
+ return parsed;
18890
+ } catch (error51) {
18891
+ const reason = error51 instanceof Error ? error51.message : "invalid JSON";
18892
+ throw new Error(`cannot parse existing settings.json: ${reason}`);
18893
+ }
18894
+ }
18895
+ function claudeSettings(described, input, mapping, existing) {
18896
+ if (mapping.defaultModel === "")
18897
+ throw new Error("default model is required");
18898
+ const ids = new Set(described.map(({ model }) => model.id));
18899
+ const visibleId = (slot, id) => {
18900
+ if (!ids.has(id))
18901
+ throw new Error(`${slot} names unknown virtual model "${id}"`);
18902
+ const useMirror = input.discoveryMirrors === true && !/^(?:claude|anthropic)/i.test(id);
18903
+ return useMirror ? `claude/${id}` : id;
18904
+ };
18905
+ const settings = settingsObject(existing);
18906
+ const currentEnv = settings.env;
18907
+ const env2 = typeof currentEnv === "object" && currentEnv !== null && !Array.isArray(currentEnv) ? { ...currentEnv } : {};
18908
+ for (const key of Object.values(CLAUDE_MAPPING_KEYS))
18909
+ delete env2[key];
18910
+ delete env2.CLAUDE_CODE_MAX_CONTEXT_TOKENS;
18911
+ env2.ANTHROPIC_BASE_URL = input.baseUrl;
18912
+ env2.ANTHROPIC_AUTH_TOKEN = input.apiKey ?? KEY_PLACEHOLDER;
18913
+ env2.CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY = "1";
18914
+ for (const [slot, key] of Object.entries(CLAUDE_MAPPING_KEYS)) {
18915
+ const id = mapping[slot];
18916
+ if (id !== undefined && id !== "")
18917
+ env2[key] = visibleId(slot, id);
18918
+ }
18919
+ return {
18920
+ path: "settings.json",
18921
+ contents: `${JSON.stringify({ ...settings, env: env2 }, null, 2)}
18922
+ `
18923
+ };
18924
+ }
18925
+ function opencodeConfig(described, input, mapping) {
18926
+ if (mapping.defaultModel === "")
18927
+ throw new Error("default model is required");
18928
+ const byId = new Map(described.map((entry) => [entry.model.id, entry]));
18929
+ const selected = [];
18930
+ const seen = new Set;
18931
+ for (const slot of Object.keys(CLAUDE_MAPPING_KEYS)) {
18932
+ const id = mapping[slot];
18933
+ if (id === undefined || id === "")
18934
+ continue;
18935
+ const entry = byId.get(id);
18936
+ if (entry === undefined)
18937
+ throw new Error(`${slot} names unknown virtual model "${id}"`);
18938
+ if (!seen.has(id)) {
18939
+ seen.add(id);
18940
+ selected.push(entry);
18941
+ }
18942
+ }
18943
+ const models = {};
18944
+ for (const { model, limits, label: label2 } of selected) {
18945
+ const limit = limits.contextWindow === undefined ? undefined : {
18946
+ context: limits.contextWindow,
18947
+ ...limits.maxOutputTokens === undefined ? {} : { output: limits.maxOutputTokens }
18948
+ };
18949
+ models[model.id] = { name: label2, ...limit === undefined ? {} : { limit } };
18950
+ }
18951
+ const contents = `${JSON.stringify({
18952
+ $schema: "https://opencode.ai/config.json",
18953
+ model: `omnigateway/${mapping.defaultModel}`,
18954
+ provider: {
18955
+ omnigateway: {
18956
+ npm: "@ai-sdk/openai-compatible",
18957
+ name: "OmniGateway",
18958
+ options: {
18959
+ baseURL: `${input.baseUrl.replace(/\/+$/, "").replace(/(?:\/v1)+$/i, "")}/v1`,
18960
+ apiKey: input.apiKey ?? KEY_PLACEHOLDER
18961
+ },
18962
+ models
18963
+ }
18964
+ }
18965
+ }, null, 2)}
18966
+ `;
18967
+ return { path: "opencode.json", contents };
18968
+ }
18344
18969
  // packages/control/src/tail.ts
18345
18970
  import { closeSync, fstatSync, openSync, readSync, statSync } from "fs";
18346
18971
  var CHUNK = 64 * 1024;
@@ -19256,8 +19881,8 @@ var modelsShow = {
19256
19881
  price(target.costPerMTok.cacheRead),
19257
19882
  price(target.costPerMTok.cacheWrite5m),
19258
19883
  price(target.costPerMTok.cacheWrite1h),
19259
- tokens(target.contextWindow),
19260
- tokens(target.maxOutputTokens),
19884
+ tokens2(target.contextWindow),
19885
+ tokens2(target.maxOutputTokens),
19261
19886
  capabilityList(target)
19262
19887
  ]))
19263
19888
  ].join(`
@@ -19267,7 +19892,7 @@ var modelsShow = {
19267
19892
  function price(value) {
19268
19893
  return value === undefined ? "\u2014" : value.toFixed(2);
19269
19894
  }
19270
- function tokens(value) {
19895
+ function tokens2(value) {
19271
19896
  return value === undefined ? "\u2014" : value.toLocaleString("en-US");
19272
19897
  }
19273
19898
  function capabilityList(target) {
@@ -19335,9 +19960,9 @@ var modelsCatalog = {
19335
19960
  price(listed?.cacheRead),
19336
19961
  price(listed?.cacheWrite5m),
19337
19962
  price(listed?.cacheWrite1h),
19338
- tokens(limits?.contextWindow),
19339
- oauth?.contextWindow === limits?.contextWindow ? "\u2014" : tokens(oauth?.contextWindow),
19340
- tokens(limits?.maxOutputTokens)
19963
+ tokens2(limits?.contextWindow),
19964
+ oauth?.contextWindow === limits?.contextWindow ? "\u2014" : tokens2(oauth?.contextWindow),
19965
+ tokens2(limits?.maxOutputTokens)
19341
19966
  ];
19342
19967
  })));
19343
19968
  }
@@ -19721,6 +20346,104 @@ var settingsSet = {
19721
20346
  }
19722
20347
  };
19723
20348
 
20349
+ // apps/cli/src/commands/setup.ts
20350
+ import { join as join4 } from "path";
20351
+ var OPTIONS = {
20352
+ dir: { type: "string" },
20353
+ key: { type: "string" },
20354
+ "dry-run": { type: "boolean" }
20355
+ };
20356
+ async function described(ctx) {
20357
+ const models = await describeModelsForSetup(await ctx.store());
20358
+ if (models.length === 0) {
20359
+ throw new CliError("no virtual models configured; add one with `omni models put`");
20360
+ }
20361
+ return models;
20362
+ }
20363
+ function baseUrl(ctx) {
20364
+ if (ctx.configError !== null)
20365
+ throw new CliError(ctx.configError);
20366
+ return ctx.config().baseUrl;
20367
+ }
20368
+ function finish(ctx, writer, files, dryRun2, key, write) {
20369
+ if (!dryRun2) {
20370
+ for (const file2 of files)
20371
+ write(file2.path, file2.contents);
20372
+ }
20373
+ emit(ctx, writer, { files }, () => dryRun2 ? files.map((f) => `--- ${f.path}
20374
+ ${f.contents}`).join(`
20375
+ `) : files.map((f) => f.path).join(`
20376
+ `));
20377
+ if (key === undefined) {
20378
+ note(ctx, writer, "no --key given: the files carry a placeholder, so paste your key over it");
20379
+ }
20380
+ }
20381
+ function at(dir, file2) {
20382
+ return { path: join4(dir, file2.path), contents: file2.contents };
20383
+ }
20384
+ async function promptMapping(models, prompt) {
20385
+ const choices = models.map(({ model }) => model.id).join(", ");
20386
+ if (prompt.input === undefined) {
20387
+ throw new CliError("model mapping requires an interactive terminal");
20388
+ }
20389
+ const ask = async (label2, required2) => {
20390
+ const answer = await prompt.input?.(`${label2} model${required2 ? "" : " (blank to omit)"} [${choices}]: `);
20391
+ if (required2 && answer === "")
20392
+ throw new CliError("default model is required");
20393
+ return answer === "" ? undefined : answer;
20394
+ };
20395
+ const defaultModel = await ask("Default", true);
20396
+ if (defaultModel === undefined)
20397
+ throw new CliError("default model is required");
20398
+ const fableModel = await ask("Fable", false);
20399
+ const opusModel = await ask("Opus", false);
20400
+ const sonnetModel = await ask("Sonnet", false);
20401
+ const haikuModel = await ask("Haiku", false);
20402
+ return {
20403
+ defaultModel,
20404
+ ...fableModel === undefined ? {} : { fableModel },
20405
+ ...opusModel === undefined ? {} : { opusModel },
20406
+ ...sonnetModel === undefined ? {} : { sonnetModel },
20407
+ ...haikuModel === undefined ? {} : { haikuModel }
20408
+ };
20409
+ }
20410
+ var setupClaude = {
20411
+ usage: "setup claude [--dir <path>] [--key <key>] [--dry-run]",
20412
+ summary: "Write Claude Code settings for this gateway",
20413
+ options: OPTIONS,
20414
+ async run(args, { ctx, writer, prompt, setupFs }) {
20415
+ const models = await described(ctx);
20416
+ const dir = stringFlag(args.values, "dir") ?? join4(setupFs.homeDir, ".claude");
20417
+ const key = stringFlag(args.values, "key");
20418
+ const dryRun2 = boolFlag(args.values, "dry-run");
20419
+ const mapping = await promptMapping(models, prompt);
20420
+ const path = join4(dir, "settings.json");
20421
+ const file2 = claudeSettings(models, {
20422
+ baseUrl: baseUrl(ctx),
20423
+ discoveryMirrors: ctx.config().exposeClaudeCodeAliases,
20424
+ ...key === undefined ? {} : { apiKey: key }
20425
+ }, mapping, setupFs.read(path) ?? undefined);
20426
+ finish(ctx, writer, [at(dir, file2)], dryRun2, key, setupFs.write);
20427
+ }
20428
+ };
20429
+ var setupOpencode = {
20430
+ usage: "setup opencode [--dir <path>] [--key <key>] [--dry-run]",
20431
+ summary: "Write an opencode.json provider entry for this gateway",
20432
+ options: OPTIONS,
20433
+ async run(args, { ctx, writer, prompt, setupFs }) {
20434
+ const models = await described(ctx);
20435
+ const dir = stringFlag(args.values, "dir") ?? setupFs.cwd;
20436
+ const key = stringFlag(args.values, "key");
20437
+ const dryRun2 = boolFlag(args.values, "dry-run");
20438
+ const mapping = await promptMapping(models, prompt);
20439
+ const file2 = opencodeConfig(models, {
20440
+ baseUrl: baseUrl(ctx),
20441
+ ...key === undefined ? {} : { apiKey: key }
20442
+ }, mapping);
20443
+ finish(ctx, writer, [at(dir, file2)], dryRun2, key, setupFs.write);
20444
+ }
20445
+ };
20446
+
19724
20447
  // apps/cli/src/commands/status.ts
19725
20448
  var WINDOW_ORDER = {
19726
20449
  fiveHour: 0,
@@ -19955,6 +20678,8 @@ var COMMANDS = {
19955
20678
  "keys revoke": keysRevoke,
19956
20679
  "settings get": settingsGet,
19957
20680
  "settings set": settingsSet,
20681
+ "setup claude": setupClaude,
20682
+ "setup opencode": setupOpencode,
19958
20683
  "admin set-password": adminSetPassword,
19959
20684
  "db migrate": dbMigrate
19960
20685
  };
@@ -20097,6 +20822,12 @@ function createPrompt(ctx, writer) {
20097
20822
  const isTty = process.stdin.isTTY === true;
20098
20823
  return {
20099
20824
  isTty,
20825
+ async input(question) {
20826
+ if (!isTty)
20827
+ throw new CliError(`${question} \u2014 refusing without a terminal`);
20828
+ writer.err(question);
20829
+ return (await readLine()).trim();
20830
+ },
20100
20831
  async secret(question) {
20101
20832
  if (!isTty)
20102
20833
  return (await readAllStdin()).trim();
@@ -20117,6 +20848,20 @@ function createPrompt(ctx, writer) {
20117
20848
  };
20118
20849
  }
20119
20850
 
20851
+ // apps/cli/src/setupFs.ts
20852
+ import { dirname as dirname2 } from "path";
20853
+ function atomicWriteFile(path, contents, ops) {
20854
+ const temporary = `${path}.${process.pid}.tmp`;
20855
+ ops.mkdir(dirname2(path));
20856
+ try {
20857
+ ops.write(temporary, contents);
20858
+ ops.rename(temporary, path);
20859
+ } catch (error51) {
20860
+ ops.remove(temporary);
20861
+ throw error51;
20862
+ }
20863
+ }
20864
+
20120
20865
  // apps/cli/src/run.ts
20121
20866
  var VERSION = "0.0.0";
20122
20867
  async function run(argv, writer, options = {}) {
@@ -20155,6 +20900,17 @@ async function run(argv, writer, options = {}) {
20155
20900
  ctx,
20156
20901
  writer,
20157
20902
  prompt,
20903
+ setupFs: options.setupFs ?? {
20904
+ homeDir: homedir3(),
20905
+ cwd: process.cwd(),
20906
+ read: (path) => existsSync6(path) ? readFileSync3(path, "utf8") : null,
20907
+ write: (path, contents) => atomicWriteFile(path, contents, {
20908
+ mkdir: (directory) => mkdirSync2(directory, { recursive: true }),
20909
+ write: (temporary, data) => writeFileSync2(temporary, data),
20910
+ rename: renameSync,
20911
+ remove: (temporary) => rmSync2(temporary, { force: true })
20912
+ })
20913
+ },
20158
20914
  service: () => options.service?.({ root: ctx.root.root, env: ctx.env }) ?? createServiceDeps({ root: ctx.root.root, env: ctx.env, scope, now: ctx.now }),
20159
20915
  foreground: options.foreground ?? runForeground,
20160
20916
  connect: (store) => options.connect?.(store) ?? createConnectFlows({