cascade-ai 0.76.0 → 0.77.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.
package/dist/index.cjs CHANGED
@@ -1,14 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  var Database2 = require('better-sqlite3');
4
- var path27 = require('path');
5
- var fs26 = require('fs');
4
+ var path28 = require('path');
5
+ var fs27 = require('fs');
6
6
  var os6 = require('os');
7
7
  var crypto = require('crypto');
8
8
  var EventEmitter = require('events');
9
9
  var glob = require('glob');
10
10
  var Anthropic = require('@anthropic-ai/sdk');
11
- var dns = require('dns');
11
+ var dnsCallback = require('dns');
12
12
  var http = require('http');
13
13
  var https = require('https');
14
14
  var zlib = require('zlib');
@@ -24,6 +24,7 @@ var simpleGit = require('simple-git');
24
24
  var PDFDocument = require('pdfkit');
25
25
  var dns2 = require('dns/promises');
26
26
  var net = require('net');
27
+ var undici = require('undici');
27
28
  var index_js = require('@modelcontextprotocol/sdk/client/index.js');
28
29
  var stdio_js = require('@modelcontextprotocol/sdk/client/stdio.js');
29
30
  var streamableHttp_js = require('@modelcontextprotocol/sdk/client/streamableHttp.js');
@@ -61,13 +62,13 @@ function _interopNamespace(e) {
61
62
  }
62
63
 
63
64
  var Database2__default = /*#__PURE__*/_interopDefault(Database2);
64
- var path27__default = /*#__PURE__*/_interopDefault(path27);
65
- var fs26__default = /*#__PURE__*/_interopDefault(fs26);
65
+ var path28__default = /*#__PURE__*/_interopDefault(path28);
66
+ var fs27__default = /*#__PURE__*/_interopDefault(fs27);
66
67
  var os6__default = /*#__PURE__*/_interopDefault(os6);
67
68
  var crypto__default = /*#__PURE__*/_interopDefault(crypto);
68
69
  var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
69
70
  var Anthropic__default = /*#__PURE__*/_interopDefault(Anthropic);
70
- var dns__default = /*#__PURE__*/_interopDefault(dns);
71
+ var dnsCallback__default = /*#__PURE__*/_interopDefault(dnsCallback);
71
72
  var http__default = /*#__PURE__*/_interopDefault(http);
72
73
  var https__default = /*#__PURE__*/_interopDefault(https);
73
74
  var zlib__default = /*#__PURE__*/_interopDefault(zlib);
@@ -178,16 +179,16 @@ var require_isarray = __commonJS({
178
179
  }
179
180
  });
180
181
 
181
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js
182
+ // node_modules/readable-stream/lib/internal/streams/stream.js
182
183
  var require_stream = __commonJS({
183
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js"(exports, module) {
184
+ "node_modules/readable-stream/lib/internal/streams/stream.js"(exports, module) {
184
185
  module.exports = __require("stream");
185
186
  }
186
187
  });
187
188
 
188
- // node_modules/jszip/node_modules/safe-buffer/index.js
189
+ // node_modules/readable-stream/node_modules/safe-buffer/index.js
189
190
  var require_safe_buffer = __commonJS({
190
- "node_modules/jszip/node_modules/safe-buffer/index.js"(exports, module) {
191
+ "node_modules/readable-stream/node_modules/safe-buffer/index.js"(exports, module) {
191
192
  var buffer = __require("buffer");
192
193
  var Buffer2 = buffer.Buffer;
193
194
  function copyProps(src, dst) {
@@ -305,7 +306,7 @@ var require_util = __commonJS({
305
306
  typeof arg === "undefined";
306
307
  }
307
308
  exports.isPrimitive = isPrimitive;
308
- exports.isBuffer = Buffer.isBuffer;
309
+ exports.isBuffer = __require("buffer").Buffer.isBuffer;
309
310
  function objectToString(o) {
310
311
  return Object.prototype.toString.call(o);
311
312
  }
@@ -358,9 +359,9 @@ var require_inherits = __commonJS({
358
359
  }
359
360
  });
360
361
 
361
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js
362
+ // node_modules/readable-stream/lib/internal/streams/BufferList.js
362
363
  var require_BufferList = __commonJS({
363
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js"(exports, module) {
364
+ "node_modules/readable-stream/lib/internal/streams/BufferList.js"(exports, module) {
364
365
  function _classCallCheck(instance, Constructor) {
365
366
  if (!(instance instanceof Constructor)) {
366
367
  throw new TypeError("Cannot call a class as a function");
@@ -435,9 +436,9 @@ var require_BufferList = __commonJS({
435
436
  }
436
437
  });
437
438
 
438
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js
439
+ // node_modules/readable-stream/lib/internal/streams/destroy.js
439
440
  var require_destroy = __commonJS({
440
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
441
+ "node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
441
442
  var pna = require_process_nextick_args();
442
443
  function destroy(err, cb) {
443
444
  var _this = this;
@@ -510,9 +511,9 @@ var require_node = __commonJS({
510
511
  }
511
512
  });
512
513
 
513
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js
514
+ // node_modules/readable-stream/lib/_stream_writable.js
514
515
  var require_stream_writable = __commonJS({
515
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js"(exports, module) {
516
+ "node_modules/readable-stream/lib/_stream_writable.js"(exports, module) {
516
517
  var pna = require_process_nextick_args();
517
518
  module.exports = Writable;
518
519
  function CorkedRequest(state) {
@@ -949,9 +950,9 @@ var require_stream_writable = __commonJS({
949
950
  }
950
951
  });
951
952
 
952
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js
953
+ // node_modules/readable-stream/lib/_stream_duplex.js
953
954
  var require_stream_duplex = __commonJS({
954
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js"(exports, module) {
955
+ "node_modules/readable-stream/lib/_stream_duplex.js"(exports, module) {
955
956
  var pna = require_process_nextick_args();
956
957
  var objectKeys = Object.keys || function(obj) {
957
958
  var keys2 = [];
@@ -1025,10 +1026,67 @@ var require_stream_duplex = __commonJS({
1025
1026
  }
1026
1027
  });
1027
1028
 
1028
- // node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js
1029
+ // node_modules/string_decoder/node_modules/safe-buffer/index.js
1030
+ var require_safe_buffer2 = __commonJS({
1031
+ "node_modules/string_decoder/node_modules/safe-buffer/index.js"(exports, module) {
1032
+ var buffer = __require("buffer");
1033
+ var Buffer2 = buffer.Buffer;
1034
+ function copyProps(src, dst) {
1035
+ for (var key in src) {
1036
+ dst[key] = src[key];
1037
+ }
1038
+ }
1039
+ if (Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow) {
1040
+ module.exports = buffer;
1041
+ } else {
1042
+ copyProps(buffer, exports);
1043
+ exports.Buffer = SafeBuffer;
1044
+ }
1045
+ function SafeBuffer(arg, encodingOrOffset, length) {
1046
+ return Buffer2(arg, encodingOrOffset, length);
1047
+ }
1048
+ copyProps(Buffer2, SafeBuffer);
1049
+ SafeBuffer.from = function(arg, encodingOrOffset, length) {
1050
+ if (typeof arg === "number") {
1051
+ throw new TypeError("Argument must not be a number");
1052
+ }
1053
+ return Buffer2(arg, encodingOrOffset, length);
1054
+ };
1055
+ SafeBuffer.alloc = function(size, fill, encoding) {
1056
+ if (typeof size !== "number") {
1057
+ throw new TypeError("Argument must be a number");
1058
+ }
1059
+ var buf = Buffer2(size);
1060
+ if (fill !== void 0) {
1061
+ if (typeof encoding === "string") {
1062
+ buf.fill(fill, encoding);
1063
+ } else {
1064
+ buf.fill(fill);
1065
+ }
1066
+ } else {
1067
+ buf.fill(0);
1068
+ }
1069
+ return buf;
1070
+ };
1071
+ SafeBuffer.allocUnsafe = function(size) {
1072
+ if (typeof size !== "number") {
1073
+ throw new TypeError("Argument must be a number");
1074
+ }
1075
+ return Buffer2(size);
1076
+ };
1077
+ SafeBuffer.allocUnsafeSlow = function(size) {
1078
+ if (typeof size !== "number") {
1079
+ throw new TypeError("Argument must be a number");
1080
+ }
1081
+ return buffer.SlowBuffer(size);
1082
+ };
1083
+ }
1084
+ });
1085
+
1086
+ // node_modules/string_decoder/lib/string_decoder.js
1029
1087
  var require_string_decoder = __commonJS({
1030
- "node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js"(exports) {
1031
- var Buffer2 = require_safe_buffer().Buffer;
1088
+ "node_modules/string_decoder/lib/string_decoder.js"(exports) {
1089
+ var Buffer2 = require_safe_buffer2().Buffer;
1032
1090
  var isEncoding = Buffer2.isEncoding || function(encoding) {
1033
1091
  encoding = "" + encoding;
1034
1092
  switch (encoding && encoding.toLowerCase()) {
@@ -1262,9 +1320,9 @@ var require_string_decoder = __commonJS({
1262
1320
  }
1263
1321
  });
1264
1322
 
1265
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js
1323
+ // node_modules/readable-stream/lib/_stream_readable.js
1266
1324
  var require_stream_readable = __commonJS({
1267
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js"(exports, module) {
1325
+ "node_modules/readable-stream/lib/_stream_readable.js"(exports, module) {
1268
1326
  var pna = require_process_nextick_args();
1269
1327
  module.exports = Readable2;
1270
1328
  var isArray = require_isarray();
@@ -1947,9 +2005,9 @@ var require_stream_readable = __commonJS({
1947
2005
  }
1948
2006
  });
1949
2007
 
1950
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js
2008
+ // node_modules/readable-stream/lib/_stream_transform.js
1951
2009
  var require_stream_transform = __commonJS({
1952
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js"(exports, module) {
2010
+ "node_modules/readable-stream/lib/_stream_transform.js"(exports, module) {
1953
2011
  module.exports = Transform;
1954
2012
  var Duplex = require_stream_duplex();
1955
2013
  var util = Object.create(require_util());
@@ -2046,9 +2104,9 @@ var require_stream_transform = __commonJS({
2046
2104
  }
2047
2105
  });
2048
2106
 
2049
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js
2107
+ // node_modules/readable-stream/lib/_stream_passthrough.js
2050
2108
  var require_stream_passthrough = __commonJS({
2051
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js"(exports, module) {
2109
+ "node_modules/readable-stream/lib/_stream_passthrough.js"(exports, module) {
2052
2110
  module.exports = PassThrough;
2053
2111
  var Transform = require_stream_transform();
2054
2112
  var util = Object.create(require_util());
@@ -2064,9 +2122,9 @@ var require_stream_passthrough = __commonJS({
2064
2122
  }
2065
2123
  });
2066
2124
 
2067
- // node_modules/jszip/node_modules/readable-stream/readable.js
2125
+ // node_modules/readable-stream/readable.js
2068
2126
  var require_readable = __commonJS({
2069
- "node_modules/jszip/node_modules/readable-stream/readable.js"(exports, module) {
2127
+ "node_modules/readable-stream/readable.js"(exports, module) {
2070
2128
  var Stream = __require("stream");
2071
2129
  if (process.env.READABLE_STREAM === "disable" && Stream) {
2072
2130
  module.exports = Stream;
@@ -2975,8 +3033,8 @@ var require_utils = __commonJS({
2975
3033
  var result = transform[inputType][outputType](input);
2976
3034
  return result;
2977
3035
  };
2978
- exports.resolve = function(path29) {
2979
- var parts = path29.split("/");
3036
+ exports.resolve = function(path30) {
3037
+ var parts = path30.split("/");
2980
3038
  var result = [];
2981
3039
  for (var index = 0; index < parts.length; index++) {
2982
3040
  var part = parts[index];
@@ -8791,18 +8849,18 @@ var require_object = __commonJS({
8791
8849
  var object = new ZipObject(name, zipObjectContent, o);
8792
8850
  this.files[name] = object;
8793
8851
  };
8794
- var parentFolder = function(path29) {
8795
- if (path29.slice(-1) === "/") {
8796
- path29 = path29.substring(0, path29.length - 1);
8852
+ var parentFolder = function(path30) {
8853
+ if (path30.slice(-1) === "/") {
8854
+ path30 = path30.substring(0, path30.length - 1);
8797
8855
  }
8798
- var lastSlash = path29.lastIndexOf("/");
8799
- return lastSlash > 0 ? path29.substring(0, lastSlash) : "";
8856
+ var lastSlash = path30.lastIndexOf("/");
8857
+ return lastSlash > 0 ? path30.substring(0, lastSlash) : "";
8800
8858
  };
8801
- var forceTrailingSlash = function(path29) {
8802
- if (path29.slice(-1) !== "/") {
8803
- path29 += "/";
8859
+ var forceTrailingSlash = function(path30) {
8860
+ if (path30.slice(-1) !== "/") {
8861
+ path30 += "/";
8804
8862
  }
8805
- return path29;
8863
+ return path30;
8806
8864
  };
8807
8865
  var folderAdd = function(name, createFolders) {
8808
8866
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -9801,12 +9859,12 @@ var init_audit_logger = __esm({
9801
9859
  constructor(workspacePath, debugMode = false) {
9802
9860
  this.workspacePath = workspacePath;
9803
9861
  this.debugMode = debugMode;
9804
- const cascadeDir = path27__default.default.join(workspacePath, ".cascade");
9805
- if (!fs26__default.default.existsSync(cascadeDir)) {
9806
- fs26__default.default.mkdirSync(cascadeDir, { recursive: true });
9862
+ const cascadeDir = path28__default.default.join(workspacePath, ".cascade");
9863
+ if (!fs27__default.default.existsSync(cascadeDir)) {
9864
+ fs27__default.default.mkdirSync(cascadeDir, { recursive: true });
9807
9865
  }
9808
- this.keyPath = path27__default.default.join(cascadeDir, "audit_log.key");
9809
- this.dbPath = path27__default.default.join(cascadeDir, "audit_log.db");
9866
+ this.keyPath = path28__default.default.join(cascadeDir, "audit_log.key");
9867
+ this.dbPath = path28__default.default.join(cascadeDir, "audit_log.db");
9810
9868
  this.initEncryptionKey();
9811
9869
  this.db = new Database2__default.default(this.dbPath);
9812
9870
  this.db.pragma("journal_mode = WAL");
@@ -9836,11 +9894,11 @@ var init_audit_logger = __esm({
9836
9894
  return crypto__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
9837
9895
  }
9838
9896
  initEncryptionKey() {
9839
- if (fs26__default.default.existsSync(this.keyPath)) {
9840
- this.encryptionKey = fs26__default.default.readFileSync(this.keyPath);
9897
+ if (fs27__default.default.existsSync(this.keyPath)) {
9898
+ this.encryptionKey = fs27__default.default.readFileSync(this.keyPath);
9841
9899
  } else {
9842
9900
  this.encryptionKey = crypto__default.default.randomBytes(32);
9843
- fs26__default.default.writeFileSync(this.keyPath, this.encryptionKey);
9901
+ fs27__default.default.writeFileSync(this.keyPath, this.encryptionKey);
9844
9902
  }
9845
9903
  }
9846
9904
  encrypt(text) {
@@ -9918,9 +9976,9 @@ var init_audit_logger = __esm({
9918
9976
  dumpDebugIfNeeded() {
9919
9977
  if (!this.debugMode) return;
9920
9978
  try {
9921
- const dumpPath = path27__default.default.join(os6__default.default.tmpdir(), "cascade_audit_logs_debug.json");
9979
+ const dumpPath = path28__default.default.join(os6__default.default.tmpdir(), "cascade_audit_logs_debug.json");
9922
9980
  const entries = this.getAllLogs();
9923
- fs26__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
9981
+ fs27__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
9924
9982
  } catch (err) {
9925
9983
  console.error("Failed to dump debug audit logs", err);
9926
9984
  }
@@ -11611,7 +11669,7 @@ function reconcilePrice(model, live) {
11611
11669
  }
11612
11670
 
11613
11671
  // src/constants.ts
11614
- var CASCADE_VERSION = "0.76.0";
11672
+ var CASCADE_VERSION = "0.77.0";
11615
11673
  var CASCADE_CONFIG_DIR = ".cascade";
11616
11674
  var CASCADE_MD_FILE = "CASCADE.md";
11617
11675
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -11632,64 +11690,88 @@ var DEFAULT_AUTO_SUMMARIZE_AT = 15e4;
11632
11690
  var DEFAULT_MAX_SESSION_MESSAGES = 1e3;
11633
11691
  var DEFAULT_RETENTION_DAYS = 90;
11634
11692
  var MODELS = {
11635
- // Anthropic
11636
- "claude-opus-4": {
11637
- id: "claude-opus-4-5",
11638
- name: "Claude Opus 4",
11693
+ // Anthropic — active Claude API models (2026-08-27).
11694
+ "claude-fable-5": {
11695
+ id: "claude-fable-5",
11696
+ name: "Claude Fable 5",
11639
11697
  provider: "anthropic",
11640
- contextWindow: 2e5,
11698
+ contextWindow: 1e6,
11641
11699
  isVisionCapable: true,
11642
- inputCostPer1kTokens: 0.015,
11643
- outputCostPer1kTokens: 0.075,
11644
- maxOutputTokens: 32e3,
11700
+ inputCostPer1kTokens: 0.01,
11701
+ outputCostPer1kTokens: 0.05,
11702
+ maxOutputTokens: 128e3,
11645
11703
  supportsStreaming: true,
11646
11704
  isLocal: false
11647
11705
  },
11648
- "claude-sonnet-4": {
11649
- id: "claude-sonnet-4-5",
11650
- name: "Claude Sonnet 4",
11706
+ "claude-opus-5": {
11707
+ id: "claude-opus-5",
11708
+ name: "Claude Opus 5",
11651
11709
  provider: "anthropic",
11652
- contextWindow: 2e5,
11710
+ contextWindow: 1e6,
11653
11711
  isVisionCapable: true,
11654
- inputCostPer1kTokens: 3e-3,
11655
- outputCostPer1kTokens: 0.015,
11656
- maxOutputTokens: 16e3,
11712
+ inputCostPer1kTokens: 5e-3,
11713
+ outputCostPer1kTokens: 0.025,
11714
+ maxOutputTokens: 128e3,
11657
11715
  supportsStreaming: true,
11658
11716
  isLocal: false
11659
11717
  },
11660
- "claude-haiku-3-5": {
11661
- id: "claude-haiku-3-5-20251001",
11662
- name: "Claude Haiku 3.5",
11718
+ "claude-opus-4-8": {
11719
+ id: "claude-opus-4-8",
11720
+ name: "Claude Opus 4.8",
11663
11721
  provider: "anthropic",
11664
- contextWindow: 2e5,
11722
+ contextWindow: 1e6,
11665
11723
  isVisionCapable: true,
11666
- inputCostPer1kTokens: 8e-4,
11667
- outputCostPer1kTokens: 4e-3,
11668
- maxOutputTokens: 8e3,
11724
+ inputCostPer1kTokens: 5e-3,
11725
+ outputCostPer1kTokens: 0.025,
11726
+ maxOutputTokens: 128e3,
11669
11727
  supportsStreaming: true,
11670
11728
  isLocal: false
11671
11729
  },
11672
- "claude-haiku-4-5": {
11673
- id: "claude-haiku-4-5-20251001",
11674
- name: "Claude Haiku 4.5",
11730
+ "claude-opus-4-7": {
11731
+ id: "claude-opus-4-7",
11732
+ name: "Claude Opus 4.7",
11675
11733
  provider: "anthropic",
11676
- contextWindow: 2e5,
11734
+ contextWindow: 1e6,
11677
11735
  isVisionCapable: true,
11678
- inputCostPer1kTokens: 8e-4,
11679
- outputCostPer1kTokens: 4e-3,
11680
- maxOutputTokens: 8e3,
11736
+ inputCostPer1kTokens: 5e-3,
11737
+ outputCostPer1kTokens: 0.025,
11738
+ maxOutputTokens: 128e3,
11681
11739
  supportsStreaming: true,
11682
11740
  isLocal: false
11683
11741
  },
11684
- "claude-opus-4-8": {
11685
- id: "claude-opus-4-8",
11686
- name: "Claude Opus 4.8",
11742
+ "claude-opus-4-6": {
11743
+ id: "claude-opus-4-6",
11744
+ name: "Claude Opus 4.6",
11745
+ provider: "anthropic",
11746
+ contextWindow: 1e6,
11747
+ isVisionCapable: true,
11748
+ inputCostPer1kTokens: 5e-3,
11749
+ outputCostPer1kTokens: 0.025,
11750
+ maxOutputTokens: 128e3,
11751
+ supportsStreaming: true,
11752
+ isLocal: false
11753
+ },
11754
+ "claude-opus-4-5": {
11755
+ id: "claude-opus-4-5-20251101",
11756
+ name: "Claude Opus 4.5",
11687
11757
  provider: "anthropic",
11688
11758
  contextWindow: 2e5,
11689
11759
  isVisionCapable: true,
11690
- inputCostPer1kTokens: 0.015,
11691
- outputCostPer1kTokens: 0.075,
11692
- maxOutputTokens: 32e3,
11760
+ inputCostPer1kTokens: 5e-3,
11761
+ outputCostPer1kTokens: 0.025,
11762
+ maxOutputTokens: 64e3,
11763
+ supportsStreaming: true,
11764
+ isLocal: false
11765
+ },
11766
+ "claude-sonnet-5": {
11767
+ id: "claude-sonnet-5",
11768
+ name: "Claude Sonnet 5",
11769
+ provider: "anthropic",
11770
+ contextWindow: 1e6,
11771
+ isVisionCapable: true,
11772
+ inputCostPer1kTokens: 2e-3,
11773
+ outputCostPer1kTokens: 0.01,
11774
+ maxOutputTokens: 128e3,
11693
11775
  supportsStreaming: true,
11694
11776
  isLocal: false
11695
11777
  },
@@ -11697,76 +11779,135 @@ var MODELS = {
11697
11779
  id: "claude-sonnet-4-6",
11698
11780
  name: "Claude Sonnet 4.6",
11699
11781
  provider: "anthropic",
11782
+ contextWindow: 1e6,
11783
+ isVisionCapable: true,
11784
+ inputCostPer1kTokens: 3e-3,
11785
+ outputCostPer1kTokens: 0.015,
11786
+ maxOutputTokens: 64e3,
11787
+ supportsStreaming: true,
11788
+ isLocal: false
11789
+ },
11790
+ "claude-sonnet-4-5": {
11791
+ id: "claude-sonnet-4-5-20250929",
11792
+ name: "Claude Sonnet 4.5",
11793
+ provider: "anthropic",
11700
11794
  contextWindow: 2e5,
11701
11795
  isVisionCapable: true,
11702
11796
  inputCostPer1kTokens: 3e-3,
11703
11797
  outputCostPer1kTokens: 0.015,
11704
- maxOutputTokens: 16e3,
11798
+ maxOutputTokens: 64e3,
11705
11799
  supportsStreaming: true,
11706
11800
  isLocal: false
11707
11801
  },
11708
- // OpenAI
11709
- "gpt-4o": {
11710
- id: "gpt-4o",
11711
- name: "GPT-4o",
11802
+ "claude-haiku-4-5": {
11803
+ id: "claude-haiku-4-5-20251001",
11804
+ name: "Claude Haiku 4.5",
11805
+ provider: "anthropic",
11806
+ contextWindow: 2e5,
11807
+ isVisionCapable: true,
11808
+ inputCostPer1kTokens: 1e-3,
11809
+ outputCostPer1kTokens: 5e-3,
11810
+ maxOutputTokens: 64e3,
11811
+ supportsStreaming: true,
11812
+ isLocal: false
11813
+ },
11814
+ // OpenAI — active chat-capable models used by Cascade's Chat Completions path.
11815
+ "gpt-5.6-sol": {
11816
+ id: "gpt-5.6-sol",
11817
+ name: "GPT-5.6 Sol",
11712
11818
  provider: "openai",
11713
- contextWindow: 128e3,
11819
+ contextWindow: 105e4,
11820
+ isVisionCapable: true,
11821
+ inputCostPer1kTokens: 4e-3,
11822
+ outputCostPer1kTokens: 0.02,
11823
+ maxOutputTokens: 128e3,
11824
+ supportsStreaming: true,
11825
+ isLocal: false
11826
+ },
11827
+ "gpt-5.6-terra": {
11828
+ id: "gpt-5.6-terra",
11829
+ name: "GPT-5.6 Terra",
11830
+ provider: "openai",
11831
+ contextWindow: 105e4,
11832
+ isVisionCapable: true,
11833
+ inputCostPer1kTokens: 2e-3,
11834
+ outputCostPer1kTokens: 0.012,
11835
+ maxOutputTokens: 128e3,
11836
+ supportsStreaming: true,
11837
+ isLocal: false
11838
+ },
11839
+ "gpt-5.6-luna": {
11840
+ id: "gpt-5.6-luna",
11841
+ name: "GPT-5.6 Luna",
11842
+ provider: "openai",
11843
+ contextWindow: 105e4,
11844
+ isVisionCapable: true,
11845
+ inputCostPer1kTokens: 2e-4,
11846
+ outputCostPer1kTokens: 12e-4,
11847
+ maxOutputTokens: 128e3,
11848
+ supportsStreaming: true,
11849
+ isLocal: false
11850
+ },
11851
+ "gpt-5.5": {
11852
+ id: "gpt-5.5",
11853
+ name: "GPT-5.5",
11854
+ provider: "openai",
11855
+ contextWindow: 105e4,
11714
11856
  isVisionCapable: true,
11715
11857
  inputCostPer1kTokens: 5e-3,
11716
- outputCostPer1kTokens: 0.015,
11717
- maxOutputTokens: 16e3,
11858
+ outputCostPer1kTokens: 0.03,
11859
+ maxOutputTokens: 128e3,
11718
11860
  supportsStreaming: true,
11719
11861
  isLocal: false
11720
11862
  },
11721
- "gpt-4o-mini": {
11722
- id: "gpt-4o-mini",
11723
- name: "GPT-4o Mini",
11863
+ "gpt-5.4": {
11864
+ id: "gpt-5.4",
11865
+ name: "GPT-5.4",
11724
11866
  provider: "openai",
11725
- contextWindow: 128e3,
11867
+ contextWindow: 105e4,
11726
11868
  isVisionCapable: true,
11727
- inputCostPer1kTokens: 15e-5,
11728
- outputCostPer1kTokens: 6e-4,
11729
- maxOutputTokens: 16e3,
11869
+ inputCostPer1kTokens: 25e-4,
11870
+ outputCostPer1kTokens: 0.015,
11871
+ maxOutputTokens: 128e3,
11730
11872
  supportsStreaming: true,
11731
11873
  isLocal: false
11732
11874
  },
11733
- "gpt-4.1": {
11734
- id: "gpt-4.1",
11735
- name: "GPT-4.1",
11875
+ "gpt-5.4-mini": {
11876
+ id: "gpt-5.4-mini",
11877
+ name: "GPT-5.4 Mini",
11736
11878
  provider: "openai",
11737
- contextWindow: 1047576,
11879
+ contextWindow: 4e5,
11738
11880
  isVisionCapable: true,
11739
- inputCostPer1kTokens: 2e-3,
11740
- outputCostPer1kTokens: 8e-3,
11741
- maxOutputTokens: 32768,
11881
+ inputCostPer1kTokens: 75e-5,
11882
+ outputCostPer1kTokens: 45e-4,
11883
+ maxOutputTokens: 128e3,
11742
11884
  supportsStreaming: true,
11743
11885
  isLocal: false
11744
11886
  },
11745
- "gpt-4.1-mini": {
11746
- id: "gpt-4.1-mini",
11747
- name: "GPT-4.1 Mini",
11887
+ "gpt-5.4-nano": {
11888
+ id: "gpt-5.4-nano",
11889
+ name: "GPT-5.4 Nano",
11748
11890
  provider: "openai",
11749
- contextWindow: 1047576,
11891
+ contextWindow: 4e5,
11750
11892
  isVisionCapable: true,
11751
- inputCostPer1kTokens: 4e-4,
11752
- outputCostPer1kTokens: 16e-4,
11753
- maxOutputTokens: 32768,
11893
+ inputCostPer1kTokens: 2e-4,
11894
+ outputCostPer1kTokens: 125e-5,
11895
+ maxOutputTokens: 128e3,
11754
11896
  supportsStreaming: true,
11755
11897
  isLocal: false
11756
11898
  },
11757
- "gpt-4.1-nano": {
11758
- id: "gpt-4.1-nano",
11759
- name: "GPT-4.1 Nano",
11899
+ "gpt-5.2": {
11900
+ id: "gpt-5.2",
11901
+ name: "GPT-5.2",
11760
11902
  provider: "openai",
11761
- contextWindow: 1047576,
11903
+ contextWindow: 4e5,
11762
11904
  isVisionCapable: true,
11763
- inputCostPer1kTokens: 1e-4,
11764
- outputCostPer1kTokens: 4e-4,
11765
- maxOutputTokens: 32768,
11905
+ inputCostPer1kTokens: 175e-5,
11906
+ outputCostPer1kTokens: 0.014,
11907
+ maxOutputTokens: 128e3,
11766
11908
  supportsStreaming: true,
11767
11909
  isLocal: false
11768
11910
  },
11769
- // GPT-5 family (offline baseline pricing; the live fetch keeps it current).
11770
11911
  "gpt-5": {
11771
11912
  id: "gpt-5",
11772
11913
  name: "GPT-5",
@@ -11803,79 +11944,149 @@ var MODELS = {
11803
11944
  supportsStreaming: true,
11804
11945
  isLocal: false
11805
11946
  },
11806
- // GPT-5 point releases — genuinely distinct capability, so they route as their
11807
- // own families (not folded into gpt-5). Offline baseline pricing; live fetch
11808
- // keeps it current. gpt-5.5 "Spud" (Apr 2026) is the current SWE-bench leader.
11809
- "gpt-5.5": {
11810
- id: "gpt-5.5",
11811
- name: "GPT-5.5",
11947
+ "gpt-4.1": {
11948
+ id: "gpt-4.1",
11949
+ name: "GPT-4.1",
11812
11950
  provider: "openai",
11813
- contextWindow: 4e5,
11951
+ contextWindow: 1047576,
11814
11952
  isVisionCapable: true,
11815
- inputCostPer1kTokens: 25e-4,
11816
- outputCostPer1kTokens: 0.02,
11817
- maxOutputTokens: 128e3,
11953
+ inputCostPer1kTokens: 2e-3,
11954
+ outputCostPer1kTokens: 8e-3,
11955
+ maxOutputTokens: 32768,
11818
11956
  supportsStreaming: true,
11819
11957
  isLocal: false
11820
11958
  },
11821
- "gpt-5.4": {
11822
- id: "gpt-5.4",
11823
- name: "GPT-5.4",
11959
+ "gpt-4.1-mini": {
11960
+ id: "gpt-4.1-mini",
11961
+ name: "GPT-4.1 Mini",
11824
11962
  provider: "openai",
11825
- contextWindow: 4e5,
11963
+ contextWindow: 1047576,
11826
11964
  isVisionCapable: true,
11827
- inputCostPer1kTokens: 125e-5,
11965
+ inputCostPer1kTokens: 4e-4,
11966
+ outputCostPer1kTokens: 16e-4,
11967
+ maxOutputTokens: 32768,
11968
+ supportsStreaming: true,
11969
+ isLocal: false
11970
+ },
11971
+ "gpt-4.1-nano": {
11972
+ id: "gpt-4.1-nano",
11973
+ name: "GPT-4.1 Nano",
11974
+ provider: "openai",
11975
+ contextWindow: 1047576,
11976
+ isVisionCapable: true,
11977
+ inputCostPer1kTokens: 1e-4,
11978
+ outputCostPer1kTokens: 4e-4,
11979
+ maxOutputTokens: 32768,
11980
+ supportsStreaming: true,
11981
+ isLocal: false
11982
+ },
11983
+ "gpt-4o": {
11984
+ id: "gpt-4o",
11985
+ name: "GPT-4o",
11986
+ provider: "openai",
11987
+ contextWindow: 128e3,
11988
+ isVisionCapable: true,
11989
+ inputCostPer1kTokens: 25e-4,
11828
11990
  outputCostPer1kTokens: 0.01,
11829
- maxOutputTokens: 128e3,
11991
+ maxOutputTokens: 16e3,
11830
11992
  supportsStreaming: true,
11831
11993
  isLocal: false
11832
11994
  },
11833
- "gpt-5.4-mini": {
11834
- id: "gpt-5.4-mini",
11835
- name: "GPT-5.4 Mini",
11995
+ "gpt-4o-mini": {
11996
+ id: "gpt-4o-mini",
11997
+ name: "GPT-4o Mini",
11836
11998
  provider: "openai",
11837
- contextWindow: 4e5,
11999
+ contextWindow: 128e3,
11838
12000
  isVisionCapable: true,
11839
- inputCostPer1kTokens: 25e-5,
11840
- outputCostPer1kTokens: 2e-3,
11841
- maxOutputTokens: 128e3,
12001
+ inputCostPer1kTokens: 15e-5,
12002
+ outputCostPer1kTokens: 6e-4,
12003
+ maxOutputTokens: 16e3,
11842
12004
  supportsStreaming: true,
11843
12005
  isLocal: false
11844
12006
  },
11845
- // Google
11846
- "gemini-2.0-flash": {
11847
- id: "gemini-2.0-flash",
11848
- name: "Gemini 2.0 Flash",
12007
+ // Google Gemini — active GA + preview chat models. Gemini 2.0/1.5 are no
12008
+ // longer advertised because the Gemini API shut those generations down.
12009
+ "gemini-3.7-flash": {
12010
+ id: "gemini-3.7-flash",
12011
+ name: "Gemini 3.7 Flash",
11849
12012
  provider: "gemini",
11850
- contextWindow: 1e6,
12013
+ contextWindow: 1048576,
11851
12014
  isVisionCapable: true,
11852
- inputCostPer1kTokens: 1e-4,
11853
- outputCostPer1kTokens: 4e-4,
11854
- maxOutputTokens: 8e3,
12015
+ inputCostPer1kTokens: 75e-5,
12016
+ outputCostPer1kTokens: 375e-5,
12017
+ maxOutputTokens: 65536,
11855
12018
  supportsStreaming: true,
11856
12019
  isLocal: false
11857
12020
  },
11858
- "gemini-1.5-pro": {
11859
- id: "gemini-1.5-pro",
11860
- name: "Gemini 1.5 Pro",
12021
+ "gemini-3.6-flash": {
12022
+ id: "gemini-3.6-flash",
12023
+ name: "Gemini 3.6 Flash",
11861
12024
  provider: "gemini",
11862
- contextWindow: 1e6,
12025
+ contextWindow: 1048576,
11863
12026
  isVisionCapable: true,
11864
- inputCostPer1kTokens: 125e-5,
11865
- outputCostPer1kTokens: 5e-3,
11866
- maxOutputTokens: 8e3,
12027
+ inputCostPer1kTokens: 75e-5,
12028
+ outputCostPer1kTokens: 375e-5,
12029
+ maxOutputTokens: 65536,
11867
12030
  supportsStreaming: true,
11868
12031
  isLocal: false
11869
12032
  },
11870
- "gemini-2.0-flash-lite": {
11871
- id: "gemini-2.0-flash-lite",
11872
- name: "Gemini 2.0 Flash-Lite",
12033
+ "gemini-3.5-flash": {
12034
+ id: "gemini-3.5-flash",
12035
+ name: "Gemini 3.5 Flash",
11873
12036
  provider: "gemini",
11874
- contextWindow: 1e6,
12037
+ contextWindow: 1048576,
11875
12038
  isVisionCapable: true,
11876
- inputCostPer1kTokens: 5e-5,
11877
- outputCostPer1kTokens: 2e-4,
11878
- maxOutputTokens: 8e3,
12039
+ inputCostPer1kTokens: 15e-4,
12040
+ outputCostPer1kTokens: 9e-3,
12041
+ maxOutputTokens: 65536,
12042
+ supportsStreaming: true,
12043
+ isLocal: false
12044
+ },
12045
+ "gemini-3.5-flash-lite": {
12046
+ id: "gemini-3.5-flash-lite",
12047
+ name: "Gemini 3.5 Flash-Lite",
12048
+ provider: "gemini",
12049
+ contextWindow: 1048576,
12050
+ isVisionCapable: true,
12051
+ inputCostPer1kTokens: 3e-4,
12052
+ outputCostPer1kTokens: 25e-4,
12053
+ maxOutputTokens: 65536,
12054
+ supportsStreaming: true,
12055
+ isLocal: false
12056
+ },
12057
+ "gemini-3.1-flash-lite": {
12058
+ id: "gemini-3.1-flash-lite",
12059
+ name: "Gemini 3.1 Flash-Lite",
12060
+ provider: "gemini",
12061
+ contextWindow: 1048576,
12062
+ isVisionCapable: true,
12063
+ inputCostPer1kTokens: 25e-5,
12064
+ outputCostPer1kTokens: 15e-4,
12065
+ maxOutputTokens: 65536,
12066
+ supportsStreaming: true,
12067
+ isLocal: false
12068
+ },
12069
+ "gemini-3.1-pro-preview": {
12070
+ id: "gemini-3.1-pro-preview",
12071
+ name: "Gemini 3.1 Pro Preview",
12072
+ provider: "gemini",
12073
+ contextWindow: 1048576,
12074
+ isVisionCapable: true,
12075
+ inputCostPer1kTokens: 2e-3,
12076
+ outputCostPer1kTokens: 0.012,
12077
+ maxOutputTokens: 65536,
12078
+ supportsStreaming: true,
12079
+ isLocal: false
12080
+ },
12081
+ "gemini-3-flash-preview": {
12082
+ id: "gemini-3-flash-preview",
12083
+ name: "Gemini 3 Flash Preview",
12084
+ provider: "gemini",
12085
+ contextWindow: 1048576,
12086
+ isVisionCapable: true,
12087
+ inputCostPer1kTokens: 5e-4,
12088
+ outputCostPer1kTokens: 3e-3,
12089
+ maxOutputTokens: 65536,
11879
12090
  supportsStreaming: true,
11880
12091
  isLocal: false
11881
12092
  },
@@ -11883,7 +12094,7 @@ var MODELS = {
11883
12094
  id: "gemini-2.5-pro",
11884
12095
  name: "Gemini 2.5 Pro",
11885
12096
  provider: "gemini",
11886
- contextWindow: 1e6,
12097
+ contextWindow: 1048576,
11887
12098
  isVisionCapable: true,
11888
12099
  inputCostPer1kTokens: 125e-5,
11889
12100
  outputCostPer1kTokens: 0.01,
@@ -11895,10 +12106,22 @@ var MODELS = {
11895
12106
  id: "gemini-2.5-flash",
11896
12107
  name: "Gemini 2.5 Flash",
11897
12108
  provider: "gemini",
11898
- contextWindow: 1e6,
12109
+ contextWindow: 1048576,
11899
12110
  isVisionCapable: true,
11900
- inputCostPer1kTokens: 15e-5,
11901
- outputCostPer1kTokens: 6e-4,
12111
+ inputCostPer1kTokens: 3e-4,
12112
+ outputCostPer1kTokens: 25e-4,
12113
+ maxOutputTokens: 65536,
12114
+ supportsStreaming: true,
12115
+ isLocal: false
12116
+ },
12117
+ "gemini-2.5-flash-lite": {
12118
+ id: "gemini-2.5-flash-lite",
12119
+ name: "Gemini 2.5 Flash-Lite",
12120
+ provider: "gemini",
12121
+ contextWindow: 1048576,
12122
+ isVisionCapable: true,
12123
+ inputCostPer1kTokens: 1e-4,
12124
+ outputCostPer1kTokens: 4e-4,
11902
12125
  maxOutputTokens: 65536,
11903
12126
  supportsStreaming: true,
11904
12127
  isLocal: false
@@ -11968,43 +12191,57 @@ for (const _m of Object.values(MODELS)) {
11968
12191
  _m.pricingUnknown = false;
11969
12192
  }
11970
12193
  var T1_MODEL_PRIORITY = [
12194
+ "gpt-5.6-sol",
12195
+ "claude-fable-5",
12196
+ "claude-opus-5",
11971
12197
  "gpt-5.5",
11972
12198
  "gpt-5.4",
12199
+ "gemini-3.1-pro-preview",
12200
+ "claude-opus-4-8",
12201
+ "claude-sonnet-5",
12202
+ "gemini-3.7-flash",
12203
+ "gpt-5.2",
11973
12204
  "gpt-5",
11974
- "claude-opus-4",
11975
- "claude-sonnet-4",
11976
12205
  "gemini-2.5-pro",
11977
12206
  "gpt-4.1",
11978
- "gpt-4o",
11979
- "gemini-1.5-pro"
12207
+ "gpt-4o"
11980
12208
  ];
11981
12209
  var T2_MODEL_PRIORITY = [
12210
+ "gpt-5.6-terra",
12211
+ "claude-sonnet-5",
12212
+ "gemini-3.7-flash",
12213
+ "gemini-3.6-flash",
11982
12214
  "gpt-5.4-mini",
11983
12215
  "gpt-5-mini",
11984
- "claude-sonnet-4",
12216
+ "claude-sonnet-4-6",
11985
12217
  "claude-haiku-4-5",
11986
- "claude-haiku-3-5",
12218
+ "gemini-3.5-flash",
11987
12219
  "gemini-2.5-flash",
11988
12220
  "gpt-4.1-mini",
11989
12221
  "gpt-4o-mini",
11990
- "gemini-2.0-flash",
11991
12222
  "llama3:70b"
11992
12223
  ];
11993
12224
  var T3_MODEL_PRIORITY = [
12225
+ "gpt-5.6-luna",
12226
+ "gpt-5.4-nano",
12227
+ "gemini-3.5-flash-lite",
12228
+ "gemini-3.1-flash-lite",
12229
+ "claude-haiku-4-5",
11994
12230
  "gpt-5-nano",
12231
+ "gemini-2.5-flash-lite",
11995
12232
  "llama3.2:3b",
11996
12233
  "mistral:7b",
11997
- "claude-haiku-4-5",
11998
- "claude-haiku-3-5",
11999
12234
  "gpt-4.1-nano",
12000
12235
  "gpt-4o-mini",
12001
- "gemini-2.5-flash",
12002
- "gemini-2.0-flash"
12236
+ "gemini-2.5-flash"
12003
12237
  ];
12004
12238
  var VISION_MODEL_PRIORITY = [
12005
- "claude-sonnet-4",
12239
+ "gpt-5.6-sol",
12240
+ "claude-fable-5",
12241
+ "claude-sonnet-5",
12242
+ "gemini-3.7-flash",
12006
12243
  "gpt-4o",
12007
- "gemini-2.0-flash",
12244
+ "gemini-2.5-flash",
12008
12245
  "llava"
12009
12246
  ];
12010
12247
  var COMPLEXITY_T2_COUNT = {
@@ -12185,7 +12422,7 @@ function isChatModel(id, methods) {
12185
12422
  return !NON_CHAT.test(id);
12186
12423
  }
12187
12424
  try {
12188
- dns__default.default.setDefaultResultOrder("ipv4first");
12425
+ dnsCallback__default.default.setDefaultResultOrder("ipv4first");
12189
12426
  } catch {
12190
12427
  }
12191
12428
  function preferIpv4Host(url) {
@@ -12201,8 +12438,8 @@ function normalizeEndpoint(url) {
12201
12438
  if (!raw) return "";
12202
12439
  try {
12203
12440
  const u = new URL(raw);
12204
- const path29 = stripTrailingSlashes(`${u.pathname}${u.search}`);
12205
- return `${u.protocol}//${u.host.toLowerCase()}${path29}`;
12441
+ const path30 = stripTrailingSlashes(`${u.pathname}${u.search}`);
12442
+ return `${u.protocol}//${u.host.toLowerCase()}${path30}`;
12206
12443
  } catch {
12207
12444
  return raw.toLowerCase();
12208
12445
  }
@@ -12869,52 +13106,127 @@ var OpenAIProvider = class extends BaseProvider {
12869
13106
  }
12870
13107
  };
12871
13108
 
13109
+ // src/providers/cloud-model-catalog.json
13110
+ var cloud_model_catalog_default = {
13111
+ models: [
13112
+ { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13113
+ { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13114
+ { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13115
+ { id: "gpt-5.5", name: "GPT-5.5", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13116
+ { id: "gpt-5.4", name: "GPT-5.4", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13117
+ { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13118
+ { id: "gpt-5.4-nano", name: "GPT-5.4 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13119
+ { id: "gpt-5", name: "GPT-5", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13120
+ { id: "gpt-5-mini", name: "GPT-5 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13121
+ { id: "gpt-5-nano", name: "GPT-5 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13122
+ { id: "gpt-4.1", name: "GPT-4.1", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
13123
+ { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
13124
+ { id: "gpt-4.1-nano", name: "GPT-4.1 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
13125
+ { id: "gpt-4o", name: "GPT-4o", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 128e3, maxOutputTokens: 16384, vision: true, toolUse: true },
13126
+ { id: "gpt-4o-mini", name: "GPT-4o Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 128e3, maxOutputTokens: 16384, vision: true, toolUse: true },
13127
+ { id: "claude-fable-5", name: "Claude Fable 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13128
+ { id: "claude-opus-5", name: "Claude Opus 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13129
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13130
+ { id: "claude-opus-4-7", name: "Claude Opus 4.7", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13131
+ { id: "claude-opus-4-6", name: "Claude Opus 4.6", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, vision: true, toolUse: true },
13132
+ { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, vision: true, toolUse: true },
13133
+ { id: "claude-sonnet-5", name: "Claude Sonnet 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13134
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13135
+ { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, vision: true, toolUse: true },
13136
+ { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, maxOutputTokens: 64e3, vision: true, toolUse: true },
13137
+ { id: "gemini-3.7-flash", name: "Gemini 3.7 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13138
+ { id: "gemini-3.6-flash", name: "Gemini 3.6 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13139
+ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13140
+ { id: "gemini-3.5-flash-lite", name: "Gemini 3.5 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13141
+ { id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13142
+ { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", providers: ["gemini"], lifecycle: "preview", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13143
+ { id: "gemini-3-flash-preview", name: "Gemini 3 Flash", providers: ["gemini"], lifecycle: "preview", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13144
+ { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13145
+ { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13146
+ { id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true }
13147
+ ]
13148
+ };
13149
+
12872
13150
  // src/providers/azure.ts
12873
13151
  var DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
12874
- var AZURE_BASE_MODEL_RULES = [
12875
- [/gpt-?5\.5/, "gpt-5.5"],
12876
- [/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
12877
- [/gpt-?5\.4/, "gpt-5.4"],
12878
- [/gpt-?5.*nano/, "gpt-5-nano"],
12879
- [/gpt-?5.*mini/, "gpt-5-mini"],
12880
- [/gpt-?5/, "gpt-5"],
12881
- [/gpt-?4\.1-nano/, "gpt-4.1-nano"],
12882
- [/gpt-?4\.1-mini/, "gpt-4.1-mini"],
12883
- [/gpt-?4\.1/, "gpt-4.1"],
12884
- [/gpt-?4o-mini/, "gpt-4o-mini"],
12885
- [/gpt-?4o/, "gpt-4o"]
12886
- ];
12887
- var AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
13152
+ var CLOUD_MODELS = cloud_model_catalog_default.models;
13153
+ var AZURE_CATALOG = CLOUD_MODELS.filter((m) => m.providers.includes("azure")).slice().sort((a, b) => b.id.length - a.id.length || a.id.localeCompare(b.id));
13154
+ var AZURE_BASE_MODELS = AZURE_CATALOG.map((m) => m.id);
13155
+ function compactModelId(id) {
13156
+ return id.toLowerCase().replace(/[^a-z0-9.]/g, "");
13157
+ }
13158
+ function catalogEntry(id) {
13159
+ const n = id.toLowerCase();
13160
+ return CLOUD_MODELS.find((m) => m.id.toLowerCase() === n);
13161
+ }
12888
13162
  function inferAzureBaseModel(deploymentName) {
12889
13163
  const n = deploymentName.toLowerCase();
12890
- for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
13164
+ const compact = compactModelId(n);
13165
+ const hasBareGpt56Alias = compact.includes("gpt5.6");
13166
+ for (const model of AZURE_CATALOG) {
13167
+ if (hasBareGpt56Alias && model.id === "gpt-5") continue;
13168
+ if (n.includes(model.id.toLowerCase()) || compact.includes(compactModelId(model.id))) {
13169
+ return model.id;
13170
+ }
13171
+ }
13172
+ if (hasBareGpt56Alias) return "gpt-5.6-sol";
13173
+ const gpt5At = compact.indexOf("gpt5");
13174
+ if (gpt5At !== -1) {
13175
+ const suffix = compact.slice(gpt5At + "gpt5".length);
13176
+ if (suffix.includes("nano")) return "gpt-5-nano";
13177
+ if (suffix.includes("mini")) return "gpt-5-mini";
13178
+ return "gpt-5";
13179
+ }
13180
+ if (compact.includes("gpt4.1nano")) return "gpt-4.1-nano";
13181
+ if (compact.includes("gpt4.1mini")) return "gpt-4.1-mini";
13182
+ if (compact.includes("gpt4.1")) return "gpt-4.1";
13183
+ if (compact.includes("gpt4omini")) return "gpt-4o-mini";
13184
+ if (compact.includes("gpt4o")) return "gpt-4o";
12891
13185
  return null;
12892
13186
  }
13187
+ function catalogModelInfo(baseModelId) {
13188
+ const entry = catalogEntry(baseModelId);
13189
+ if (!entry?.contextWindow) return void 0;
13190
+ return {
13191
+ id: entry.id,
13192
+ name: entry.name,
13193
+ provider: "openai",
13194
+ contextWindow: entry.contextWindow,
13195
+ isVisionCapable: entry.vision ?? false,
13196
+ inputCostPer1kTokens: 0,
13197
+ outputCostPer1kTokens: 0,
13198
+ maxOutputTokens: entry.maxOutputTokens ?? 16e3,
13199
+ supportsStreaming: true,
13200
+ isLocal: false,
13201
+ supportsToolUse: entry.toolUse ?? true,
13202
+ pricingUnknown: true
13203
+ };
13204
+ }
12893
13205
  function azureModelForDeployment(cfg) {
12894
13206
  if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
12895
13207
  const id = cfg.deploymentName.trim();
12896
13208
  const name = cfg.label?.trim() || id;
12897
13209
  const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
12898
- const base = baseModelId ? MODELS[baseModelId] : void 0;
12899
- if (base) {
13210
+ const base = baseModelId ? MODELS[baseModelId] ?? catalogModelInfo(baseModelId) : void 0;
13211
+ if (base && baseModelId) {
12900
13212
  const azurePrice = resolvePricing(
12901
13213
  { id, provider: "azure", isLocal: false, baseModelId },
12902
13214
  { region: cfg.region }
12903
13215
  );
13216
+ const exactAzurePrice = !azurePrice.unknown && !azurePrice.estimatedFromProvider;
13217
+ const nativeToolsOnThisTransport = /^gpt-5\.6-(?:sol|terra|luna)$/i.test(baseModelId) ? false : base.supportsToolUse ?? true;
12904
13218
  return {
12905
13219
  ...base,
12906
13220
  id,
12907
- // callable deployment name
12908
13221
  name,
12909
13222
  provider: "azure",
12910
13223
  baseModelId,
12911
- // real identity for benchmark + live pricing
12912
- supportsToolUse: base.supportsToolUse ?? true,
12913
- ...azurePrice.unknown ? {} : {
13224
+ supportsToolUse: nativeToolsOnThisTransport,
13225
+ ...!azurePrice.unknown ? {
12914
13226
  inputCostPer1kTokens: azurePrice.input,
12915
13227
  outputCostPer1kTokens: azurePrice.output,
12916
- pricingUnknown: false
12917
- }
13228
+ pricingUnknown: !exactAzurePrice
13229
+ } : { pricingUnknown: true }
12918
13230
  };
12919
13231
  }
12920
13232
  return {
@@ -12929,18 +13241,23 @@ function azureModelForDeployment(cfg) {
12929
13241
  maxOutputTokens: 16e3,
12930
13242
  supportsStreaming: true,
12931
13243
  isLocal: false,
12932
- supportsToolUse: true
13244
+ supportsToolUse: true,
13245
+ pricingUnknown: true
12933
13246
  };
12934
13247
  }
13248
+ function stripTrailingSlashes3(value) {
13249
+ let end = value.length;
13250
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
13251
+ return value.slice(0, end);
13252
+ }
12935
13253
  var AzureOpenAIProvider = class extends OpenAIProvider {
12936
13254
  constructor(config, model) {
12937
13255
  const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
12938
- const endpoint = rawUrl.replace(/\/+$/, "");
13256
+ const endpoint = stripTrailingSlashes3(rawUrl);
12939
13257
  super(
12940
13258
  {
12941
13259
  ...config,
12942
13260
  baseUrl: endpoint
12943
- // Kept for superclass compatibility if it reads it
12944
13261
  },
12945
13262
  model
12946
13263
  );
@@ -14632,13 +14949,13 @@ var DeadModelStore = class {
14632
14949
  function fileDeadModelPersistence(filePath) {
14633
14950
  return {
14634
14951
  load() {
14635
- if (!fs26.existsSync(filePath)) return [];
14636
- const raw = JSON.parse(fs26.readFileSync(filePath, "utf-8"));
14952
+ if (!fs27.existsSync(filePath)) return [];
14953
+ const raw = JSON.parse(fs27.readFileSync(filePath, "utf-8"));
14637
14954
  return Array.isArray(raw) ? raw : [];
14638
14955
  },
14639
14956
  save(records) {
14640
- fs26.mkdirSync(path27.dirname(filePath), { recursive: true });
14641
- fs26.writeFileSync(filePath, JSON.stringify(records, null, 2));
14957
+ fs27.mkdirSync(path28.dirname(filePath), { recursive: true });
14958
+ fs27.writeFileSync(filePath, JSON.stringify(records, null, 2));
14642
14959
  }
14643
14960
  };
14644
14961
  }
@@ -14683,7 +15000,7 @@ var Keystore = class {
14683
15000
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
14684
15001
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
14685
15002
  this.backend = "keytar";
14686
- if (password && fs26__default.default.existsSync(this.storePath)) {
15003
+ if (password && fs27__default.default.existsSync(this.storePath)) {
14687
15004
  try {
14688
15005
  const fileEntries = this.decryptFile(password);
14689
15006
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -14702,7 +15019,7 @@ var Keystore = class {
14702
15019
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
14703
15020
  );
14704
15021
  }
14705
- if (!fs26__default.default.existsSync(this.storePath)) {
15022
+ if (!fs27__default.default.existsSync(this.storePath)) {
14706
15023
  const salt = crypto__default.default.randomBytes(SALT_LEN);
14707
15024
  this.masterKey = this.deriveKey(password, salt);
14708
15025
  this.writeWithSalt({}, salt);
@@ -14716,7 +15033,7 @@ var Keystore = class {
14716
15033
  }
14717
15034
  /** Synchronous legacy unlock kept for AES-only environments. */
14718
15035
  unlockSync(password) {
14719
- if (!fs26__default.default.existsSync(this.storePath)) {
15036
+ if (!fs27__default.default.existsSync(this.storePath)) {
14720
15037
  const salt = crypto__default.default.randomBytes(SALT_LEN);
14721
15038
  this.masterKey = this.deriveKey(password, salt);
14722
15039
  this.writeWithSalt({}, salt);
@@ -14774,7 +15091,7 @@ var Keystore = class {
14774
15091
  }
14775
15092
  }
14776
15093
  decryptFile(password, knownSalt) {
14777
- if (!fs26__default.default.existsSync(this.storePath)) return {};
15094
+ if (!fs27__default.default.existsSync(this.storePath)) return {};
14778
15095
  try {
14779
15096
  const { salt, ciphertext, iv, tag } = this.readRaw();
14780
15097
  const useSalt = knownSalt ?? salt;
@@ -14796,8 +15113,8 @@ var Keystore = class {
14796
15113
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14797
15114
  const tag = cipher.getAuthTag();
14798
15115
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
14799
- fs26__default.default.mkdirSync(path27__default.default.dirname(this.storePath), { recursive: true });
14800
- fs26__default.default.writeFileSync(this.storePath, out, { mode: 384 });
15116
+ fs27__default.default.mkdirSync(path28__default.default.dirname(this.storePath), { recursive: true });
15117
+ fs27__default.default.writeFileSync(this.storePath, out, { mode: 384 });
14801
15118
  }
14802
15119
  writeWithSalt(data, salt) {
14803
15120
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -14807,11 +15124,11 @@ var Keystore = class {
14807
15124
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14808
15125
  const tag = cipher.getAuthTag();
14809
15126
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
14810
- fs26__default.default.mkdirSync(path27__default.default.dirname(this.storePath), { recursive: true });
14811
- fs26__default.default.writeFileSync(this.storePath, out, { mode: 384 });
15127
+ fs27__default.default.mkdirSync(path28__default.default.dirname(this.storePath), { recursive: true });
15128
+ fs27__default.default.writeFileSync(this.storePath, out, { mode: 384 });
14812
15129
  }
14813
15130
  readRaw() {
14814
- const buf = fs26__default.default.readFileSync(this.storePath);
15131
+ const buf = fs27__default.default.readFileSync(this.storePath);
14815
15132
  let offset = 0;
14816
15133
  const salt = buf.subarray(offset, offset + SALT_LEN);
14817
15134
  offset += SALT_LEN;
@@ -14844,7 +15161,7 @@ var CascadeIgnore = class {
14844
15161
  ]);
14845
15162
  }
14846
15163
  async load(workspacePath) {
14847
- const filePath = path27__default.default.join(workspacePath, ".cascadeignore");
15164
+ const filePath = path28__default.default.join(workspacePath, ".cascadeignore");
14848
15165
  try {
14849
15166
  const content = await fs11__default.default.readFile(filePath, "utf-8");
14850
15167
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
@@ -14855,7 +15172,7 @@ var CascadeIgnore = class {
14855
15172
  }
14856
15173
  isIgnored(filePath, workspacePath) {
14857
15174
  try {
14858
- const relative = workspacePath ? path27__default.default.relative(workspacePath, filePath) : filePath;
15175
+ const relative = workspacePath ? path28__default.default.relative(workspacePath, filePath) : filePath;
14859
15176
  return this.ig.ignores(relative);
14860
15177
  } catch {
14861
15178
  return false;
@@ -14866,7 +15183,7 @@ var CascadeIgnore = class {
14866
15183
  }
14867
15184
  };
14868
15185
  async function loadCascadeMd(workspacePath) {
14869
- const filePath = path27__default.default.join(workspacePath, "CASCADE.md");
15186
+ const filePath = path28__default.default.join(workspacePath, "CASCADE.md");
14870
15187
  try {
14871
15188
  const raw = await fs11__default.default.readFile(filePath, "utf-8");
14872
15189
  return parseCascadeMd(raw);
@@ -14897,7 +15214,7 @@ ${raw.trim()}`;
14897
15214
  var MemoryStore = class _MemoryStore {
14898
15215
  db;
14899
15216
  constructor(dbPath) {
14900
- fs26__default.default.mkdirSync(path27__default.default.dirname(dbPath), { recursive: true });
15217
+ fs27__default.default.mkdirSync(path28__default.default.dirname(dbPath), { recursive: true });
14901
15218
  try {
14902
15219
  this.db = new Database2__default.default(dbPath, { timeout: 5e3 });
14903
15220
  this.db.pragma("journal_mode = WAL");
@@ -15795,7 +16112,18 @@ var WebSearchConfigSchema = zod.z.object({
15795
16112
  /** Tavily API key — get one at https://tavily.com */
15796
16113
  tavilyApiKey: zod.z.string().optional(),
15797
16114
  /** Max results per search (default 5) */
15798
- maxResults: zod.z.number().default(5)
16115
+ maxResults: zod.z.number().default(5),
16116
+ /**
16117
+ * Send `searxngUrl` through the SSRF guard. Off for a local run, where the
16118
+ * URL is the machine owner's own config and a self-hosted SearXNG is
16119
+ * routinely on a private address; on for a host that accepts it from a
16120
+ * request. See `src/tools/web-search.ts`.
16121
+ *
16122
+ * It has to be named HERE, not just on the TypeScript interfaces: the config
16123
+ * passes through `CascadeConfigSchema.parse()` on its way into a run, and
16124
+ * zod drops every key this object does not declare.
16125
+ */
16126
+ guardSearxngUrl: zod.z.boolean().optional()
15799
16127
  });
15800
16128
  var ToolsConfigSchema = zod.z.object({
15801
16129
  shellAllowlist: zod.z.array(zod.z.string()).default([]),
@@ -16199,7 +16527,7 @@ function credentialEndpointsConflict(type, rowEndpoint, credentialEndpoint) {
16199
16527
 
16200
16528
  // src/config/global-credentials.ts
16201
16529
  function credentialsPath(globalDir2) {
16202
- return path27__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
16530
+ return path28__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
16203
16531
  }
16204
16532
  function providerKey(p) {
16205
16533
  return p.type;
@@ -16229,7 +16557,7 @@ function isPersistable(p) {
16229
16557
  }
16230
16558
  function loadGlobalCredentials(globalDir2) {
16231
16559
  try {
16232
- const raw = fs26__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
16560
+ const raw = fs27__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
16233
16561
  const parsed = JSON.parse(raw);
16234
16562
  if (!Array.isArray(parsed.providers)) return [];
16235
16563
  return parsed.providers.filter(
@@ -16241,13 +16569,13 @@ function loadGlobalCredentials(globalDir2) {
16241
16569
  }
16242
16570
  function saveGlobalCredentials(globalDir2, providers) {
16243
16571
  const filePath = credentialsPath(globalDir2);
16244
- fs26__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
16572
+ fs27__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
16245
16573
  const body = { version: 1, providers: providers.filter(isPersistable) };
16246
16574
  const tmp = `${filePath}.tmp`;
16247
- fs26__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
16248
- fs26__default.default.renameSync(tmp, filePath);
16575
+ fs27__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
16576
+ fs27__default.default.renameSync(tmp, filePath);
16249
16577
  try {
16250
- fs26__default.default.chmodSync(filePath, 384);
16578
+ fs27__default.default.chmodSync(filePath, 384);
16251
16579
  } catch {
16252
16580
  }
16253
16581
  }
@@ -16648,7 +16976,7 @@ var ConfigManager = class {
16648
16976
  /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
16649
16977
  constructor(workspacePath = process.cwd(), globalDirOverride) {
16650
16978
  this.workspacePath = workspacePath;
16651
- this.globalDir = globalDirOverride ?? path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
16979
+ this.globalDir = globalDirOverride ?? path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
16652
16980
  }
16653
16981
  async load() {
16654
16982
  this.config = await this.loadConfig();
@@ -16662,8 +16990,8 @@ var ConfigManager = class {
16662
16990
  this.ignore = new CascadeIgnore();
16663
16991
  await this.ignore.load(this.workspacePath);
16664
16992
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
16665
- this.keystore = new Keystore(path27__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
16666
- this.store = new MemoryStore(path27__default.default.join(this.workspacePath, CASCADE_DB_FILE));
16993
+ this.keystore = new Keystore(path28__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
16994
+ this.store = new MemoryStore(path28__default.default.join(this.workspacePath, CASCADE_DB_FILE));
16667
16995
  const globalRevoked = stripRevokedCredentials(loadGlobalCredentials(this.globalDir));
16668
16996
  const globalCreds = filterRetiredCredentials(globalRevoked.kept);
16669
16997
  if (globalRevoked.removed > 0) this.revokedCredentials += globalRevoked.removed;
@@ -16736,8 +17064,8 @@ var ConfigManager = class {
16736
17064
  * never reached the disk (see `commitSettings`).
16737
17065
  */
16738
17066
  async save(config = this.config) {
16739
- const configPath = path27__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
16740
- await fs11__default.default.mkdir(path27__default.default.dirname(configPath), { recursive: true });
17067
+ const configPath = path28__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
17068
+ await fs11__default.default.mkdir(path28__default.default.dirname(configPath), { recursive: true });
16741
17069
  await fs11__default.default.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
16742
17070
  try {
16743
17071
  saveGlobalCredentials(this.globalDir, config.providers);
@@ -16787,7 +17115,7 @@ var ConfigManager = class {
16787
17115
  this.revokedCredentials = 0;
16788
17116
  this.revokedPins = [];
16789
17117
  this.revokedNotice = void 0;
16790
- const configPath = path27__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
17118
+ const configPath = path28__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
16791
17119
  try {
16792
17120
  const raw = await fs11__default.default.readFile(configPath, "utf-8");
16793
17121
  const parsed = JSON.parse(raw);
@@ -16881,7 +17209,7 @@ var ConfigManager = class {
16881
17209
  return onResource;
16882
17210
  }
16883
17211
  async persistClearedPins(tiers) {
16884
- const configPath = path27__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
17212
+ const configPath = path28__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
16885
17213
  try {
16886
17214
  const raw = JSON.parse(await fs11__default.default.readFile(configPath, "utf-8"));
16887
17215
  const models = raw["models"];
@@ -17475,7 +17803,7 @@ function computeDelegationSavings(stats, t1Model) {
17475
17803
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
17476
17804
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
17477
17805
  var FETCH_TIMEOUT_MS = 8e3;
17478
- var DEFAULT_CACHE_FILE = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
17806
+ var DEFAULT_CACHE_FILE = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
17479
17807
  function normalizeModelId2(id) {
17480
17808
  let s = id.toLowerCase();
17481
17809
  const slash = s.lastIndexOf("/");
@@ -17622,7 +17950,7 @@ var LiveDataProvider = class {
17622
17950
  }
17623
17951
  async saveCache() {
17624
17952
  try {
17625
- await fs11__default.default.mkdir(path27__default.default.dirname(this.opts.cacheFile), { recursive: true });
17953
+ await fs11__default.default.mkdir(path28__default.default.dirname(this.opts.cacheFile), { recursive: true });
17626
17954
  const cache = {
17627
17955
  fetchedAt: this.fetchedAt,
17628
17956
  snapshot: this.snapshot ?? void 0,
@@ -17729,9 +18057,15 @@ var FAMILY_BENCHMARKS = {
17729
18057
  "claude-opus": { code: 95, analysis: 92, creative: 90, data: 88 },
17730
18058
  "claude-sonnet": { code: 93, analysis: 88, creative: 87, data: 85 },
17731
18059
  "claude-haiku": { code: 80, analysis: 75, creative: 76, data: 72 },
17732
- // OpenAI GPT-5 family — current flagship; strongest all-round. Offline
17733
- // baseline (the live fetch refreshes these). Distinct point releases carry
17734
- // their own scores: gpt-5.5 "Spud" leads SWE-bench (88.7%); gpt-5.4 ~80%.
18060
+ // OpenAI GPT-5 family. GPT-5.6 is split by durable capability tier instead
18061
+ // of being folded into generic gpt-5: Sol, Terra and Luna have materially
18062
+ // different benchmark/cost envelopes and should compete independently.
18063
+ // Scores are the committed normalized snapshot; provenance is in
18064
+ // scripts/benchmarks/sources/openai-gpt-5.6-2026-08.json.
18065
+ "gpt-5.6-sol": { code: 100, analysis: 98, creative: 88, data: 88 },
18066
+ "gpt-5.6-terra": { code: 97, analysis: 92, creative: 82, data: 85 },
18067
+ "gpt-5.6-luna": { code: 93, analysis: 85, creative: 74, data: 60 },
18068
+ // Older GPT-5 point releases remain distinct where public data supports it.
17735
18069
  "gpt-5.5": { code: 97, analysis: 96, creative: 94, data: 95 },
17736
18070
  "gpt-5.4": { code: 94, analysis: 94, creative: 92, data: 92 },
17737
18071
  "gpt-5.4-mini": { code: 86, analysis: 85, creative: 85, data: 83 },
@@ -17767,13 +18101,19 @@ var FAMILY_BENCHMARKS = {
17767
18101
  "llama-small": { code: 55, analysis: 56, creative: 60, data: 54 }
17768
18102
  };
17769
18103
  var FAMILY_MATCHERS = [
18104
+ [/fable/i, "claude-opus"],
17770
18105
  [/opus/i, "claude-opus"],
17771
18106
  [/sonnet/i, "claude-sonnet"],
17772
18107
  [/haiku/i, "claude-haiku"],
17773
- // GPT-5 family — ordered most-specific first. Distinct point releases
17774
- // (gpt-5.5, gpt-5.4, gpt-5.4-mini) match their own family; nano/mini before
17775
- // the base; unrecognised gpt-5.x still fold into the gpt-5 base.
18108
+ // GPT-5 family — ordered most-specific first. GPT-5.6 durable tiers carry
18109
+ // their own profiles. 5.4 Nano intentionally maps to the nano class rather
18110
+ // than being swallowed by the generic 5.4 rule.
18111
+ [/gpt-?5\.6.*sol/i, "gpt-5.6-sol"],
18112
+ [/gpt-?5\.6.*terra/i, "gpt-5.6-terra"],
18113
+ [/gpt-?5\.6.*luna/i, "gpt-5.6-luna"],
18114
+ [/gpt-?5\.6(?:\s|$)/i, "gpt-5.6-sol"],
17776
18115
  [/gpt-?5\.5/i, "gpt-5.5"],
18116
+ [/gpt-?5\.4.*nano/i, "gpt-5-nano"],
17777
18117
  [/gpt-?5\.4.*mini/i, "gpt-5.4-mini"],
17778
18118
  [/gpt-?5\.4/i, "gpt-5.4"],
17779
18119
  [/gpt-?5.*nano/i, "gpt-5-nano"],
@@ -20832,8 +21172,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
20832
21172
  // Media generation can run for a minute; without this a cancelled run
20833
21173
  // still pays for an image nobody will see.
20834
21174
  ...this.signal ? { signal: this.signal } : {},
20835
- saveSnapshot: async (path29, content) => {
20836
- this.store?.addFileSnapshot(this.taskId, path29, content);
21175
+ saveSnapshot: async (path30, content) => {
21176
+ this.store?.addFileSnapshot(this.taskId, path30, content);
20837
21177
  },
20838
21178
  sendPeerSync: (to, syncType, content) => {
20839
21179
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -21036,7 +21376,7 @@ ${assignment.expectedOutput}`;
21036
21376
  const { promisify: promisify4 } = await import('util');
21037
21377
  const execFileAsync3 = promisify4(execFile4);
21038
21378
  for (const artifactPath of artifactPaths) {
21039
- const absolutePath = path27__default.default.resolve(this.artifactRoot(), artifactPath);
21379
+ const absolutePath = path28__default.default.resolve(this.artifactRoot(), artifactPath);
21040
21380
  try {
21041
21381
  const stat = await fs11__default.default.stat(absolutePath);
21042
21382
  if (!stat.isFile()) {
@@ -21071,7 +21411,7 @@ ${assignment.expectedOutput}`;
21071
21411
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
21072
21412
  continue;
21073
21413
  }
21074
- const ext = path27__default.default.extname(absolutePath).toLowerCase();
21414
+ const ext = path28__default.default.extname(absolutePath).toLowerCase();
21075
21415
  const check = ext === ".ts" || ext === ".tsx" ? { cmd: "npx", args: ["tsc", "--noEmit", absolutePath] } : ext === ".js" || ext === ".jsx" ? { cmd: process.execPath, args: ["--check", absolutePath] } : ext === ".py" ? { cmd: "python", args: ["-m", "py_compile", absolutePath] } : null;
21076
21416
  if (check) {
21077
21417
  try {
@@ -21163,7 +21503,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
21163
21503
  async checkAcceptance(assignment) {
21164
21504
  const criteria = assignment.acceptance ?? [];
21165
21505
  if (!criteria.length) return [];
21166
- const resolve2 = (target) => path27__default.default.resolve(this.artifactRoot(), target);
21506
+ const resolve2 = (target) => path28__default.default.resolve(this.artifactRoot(), target);
21167
21507
  const workerCanWriteFiles = canProduceFiles(this.tools.map((t) => t.name));
21168
21508
  return evaluateAcceptance(criteria, assignment.files ?? [], {
21169
21509
  stat: async (target) => {
@@ -24467,16 +24807,16 @@ function resolveInWorkspace(workspaceRoot, input) {
24467
24807
  if (typeof input !== "string" || input.length === 0) {
24468
24808
  throw new WorkspaceSandboxError(String(input), workspaceRoot);
24469
24809
  }
24470
- const root = path27__default.default.resolve(workspaceRoot);
24471
- const abs = path27__default.default.isAbsolute(input) ? path27__default.default.resolve(input) : path27__default.default.resolve(root, input);
24472
- const rel = path27__default.default.relative(root, abs);
24473
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path27__default.default.isAbsolute(rel)) {
24810
+ const root = path28__default.default.resolve(workspaceRoot);
24811
+ const abs = path28__default.default.isAbsolute(input) ? path28__default.default.resolve(input) : path28__default.default.resolve(root, input);
24812
+ const rel = path28__default.default.relative(root, abs);
24813
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path28__default.default.isAbsolute(rel)) {
24474
24814
  throw new WorkspaceSandboxError(input, root);
24475
24815
  }
24476
24816
  try {
24477
- const real = fs26__default.default.realpathSync(abs);
24478
- const realRel = path27__default.default.relative(root, real);
24479
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path27__default.default.isAbsolute(realRel))) {
24817
+ const real = fs27__default.default.realpathSync(abs);
24818
+ const realRel = path28__default.default.relative(root, real);
24819
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path28__default.default.isAbsolute(realRel))) {
24480
24820
  throw new WorkspaceSandboxError(input, root);
24481
24821
  }
24482
24822
  } catch (e) {
@@ -25025,9 +25365,9 @@ async function animatePptx(bytes, scheme) {
25025
25365
  const zip = await import_jszip.default.loadAsync(bytes);
25026
25366
  const slidePaths = Object.keys(zip.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n));
25027
25367
  if (slidePaths.length === 0) return bytes;
25028
- for (const path29 of slidePaths) {
25029
- const xml = await zip.file(path29).async("string");
25030
- zip.file(path29, animateSlideXml(xml, scheme));
25368
+ for (const path30 of slidePaths) {
25369
+ const xml = await zip.file(path30).async("string");
25370
+ zip.file(path30, animateSlideXml(xml, scheme));
25031
25371
  }
25032
25372
  return await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
25033
25373
  } catch {
@@ -25413,7 +25753,7 @@ var GenerateDocumentTool = class extends BaseTool {
25413
25753
  }
25414
25754
  }
25415
25755
  const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
25416
- await fs11__default.default.mkdir(path27__default.default.dirname(absPath), { recursive: true });
25756
+ await fs11__default.default.mkdir(path28__default.default.dirname(absPath), { recursive: true });
25417
25757
  await fs11__default.default.writeFile(absPath, bytes);
25418
25758
  const charts = countCharts(content);
25419
25759
  return [
@@ -25631,7 +25971,7 @@ var FileWriteTool = class extends BaseTool {
25631
25971
  } catch {
25632
25972
  }
25633
25973
  }
25634
- await fs11__default.default.mkdir(path27__default.default.dirname(absPath), { recursive: true });
25974
+ await fs11__default.default.mkdir(path28__default.default.dirname(absPath), { recursive: true });
25635
25975
  await fs11__default.default.writeFile(absPath, content, "utf-8");
25636
25976
  return `Written ${content.length} characters to ${filePath}`;
25637
25977
  }
@@ -25989,12 +26329,10 @@ ${data.description}`;
25989
26329
  }
25990
26330
  }
25991
26331
  };
25992
-
25993
- // src/tools/browser.ts
25994
26332
  var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
25995
26333
  var BrowserTool = class extends BaseTool {
25996
26334
  name = "browser";
25997
- description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
26335
+ description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Screenshots are saved to a file for image_analyze to read.";
25998
26336
  inputSchema = {
25999
26337
  type: "object",
26000
26338
  properties: {
@@ -26061,7 +26399,14 @@ var BrowserTool = class extends BaseTool {
26061
26399
  }
26062
26400
  case "screenshot": {
26063
26401
  const buf = await page2.screenshot({ type: "png" });
26064
- return `data:image/png;base64,${buf.toString("base64")}`;
26402
+ const rel = path28__default.default.join(".cascade", "screenshots", `screenshot-${Date.now()}-${crypto.randomUUID().slice(0, 8)}.png`);
26403
+ const abs = resolveInWorkspace(this.workspaceRoot, rel);
26404
+ await fs11__default.default.mkdir(path28__default.default.dirname(abs), { recursive: true });
26405
+ await fs11__default.default.writeFile(abs, buf);
26406
+ return [
26407
+ `Screenshot saved to ${abs} (${Math.round(buf.byteLength / 1024)} KB).`,
26408
+ `Call image_analyze with that path to see what is on it \u2014 that needs a vision-capable model.`
26409
+ ].join(" ");
26065
26410
  }
26066
26411
  case "evaluate": {
26067
26412
  const result = await page2.evaluate(input["script"]);
@@ -26129,7 +26474,7 @@ var ImageAnalyzeTool = class extends BaseTool {
26129
26474
  };
26130
26475
  async function fileToImageAttachment(filePath) {
26131
26476
  const data = await fs11__default.default.readFile(filePath);
26132
- const ext = path27__default.default.extname(filePath).toLowerCase();
26477
+ const ext = path28__default.default.extname(filePath).toLowerCase();
26133
26478
  const mimeMap = {
26134
26479
  ".jpg": "image/jpeg",
26135
26480
  ".jpeg": "image/jpeg",
@@ -26163,14 +26508,14 @@ var PDFCreateTool = class extends BaseTool {
26163
26508
  const filePath = input["path"];
26164
26509
  const content = input["content"];
26165
26510
  const title = input["title"];
26166
- const dir = path27__default.default.dirname(filePath);
26167
- if (!fs26__default.default.existsSync(dir)) {
26168
- fs26__default.default.mkdirSync(dir, { recursive: true });
26511
+ const dir = path28__default.default.dirname(filePath);
26512
+ if (!fs27__default.default.existsSync(dir)) {
26513
+ fs27__default.default.mkdirSync(dir, { recursive: true });
26169
26514
  }
26170
26515
  return new Promise((resolve2, reject) => {
26171
26516
  try {
26172
26517
  const doc = new PDFDocument__default.default({ margin: 50 });
26173
- const stream = fs26__default.default.createWriteStream(filePath);
26518
+ const stream = fs27__default.default.createWriteStream(filePath);
26174
26519
  doc.pipe(stream);
26175
26520
  if (title) {
26176
26521
  doc.info["Title"] = title;
@@ -26248,22 +26593,22 @@ var CodeInterpreterTool = class extends BaseTool {
26248
26593
  }
26249
26594
  cmdPrefix = NODE_CMD;
26250
26595
  }
26251
- const tmpDir = path27__default.default.join(this.workspaceRoot, ".cascade", "tmp");
26252
- if (!fs26__default.default.existsSync(tmpDir)) {
26253
- fs26__default.default.mkdirSync(tmpDir, { recursive: true });
26596
+ const tmpDir = path28__default.default.join(this.workspaceRoot, ".cascade", "tmp");
26597
+ if (!fs27__default.default.existsSync(tmpDir)) {
26598
+ fs27__default.default.mkdirSync(tmpDir, { recursive: true });
26254
26599
  }
26255
26600
  const extension = language === "python" ? "py" : "js";
26256
26601
  const fileName = `intp_${crypto.randomUUID().slice(0, 8)}.${extension}`;
26257
- const filePath = path27__default.default.join(tmpDir, fileName);
26258
- fs26__default.default.writeFileSync(filePath, code, "utf-8");
26602
+ const filePath = path28__default.default.join(tmpDir, fileName);
26603
+ fs27__default.default.writeFileSync(filePath, code, "utf-8");
26259
26604
  const execArgs = [filePath, ...args];
26260
26605
  return new Promise((resolve2) => {
26261
26606
  const startMs = Date.now();
26262
26607
  child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
26263
26608
  const duration = Date.now() - startMs;
26264
26609
  try {
26265
- if (fs26__default.default.existsSync(filePath)) {
26266
- fs26__default.default.unlinkSync(filePath);
26610
+ if (fs27__default.default.existsSync(filePath)) {
26611
+ fs27__default.default.unlinkSync(filePath);
26267
26612
  }
26268
26613
  } catch (cleanupErr) {
26269
26614
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -26343,18 +26688,178 @@ ${formatted}`;
26343
26688
  return `Unknown action: ${action}`;
26344
26689
  }
26345
26690
  };
26691
+ var SsrfBlockedError = class extends Error {
26692
+ constructor(message) {
26693
+ super(message);
26694
+ this.name = "SsrfBlockedError";
26695
+ }
26696
+ };
26697
+ var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
26698
+ var MAX_REDIRECTS2 = 5;
26699
+ function allowLocal() {
26700
+ return process.env["CASCADE_ALLOW_LOCAL_FETCH"] === "1";
26701
+ }
26702
+ function isPrivateAddress(ip) {
26703
+ const type = net__default.default.isIP(ip);
26704
+ if (type === 4) return isPrivateIPv4(ip);
26705
+ if (type === 6) return isPrivateIPv6(ip);
26706
+ return true;
26707
+ }
26708
+ function isPrivateIPv4(ip) {
26709
+ const parts = ip.split(".").map((p) => Number(p));
26710
+ if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return true;
26711
+ const [a, b] = parts;
26712
+ if (a === 0) return true;
26713
+ if (a === 10) return true;
26714
+ if (a === 127) return true;
26715
+ if (a === 169 && b === 254) return true;
26716
+ if (a === 172 && b >= 16 && b <= 31) return true;
26717
+ if (a === 192 && b === 168) return true;
26718
+ if (a === 100 && b >= 64 && b <= 127) return true;
26719
+ if (a >= 224) return true;
26720
+ return false;
26721
+ }
26722
+ function ipv6Groups(ip) {
26723
+ let s = ip.toLowerCase().replace(/^\[|\]$/g, "");
26724
+ const zone = s.indexOf("%");
26725
+ if (zone !== -1) s = s.slice(0, zone);
26726
+ const dotted = /^(.*:)(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(s);
26727
+ if (dotted) {
26728
+ const o = [dotted[2], dotted[3], dotted[4], dotted[5]].map((p) => Number(p));
26729
+ if (o.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
26730
+ s = `${dotted[1]}${(o[0] << 8 | o[1]).toString(16)}:${(o[2] << 8 | o[3]).toString(16)}`;
26731
+ }
26732
+ const halves = s.split("::");
26733
+ if (halves.length > 2) return null;
26734
+ const split = (part) => part ? part.split(":") : [];
26735
+ const head = split(halves[0] ?? "");
26736
+ const tail = halves.length === 2 ? split(halves[1] ?? "") : [];
26737
+ const zeros = halves.length === 2 ? 8 - head.length - tail.length : 0;
26738
+ if (zeros < 0) return null;
26739
+ const groups = [...head, ...Array(zeros).fill("0"), ...tail];
26740
+ if (groups.length !== 8) return null;
26741
+ const out = [];
26742
+ for (const g of groups) {
26743
+ if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
26744
+ out.push(parseInt(g, 16));
26745
+ }
26746
+ return out;
26747
+ }
26748
+ function embeddedIPv4(g) {
26749
+ const quad = (hi, lo) => `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;
26750
+ const leadingZero = (n) => g.slice(0, n).every((x) => x === 0);
26751
+ if (leadingZero(5) && g[5] === 65535) return quad(g[6], g[7]);
26752
+ if (leadingZero(6)) return quad(g[6], g[7]);
26753
+ if (g[0] === 100 && g[1] === 65435) {
26754
+ if (g[2] === 1) {
26755
+ return quad(g[3], (g[4] & 255) << 8 | g[5] >> 8);
26756
+ }
26757
+ if (g[2] === 0 && g[3] === 0 && g[4] === 0 && g[5] === 0) return quad(g[6], g[7]);
26758
+ return null;
26759
+ }
26760
+ if (g[0] === 8194) return quad(g[1], g[2]);
26761
+ return null;
26762
+ }
26763
+ function isPrivateIPv6(ip) {
26764
+ const g = ipv6Groups(ip);
26765
+ if (!g) return true;
26766
+ const v4 = embeddedIPv4(g);
26767
+ if (v4) return isPrivateIPv4(v4);
26768
+ if (g[0] === 100 && g[1] === 65435) return true;
26769
+ if (g.every((x) => x === 0)) return true;
26770
+ if (leading7Zero(g) && g[7] === 1) return true;
26771
+ if ((g[0] & 65472) === 65152) return true;
26772
+ if ((g[0] & 65024) === 64512) return true;
26773
+ if ((g[0] & 65280) === 65280) return true;
26774
+ return false;
26775
+ }
26776
+ function leading7Zero(g) {
26777
+ return g.slice(0, 7).every((x) => x === 0);
26778
+ }
26779
+ async function assertPublicUrl(rawUrl) {
26780
+ let url;
26781
+ try {
26782
+ url = new URL(rawUrl);
26783
+ } catch {
26784
+ throw new SsrfBlockedError(`Invalid URL: ${rawUrl}`);
26785
+ }
26786
+ if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
26787
+ throw new SsrfBlockedError(`Blocked URL scheme "${url.protocol}" \u2014 only http and https are allowed.`);
26788
+ }
26789
+ if (allowLocal()) return url;
26790
+ const host = url.hostname.replace(/^\[|\]$/g, "");
26791
+ if (net__default.default.isIP(host)) {
26792
+ if (isPrivateAddress(host)) {
26793
+ throw new SsrfBlockedError(`Blocked request to non-public address ${host}.`);
26794
+ }
26795
+ return url;
26796
+ }
26797
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
26798
+ throw new SsrfBlockedError(`Blocked request to local hostname "${host}".`);
26799
+ }
26800
+ let addresses;
26801
+ try {
26802
+ const records = await dns2__default.default.lookup(host, { all: true });
26803
+ addresses = records.map((r) => r.address);
26804
+ } catch {
26805
+ throw new SsrfBlockedError(`Could not resolve host "${host}".`);
26806
+ }
26807
+ if (addresses.length === 0 || addresses.some((addr) => isPrivateAddress(addr))) {
26808
+ throw new SsrfBlockedError(`Blocked request to "${host}" \u2014 resolves to a non-public address.`);
26809
+ }
26810
+ return url;
26811
+ }
26812
+ var ssrfAgent = new undici.Agent({
26813
+ connect: {
26814
+ lookup(hostname, options, callback) {
26815
+ if (allowLocal()) {
26816
+ dnsCallback__default.default.lookup(hostname, options, callback);
26817
+ return;
26818
+ }
26819
+ dnsCallback__default.default.lookup(hostname, { ...options, all: true }, (err, addresses) => {
26820
+ if (err) return callback(err, "", 0);
26821
+ const list = Array.isArray(addresses) ? addresses : [addresses];
26822
+ const bad = list.find((a) => isPrivateAddress(a.address));
26823
+ if (bad) {
26824
+ return callback(
26825
+ new SsrfBlockedError(`Blocked connection to "${hostname}" \u2014 resolved to non-public address ${bad.address}.`),
26826
+ "",
26827
+ 0
26828
+ );
26829
+ }
26830
+ if (options.all) return callback(null, list);
26831
+ const first = list[0];
26832
+ return callback(null, first.address, first.family);
26833
+ });
26834
+ }
26835
+ }
26836
+ });
26837
+ async function safeFetch(rawUrl, init = {}) {
26838
+ let currentUrl = (await assertPublicUrl(rawUrl)).toString();
26839
+ for (let i = 0; i <= MAX_REDIRECTS2; i++) {
26840
+ const resp = await fetch(currentUrl, { ...init, redirect: "manual", dispatcher: ssrfAgent });
26841
+ if (resp.status < 300 || resp.status >= 400) return resp;
26842
+ const location = resp.headers.get("location");
26843
+ if (!location) return resp;
26844
+ const next = new URL(location, currentUrl);
26845
+ await assertPublicUrl(next.toString());
26846
+ currentUrl = next.toString();
26847
+ }
26848
+ throw new SsrfBlockedError(`Too many redirects (>${MAX_REDIRECTS2}).`);
26849
+ }
26346
26850
 
26347
26851
  // src/tools/web-search.ts
26348
- async function searchSearXNG(query, baseUrl, maxResults) {
26852
+ async function searchSearXNG(query, baseUrl, maxResults, guardUrl = false) {
26349
26853
  const url = new URL("/search", baseUrl);
26350
26854
  url.searchParams.set("q", query);
26351
26855
  url.searchParams.set("format", "json");
26352
26856
  url.searchParams.set("categories", "general");
26353
26857
  url.searchParams.set("engines", "google,bing,duckduckgo");
26354
- const resp = await fetch(url.toString(), {
26858
+ const init = {
26355
26859
  headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
26356
26860
  signal: AbortSignal.timeout(1e4)
26357
- });
26861
+ };
26862
+ const resp = guardUrl ? await safeFetch(url.toString(), init) : await fetch(url.toString(), init);
26358
26863
  if (!resp.ok) {
26359
26864
  throw new Error(`SearXNG returned HTTP ${resp.status}`);
26360
26865
  }
@@ -26483,7 +26988,11 @@ var WebSearchTool = class extends BaseTool {
26483
26988
  searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
26484
26989
  braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
26485
26990
  tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
26486
- maxResults: config.maxResults ?? 5
26991
+ maxResults: config.maxResults ?? 5,
26992
+ // Note this reads only from `config`, never from the environment: the
26993
+ // guard tracks the trustworthiness of the URL the host passed in, and an
26994
+ // env var says nothing about that.
26995
+ guardSearxngUrl: config.guardSearxngUrl ?? false
26487
26996
  };
26488
26997
  }
26489
26998
  async execute(input, _options) {
@@ -26498,7 +27007,7 @@ var WebSearchTool = class extends BaseTool {
26498
27007
  let anyBackendReachable = false;
26499
27008
  if (this.config.searxngUrl) {
26500
27009
  try {
26501
- results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
27010
+ results = await searchSearXNG(query, this.config.searxngUrl, maxResults, this.config.guardSearxngUrl);
26502
27011
  anyBackendReachable = true;
26503
27012
  if (results.length > 0) return this.formatResults(query, results);
26504
27013
  errors.push("SearXNG: returned 0 results");
@@ -26575,7 +27084,7 @@ var GlobTool = class extends BaseTool {
26575
27084
  };
26576
27085
  async execute(input, _options) {
26577
27086
  const pattern = input["pattern"];
26578
- const searchPath = input["path"] ? path27__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27087
+ const searchPath = input["path"] ? path28__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
26579
27088
  const matches = await glob.glob(pattern, {
26580
27089
  cwd: searchPath,
26581
27090
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -26588,7 +27097,7 @@ var GlobTool = class extends BaseTool {
26588
27097
  const withMtime = await Promise.all(
26589
27098
  matches.map(async (rel) => {
26590
27099
  try {
26591
- const stat = await fs11__default.default.stat(path27__default.default.join(searchPath, rel));
27100
+ const stat = await fs11__default.default.stat(path28__default.default.join(searchPath, rel));
26592
27101
  return { rel, mtime: stat.mtimeMs };
26593
27102
  } catch {
26594
27103
  return { rel, mtime: 0 };
@@ -26637,7 +27146,7 @@ var GrepTool = class extends BaseTool {
26637
27146
  };
26638
27147
  async execute(input, _options) {
26639
27148
  const pattern = input["pattern"];
26640
- const searchPath = input["path"] ? path27__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27149
+ const searchPath = input["path"] ? path28__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
26641
27150
  const globPattern = input["glob"];
26642
27151
  const outputMode = input["output_mode"] ?? "content";
26643
27152
  const context = input["context"] ?? 0;
@@ -26691,12 +27200,12 @@ var GrepTool = class extends BaseTool {
26691
27200
  nodir: true
26692
27201
  });
26693
27202
  } catch {
26694
- files = [path27__default.default.relative(searchPath, searchPath) || "."];
27203
+ files = [path28__default.default.relative(searchPath, searchPath) || "."];
26695
27204
  }
26696
27205
  const results = [];
26697
27206
  let totalCount = 0;
26698
27207
  for (const rel of files) {
26699
- const abs = path27__default.default.join(searchPath, rel);
27208
+ const abs = path28__default.default.join(searchPath, rel);
26700
27209
  let content;
26701
27210
  try {
26702
27211
  content = await fs11__default.default.readFile(abs, "utf-8");
@@ -26737,92 +27246,6 @@ Total: ${totalCount} matches`);
26737
27246
  return results.join("\n");
26738
27247
  }
26739
27248
  };
26740
- var SsrfBlockedError = class extends Error {
26741
- constructor(message) {
26742
- super(message);
26743
- this.name = "SsrfBlockedError";
26744
- }
26745
- };
26746
- var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
26747
- var MAX_REDIRECTS2 = 5;
26748
- function allowLocal() {
26749
- return process.env["CASCADE_ALLOW_LOCAL_FETCH"] === "1";
26750
- }
26751
- function isPrivateAddress(ip) {
26752
- const type = net__default.default.isIP(ip);
26753
- if (type === 4) return isPrivateIPv4(ip);
26754
- if (type === 6) return isPrivateIPv6(ip);
26755
- return true;
26756
- }
26757
- function isPrivateIPv4(ip) {
26758
- const parts = ip.split(".").map((p) => Number(p));
26759
- if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return true;
26760
- const [a, b] = parts;
26761
- if (a === 0) return true;
26762
- if (a === 10) return true;
26763
- if (a === 127) return true;
26764
- if (a === 169 && b === 254) return true;
26765
- if (a === 172 && b >= 16 && b <= 31) return true;
26766
- if (a === 192 && b === 168) return true;
26767
- if (a === 100 && b >= 64 && b <= 127) return true;
26768
- if (a >= 224) return true;
26769
- return false;
26770
- }
26771
- function isPrivateIPv6(ip) {
26772
- const lower = ip.toLowerCase().replace(/^\[|\]$/g, "");
26773
- if (lower === "::1" || lower === "::") return true;
26774
- if (lower.startsWith("fe80")) return true;
26775
- if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
26776
- const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(lower);
26777
- if (mapped) return isPrivateIPv4(mapped[1]);
26778
- return false;
26779
- }
26780
- async function assertPublicUrl(rawUrl) {
26781
- let url;
26782
- try {
26783
- url = new URL(rawUrl);
26784
- } catch {
26785
- throw new SsrfBlockedError(`Invalid URL: ${rawUrl}`);
26786
- }
26787
- if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
26788
- throw new SsrfBlockedError(`Blocked URL scheme "${url.protocol}" \u2014 only http and https are allowed.`);
26789
- }
26790
- if (allowLocal()) return url;
26791
- const host = url.hostname.replace(/^\[|\]$/g, "");
26792
- if (net__default.default.isIP(host)) {
26793
- if (isPrivateAddress(host)) {
26794
- throw new SsrfBlockedError(`Blocked request to non-public address ${host}.`);
26795
- }
26796
- return url;
26797
- }
26798
- if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
26799
- throw new SsrfBlockedError(`Blocked request to local hostname "${host}".`);
26800
- }
26801
- let addresses;
26802
- try {
26803
- const records = await dns2__default.default.lookup(host, { all: true });
26804
- addresses = records.map((r) => r.address);
26805
- } catch {
26806
- throw new SsrfBlockedError(`Could not resolve host "${host}".`);
26807
- }
26808
- if (addresses.length === 0 || addresses.some((addr) => isPrivateAddress(addr))) {
26809
- throw new SsrfBlockedError(`Blocked request to "${host}" \u2014 resolves to a non-public address.`);
26810
- }
26811
- return url;
26812
- }
26813
- async function safeFetch(rawUrl, init = {}) {
26814
- let currentUrl = (await assertPublicUrl(rawUrl)).toString();
26815
- for (let i = 0; i <= MAX_REDIRECTS2; i++) {
26816
- const resp = await fetch(currentUrl, { ...init, redirect: "manual" });
26817
- if (resp.status < 300 || resp.status >= 400) return resp;
26818
- const location = resp.headers.get("location");
26819
- if (!location) return resp;
26820
- const next = new URL(location, currentUrl);
26821
- await assertPublicUrl(next.toString());
26822
- currentUrl = next.toString();
26823
- }
26824
- throw new SsrfBlockedError(`Too many redirects (>${MAX_REDIRECTS2}).`);
26825
- }
26826
27249
 
26827
27250
  // src/tools/web-fetch.ts
26828
27251
  var MAX_CHARS = 5e4;
@@ -27095,10 +27518,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
27095
27518
  }
27096
27519
  isIgnored(filePath) {
27097
27520
  if (!filePath) return false;
27098
- const abs = path27__default.default.resolve(this.workspaceRoot, filePath);
27099
- const rel = path27__default.default.relative(this.workspaceRoot, abs);
27100
- if (!rel || rel.startsWith("..") || path27__default.default.isAbsolute(rel)) return true;
27101
- const posixRel = rel.split(path27__default.default.sep).join("/");
27521
+ const abs = path28__default.default.resolve(this.workspaceRoot, filePath);
27522
+ const rel = path28__default.default.relative(this.workspaceRoot, abs);
27523
+ if (!rel || rel.startsWith("..") || path28__default.default.isAbsolute(rel)) return true;
27524
+ const posixRel = rel.split(path28__default.default.sep).join("/");
27102
27525
  return this.ignoreMatcher.ignores(posixRel);
27103
27526
  }
27104
27527
  };
@@ -27332,22 +27755,22 @@ var FileMcpOAuthStore = class {
27332
27755
  filePath;
27333
27756
  load() {
27334
27757
  try {
27335
- return JSON.parse(fs26__default.default.readFileSync(this.filePath, "utf-8"));
27758
+ return JSON.parse(fs27__default.default.readFileSync(this.filePath, "utf-8"));
27336
27759
  } catch {
27337
27760
  return void 0;
27338
27761
  }
27339
27762
  }
27340
27763
  save(state) {
27341
- fs26__default.default.mkdirSync(path27__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
27342
- fs26__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
27764
+ fs27__default.default.mkdirSync(path28__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
27765
+ fs27__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
27343
27766
  try {
27344
- fs26__default.default.chmodSync(this.filePath, 384);
27767
+ fs27__default.default.chmodSync(this.filePath, 384);
27345
27768
  } catch {
27346
27769
  }
27347
27770
  }
27348
27771
  clear() {
27349
27772
  try {
27350
- fs26__default.default.rmSync(this.filePath);
27773
+ fs27__default.default.rmSync(this.filePath);
27351
27774
  } catch {
27352
27775
  }
27353
27776
  }
@@ -28185,7 +28608,7 @@ var TaskAnalyzer = class {
28185
28608
  analysisCache.clear();
28186
28609
  }
28187
28610
  };
28188
- var DEFAULT_STATS_FILE = path27__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
28611
+ var DEFAULT_STATS_FILE = path28__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
28189
28612
  var ModelPerformanceTracker = class _ModelPerformanceTracker {
28190
28613
  stats = /* @__PURE__ */ new Map();
28191
28614
  featureStats = /* @__PURE__ */ new Map();
@@ -28225,7 +28648,7 @@ var ModelPerformanceTracker = class _ModelPerformanceTracker {
28225
28648
  async save() {
28226
28649
  if (this.readOnly) return;
28227
28650
  try {
28228
- await fs11__default.default.mkdir(path27__default.default.dirname(this.statsFile), { recursive: true });
28651
+ await fs11__default.default.mkdir(path28__default.default.dirname(this.statsFile), { recursive: true });
28229
28652
  const modelsObj = {};
28230
28653
  const featuresObj = {};
28231
28654
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
@@ -28858,7 +29281,7 @@ Required capability: ${description.slice(0, 300)}`;
28858
29281
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
28859
29282
  async loadPersistedTools() {
28860
29283
  if (!this.workspacePath || !this.persistEnabled) return;
28861
- const file = path27__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
29284
+ const file = path28__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
28862
29285
  try {
28863
29286
  const raw = await fs11__default.default.readFile(file, "utf-8");
28864
29287
  const specs = JSON.parse(raw);
@@ -28882,8 +29305,8 @@ Required capability: ${description.slice(0, 300)}`;
28882
29305
  }
28883
29306
  async persist() {
28884
29307
  if (!this.workspacePath || !this.persistEnabled) return;
28885
- const dir = path27__default.default.join(this.workspacePath, ".cascade");
28886
- const file = path27__default.default.join(dir, DYNAMIC_TOOLS_FILE);
29308
+ const dir = path28__default.default.join(this.workspacePath, ".cascade");
29309
+ const file = path28__default.default.join(dir, DYNAMIC_TOOLS_FILE);
28887
29310
  try {
28888
29311
  await fs11__default.default.mkdir(dir, { recursive: true });
28889
29312
  await fs11__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
@@ -28903,12 +29326,12 @@ var WorldStateDB = class {
28903
29326
  constructor(workspacePath, debugMode = false) {
28904
29327
  this.workspacePath = workspacePath;
28905
29328
  this.debugMode = debugMode;
28906
- const cascadeDir = path27__default.default.join(workspacePath, ".cascade");
28907
- if (!fs26__default.default.existsSync(cascadeDir)) {
28908
- fs26__default.default.mkdirSync(cascadeDir, { recursive: true });
29329
+ const cascadeDir = path28__default.default.join(workspacePath, ".cascade");
29330
+ if (!fs27__default.default.existsSync(cascadeDir)) {
29331
+ fs27__default.default.mkdirSync(cascadeDir, { recursive: true });
28909
29332
  }
28910
- this.keyPath = path27__default.default.join(cascadeDir, "world_state.key");
28911
- this.dbPath = path27__default.default.join(cascadeDir, "world_state.db");
29333
+ this.keyPath = path28__default.default.join(cascadeDir, "world_state.key");
29334
+ this.dbPath = path28__default.default.join(cascadeDir, "world_state.db");
28912
29335
  this.initEncryptionKey();
28913
29336
  this.db = new Database2__default.default(this.dbPath);
28914
29337
  this.db.pragma("journal_mode = WAL");
@@ -28965,11 +29388,11 @@ var WorldStateDB = class {
28965
29388
  ).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
28966
29389
  }
28967
29390
  initEncryptionKey() {
28968
- if (fs26__default.default.existsSync(this.keyPath)) {
28969
- this.encryptionKey = fs26__default.default.readFileSync(this.keyPath);
29391
+ if (fs27__default.default.existsSync(this.keyPath)) {
29392
+ this.encryptionKey = fs27__default.default.readFileSync(this.keyPath);
28970
29393
  } else {
28971
29394
  this.encryptionKey = crypto__default.default.randomBytes(32);
28972
- fs26__default.default.writeFileSync(this.keyPath, this.encryptionKey);
29395
+ fs27__default.default.writeFileSync(this.keyPath, this.encryptionKey);
28973
29396
  }
28974
29397
  }
28975
29398
  encrypt(text) {
@@ -29255,9 +29678,9 @@ var WorldStateDB = class {
29255
29678
  dumpDebugIfNeeded() {
29256
29679
  if (!this.debugMode) return;
29257
29680
  try {
29258
- const dumpPath = path27__default.default.join(os6__default.default.tmpdir(), "cascade_world_state_debug.json");
29681
+ const dumpPath = path28__default.default.join(os6__default.default.tmpdir(), "cascade_world_state_debug.json");
29259
29682
  const entries = this.getAllEntries();
29260
- fs26__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
29683
+ fs27__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
29261
29684
  } catch (err) {
29262
29685
  console.error("Failed to dump debug world state", err);
29263
29686
  }
@@ -29313,11 +29736,11 @@ var ResumeStore = class {
29313
29736
  version: RESUME_SCHEMA_VERSION,
29314
29737
  createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
29315
29738
  };
29316
- await fs26.promises.mkdir(this.dir, { recursive: true });
29317
- const finalPath = path27__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
29739
+ await fs27.promises.mkdir(this.dir, { recursive: true });
29740
+ const finalPath = path28__default.default.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
29318
29741
  const tempPath = `${finalPath}.${process.pid}.tmp`;
29319
- await fs26.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
29320
- await fs26.promises.rename(tempPath, finalPath);
29742
+ await fs27.promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
29743
+ await fs27.promises.rename(tempPath, finalPath);
29321
29744
  await this.prune();
29322
29745
  return true;
29323
29746
  } catch {
@@ -29328,7 +29751,7 @@ var ResumeStore = class {
29328
29751
  async list() {
29329
29752
  let entries;
29330
29753
  try {
29331
- entries = await fs26.promises.readdir(this.dir);
29754
+ entries = await fs27.promises.readdir(this.dir);
29332
29755
  } catch {
29333
29756
  return [];
29334
29757
  }
@@ -29337,7 +29760,7 @@ var ResumeStore = class {
29337
29760
  const checkpoints = [];
29338
29761
  for (const candidate of candidates) {
29339
29762
  try {
29340
- const raw = await fs26.promises.readFile(path27__default.default.join(this.dir, candidate.name), "utf-8");
29763
+ const raw = await fs27.promises.readFile(path28__default.default.join(this.dir, candidate.name), "utf-8");
29341
29764
  const parsed = JSON.parse(raw);
29342
29765
  if (isCheckpoint(parsed)) checkpoints.push(parsed);
29343
29766
  } catch {
@@ -29371,12 +29794,12 @@ var ResumeStore = class {
29371
29794
  const target = taskId ? all.find((c) => c.taskId === taskId) : all[0];
29372
29795
  if (!target) return null;
29373
29796
  try {
29374
- const entries = await fs26.promises.readdir(this.dir);
29797
+ const entries = await fs27.promises.readdir(this.dir);
29375
29798
  const wanted = safeId(target.taskId);
29376
29799
  const source = entries.find((name) => FILE_PATTERN.exec(name)?.[2] === wanted);
29377
29800
  if (!source) return null;
29378
29801
  const claimId = `${this.now()}-${safeId(target.taskId)}`;
29379
- await fs26.promises.rename(path27__default.default.join(this.dir, source), path27__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
29802
+ await fs27.promises.rename(path28__default.default.join(this.dir, source), path28__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
29380
29803
  return { checkpoint: target, claimId };
29381
29804
  } catch {
29382
29805
  return null;
@@ -29385,9 +29808,9 @@ var ResumeStore = class {
29385
29808
  /** Give a claim back, so the work stays recoverable after a failed start. */
29386
29809
  async release(claimId) {
29387
29810
  try {
29388
- await fs26.promises.rename(
29389
- path27__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
29390
- path27__default.default.join(this.dir, `${claimId}.json`)
29811
+ await fs27.promises.rename(
29812
+ path28__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
29813
+ path28__default.default.join(this.dir, `${claimId}.json`)
29391
29814
  );
29392
29815
  } catch {
29393
29816
  }
@@ -29395,7 +29818,7 @@ var ResumeStore = class {
29395
29818
  /** Discard a claim for good — the resumed run has taken ownership. */
29396
29819
  async settle(claimId) {
29397
29820
  try {
29398
- await fs26.promises.rm(path27__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
29821
+ await fs27.promises.rm(path28__default.default.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
29399
29822
  } catch {
29400
29823
  }
29401
29824
  }
@@ -29417,15 +29840,15 @@ var ResumeStore = class {
29417
29840
  async reclaimStale() {
29418
29841
  let reclaimed = 0;
29419
29842
  try {
29420
- const entries = await fs26.promises.readdir(this.dir);
29843
+ const entries = await fs27.promises.readdir(this.dir);
29421
29844
  const cutoff = this.now() - this.claimTimeoutMs;
29422
29845
  for (const name of entries) {
29423
29846
  if (!name.endsWith(CLAIM_SUFFIX)) continue;
29424
29847
  const stamp2 = Number(/^(\d+)-/.exec(name)?.[1]);
29425
29848
  if (!Number.isFinite(stamp2) || stamp2 >= cutoff) continue;
29426
- await fs26.promises.rename(
29427
- path27__default.default.join(this.dir, name),
29428
- path27__default.default.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
29849
+ await fs27.promises.rename(
29850
+ path28__default.default.join(this.dir, name),
29851
+ path28__default.default.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
29429
29852
  );
29430
29853
  reclaimed++;
29431
29854
  }
@@ -29436,12 +29859,12 @@ var ResumeStore = class {
29436
29859
  /** Remove every checkpoint for a task id. */
29437
29860
  async delete(taskId) {
29438
29861
  try {
29439
- const entries = await fs26.promises.readdir(this.dir);
29862
+ const entries = await fs27.promises.readdir(this.dir);
29440
29863
  const wanted = safeId(taskId);
29441
29864
  await Promise.all(entries.map(async (name) => {
29442
29865
  const match = FILE_PATTERN.exec(name);
29443
29866
  if (match?.[2] === wanted) {
29444
- await fs26.promises.rm(path27__default.default.join(this.dir, name), { force: true });
29867
+ await fs27.promises.rm(path28__default.default.join(this.dir, name), { force: true });
29445
29868
  }
29446
29869
  }));
29447
29870
  } catch {
@@ -29450,13 +29873,13 @@ var ResumeStore = class {
29450
29873
  /** Drop expired checkpoints, then any beyond the newest `maxCheckpoints`. */
29451
29874
  async prune() {
29452
29875
  try {
29453
- const entries = await fs26.promises.readdir(this.dir);
29876
+ const entries = await fs27.promises.readdir(this.dir);
29454
29877
  const cutoff = this.now() - this.maxAgeMs;
29455
29878
  const files = entries.map((name) => ({ name, match: FILE_PATTERN.exec(name) })).filter((e) => e.match !== null).map((e) => ({ name: e.name, timestamp: Number(e.match[1]) })).sort((a, b) => b.timestamp - a.timestamp);
29456
29879
  const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
29457
29880
  const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
29458
29881
  await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
29459
- (name) => fs26.promises.rm(path27__default.default.join(this.dir, name), { force: true })
29882
+ (name) => fs27.promises.rm(path28__default.default.join(this.dir, name), { force: true })
29460
29883
  ));
29461
29884
  } catch {
29462
29885
  }
@@ -29833,7 +30256,7 @@ var WorkspaceIndex = class {
29833
30256
  isIgnored;
29834
30257
  chunker;
29835
30258
  constructor(opts) {
29836
- this.root = path27__default.default.resolve(opts.root);
30259
+ this.root = path28__default.default.resolve(opts.root);
29837
30260
  this.db = opts.db;
29838
30261
  this.namespace = opts.namespace ?? "code";
29839
30262
  this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
@@ -29865,14 +30288,14 @@ var WorkspaceIndex = class {
29865
30288
  return;
29866
30289
  }
29867
30290
  for (const entry of entries) {
29868
- const abs = path27__default.default.join(dir, entry.name);
30291
+ const abs = path28__default.default.join(dir, entry.name);
29869
30292
  if (entry.isDirectory()) {
29870
30293
  if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
29871
30294
  await walk(abs);
29872
30295
  } else if (entry.isFile()) {
29873
- const ext = path27__default.default.extname(entry.name).slice(1).toLowerCase();
30296
+ const ext = path28__default.default.extname(entry.name).slice(1).toLowerCase();
29874
30297
  if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
29875
- out.push(path27__default.default.relative(this.root, abs));
30298
+ out.push(path28__default.default.relative(this.root, abs));
29876
30299
  }
29877
30300
  }
29878
30301
  };
@@ -29902,7 +30325,7 @@ var WorkspaceIndex = class {
29902
30325
  const entries = [];
29903
30326
  for (const rel of rels) {
29904
30327
  try {
29905
- const abs = path27__default.default.join(this.root, rel);
30328
+ const abs = path28__default.default.join(this.root, rel);
29906
30329
  const stat = await fs11__default.default.stat(abs);
29907
30330
  if (stat.size > this.maxFileBytes) continue;
29908
30331
  const buf = await fs11__default.default.readFile(abs);
@@ -30392,13 +30815,13 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
30392
30815
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
30393
30816
  try {
30394
30817
  this.router.setDeadModelStore(new DeadModelStore(
30395
- fileDeadModelPersistence(path27__default.default.join(workspacePath, ".cascade", "dead-models.json"))
30818
+ fileDeadModelPersistence(path28__default.default.join(workspacePath, ".cascade", "dead-models.json"))
30396
30819
  ));
30397
30820
  } catch {
30398
30821
  }
30399
30822
  try {
30400
30823
  this.resumeStore = new ResumeStore({
30401
- dir: path27__default.default.join(workspacePath, ".cascade", "resume")
30824
+ dir: path28__default.default.join(workspacePath, ".cascade", "resume")
30402
30825
  });
30403
30826
  } catch {
30404
30827
  }
@@ -30486,8 +30909,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
30486
30909
  this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
30487
30910
  return;
30488
30911
  }
30489
- const dbPath = ci.dbPath || path27__default.default.join(this.workspacePath, ".cascade", "code-index.db");
30490
- fs26__default.default.mkdirSync(path27__default.default.dirname(dbPath), { recursive: true });
30912
+ const dbPath = ci.dbPath || path28__default.default.join(this.workspacePath, ".cascade", "code-index.db");
30913
+ fs27__default.default.mkdirSync(path28__default.default.dirname(dbPath), { recursive: true });
30491
30914
  const db = new Database2__default.default(dbPath);
30492
30915
  db.pragma("journal_mode = WAL");
30493
30916
  const complete = chatCompleterFromProviders(this.config.providers);
@@ -32261,8 +32684,8 @@ function replaceInPlace(target, source) {
32261
32684
  }
32262
32685
  function writeConfigFile(configPath, config) {
32263
32686
  try {
32264
- fs26__default.default.mkdirSync(path27__default.default.dirname(configPath), { recursive: true });
32265
- fs26__default.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
32687
+ fs27__default.default.mkdirSync(path28__default.default.dirname(configPath), { recursive: true });
32688
+ fs27__default.default.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
32266
32689
  return { ok: true };
32267
32690
  } catch (err) {
32268
32691
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -32672,7 +33095,7 @@ function aggregateCostStats(sessions, opts = {}) {
32672
33095
  }
32673
33096
 
32674
33097
  // src/dashboard/server.ts
32675
- var __dirname$1 = path27__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
33098
+ var __dirname$1 = path28__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
32676
33099
  var ORPHANED_ESCALATION_GRACE_MS = 3e4;
32677
33100
  var MASK = "***";
32678
33101
  function masked(key, value) {
@@ -32991,7 +33414,7 @@ var DashboardServer = class {
32991
33414
  * silently and still typecheck.
32992
33415
  */
32993
33416
  persistConfig(config) {
32994
- const result = writeConfigFile(path27__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
33417
+ const result = writeConfigFile(path28__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
32995
33418
  if (!result.ok) console.warn(`[dashboard] Failed to persist config: ${result.error}`);
32996
33419
  return result;
32997
33420
  }
@@ -33010,7 +33433,7 @@ var DashboardServer = class {
33010
33433
  */
33011
33434
  syncGlobalCredentials() {
33012
33435
  try {
33013
- saveGlobalCredentials(path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
33436
+ saveGlobalCredentials(path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
33014
33437
  } catch (err) {
33015
33438
  console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
33016
33439
  }
@@ -33025,15 +33448,15 @@ var DashboardServer = class {
33025
33448
  resolveDashboardSecret() {
33026
33449
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
33027
33450
  if (fromConfig) return fromConfig;
33028
- const secretPath = path27__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
33451
+ const secretPath = path28__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
33029
33452
  try {
33030
- if (fs26__default.default.existsSync(secretPath)) {
33031
- const existing = fs26__default.default.readFileSync(secretPath, "utf-8").trim();
33453
+ if (fs27__default.default.existsSync(secretPath)) {
33454
+ const existing = fs27__default.default.readFileSync(secretPath, "utf-8").trim();
33032
33455
  if (existing.length >= 16) return existing;
33033
33456
  }
33034
33457
  const generated = crypto.randomUUID();
33035
- fs26__default.default.mkdirSync(path27__default.default.dirname(secretPath), { recursive: true });
33036
- fs26__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
33458
+ fs27__default.default.mkdirSync(path28__default.default.dirname(secretPath), { recursive: true });
33459
+ fs27__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
33037
33460
  if (this.config.dashboard.auth) {
33038
33461
  console.warn(
33039
33462
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -33060,7 +33483,7 @@ var DashboardServer = class {
33060
33483
  // ── Setup ─────────────────────────────────────
33061
33484
  getGlobalStore() {
33062
33485
  if (!this.globalStore) {
33063
- const globalDbPath = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33486
+ const globalDbPath = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33064
33487
  this.globalStore = new MemoryStore(globalDbPath);
33065
33488
  }
33066
33489
  return this.globalStore;
@@ -33440,12 +33863,12 @@ ${prompt}`;
33440
33863
  }
33441
33864
  }
33442
33865
  watchRuntimeChanges() {
33443
- const workspaceDbPath = path27__default.default.join(this.workspacePath, CASCADE_DB_FILE);
33444
- const globalDbPath = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33866
+ const workspaceDbPath = path28__default.default.join(this.workspacePath, CASCADE_DB_FILE);
33867
+ const globalDbPath = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33445
33868
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
33446
33869
  for (const watchPath of watchPaths) {
33447
- if (!fs26__default.default.existsSync(watchPath)) continue;
33448
- fs26__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
33870
+ if (!fs27__default.default.existsSync(watchPath)) continue;
33871
+ fs27__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
33449
33872
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
33450
33873
  });
33451
33874
  }
@@ -33567,7 +33990,7 @@ ${prompt}`;
33567
33990
  const sessionId = req.params.id;
33568
33991
  this.store.deleteSession(sessionId);
33569
33992
  this.store.deleteRuntimeSession(sessionId);
33570
- const globalDbPath = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33993
+ const globalDbPath = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33571
33994
  const globalStore = new MemoryStore(globalDbPath);
33572
33995
  try {
33573
33996
  globalStore.deleteRuntimeSession(sessionId);
@@ -33660,7 +34083,7 @@ ${prompt}`;
33660
34083
  });
33661
34084
  this.app.delete("/api/sessions", auth2, (req, res) => {
33662
34085
  const body = req.body;
33663
- const globalDbPath = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
34086
+ const globalDbPath = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33664
34087
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
33665
34088
  const globalStore = new MemoryStore(globalDbPath);
33666
34089
  try {
@@ -33683,7 +34106,7 @@ ${prompt}`;
33683
34106
  });
33684
34107
  this.app.delete("/api/runtime", auth2, (_req, res) => {
33685
34108
  this.store.deleteAllRuntimeNodes();
33686
- const globalDbPath = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
34109
+ const globalDbPath = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33687
34110
  const globalStore = new MemoryStore(globalDbPath);
33688
34111
  try {
33689
34112
  globalStore.deleteAllRuntimeNodes();
@@ -33768,12 +34191,12 @@ ${prompt}`;
33768
34191
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
33769
34192
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
33770
34193
  try {
33771
- const configPath = path27__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
33772
- const existing = fs26__default.default.existsSync(configPath) ? JSON.parse(fs26__default.default.readFileSync(configPath, "utf-8")) : {};
34194
+ const configPath = path28__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
34195
+ const existing = fs27__default.default.existsSync(configPath) ? JSON.parse(fs27__default.default.readFileSync(configPath, "utf-8")) : {};
33773
34196
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
33774
34197
  const tmp = configPath + ".tmp";
33775
- fs26__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
33776
- fs26__default.default.renameSync(tmp, configPath);
34198
+ fs27__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
34199
+ fs27__default.default.renameSync(tmp, configPath);
33777
34200
  res.json({ ok: true });
33778
34201
  } catch (err) {
33779
34202
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -33801,7 +34224,7 @@ ${prompt}`;
33801
34224
  this.app.get("/api/runtime", auth2, (req, res) => {
33802
34225
  const scope = req.query["scope"] ?? "workspace";
33803
34226
  if (scope === "global") {
33804
- const globalDbPath = path27__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
34227
+ const globalDbPath = path28__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33805
34228
  const globalStore = new MemoryStore(globalDbPath);
33806
34229
  try {
33807
34230
  res.json({
@@ -34142,13 +34565,13 @@ ${prompt}`;
34142
34565
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
34143
34566
  }
34144
34567
  });
34145
- const prodPath = path27__default.default.resolve(__dirname$1, "../web/dist");
34146
- const devPath = path27__default.default.resolve(__dirname$1, "../../web/dist");
34147
- const webDistPath = fs26__default.default.existsSync(prodPath) ? prodPath : devPath;
34148
- if (fs26__default.default.existsSync(webDistPath)) {
34568
+ const prodPath = path28__default.default.resolve(__dirname$1, "../web/dist");
34569
+ const devPath = path28__default.default.resolve(__dirname$1, "../../web/dist");
34570
+ const webDistPath = fs27__default.default.existsSync(prodPath) ? prodPath : devPath;
34571
+ if (fs27__default.default.existsSync(webDistPath)) {
34149
34572
  this.app.use(express__default.default.static(webDistPath));
34150
34573
  this.app.get("*", (_req, res) => {
34151
- res.sendFile(path27__default.default.join(webDistPath, "index.html"));
34574
+ res.sendFile(path28__default.default.join(webDistPath, "index.html"));
34152
34575
  });
34153
34576
  } else {
34154
34577
  this.app.get("/", (_req, res) => {
@@ -34335,14 +34758,14 @@ function planRetrieval(ctx) {
34335
34758
  }
34336
34759
  var CLOUD_SESSION_FILE = "cloud-session.json";
34337
34760
  function globalDir(homeDir = os6__default.default.homedir()) {
34338
- return path27__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
34761
+ return path28__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
34339
34762
  }
34340
34763
  function cloudSessionPath(dir = globalDir()) {
34341
- return path27__default.default.join(dir, CLOUD_SESSION_FILE);
34764
+ return path28__default.default.join(dir, CLOUD_SESSION_FILE);
34342
34765
  }
34343
34766
  function loadCloudSession(dir = globalDir()) {
34344
34767
  try {
34345
- const raw = fs26__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
34768
+ const raw = fs27__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
34346
34769
  const parsed = JSON.parse(raw);
34347
34770
  if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
34348
34771
  return parsed;
@@ -34351,17 +34774,17 @@ function loadCloudSession(dir = globalDir()) {
34351
34774
  }
34352
34775
  }
34353
34776
  function saveCloudSession(session, dir = globalDir()) {
34354
- fs26__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
34777
+ fs27__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
34355
34778
  const file = cloudSessionPath(dir);
34356
- fs26__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
34779
+ fs27__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
34357
34780
  try {
34358
- fs26__default.default.chmodSync(file, 384);
34781
+ fs27__default.default.chmodSync(file, 384);
34359
34782
  } catch {
34360
34783
  }
34361
34784
  }
34362
34785
  function clearCloudSession(dir = globalDir()) {
34363
34786
  try {
34364
- fs26__default.default.rmSync(cloudSessionPath(dir));
34787
+ fs27__default.default.rmSync(cloudSessionPath(dir));
34365
34788
  } catch {
34366
34789
  }
34367
34790
  }
@@ -34533,16 +34956,16 @@ var CloudClient = class _CloudClient {
34533
34956
  return (await this.refresh()).accessToken;
34534
34957
  }
34535
34958
  /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
34536
- async authed(path29, init = {}) {
34537
- const call = (token) => fetch(this.url(path29), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
34959
+ async authed(path30, init = {}) {
34960
+ const call = (token) => fetch(this.url(path30), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
34538
34961
  let res = await call(await this.accessToken());
34539
34962
  if (res.status === 401) res = await call((await this.refresh()).accessToken);
34540
34963
  if (!res.ok) throw new Error(`Request failed (${res.status}).`);
34541
34964
  const text = await res.text();
34542
34965
  return text ? JSON.parse(text) : {};
34543
34966
  }
34544
- authedGet(path29) {
34545
- return this.authed(path29);
34967
+ authedGet(path30) {
34968
+ return this.authed(path30);
34546
34969
  }
34547
34970
  async fetchMe(token) {
34548
34971
  const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
@@ -34923,6 +35346,7 @@ exports.CHARS_PER_TOKEN = CHARS_PER_TOKEN2;
34923
35346
  exports.COMPLEXITY_T2_COUNT = COMPLEXITY_T2_COUNT;
34924
35347
  exports.Cascade = Cascade;
34925
35348
  exports.CascadeCancelledError = CascadeCancelledError;
35349
+ exports.CascadeConfigSchema = CascadeConfigSchema;
34926
35350
  exports.CascadeIgnore = CascadeIgnore;
34927
35351
  exports.CascadeRouter = CascadeRouter;
34928
35352
  exports.CascadeToolError = CascadeToolError;