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.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import Database2 from 'better-sqlite3';
2
- import path27, { dirname } from 'path';
3
- import fs26, { promises, mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
2
+ import path28, { dirname } from 'path';
3
+ import fs27, { promises, mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
4
4
  import os6 from 'os';
5
5
  import crypto, { randomUUID, randomBytes, createHash, timingSafeEqual, webcrypto } from 'crypto';
6
6
  import EventEmitter, { setMaxListeners } from 'events';
7
7
  import { glob } from 'glob';
8
8
  import Anthropic from '@anthropic-ai/sdk';
9
- import dns from 'dns';
9
+ import dnsCallback from 'dns';
10
10
  import http, { createServer } from 'http';
11
11
  import https from 'https';
12
12
  import zlib from 'zlib';
@@ -23,6 +23,7 @@ import { simpleGit } from 'simple-git';
23
23
  import PDFDocument from 'pdfkit';
24
24
  import dns2 from 'dns/promises';
25
25
  import net from 'net';
26
+ import { Agent } from 'undici';
26
27
  import { Client } from '@modelcontextprotocol/sdk/client/index.js';
27
28
  import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
28
29
  import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
@@ -132,16 +133,16 @@ var require_isarray = __commonJS({
132
133
  }
133
134
  });
134
135
 
135
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js
136
+ // node_modules/readable-stream/lib/internal/streams/stream.js
136
137
  var require_stream = __commonJS({
137
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/stream.js"(exports, module) {
138
+ "node_modules/readable-stream/lib/internal/streams/stream.js"(exports, module) {
138
139
  module.exports = __require("stream");
139
140
  }
140
141
  });
141
142
 
142
- // node_modules/jszip/node_modules/safe-buffer/index.js
143
+ // node_modules/readable-stream/node_modules/safe-buffer/index.js
143
144
  var require_safe_buffer = __commonJS({
144
- "node_modules/jszip/node_modules/safe-buffer/index.js"(exports, module) {
145
+ "node_modules/readable-stream/node_modules/safe-buffer/index.js"(exports, module) {
145
146
  var buffer = __require("buffer");
146
147
  var Buffer2 = buffer.Buffer;
147
148
  function copyProps(src, dst) {
@@ -259,7 +260,7 @@ var require_util = __commonJS({
259
260
  typeof arg === "undefined";
260
261
  }
261
262
  exports.isPrimitive = isPrimitive;
262
- exports.isBuffer = Buffer.isBuffer;
263
+ exports.isBuffer = __require("buffer").Buffer.isBuffer;
263
264
  function objectToString(o) {
264
265
  return Object.prototype.toString.call(o);
265
266
  }
@@ -312,9 +313,9 @@ var require_inherits = __commonJS({
312
313
  }
313
314
  });
314
315
 
315
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js
316
+ // node_modules/readable-stream/lib/internal/streams/BufferList.js
316
317
  var require_BufferList = __commonJS({
317
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/BufferList.js"(exports, module) {
318
+ "node_modules/readable-stream/lib/internal/streams/BufferList.js"(exports, module) {
318
319
  function _classCallCheck(instance, Constructor) {
319
320
  if (!(instance instanceof Constructor)) {
320
321
  throw new TypeError("Cannot call a class as a function");
@@ -389,9 +390,9 @@ var require_BufferList = __commonJS({
389
390
  }
390
391
  });
391
392
 
392
- // node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js
393
+ // node_modules/readable-stream/lib/internal/streams/destroy.js
393
394
  var require_destroy = __commonJS({
394
- "node_modules/jszip/node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
395
+ "node_modules/readable-stream/lib/internal/streams/destroy.js"(exports, module) {
395
396
  var pna = require_process_nextick_args();
396
397
  function destroy(err, cb) {
397
398
  var _this = this;
@@ -464,9 +465,9 @@ var require_node = __commonJS({
464
465
  }
465
466
  });
466
467
 
467
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js
468
+ // node_modules/readable-stream/lib/_stream_writable.js
468
469
  var require_stream_writable = __commonJS({
469
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_writable.js"(exports, module) {
470
+ "node_modules/readable-stream/lib/_stream_writable.js"(exports, module) {
470
471
  var pna = require_process_nextick_args();
471
472
  module.exports = Writable;
472
473
  function CorkedRequest(state) {
@@ -903,9 +904,9 @@ var require_stream_writable = __commonJS({
903
904
  }
904
905
  });
905
906
 
906
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js
907
+ // node_modules/readable-stream/lib/_stream_duplex.js
907
908
  var require_stream_duplex = __commonJS({
908
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_duplex.js"(exports, module) {
909
+ "node_modules/readable-stream/lib/_stream_duplex.js"(exports, module) {
909
910
  var pna = require_process_nextick_args();
910
911
  var objectKeys = Object.keys || function(obj) {
911
912
  var keys2 = [];
@@ -979,10 +980,67 @@ var require_stream_duplex = __commonJS({
979
980
  }
980
981
  });
981
982
 
982
- // node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js
983
+ // node_modules/string_decoder/node_modules/safe-buffer/index.js
984
+ var require_safe_buffer2 = __commonJS({
985
+ "node_modules/string_decoder/node_modules/safe-buffer/index.js"(exports, module) {
986
+ var buffer = __require("buffer");
987
+ var Buffer2 = buffer.Buffer;
988
+ function copyProps(src, dst) {
989
+ for (var key in src) {
990
+ dst[key] = src[key];
991
+ }
992
+ }
993
+ if (Buffer2.from && Buffer2.alloc && Buffer2.allocUnsafe && Buffer2.allocUnsafeSlow) {
994
+ module.exports = buffer;
995
+ } else {
996
+ copyProps(buffer, exports);
997
+ exports.Buffer = SafeBuffer;
998
+ }
999
+ function SafeBuffer(arg, encodingOrOffset, length) {
1000
+ return Buffer2(arg, encodingOrOffset, length);
1001
+ }
1002
+ copyProps(Buffer2, SafeBuffer);
1003
+ SafeBuffer.from = function(arg, encodingOrOffset, length) {
1004
+ if (typeof arg === "number") {
1005
+ throw new TypeError("Argument must not be a number");
1006
+ }
1007
+ return Buffer2(arg, encodingOrOffset, length);
1008
+ };
1009
+ SafeBuffer.alloc = function(size, fill, encoding) {
1010
+ if (typeof size !== "number") {
1011
+ throw new TypeError("Argument must be a number");
1012
+ }
1013
+ var buf = Buffer2(size);
1014
+ if (fill !== void 0) {
1015
+ if (typeof encoding === "string") {
1016
+ buf.fill(fill, encoding);
1017
+ } else {
1018
+ buf.fill(fill);
1019
+ }
1020
+ } else {
1021
+ buf.fill(0);
1022
+ }
1023
+ return buf;
1024
+ };
1025
+ SafeBuffer.allocUnsafe = function(size) {
1026
+ if (typeof size !== "number") {
1027
+ throw new TypeError("Argument must be a number");
1028
+ }
1029
+ return Buffer2(size);
1030
+ };
1031
+ SafeBuffer.allocUnsafeSlow = function(size) {
1032
+ if (typeof size !== "number") {
1033
+ throw new TypeError("Argument must be a number");
1034
+ }
1035
+ return buffer.SlowBuffer(size);
1036
+ };
1037
+ }
1038
+ });
1039
+
1040
+ // node_modules/string_decoder/lib/string_decoder.js
983
1041
  var require_string_decoder = __commonJS({
984
- "node_modules/jszip/node_modules/string_decoder/lib/string_decoder.js"(exports) {
985
- var Buffer2 = require_safe_buffer().Buffer;
1042
+ "node_modules/string_decoder/lib/string_decoder.js"(exports) {
1043
+ var Buffer2 = require_safe_buffer2().Buffer;
986
1044
  var isEncoding = Buffer2.isEncoding || function(encoding) {
987
1045
  encoding = "" + encoding;
988
1046
  switch (encoding && encoding.toLowerCase()) {
@@ -1216,9 +1274,9 @@ var require_string_decoder = __commonJS({
1216
1274
  }
1217
1275
  });
1218
1276
 
1219
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js
1277
+ // node_modules/readable-stream/lib/_stream_readable.js
1220
1278
  var require_stream_readable = __commonJS({
1221
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_readable.js"(exports, module) {
1279
+ "node_modules/readable-stream/lib/_stream_readable.js"(exports, module) {
1222
1280
  var pna = require_process_nextick_args();
1223
1281
  module.exports = Readable2;
1224
1282
  var isArray = require_isarray();
@@ -1901,9 +1959,9 @@ var require_stream_readable = __commonJS({
1901
1959
  }
1902
1960
  });
1903
1961
 
1904
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js
1962
+ // node_modules/readable-stream/lib/_stream_transform.js
1905
1963
  var require_stream_transform = __commonJS({
1906
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_transform.js"(exports, module) {
1964
+ "node_modules/readable-stream/lib/_stream_transform.js"(exports, module) {
1907
1965
  module.exports = Transform;
1908
1966
  var Duplex = require_stream_duplex();
1909
1967
  var util = Object.create(require_util());
@@ -2000,9 +2058,9 @@ var require_stream_transform = __commonJS({
2000
2058
  }
2001
2059
  });
2002
2060
 
2003
- // node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js
2061
+ // node_modules/readable-stream/lib/_stream_passthrough.js
2004
2062
  var require_stream_passthrough = __commonJS({
2005
- "node_modules/jszip/node_modules/readable-stream/lib/_stream_passthrough.js"(exports, module) {
2063
+ "node_modules/readable-stream/lib/_stream_passthrough.js"(exports, module) {
2006
2064
  module.exports = PassThrough;
2007
2065
  var Transform = require_stream_transform();
2008
2066
  var util = Object.create(require_util());
@@ -2018,9 +2076,9 @@ var require_stream_passthrough = __commonJS({
2018
2076
  }
2019
2077
  });
2020
2078
 
2021
- // node_modules/jszip/node_modules/readable-stream/readable.js
2079
+ // node_modules/readable-stream/readable.js
2022
2080
  var require_readable = __commonJS({
2023
- "node_modules/jszip/node_modules/readable-stream/readable.js"(exports, module) {
2081
+ "node_modules/readable-stream/readable.js"(exports, module) {
2024
2082
  var Stream = __require("stream");
2025
2083
  if (process.env.READABLE_STREAM === "disable" && Stream) {
2026
2084
  module.exports = Stream;
@@ -2929,8 +2987,8 @@ var require_utils = __commonJS({
2929
2987
  var result = transform[inputType][outputType](input);
2930
2988
  return result;
2931
2989
  };
2932
- exports.resolve = function(path29) {
2933
- var parts = path29.split("/");
2990
+ exports.resolve = function(path30) {
2991
+ var parts = path30.split("/");
2934
2992
  var result = [];
2935
2993
  for (var index = 0; index < parts.length; index++) {
2936
2994
  var part = parts[index];
@@ -8745,18 +8803,18 @@ var require_object = __commonJS({
8745
8803
  var object = new ZipObject(name, zipObjectContent, o);
8746
8804
  this.files[name] = object;
8747
8805
  };
8748
- var parentFolder = function(path29) {
8749
- if (path29.slice(-1) === "/") {
8750
- path29 = path29.substring(0, path29.length - 1);
8806
+ var parentFolder = function(path30) {
8807
+ if (path30.slice(-1) === "/") {
8808
+ path30 = path30.substring(0, path30.length - 1);
8751
8809
  }
8752
- var lastSlash = path29.lastIndexOf("/");
8753
- return lastSlash > 0 ? path29.substring(0, lastSlash) : "";
8810
+ var lastSlash = path30.lastIndexOf("/");
8811
+ return lastSlash > 0 ? path30.substring(0, lastSlash) : "";
8754
8812
  };
8755
- var forceTrailingSlash = function(path29) {
8756
- if (path29.slice(-1) !== "/") {
8757
- path29 += "/";
8813
+ var forceTrailingSlash = function(path30) {
8814
+ if (path30.slice(-1) !== "/") {
8815
+ path30 += "/";
8758
8816
  }
8759
- return path29;
8817
+ return path30;
8760
8818
  };
8761
8819
  var folderAdd = function(name, createFolders) {
8762
8820
  createFolders = typeof createFolders !== "undefined" ? createFolders : defaults.createFolders;
@@ -9755,12 +9813,12 @@ var init_audit_logger = __esm({
9755
9813
  constructor(workspacePath, debugMode = false) {
9756
9814
  this.workspacePath = workspacePath;
9757
9815
  this.debugMode = debugMode;
9758
- const cascadeDir = path27.join(workspacePath, ".cascade");
9759
- if (!fs26.existsSync(cascadeDir)) {
9760
- fs26.mkdirSync(cascadeDir, { recursive: true });
9816
+ const cascadeDir = path28.join(workspacePath, ".cascade");
9817
+ if (!fs27.existsSync(cascadeDir)) {
9818
+ fs27.mkdirSync(cascadeDir, { recursive: true });
9761
9819
  }
9762
- this.keyPath = path27.join(cascadeDir, "audit_log.key");
9763
- this.dbPath = path27.join(cascadeDir, "audit_log.db");
9820
+ this.keyPath = path28.join(cascadeDir, "audit_log.key");
9821
+ this.dbPath = path28.join(cascadeDir, "audit_log.db");
9764
9822
  this.initEncryptionKey();
9765
9823
  this.db = new Database2(this.dbPath);
9766
9824
  this.db.pragma("journal_mode = WAL");
@@ -9790,11 +9848,11 @@ var init_audit_logger = __esm({
9790
9848
  return crypto.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
9791
9849
  }
9792
9850
  initEncryptionKey() {
9793
- if (fs26.existsSync(this.keyPath)) {
9794
- this.encryptionKey = fs26.readFileSync(this.keyPath);
9851
+ if (fs27.existsSync(this.keyPath)) {
9852
+ this.encryptionKey = fs27.readFileSync(this.keyPath);
9795
9853
  } else {
9796
9854
  this.encryptionKey = crypto.randomBytes(32);
9797
- fs26.writeFileSync(this.keyPath, this.encryptionKey);
9855
+ fs27.writeFileSync(this.keyPath, this.encryptionKey);
9798
9856
  }
9799
9857
  }
9800
9858
  encrypt(text) {
@@ -9872,9 +9930,9 @@ var init_audit_logger = __esm({
9872
9930
  dumpDebugIfNeeded() {
9873
9931
  if (!this.debugMode) return;
9874
9932
  try {
9875
- const dumpPath = path27.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
9933
+ const dumpPath = path28.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
9876
9934
  const entries = this.getAllLogs();
9877
- fs26.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
9935
+ fs27.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
9878
9936
  } catch (err) {
9879
9937
  console.error("Failed to dump debug audit logs", err);
9880
9938
  }
@@ -11565,7 +11623,7 @@ function reconcilePrice(model, live) {
11565
11623
  }
11566
11624
 
11567
11625
  // src/constants.ts
11568
- var CASCADE_VERSION = "0.76.0";
11626
+ var CASCADE_VERSION = "0.77.0";
11569
11627
  var CASCADE_CONFIG_DIR = ".cascade";
11570
11628
  var CASCADE_MD_FILE = "CASCADE.md";
11571
11629
  var CASCADE_IGNORE_FILE = ".cascadeignore";
@@ -11586,64 +11644,88 @@ var DEFAULT_AUTO_SUMMARIZE_AT = 15e4;
11586
11644
  var DEFAULT_MAX_SESSION_MESSAGES = 1e3;
11587
11645
  var DEFAULT_RETENTION_DAYS = 90;
11588
11646
  var MODELS = {
11589
- // Anthropic
11590
- "claude-opus-4": {
11591
- id: "claude-opus-4-5",
11592
- name: "Claude Opus 4",
11647
+ // Anthropic — active Claude API models (2026-08-27).
11648
+ "claude-fable-5": {
11649
+ id: "claude-fable-5",
11650
+ name: "Claude Fable 5",
11593
11651
  provider: "anthropic",
11594
- contextWindow: 2e5,
11652
+ contextWindow: 1e6,
11595
11653
  isVisionCapable: true,
11596
- inputCostPer1kTokens: 0.015,
11597
- outputCostPer1kTokens: 0.075,
11598
- maxOutputTokens: 32e3,
11654
+ inputCostPer1kTokens: 0.01,
11655
+ outputCostPer1kTokens: 0.05,
11656
+ maxOutputTokens: 128e3,
11599
11657
  supportsStreaming: true,
11600
11658
  isLocal: false
11601
11659
  },
11602
- "claude-sonnet-4": {
11603
- id: "claude-sonnet-4-5",
11604
- name: "Claude Sonnet 4",
11660
+ "claude-opus-5": {
11661
+ id: "claude-opus-5",
11662
+ name: "Claude Opus 5",
11605
11663
  provider: "anthropic",
11606
- contextWindow: 2e5,
11664
+ contextWindow: 1e6,
11607
11665
  isVisionCapable: true,
11608
- inputCostPer1kTokens: 3e-3,
11609
- outputCostPer1kTokens: 0.015,
11610
- maxOutputTokens: 16e3,
11666
+ inputCostPer1kTokens: 5e-3,
11667
+ outputCostPer1kTokens: 0.025,
11668
+ maxOutputTokens: 128e3,
11611
11669
  supportsStreaming: true,
11612
11670
  isLocal: false
11613
11671
  },
11614
- "claude-haiku-3-5": {
11615
- id: "claude-haiku-3-5-20251001",
11616
- name: "Claude Haiku 3.5",
11672
+ "claude-opus-4-8": {
11673
+ id: "claude-opus-4-8",
11674
+ name: "Claude Opus 4.8",
11617
11675
  provider: "anthropic",
11618
- contextWindow: 2e5,
11676
+ contextWindow: 1e6,
11619
11677
  isVisionCapable: true,
11620
- inputCostPer1kTokens: 8e-4,
11621
- outputCostPer1kTokens: 4e-3,
11622
- maxOutputTokens: 8e3,
11678
+ inputCostPer1kTokens: 5e-3,
11679
+ outputCostPer1kTokens: 0.025,
11680
+ maxOutputTokens: 128e3,
11623
11681
  supportsStreaming: true,
11624
11682
  isLocal: false
11625
11683
  },
11626
- "claude-haiku-4-5": {
11627
- id: "claude-haiku-4-5-20251001",
11628
- name: "Claude Haiku 4.5",
11684
+ "claude-opus-4-7": {
11685
+ id: "claude-opus-4-7",
11686
+ name: "Claude Opus 4.7",
11629
11687
  provider: "anthropic",
11630
- contextWindow: 2e5,
11688
+ contextWindow: 1e6,
11631
11689
  isVisionCapable: true,
11632
- inputCostPer1kTokens: 8e-4,
11633
- outputCostPer1kTokens: 4e-3,
11634
- maxOutputTokens: 8e3,
11690
+ inputCostPer1kTokens: 5e-3,
11691
+ outputCostPer1kTokens: 0.025,
11692
+ maxOutputTokens: 128e3,
11635
11693
  supportsStreaming: true,
11636
11694
  isLocal: false
11637
11695
  },
11638
- "claude-opus-4-8": {
11639
- id: "claude-opus-4-8",
11640
- name: "Claude Opus 4.8",
11696
+ "claude-opus-4-6": {
11697
+ id: "claude-opus-4-6",
11698
+ name: "Claude Opus 4.6",
11699
+ provider: "anthropic",
11700
+ contextWindow: 1e6,
11701
+ isVisionCapable: true,
11702
+ inputCostPer1kTokens: 5e-3,
11703
+ outputCostPer1kTokens: 0.025,
11704
+ maxOutputTokens: 128e3,
11705
+ supportsStreaming: true,
11706
+ isLocal: false
11707
+ },
11708
+ "claude-opus-4-5": {
11709
+ id: "claude-opus-4-5-20251101",
11710
+ name: "Claude Opus 4.5",
11641
11711
  provider: "anthropic",
11642
11712
  contextWindow: 2e5,
11643
11713
  isVisionCapable: true,
11644
- inputCostPer1kTokens: 0.015,
11645
- outputCostPer1kTokens: 0.075,
11646
- maxOutputTokens: 32e3,
11714
+ inputCostPer1kTokens: 5e-3,
11715
+ outputCostPer1kTokens: 0.025,
11716
+ maxOutputTokens: 64e3,
11717
+ supportsStreaming: true,
11718
+ isLocal: false
11719
+ },
11720
+ "claude-sonnet-5": {
11721
+ id: "claude-sonnet-5",
11722
+ name: "Claude Sonnet 5",
11723
+ provider: "anthropic",
11724
+ contextWindow: 1e6,
11725
+ isVisionCapable: true,
11726
+ inputCostPer1kTokens: 2e-3,
11727
+ outputCostPer1kTokens: 0.01,
11728
+ maxOutputTokens: 128e3,
11647
11729
  supportsStreaming: true,
11648
11730
  isLocal: false
11649
11731
  },
@@ -11651,76 +11733,135 @@ var MODELS = {
11651
11733
  id: "claude-sonnet-4-6",
11652
11734
  name: "Claude Sonnet 4.6",
11653
11735
  provider: "anthropic",
11736
+ contextWindow: 1e6,
11737
+ isVisionCapable: true,
11738
+ inputCostPer1kTokens: 3e-3,
11739
+ outputCostPer1kTokens: 0.015,
11740
+ maxOutputTokens: 64e3,
11741
+ supportsStreaming: true,
11742
+ isLocal: false
11743
+ },
11744
+ "claude-sonnet-4-5": {
11745
+ id: "claude-sonnet-4-5-20250929",
11746
+ name: "Claude Sonnet 4.5",
11747
+ provider: "anthropic",
11654
11748
  contextWindow: 2e5,
11655
11749
  isVisionCapable: true,
11656
11750
  inputCostPer1kTokens: 3e-3,
11657
11751
  outputCostPer1kTokens: 0.015,
11658
- maxOutputTokens: 16e3,
11752
+ maxOutputTokens: 64e3,
11659
11753
  supportsStreaming: true,
11660
11754
  isLocal: false
11661
11755
  },
11662
- // OpenAI
11663
- "gpt-4o": {
11664
- id: "gpt-4o",
11665
- name: "GPT-4o",
11756
+ "claude-haiku-4-5": {
11757
+ id: "claude-haiku-4-5-20251001",
11758
+ name: "Claude Haiku 4.5",
11759
+ provider: "anthropic",
11760
+ contextWindow: 2e5,
11761
+ isVisionCapable: true,
11762
+ inputCostPer1kTokens: 1e-3,
11763
+ outputCostPer1kTokens: 5e-3,
11764
+ maxOutputTokens: 64e3,
11765
+ supportsStreaming: true,
11766
+ isLocal: false
11767
+ },
11768
+ // OpenAI — active chat-capable models used by Cascade's Chat Completions path.
11769
+ "gpt-5.6-sol": {
11770
+ id: "gpt-5.6-sol",
11771
+ name: "GPT-5.6 Sol",
11666
11772
  provider: "openai",
11667
- contextWindow: 128e3,
11773
+ contextWindow: 105e4,
11774
+ isVisionCapable: true,
11775
+ inputCostPer1kTokens: 4e-3,
11776
+ outputCostPer1kTokens: 0.02,
11777
+ maxOutputTokens: 128e3,
11778
+ supportsStreaming: true,
11779
+ isLocal: false
11780
+ },
11781
+ "gpt-5.6-terra": {
11782
+ id: "gpt-5.6-terra",
11783
+ name: "GPT-5.6 Terra",
11784
+ provider: "openai",
11785
+ contextWindow: 105e4,
11786
+ isVisionCapable: true,
11787
+ inputCostPer1kTokens: 2e-3,
11788
+ outputCostPer1kTokens: 0.012,
11789
+ maxOutputTokens: 128e3,
11790
+ supportsStreaming: true,
11791
+ isLocal: false
11792
+ },
11793
+ "gpt-5.6-luna": {
11794
+ id: "gpt-5.6-luna",
11795
+ name: "GPT-5.6 Luna",
11796
+ provider: "openai",
11797
+ contextWindow: 105e4,
11798
+ isVisionCapable: true,
11799
+ inputCostPer1kTokens: 2e-4,
11800
+ outputCostPer1kTokens: 12e-4,
11801
+ maxOutputTokens: 128e3,
11802
+ supportsStreaming: true,
11803
+ isLocal: false
11804
+ },
11805
+ "gpt-5.5": {
11806
+ id: "gpt-5.5",
11807
+ name: "GPT-5.5",
11808
+ provider: "openai",
11809
+ contextWindow: 105e4,
11668
11810
  isVisionCapable: true,
11669
11811
  inputCostPer1kTokens: 5e-3,
11670
- outputCostPer1kTokens: 0.015,
11671
- maxOutputTokens: 16e3,
11812
+ outputCostPer1kTokens: 0.03,
11813
+ maxOutputTokens: 128e3,
11672
11814
  supportsStreaming: true,
11673
11815
  isLocal: false
11674
11816
  },
11675
- "gpt-4o-mini": {
11676
- id: "gpt-4o-mini",
11677
- name: "GPT-4o Mini",
11817
+ "gpt-5.4": {
11818
+ id: "gpt-5.4",
11819
+ name: "GPT-5.4",
11678
11820
  provider: "openai",
11679
- contextWindow: 128e3,
11821
+ contextWindow: 105e4,
11680
11822
  isVisionCapable: true,
11681
- inputCostPer1kTokens: 15e-5,
11682
- outputCostPer1kTokens: 6e-4,
11683
- maxOutputTokens: 16e3,
11823
+ inputCostPer1kTokens: 25e-4,
11824
+ outputCostPer1kTokens: 0.015,
11825
+ maxOutputTokens: 128e3,
11684
11826
  supportsStreaming: true,
11685
11827
  isLocal: false
11686
11828
  },
11687
- "gpt-4.1": {
11688
- id: "gpt-4.1",
11689
- name: "GPT-4.1",
11829
+ "gpt-5.4-mini": {
11830
+ id: "gpt-5.4-mini",
11831
+ name: "GPT-5.4 Mini",
11690
11832
  provider: "openai",
11691
- contextWindow: 1047576,
11833
+ contextWindow: 4e5,
11692
11834
  isVisionCapable: true,
11693
- inputCostPer1kTokens: 2e-3,
11694
- outputCostPer1kTokens: 8e-3,
11695
- maxOutputTokens: 32768,
11835
+ inputCostPer1kTokens: 75e-5,
11836
+ outputCostPer1kTokens: 45e-4,
11837
+ maxOutputTokens: 128e3,
11696
11838
  supportsStreaming: true,
11697
11839
  isLocal: false
11698
11840
  },
11699
- "gpt-4.1-mini": {
11700
- id: "gpt-4.1-mini",
11701
- name: "GPT-4.1 Mini",
11841
+ "gpt-5.4-nano": {
11842
+ id: "gpt-5.4-nano",
11843
+ name: "GPT-5.4 Nano",
11702
11844
  provider: "openai",
11703
- contextWindow: 1047576,
11845
+ contextWindow: 4e5,
11704
11846
  isVisionCapable: true,
11705
- inputCostPer1kTokens: 4e-4,
11706
- outputCostPer1kTokens: 16e-4,
11707
- maxOutputTokens: 32768,
11847
+ inputCostPer1kTokens: 2e-4,
11848
+ outputCostPer1kTokens: 125e-5,
11849
+ maxOutputTokens: 128e3,
11708
11850
  supportsStreaming: true,
11709
11851
  isLocal: false
11710
11852
  },
11711
- "gpt-4.1-nano": {
11712
- id: "gpt-4.1-nano",
11713
- name: "GPT-4.1 Nano",
11853
+ "gpt-5.2": {
11854
+ id: "gpt-5.2",
11855
+ name: "GPT-5.2",
11714
11856
  provider: "openai",
11715
- contextWindow: 1047576,
11857
+ contextWindow: 4e5,
11716
11858
  isVisionCapable: true,
11717
- inputCostPer1kTokens: 1e-4,
11718
- outputCostPer1kTokens: 4e-4,
11719
- maxOutputTokens: 32768,
11859
+ inputCostPer1kTokens: 175e-5,
11860
+ outputCostPer1kTokens: 0.014,
11861
+ maxOutputTokens: 128e3,
11720
11862
  supportsStreaming: true,
11721
11863
  isLocal: false
11722
11864
  },
11723
- // GPT-5 family (offline baseline pricing; the live fetch keeps it current).
11724
11865
  "gpt-5": {
11725
11866
  id: "gpt-5",
11726
11867
  name: "GPT-5",
@@ -11757,79 +11898,149 @@ var MODELS = {
11757
11898
  supportsStreaming: true,
11758
11899
  isLocal: false
11759
11900
  },
11760
- // GPT-5 point releases — genuinely distinct capability, so they route as their
11761
- // own families (not folded into gpt-5). Offline baseline pricing; live fetch
11762
- // keeps it current. gpt-5.5 "Spud" (Apr 2026) is the current SWE-bench leader.
11763
- "gpt-5.5": {
11764
- id: "gpt-5.5",
11765
- name: "GPT-5.5",
11901
+ "gpt-4.1": {
11902
+ id: "gpt-4.1",
11903
+ name: "GPT-4.1",
11766
11904
  provider: "openai",
11767
- contextWindow: 4e5,
11905
+ contextWindow: 1047576,
11768
11906
  isVisionCapable: true,
11769
- inputCostPer1kTokens: 25e-4,
11770
- outputCostPer1kTokens: 0.02,
11771
- maxOutputTokens: 128e3,
11907
+ inputCostPer1kTokens: 2e-3,
11908
+ outputCostPer1kTokens: 8e-3,
11909
+ maxOutputTokens: 32768,
11772
11910
  supportsStreaming: true,
11773
11911
  isLocal: false
11774
11912
  },
11775
- "gpt-5.4": {
11776
- id: "gpt-5.4",
11777
- name: "GPT-5.4",
11913
+ "gpt-4.1-mini": {
11914
+ id: "gpt-4.1-mini",
11915
+ name: "GPT-4.1 Mini",
11778
11916
  provider: "openai",
11779
- contextWindow: 4e5,
11917
+ contextWindow: 1047576,
11780
11918
  isVisionCapable: true,
11781
- inputCostPer1kTokens: 125e-5,
11919
+ inputCostPer1kTokens: 4e-4,
11920
+ outputCostPer1kTokens: 16e-4,
11921
+ maxOutputTokens: 32768,
11922
+ supportsStreaming: true,
11923
+ isLocal: false
11924
+ },
11925
+ "gpt-4.1-nano": {
11926
+ id: "gpt-4.1-nano",
11927
+ name: "GPT-4.1 Nano",
11928
+ provider: "openai",
11929
+ contextWindow: 1047576,
11930
+ isVisionCapable: true,
11931
+ inputCostPer1kTokens: 1e-4,
11932
+ outputCostPer1kTokens: 4e-4,
11933
+ maxOutputTokens: 32768,
11934
+ supportsStreaming: true,
11935
+ isLocal: false
11936
+ },
11937
+ "gpt-4o": {
11938
+ id: "gpt-4o",
11939
+ name: "GPT-4o",
11940
+ provider: "openai",
11941
+ contextWindow: 128e3,
11942
+ isVisionCapable: true,
11943
+ inputCostPer1kTokens: 25e-4,
11782
11944
  outputCostPer1kTokens: 0.01,
11783
- maxOutputTokens: 128e3,
11945
+ maxOutputTokens: 16e3,
11784
11946
  supportsStreaming: true,
11785
11947
  isLocal: false
11786
11948
  },
11787
- "gpt-5.4-mini": {
11788
- id: "gpt-5.4-mini",
11789
- name: "GPT-5.4 Mini",
11949
+ "gpt-4o-mini": {
11950
+ id: "gpt-4o-mini",
11951
+ name: "GPT-4o Mini",
11790
11952
  provider: "openai",
11791
- contextWindow: 4e5,
11953
+ contextWindow: 128e3,
11792
11954
  isVisionCapable: true,
11793
- inputCostPer1kTokens: 25e-5,
11794
- outputCostPer1kTokens: 2e-3,
11795
- maxOutputTokens: 128e3,
11955
+ inputCostPer1kTokens: 15e-5,
11956
+ outputCostPer1kTokens: 6e-4,
11957
+ maxOutputTokens: 16e3,
11796
11958
  supportsStreaming: true,
11797
11959
  isLocal: false
11798
11960
  },
11799
- // Google
11800
- "gemini-2.0-flash": {
11801
- id: "gemini-2.0-flash",
11802
- name: "Gemini 2.0 Flash",
11961
+ // Google Gemini — active GA + preview chat models. Gemini 2.0/1.5 are no
11962
+ // longer advertised because the Gemini API shut those generations down.
11963
+ "gemini-3.7-flash": {
11964
+ id: "gemini-3.7-flash",
11965
+ name: "Gemini 3.7 Flash",
11803
11966
  provider: "gemini",
11804
- contextWindow: 1e6,
11967
+ contextWindow: 1048576,
11805
11968
  isVisionCapable: true,
11806
- inputCostPer1kTokens: 1e-4,
11807
- outputCostPer1kTokens: 4e-4,
11808
- maxOutputTokens: 8e3,
11969
+ inputCostPer1kTokens: 75e-5,
11970
+ outputCostPer1kTokens: 375e-5,
11971
+ maxOutputTokens: 65536,
11809
11972
  supportsStreaming: true,
11810
11973
  isLocal: false
11811
11974
  },
11812
- "gemini-1.5-pro": {
11813
- id: "gemini-1.5-pro",
11814
- name: "Gemini 1.5 Pro",
11975
+ "gemini-3.6-flash": {
11976
+ id: "gemini-3.6-flash",
11977
+ name: "Gemini 3.6 Flash",
11815
11978
  provider: "gemini",
11816
- contextWindow: 1e6,
11979
+ contextWindow: 1048576,
11817
11980
  isVisionCapable: true,
11818
- inputCostPer1kTokens: 125e-5,
11819
- outputCostPer1kTokens: 5e-3,
11820
- maxOutputTokens: 8e3,
11981
+ inputCostPer1kTokens: 75e-5,
11982
+ outputCostPer1kTokens: 375e-5,
11983
+ maxOutputTokens: 65536,
11821
11984
  supportsStreaming: true,
11822
11985
  isLocal: false
11823
11986
  },
11824
- "gemini-2.0-flash-lite": {
11825
- id: "gemini-2.0-flash-lite",
11826
- name: "Gemini 2.0 Flash-Lite",
11987
+ "gemini-3.5-flash": {
11988
+ id: "gemini-3.5-flash",
11989
+ name: "Gemini 3.5 Flash",
11827
11990
  provider: "gemini",
11828
- contextWindow: 1e6,
11991
+ contextWindow: 1048576,
11829
11992
  isVisionCapable: true,
11830
- inputCostPer1kTokens: 5e-5,
11831
- outputCostPer1kTokens: 2e-4,
11832
- maxOutputTokens: 8e3,
11993
+ inputCostPer1kTokens: 15e-4,
11994
+ outputCostPer1kTokens: 9e-3,
11995
+ maxOutputTokens: 65536,
11996
+ supportsStreaming: true,
11997
+ isLocal: false
11998
+ },
11999
+ "gemini-3.5-flash-lite": {
12000
+ id: "gemini-3.5-flash-lite",
12001
+ name: "Gemini 3.5 Flash-Lite",
12002
+ provider: "gemini",
12003
+ contextWindow: 1048576,
12004
+ isVisionCapable: true,
12005
+ inputCostPer1kTokens: 3e-4,
12006
+ outputCostPer1kTokens: 25e-4,
12007
+ maxOutputTokens: 65536,
12008
+ supportsStreaming: true,
12009
+ isLocal: false
12010
+ },
12011
+ "gemini-3.1-flash-lite": {
12012
+ id: "gemini-3.1-flash-lite",
12013
+ name: "Gemini 3.1 Flash-Lite",
12014
+ provider: "gemini",
12015
+ contextWindow: 1048576,
12016
+ isVisionCapable: true,
12017
+ inputCostPer1kTokens: 25e-5,
12018
+ outputCostPer1kTokens: 15e-4,
12019
+ maxOutputTokens: 65536,
12020
+ supportsStreaming: true,
12021
+ isLocal: false
12022
+ },
12023
+ "gemini-3.1-pro-preview": {
12024
+ id: "gemini-3.1-pro-preview",
12025
+ name: "Gemini 3.1 Pro Preview",
12026
+ provider: "gemini",
12027
+ contextWindow: 1048576,
12028
+ isVisionCapable: true,
12029
+ inputCostPer1kTokens: 2e-3,
12030
+ outputCostPer1kTokens: 0.012,
12031
+ maxOutputTokens: 65536,
12032
+ supportsStreaming: true,
12033
+ isLocal: false
12034
+ },
12035
+ "gemini-3-flash-preview": {
12036
+ id: "gemini-3-flash-preview",
12037
+ name: "Gemini 3 Flash Preview",
12038
+ provider: "gemini",
12039
+ contextWindow: 1048576,
12040
+ isVisionCapable: true,
12041
+ inputCostPer1kTokens: 5e-4,
12042
+ outputCostPer1kTokens: 3e-3,
12043
+ maxOutputTokens: 65536,
11833
12044
  supportsStreaming: true,
11834
12045
  isLocal: false
11835
12046
  },
@@ -11837,7 +12048,7 @@ var MODELS = {
11837
12048
  id: "gemini-2.5-pro",
11838
12049
  name: "Gemini 2.5 Pro",
11839
12050
  provider: "gemini",
11840
- contextWindow: 1e6,
12051
+ contextWindow: 1048576,
11841
12052
  isVisionCapable: true,
11842
12053
  inputCostPer1kTokens: 125e-5,
11843
12054
  outputCostPer1kTokens: 0.01,
@@ -11849,10 +12060,22 @@ var MODELS = {
11849
12060
  id: "gemini-2.5-flash",
11850
12061
  name: "Gemini 2.5 Flash",
11851
12062
  provider: "gemini",
11852
- contextWindow: 1e6,
12063
+ contextWindow: 1048576,
11853
12064
  isVisionCapable: true,
11854
- inputCostPer1kTokens: 15e-5,
11855
- outputCostPer1kTokens: 6e-4,
12065
+ inputCostPer1kTokens: 3e-4,
12066
+ outputCostPer1kTokens: 25e-4,
12067
+ maxOutputTokens: 65536,
12068
+ supportsStreaming: true,
12069
+ isLocal: false
12070
+ },
12071
+ "gemini-2.5-flash-lite": {
12072
+ id: "gemini-2.5-flash-lite",
12073
+ name: "Gemini 2.5 Flash-Lite",
12074
+ provider: "gemini",
12075
+ contextWindow: 1048576,
12076
+ isVisionCapable: true,
12077
+ inputCostPer1kTokens: 1e-4,
12078
+ outputCostPer1kTokens: 4e-4,
11856
12079
  maxOutputTokens: 65536,
11857
12080
  supportsStreaming: true,
11858
12081
  isLocal: false
@@ -11922,43 +12145,57 @@ for (const _m of Object.values(MODELS)) {
11922
12145
  _m.pricingUnknown = false;
11923
12146
  }
11924
12147
  var T1_MODEL_PRIORITY = [
12148
+ "gpt-5.6-sol",
12149
+ "claude-fable-5",
12150
+ "claude-opus-5",
11925
12151
  "gpt-5.5",
11926
12152
  "gpt-5.4",
12153
+ "gemini-3.1-pro-preview",
12154
+ "claude-opus-4-8",
12155
+ "claude-sonnet-5",
12156
+ "gemini-3.7-flash",
12157
+ "gpt-5.2",
11927
12158
  "gpt-5",
11928
- "claude-opus-4",
11929
- "claude-sonnet-4",
11930
12159
  "gemini-2.5-pro",
11931
12160
  "gpt-4.1",
11932
- "gpt-4o",
11933
- "gemini-1.5-pro"
12161
+ "gpt-4o"
11934
12162
  ];
11935
12163
  var T2_MODEL_PRIORITY = [
12164
+ "gpt-5.6-terra",
12165
+ "claude-sonnet-5",
12166
+ "gemini-3.7-flash",
12167
+ "gemini-3.6-flash",
11936
12168
  "gpt-5.4-mini",
11937
12169
  "gpt-5-mini",
11938
- "claude-sonnet-4",
12170
+ "claude-sonnet-4-6",
11939
12171
  "claude-haiku-4-5",
11940
- "claude-haiku-3-5",
12172
+ "gemini-3.5-flash",
11941
12173
  "gemini-2.5-flash",
11942
12174
  "gpt-4.1-mini",
11943
12175
  "gpt-4o-mini",
11944
- "gemini-2.0-flash",
11945
12176
  "llama3:70b"
11946
12177
  ];
11947
12178
  var T3_MODEL_PRIORITY = [
12179
+ "gpt-5.6-luna",
12180
+ "gpt-5.4-nano",
12181
+ "gemini-3.5-flash-lite",
12182
+ "gemini-3.1-flash-lite",
12183
+ "claude-haiku-4-5",
11948
12184
  "gpt-5-nano",
12185
+ "gemini-2.5-flash-lite",
11949
12186
  "llama3.2:3b",
11950
12187
  "mistral:7b",
11951
- "claude-haiku-4-5",
11952
- "claude-haiku-3-5",
11953
12188
  "gpt-4.1-nano",
11954
12189
  "gpt-4o-mini",
11955
- "gemini-2.5-flash",
11956
- "gemini-2.0-flash"
12190
+ "gemini-2.5-flash"
11957
12191
  ];
11958
12192
  var VISION_MODEL_PRIORITY = [
11959
- "claude-sonnet-4",
12193
+ "gpt-5.6-sol",
12194
+ "claude-fable-5",
12195
+ "claude-sonnet-5",
12196
+ "gemini-3.7-flash",
11960
12197
  "gpt-4o",
11961
- "gemini-2.0-flash",
12198
+ "gemini-2.5-flash",
11962
12199
  "llava"
11963
12200
  ];
11964
12201
  var COMPLEXITY_T2_COUNT = {
@@ -12139,7 +12376,7 @@ function isChatModel(id, methods) {
12139
12376
  return !NON_CHAT.test(id);
12140
12377
  }
12141
12378
  try {
12142
- dns.setDefaultResultOrder("ipv4first");
12379
+ dnsCallback.setDefaultResultOrder("ipv4first");
12143
12380
  } catch {
12144
12381
  }
12145
12382
  function preferIpv4Host(url) {
@@ -12155,8 +12392,8 @@ function normalizeEndpoint(url) {
12155
12392
  if (!raw) return "";
12156
12393
  try {
12157
12394
  const u = new URL(raw);
12158
- const path29 = stripTrailingSlashes(`${u.pathname}${u.search}`);
12159
- return `${u.protocol}//${u.host.toLowerCase()}${path29}`;
12395
+ const path30 = stripTrailingSlashes(`${u.pathname}${u.search}`);
12396
+ return `${u.protocol}//${u.host.toLowerCase()}${path30}`;
12160
12397
  } catch {
12161
12398
  return raw.toLowerCase();
12162
12399
  }
@@ -12823,52 +13060,127 @@ var OpenAIProvider = class extends BaseProvider {
12823
13060
  }
12824
13061
  };
12825
13062
 
13063
+ // src/providers/cloud-model-catalog.json
13064
+ var cloud_model_catalog_default = {
13065
+ models: [
13066
+ { id: "gpt-5.6-sol", name: "GPT-5.6 Sol", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13067
+ { id: "gpt-5.6-terra", name: "GPT-5.6 Terra", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13068
+ { id: "gpt-5.6-luna", name: "GPT-5.6 Luna", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13069
+ { id: "gpt-5.5", name: "GPT-5.5", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13070
+ { id: "gpt-5.4", name: "GPT-5.4", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 105e4, maxOutputTokens: 128e3, vision: true, toolUse: true },
13071
+ { id: "gpt-5.4-mini", name: "GPT-5.4 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13072
+ { id: "gpt-5.4-nano", name: "GPT-5.4 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13073
+ { id: "gpt-5", name: "GPT-5", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13074
+ { id: "gpt-5-mini", name: "GPT-5 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13075
+ { id: "gpt-5-nano", name: "GPT-5 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 4e5, maxOutputTokens: 128e3, vision: true, toolUse: true },
13076
+ { id: "gpt-4.1", name: "GPT-4.1", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
13077
+ { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
13078
+ { id: "gpt-4.1-nano", name: "GPT-4.1 Nano", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 1047576, maxOutputTokens: 32768, vision: true, toolUse: true },
13079
+ { id: "gpt-4o", name: "GPT-4o", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 128e3, maxOutputTokens: 16384, vision: true, toolUse: true },
13080
+ { id: "gpt-4o-mini", name: "GPT-4o Mini", providers: ["openai", "azure"], lifecycle: "ga", contextWindow: 128e3, maxOutputTokens: 16384, vision: true, toolUse: true },
13081
+ { id: "claude-fable-5", name: "Claude Fable 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13082
+ { id: "claude-opus-5", name: "Claude Opus 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13083
+ { id: "claude-opus-4-8", name: "Claude Opus 4.8", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13084
+ { id: "claude-opus-4-7", name: "Claude Opus 4.7", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13085
+ { id: "claude-opus-4-6", name: "Claude Opus 4.6", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, vision: true, toolUse: true },
13086
+ { id: "claude-opus-4-5-20251101", name: "Claude Opus 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, vision: true, toolUse: true },
13087
+ { id: "claude-sonnet-5", name: "Claude Sonnet 5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13088
+ { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6", providers: ["anthropic"], lifecycle: "ga", contextWindow: 1e6, maxOutputTokens: 128e3, vision: true, toolUse: true },
13089
+ { id: "claude-sonnet-4-5-20250929", name: "Claude Sonnet 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, vision: true, toolUse: true },
13090
+ { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", providers: ["anthropic"], lifecycle: "ga", contextWindow: 2e5, maxOutputTokens: 64e3, vision: true, toolUse: true },
13091
+ { id: "gemini-3.7-flash", name: "Gemini 3.7 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13092
+ { id: "gemini-3.6-flash", name: "Gemini 3.6 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13093
+ { id: "gemini-3.5-flash", name: "Gemini 3.5 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13094
+ { id: "gemini-3.5-flash-lite", name: "Gemini 3.5 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13095
+ { id: "gemini-3.1-flash-lite", name: "Gemini 3.1 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13096
+ { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", providers: ["gemini"], lifecycle: "preview", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13097
+ { id: "gemini-3-flash-preview", name: "Gemini 3 Flash", providers: ["gemini"], lifecycle: "preview", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13098
+ { id: "gemini-2.5-pro", name: "Gemini 2.5 Pro", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13099
+ { id: "gemini-2.5-flash", name: "Gemini 2.5 Flash", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true },
13100
+ { id: "gemini-2.5-flash-lite", name: "Gemini 2.5 Flash-Lite", providers: ["gemini"], lifecycle: "ga", contextWindow: 1048576, maxOutputTokens: 65536, vision: true, toolUse: true }
13101
+ ]
13102
+ };
13103
+
12826
13104
  // src/providers/azure.ts
12827
13105
  var DEFAULT_AZURE_API_VERSION = "2024-12-01-preview";
12828
- var AZURE_BASE_MODEL_RULES = [
12829
- [/gpt-?5\.5/, "gpt-5.5"],
12830
- [/gpt-?5\.4.*mini/, "gpt-5.4-mini"],
12831
- [/gpt-?5\.4/, "gpt-5.4"],
12832
- [/gpt-?5.*nano/, "gpt-5-nano"],
12833
- [/gpt-?5.*mini/, "gpt-5-mini"],
12834
- [/gpt-?5/, "gpt-5"],
12835
- [/gpt-?4\.1-nano/, "gpt-4.1-nano"],
12836
- [/gpt-?4\.1-mini/, "gpt-4.1-mini"],
12837
- [/gpt-?4\.1/, "gpt-4.1"],
12838
- [/gpt-?4o-mini/, "gpt-4o-mini"],
12839
- [/gpt-?4o/, "gpt-4o"]
12840
- ];
12841
- var AZURE_BASE_MODELS = AZURE_BASE_MODEL_RULES.map(([, id]) => id);
13106
+ var CLOUD_MODELS = cloud_model_catalog_default.models;
13107
+ 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));
13108
+ var AZURE_BASE_MODELS = AZURE_CATALOG.map((m) => m.id);
13109
+ function compactModelId(id) {
13110
+ return id.toLowerCase().replace(/[^a-z0-9.]/g, "");
13111
+ }
13112
+ function catalogEntry(id) {
13113
+ const n = id.toLowerCase();
13114
+ return CLOUD_MODELS.find((m) => m.id.toLowerCase() === n);
13115
+ }
12842
13116
  function inferAzureBaseModel(deploymentName) {
12843
13117
  const n = deploymentName.toLowerCase();
12844
- for (const [re, base] of AZURE_BASE_MODEL_RULES) if (re.test(n)) return base;
13118
+ const compact = compactModelId(n);
13119
+ const hasBareGpt56Alias = compact.includes("gpt5.6");
13120
+ for (const model of AZURE_CATALOG) {
13121
+ if (hasBareGpt56Alias && model.id === "gpt-5") continue;
13122
+ if (n.includes(model.id.toLowerCase()) || compact.includes(compactModelId(model.id))) {
13123
+ return model.id;
13124
+ }
13125
+ }
13126
+ if (hasBareGpt56Alias) return "gpt-5.6-sol";
13127
+ const gpt5At = compact.indexOf("gpt5");
13128
+ if (gpt5At !== -1) {
13129
+ const suffix = compact.slice(gpt5At + "gpt5".length);
13130
+ if (suffix.includes("nano")) return "gpt-5-nano";
13131
+ if (suffix.includes("mini")) return "gpt-5-mini";
13132
+ return "gpt-5";
13133
+ }
13134
+ if (compact.includes("gpt4.1nano")) return "gpt-4.1-nano";
13135
+ if (compact.includes("gpt4.1mini")) return "gpt-4.1-mini";
13136
+ if (compact.includes("gpt4.1")) return "gpt-4.1";
13137
+ if (compact.includes("gpt4omini")) return "gpt-4o-mini";
13138
+ if (compact.includes("gpt4o")) return "gpt-4o";
12845
13139
  return null;
12846
13140
  }
13141
+ function catalogModelInfo(baseModelId) {
13142
+ const entry = catalogEntry(baseModelId);
13143
+ if (!entry?.contextWindow) return void 0;
13144
+ return {
13145
+ id: entry.id,
13146
+ name: entry.name,
13147
+ provider: "openai",
13148
+ contextWindow: entry.contextWindow,
13149
+ isVisionCapable: entry.vision ?? false,
13150
+ inputCostPer1kTokens: 0,
13151
+ outputCostPer1kTokens: 0,
13152
+ maxOutputTokens: entry.maxOutputTokens ?? 16e3,
13153
+ supportsStreaming: true,
13154
+ isLocal: false,
13155
+ supportsToolUse: entry.toolUse ?? true,
13156
+ pricingUnknown: true
13157
+ };
13158
+ }
12847
13159
  function azureModelForDeployment(cfg) {
12848
13160
  if (cfg.type !== "azure" || !cfg.deploymentName?.trim()) return null;
12849
13161
  const id = cfg.deploymentName.trim();
12850
13162
  const name = cfg.label?.trim() || id;
12851
13163
  const baseModelId = cfg.model?.trim() || inferAzureBaseModel(id) || void 0;
12852
- const base = baseModelId ? MODELS[baseModelId] : void 0;
12853
- if (base) {
13164
+ const base = baseModelId ? MODELS[baseModelId] ?? catalogModelInfo(baseModelId) : void 0;
13165
+ if (base && baseModelId) {
12854
13166
  const azurePrice = resolvePricing(
12855
13167
  { id, provider: "azure", isLocal: false, baseModelId },
12856
13168
  { region: cfg.region }
12857
13169
  );
13170
+ const exactAzurePrice = !azurePrice.unknown && !azurePrice.estimatedFromProvider;
13171
+ const nativeToolsOnThisTransport = /^gpt-5\.6-(?:sol|terra|luna)$/i.test(baseModelId) ? false : base.supportsToolUse ?? true;
12858
13172
  return {
12859
13173
  ...base,
12860
13174
  id,
12861
- // callable deployment name
12862
13175
  name,
12863
13176
  provider: "azure",
12864
13177
  baseModelId,
12865
- // real identity for benchmark + live pricing
12866
- supportsToolUse: base.supportsToolUse ?? true,
12867
- ...azurePrice.unknown ? {} : {
13178
+ supportsToolUse: nativeToolsOnThisTransport,
13179
+ ...!azurePrice.unknown ? {
12868
13180
  inputCostPer1kTokens: azurePrice.input,
12869
13181
  outputCostPer1kTokens: azurePrice.output,
12870
- pricingUnknown: false
12871
- }
13182
+ pricingUnknown: !exactAzurePrice
13183
+ } : { pricingUnknown: true }
12872
13184
  };
12873
13185
  }
12874
13186
  return {
@@ -12883,18 +13195,23 @@ function azureModelForDeployment(cfg) {
12883
13195
  maxOutputTokens: 16e3,
12884
13196
  supportsStreaming: true,
12885
13197
  isLocal: false,
12886
- supportsToolUse: true
13198
+ supportsToolUse: true,
13199
+ pricingUnknown: true
12887
13200
  };
12888
13201
  }
13202
+ function stripTrailingSlashes3(value) {
13203
+ let end = value.length;
13204
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
13205
+ return value.slice(0, end);
13206
+ }
12889
13207
  var AzureOpenAIProvider = class extends OpenAIProvider {
12890
13208
  constructor(config, model) {
12891
13209
  const rawUrl = config.baseUrl ?? AZURE_BASE_URL_TEMPLATE.replace("{resource}", "YOUR_RESOURCE");
12892
- const endpoint = rawUrl.replace(/\/+$/, "");
13210
+ const endpoint = stripTrailingSlashes3(rawUrl);
12893
13211
  super(
12894
13212
  {
12895
13213
  ...config,
12896
13214
  baseUrl: endpoint
12897
- // Kept for superclass compatibility if it reads it
12898
13215
  },
12899
13216
  model
12900
13217
  );
@@ -14637,7 +14954,7 @@ var Keystore = class {
14637
14954
  const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
14638
14955
  this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
14639
14956
  this.backend = "keytar";
14640
- if (password && fs26.existsSync(this.storePath)) {
14957
+ if (password && fs27.existsSync(this.storePath)) {
14641
14958
  try {
14642
14959
  const fileEntries = this.decryptFile(password);
14643
14960
  for (const [k, v] of Object.entries(fileEntries)) {
@@ -14656,7 +14973,7 @@ var Keystore = class {
14656
14973
  "Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
14657
14974
  );
14658
14975
  }
14659
- if (!fs26.existsSync(this.storePath)) {
14976
+ if (!fs27.existsSync(this.storePath)) {
14660
14977
  const salt = crypto.randomBytes(SALT_LEN);
14661
14978
  this.masterKey = this.deriveKey(password, salt);
14662
14979
  this.writeWithSalt({}, salt);
@@ -14670,7 +14987,7 @@ var Keystore = class {
14670
14987
  }
14671
14988
  /** Synchronous legacy unlock kept for AES-only environments. */
14672
14989
  unlockSync(password) {
14673
- if (!fs26.existsSync(this.storePath)) {
14990
+ if (!fs27.existsSync(this.storePath)) {
14674
14991
  const salt = crypto.randomBytes(SALT_LEN);
14675
14992
  this.masterKey = this.deriveKey(password, salt);
14676
14993
  this.writeWithSalt({}, salt);
@@ -14728,7 +15045,7 @@ var Keystore = class {
14728
15045
  }
14729
15046
  }
14730
15047
  decryptFile(password, knownSalt) {
14731
- if (!fs26.existsSync(this.storePath)) return {};
15048
+ if (!fs27.existsSync(this.storePath)) return {};
14732
15049
  try {
14733
15050
  const { salt, ciphertext, iv, tag } = this.readRaw();
14734
15051
  const useSalt = knownSalt ?? salt;
@@ -14750,8 +15067,8 @@ var Keystore = class {
14750
15067
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14751
15068
  const tag = cipher.getAuthTag();
14752
15069
  const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
14753
- fs26.mkdirSync(path27.dirname(this.storePath), { recursive: true });
14754
- fs26.writeFileSync(this.storePath, out, { mode: 384 });
15070
+ fs27.mkdirSync(path28.dirname(this.storePath), { recursive: true });
15071
+ fs27.writeFileSync(this.storePath, out, { mode: 384 });
14755
15072
  }
14756
15073
  writeWithSalt(data, salt) {
14757
15074
  if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
@@ -14761,11 +15078,11 @@ var Keystore = class {
14761
15078
  const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
14762
15079
  const tag = cipher.getAuthTag();
14763
15080
  const out = Buffer.concat([salt, iv, tag, ciphertext]);
14764
- fs26.mkdirSync(path27.dirname(this.storePath), { recursive: true });
14765
- fs26.writeFileSync(this.storePath, out, { mode: 384 });
15081
+ fs27.mkdirSync(path28.dirname(this.storePath), { recursive: true });
15082
+ fs27.writeFileSync(this.storePath, out, { mode: 384 });
14766
15083
  }
14767
15084
  readRaw() {
14768
- const buf = fs26.readFileSync(this.storePath);
15085
+ const buf = fs27.readFileSync(this.storePath);
14769
15086
  let offset = 0;
14770
15087
  const salt = buf.subarray(offset, offset + SALT_LEN);
14771
15088
  offset += SALT_LEN;
@@ -14798,7 +15115,7 @@ var CascadeIgnore = class {
14798
15115
  ]);
14799
15116
  }
14800
15117
  async load(workspacePath) {
14801
- const filePath = path27.join(workspacePath, ".cascadeignore");
15118
+ const filePath = path28.join(workspacePath, ".cascadeignore");
14802
15119
  try {
14803
15120
  const content = await fs11.readFile(filePath, "utf-8");
14804
15121
  const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
@@ -14809,7 +15126,7 @@ var CascadeIgnore = class {
14809
15126
  }
14810
15127
  isIgnored(filePath, workspacePath) {
14811
15128
  try {
14812
- const relative = workspacePath ? path27.relative(workspacePath, filePath) : filePath;
15129
+ const relative = workspacePath ? path28.relative(workspacePath, filePath) : filePath;
14813
15130
  return this.ig.ignores(relative);
14814
15131
  } catch {
14815
15132
  return false;
@@ -14820,7 +15137,7 @@ var CascadeIgnore = class {
14820
15137
  }
14821
15138
  };
14822
15139
  async function loadCascadeMd(workspacePath) {
14823
- const filePath = path27.join(workspacePath, "CASCADE.md");
15140
+ const filePath = path28.join(workspacePath, "CASCADE.md");
14824
15141
  try {
14825
15142
  const raw = await fs11.readFile(filePath, "utf-8");
14826
15143
  return parseCascadeMd(raw);
@@ -14851,7 +15168,7 @@ ${raw.trim()}`;
14851
15168
  var MemoryStore = class _MemoryStore {
14852
15169
  db;
14853
15170
  constructor(dbPath) {
14854
- fs26.mkdirSync(path27.dirname(dbPath), { recursive: true });
15171
+ fs27.mkdirSync(path28.dirname(dbPath), { recursive: true });
14855
15172
  try {
14856
15173
  this.db = new Database2(dbPath, { timeout: 5e3 });
14857
15174
  this.db.pragma("journal_mode = WAL");
@@ -15749,7 +16066,18 @@ var WebSearchConfigSchema = z.object({
15749
16066
  /** Tavily API key — get one at https://tavily.com */
15750
16067
  tavilyApiKey: z.string().optional(),
15751
16068
  /** Max results per search (default 5) */
15752
- maxResults: z.number().default(5)
16069
+ maxResults: z.number().default(5),
16070
+ /**
16071
+ * Send `searxngUrl` through the SSRF guard. Off for a local run, where the
16072
+ * URL is the machine owner's own config and a self-hosted SearXNG is
16073
+ * routinely on a private address; on for a host that accepts it from a
16074
+ * request. See `src/tools/web-search.ts`.
16075
+ *
16076
+ * It has to be named HERE, not just on the TypeScript interfaces: the config
16077
+ * passes through `CascadeConfigSchema.parse()` on its way into a run, and
16078
+ * zod drops every key this object does not declare.
16079
+ */
16080
+ guardSearxngUrl: z.boolean().optional()
15753
16081
  });
15754
16082
  var ToolsConfigSchema = z.object({
15755
16083
  shellAllowlist: z.array(z.string()).default([]),
@@ -16153,7 +16481,7 @@ function credentialEndpointsConflict(type, rowEndpoint, credentialEndpoint) {
16153
16481
 
16154
16482
  // src/config/global-credentials.ts
16155
16483
  function credentialsPath(globalDir2) {
16156
- return path27.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
16484
+ return path28.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
16157
16485
  }
16158
16486
  function providerKey(p) {
16159
16487
  return p.type;
@@ -16183,7 +16511,7 @@ function isPersistable(p) {
16183
16511
  }
16184
16512
  function loadGlobalCredentials(globalDir2) {
16185
16513
  try {
16186
- const raw = fs26.readFileSync(credentialsPath(globalDir2), "utf-8");
16514
+ const raw = fs27.readFileSync(credentialsPath(globalDir2), "utf-8");
16187
16515
  const parsed = JSON.parse(raw);
16188
16516
  if (!Array.isArray(parsed.providers)) return [];
16189
16517
  return parsed.providers.filter(
@@ -16195,13 +16523,13 @@ function loadGlobalCredentials(globalDir2) {
16195
16523
  }
16196
16524
  function saveGlobalCredentials(globalDir2, providers) {
16197
16525
  const filePath = credentialsPath(globalDir2);
16198
- fs26.mkdirSync(globalDir2, { recursive: true, mode: 448 });
16526
+ fs27.mkdirSync(globalDir2, { recursive: true, mode: 448 });
16199
16527
  const body = { version: 1, providers: providers.filter(isPersistable) };
16200
16528
  const tmp = `${filePath}.tmp`;
16201
- fs26.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
16202
- fs26.renameSync(tmp, filePath);
16529
+ fs27.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
16530
+ fs27.renameSync(tmp, filePath);
16203
16531
  try {
16204
- fs26.chmodSync(filePath, 384);
16532
+ fs27.chmodSync(filePath, 384);
16205
16533
  } catch {
16206
16534
  }
16207
16535
  }
@@ -16602,7 +16930,7 @@ var ConfigManager = class {
16602
16930
  /** `globalDirOverride` exists for tests — never point it at the real home dir there. */
16603
16931
  constructor(workspacePath = process.cwd(), globalDirOverride) {
16604
16932
  this.workspacePath = workspacePath;
16605
- this.globalDir = globalDirOverride ?? path27.join(os6.homedir(), GLOBAL_CONFIG_DIR);
16933
+ this.globalDir = globalDirOverride ?? path28.join(os6.homedir(), GLOBAL_CONFIG_DIR);
16606
16934
  }
16607
16935
  async load() {
16608
16936
  this.config = await this.loadConfig();
@@ -16616,8 +16944,8 @@ var ConfigManager = class {
16616
16944
  this.ignore = new CascadeIgnore();
16617
16945
  await this.ignore.load(this.workspacePath);
16618
16946
  this.cascadeMd = await loadCascadeMd(this.workspacePath);
16619
- this.keystore = new Keystore(path27.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
16620
- this.store = new MemoryStore(path27.join(this.workspacePath, CASCADE_DB_FILE));
16947
+ this.keystore = new Keystore(path28.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
16948
+ this.store = new MemoryStore(path28.join(this.workspacePath, CASCADE_DB_FILE));
16621
16949
  const globalRevoked = stripRevokedCredentials(loadGlobalCredentials(this.globalDir));
16622
16950
  const globalCreds = filterRetiredCredentials(globalRevoked.kept);
16623
16951
  if (globalRevoked.removed > 0) this.revokedCredentials += globalRevoked.removed;
@@ -16690,8 +17018,8 @@ var ConfigManager = class {
16690
17018
  * never reached the disk (see `commitSettings`).
16691
17019
  */
16692
17020
  async save(config = this.config) {
16693
- const configPath = path27.join(this.workspacePath, CASCADE_CONFIG_FILE);
16694
- await fs11.mkdir(path27.dirname(configPath), { recursive: true });
17021
+ const configPath = path28.join(this.workspacePath, CASCADE_CONFIG_FILE);
17022
+ await fs11.mkdir(path28.dirname(configPath), { recursive: true });
16695
17023
  await fs11.writeFile(configPath, JSON.stringify(config, null, 2), "utf-8");
16696
17024
  try {
16697
17025
  saveGlobalCredentials(this.globalDir, config.providers);
@@ -16741,7 +17069,7 @@ var ConfigManager = class {
16741
17069
  this.revokedCredentials = 0;
16742
17070
  this.revokedPins = [];
16743
17071
  this.revokedNotice = void 0;
16744
- const configPath = path27.join(this.workspacePath, CASCADE_CONFIG_FILE);
17072
+ const configPath = path28.join(this.workspacePath, CASCADE_CONFIG_FILE);
16745
17073
  try {
16746
17074
  const raw = await fs11.readFile(configPath, "utf-8");
16747
17075
  const parsed = JSON.parse(raw);
@@ -16835,7 +17163,7 @@ var ConfigManager = class {
16835
17163
  return onResource;
16836
17164
  }
16837
17165
  async persistClearedPins(tiers) {
16838
- const configPath = path27.join(this.workspacePath, CASCADE_CONFIG_FILE);
17166
+ const configPath = path28.join(this.workspacePath, CASCADE_CONFIG_FILE);
16839
17167
  try {
16840
17168
  const raw = JSON.parse(await fs11.readFile(configPath, "utf-8"));
16841
17169
  const models = raw["models"];
@@ -17429,7 +17757,7 @@ function computeDelegationSavings(stats, t1Model) {
17429
17757
  var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
17430
17758
  var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
17431
17759
  var FETCH_TIMEOUT_MS = 8e3;
17432
- var DEFAULT_CACHE_FILE = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
17760
+ var DEFAULT_CACHE_FILE = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
17433
17761
  function normalizeModelId2(id) {
17434
17762
  let s = id.toLowerCase();
17435
17763
  const slash = s.lastIndexOf("/");
@@ -17576,7 +17904,7 @@ var LiveDataProvider = class {
17576
17904
  }
17577
17905
  async saveCache() {
17578
17906
  try {
17579
- await fs11.mkdir(path27.dirname(this.opts.cacheFile), { recursive: true });
17907
+ await fs11.mkdir(path28.dirname(this.opts.cacheFile), { recursive: true });
17580
17908
  const cache = {
17581
17909
  fetchedAt: this.fetchedAt,
17582
17910
  snapshot: this.snapshot ?? void 0,
@@ -17683,9 +18011,15 @@ var FAMILY_BENCHMARKS = {
17683
18011
  "claude-opus": { code: 95, analysis: 92, creative: 90, data: 88 },
17684
18012
  "claude-sonnet": { code: 93, analysis: 88, creative: 87, data: 85 },
17685
18013
  "claude-haiku": { code: 80, analysis: 75, creative: 76, data: 72 },
17686
- // OpenAI GPT-5 family — current flagship; strongest all-round. Offline
17687
- // baseline (the live fetch refreshes these). Distinct point releases carry
17688
- // their own scores: gpt-5.5 "Spud" leads SWE-bench (88.7%); gpt-5.4 ~80%.
18014
+ // OpenAI GPT-5 family. GPT-5.6 is split by durable capability tier instead
18015
+ // of being folded into generic gpt-5: Sol, Terra and Luna have materially
18016
+ // different benchmark/cost envelopes and should compete independently.
18017
+ // Scores are the committed normalized snapshot; provenance is in
18018
+ // scripts/benchmarks/sources/openai-gpt-5.6-2026-08.json.
18019
+ "gpt-5.6-sol": { code: 100, analysis: 98, creative: 88, data: 88 },
18020
+ "gpt-5.6-terra": { code: 97, analysis: 92, creative: 82, data: 85 },
18021
+ "gpt-5.6-luna": { code: 93, analysis: 85, creative: 74, data: 60 },
18022
+ // Older GPT-5 point releases remain distinct where public data supports it.
17689
18023
  "gpt-5.5": { code: 97, analysis: 96, creative: 94, data: 95 },
17690
18024
  "gpt-5.4": { code: 94, analysis: 94, creative: 92, data: 92 },
17691
18025
  "gpt-5.4-mini": { code: 86, analysis: 85, creative: 85, data: 83 },
@@ -17721,13 +18055,19 @@ var FAMILY_BENCHMARKS = {
17721
18055
  "llama-small": { code: 55, analysis: 56, creative: 60, data: 54 }
17722
18056
  };
17723
18057
  var FAMILY_MATCHERS = [
18058
+ [/fable/i, "claude-opus"],
17724
18059
  [/opus/i, "claude-opus"],
17725
18060
  [/sonnet/i, "claude-sonnet"],
17726
18061
  [/haiku/i, "claude-haiku"],
17727
- // GPT-5 family — ordered most-specific first. Distinct point releases
17728
- // (gpt-5.5, gpt-5.4, gpt-5.4-mini) match their own family; nano/mini before
17729
- // the base; unrecognised gpt-5.x still fold into the gpt-5 base.
18062
+ // GPT-5 family — ordered most-specific first. GPT-5.6 durable tiers carry
18063
+ // their own profiles. 5.4 Nano intentionally maps to the nano class rather
18064
+ // than being swallowed by the generic 5.4 rule.
18065
+ [/gpt-?5\.6.*sol/i, "gpt-5.6-sol"],
18066
+ [/gpt-?5\.6.*terra/i, "gpt-5.6-terra"],
18067
+ [/gpt-?5\.6.*luna/i, "gpt-5.6-luna"],
18068
+ [/gpt-?5\.6(?:\s|$)/i, "gpt-5.6-sol"],
17730
18069
  [/gpt-?5\.5/i, "gpt-5.5"],
18070
+ [/gpt-?5\.4.*nano/i, "gpt-5-nano"],
17731
18071
  [/gpt-?5\.4.*mini/i, "gpt-5.4-mini"],
17732
18072
  [/gpt-?5\.4/i, "gpt-5.4"],
17733
18073
  [/gpt-?5.*nano/i, "gpt-5-nano"],
@@ -20786,8 +21126,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
20786
21126
  // Media generation can run for a minute; without this a cancelled run
20787
21127
  // still pays for an image nobody will see.
20788
21128
  ...this.signal ? { signal: this.signal } : {},
20789
- saveSnapshot: async (path29, content) => {
20790
- this.store?.addFileSnapshot(this.taskId, path29, content);
21129
+ saveSnapshot: async (path30, content) => {
21130
+ this.store?.addFileSnapshot(this.taskId, path30, content);
20791
21131
  },
20792
21132
  sendPeerSync: (to, syncType, content) => {
20793
21133
  this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
@@ -20990,7 +21330,7 @@ ${assignment.expectedOutput}`;
20990
21330
  const { promisify: promisify4 } = await import('util');
20991
21331
  const execFileAsync3 = promisify4(execFile4);
20992
21332
  for (const artifactPath of artifactPaths) {
20993
- const absolutePath = path27.resolve(this.artifactRoot(), artifactPath);
21333
+ const absolutePath = path28.resolve(this.artifactRoot(), artifactPath);
20994
21334
  try {
20995
21335
  const stat = await fs11.stat(absolutePath);
20996
21336
  if (!stat.isFile()) {
@@ -21025,7 +21365,7 @@ ${assignment.expectedOutput}`;
21025
21365
  issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
21026
21366
  continue;
21027
21367
  }
21028
- const ext = path27.extname(absolutePath).toLowerCase();
21368
+ const ext = path28.extname(absolutePath).toLowerCase();
21029
21369
  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;
21030
21370
  if (check) {
21031
21371
  try {
@@ -21117,7 +21457,7 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
21117
21457
  async checkAcceptance(assignment) {
21118
21458
  const criteria = assignment.acceptance ?? [];
21119
21459
  if (!criteria.length) return [];
21120
- const resolve2 = (target) => path27.resolve(this.artifactRoot(), target);
21460
+ const resolve2 = (target) => path28.resolve(this.artifactRoot(), target);
21121
21461
  const workerCanWriteFiles = canProduceFiles(this.tools.map((t) => t.name));
21122
21462
  return evaluateAcceptance(criteria, assignment.files ?? [], {
21123
21463
  stat: async (target) => {
@@ -24421,16 +24761,16 @@ function resolveInWorkspace(workspaceRoot, input) {
24421
24761
  if (typeof input !== "string" || input.length === 0) {
24422
24762
  throw new WorkspaceSandboxError(String(input), workspaceRoot);
24423
24763
  }
24424
- const root = path27.resolve(workspaceRoot);
24425
- const abs = path27.isAbsolute(input) ? path27.resolve(input) : path27.resolve(root, input);
24426
- const rel = path27.relative(root, abs);
24427
- if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path27.isAbsolute(rel)) {
24764
+ const root = path28.resolve(workspaceRoot);
24765
+ const abs = path28.isAbsolute(input) ? path28.resolve(input) : path28.resolve(root, input);
24766
+ const rel = path28.relative(root, abs);
24767
+ if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path28.isAbsolute(rel)) {
24428
24768
  throw new WorkspaceSandboxError(input, root);
24429
24769
  }
24430
24770
  try {
24431
- const real = fs26.realpathSync(abs);
24432
- const realRel = path27.relative(root, real);
24433
- if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path27.isAbsolute(realRel))) {
24771
+ const real = fs27.realpathSync(abs);
24772
+ const realRel = path28.relative(root, real);
24773
+ if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path28.isAbsolute(realRel))) {
24434
24774
  throw new WorkspaceSandboxError(input, root);
24435
24775
  }
24436
24776
  } catch (e) {
@@ -24979,9 +25319,9 @@ async function animatePptx(bytes, scheme) {
24979
25319
  const zip = await import_jszip.default.loadAsync(bytes);
24980
25320
  const slidePaths = Object.keys(zip.files).filter((n) => /^ppt\/slides\/slide\d+\.xml$/.test(n));
24981
25321
  if (slidePaths.length === 0) return bytes;
24982
- for (const path29 of slidePaths) {
24983
- const xml = await zip.file(path29).async("string");
24984
- zip.file(path29, animateSlideXml(xml, scheme));
25322
+ for (const path30 of slidePaths) {
25323
+ const xml = await zip.file(path30).async("string");
25324
+ zip.file(path30, animateSlideXml(xml, scheme));
24985
25325
  }
24986
25326
  return await zip.generateAsync({ type: "uint8array", compression: "DEFLATE" });
24987
25327
  } catch {
@@ -25367,7 +25707,7 @@ var GenerateDocumentTool = class extends BaseTool {
25367
25707
  }
25368
25708
  }
25369
25709
  const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
25370
- await fs11.mkdir(path27.dirname(absPath), { recursive: true });
25710
+ await fs11.mkdir(path28.dirname(absPath), { recursive: true });
25371
25711
  await fs11.writeFile(absPath, bytes);
25372
25712
  const charts = countCharts(content);
25373
25713
  return [
@@ -25585,7 +25925,7 @@ var FileWriteTool = class extends BaseTool {
25585
25925
  } catch {
25586
25926
  }
25587
25927
  }
25588
- await fs11.mkdir(path27.dirname(absPath), { recursive: true });
25928
+ await fs11.mkdir(path28.dirname(absPath), { recursive: true });
25589
25929
  await fs11.writeFile(absPath, content, "utf-8");
25590
25930
  return `Written ${content.length} characters to ${filePath}`;
25591
25931
  }
@@ -25943,12 +26283,10 @@ ${data.description}`;
25943
26283
  }
25944
26284
  }
25945
26285
  };
25946
-
25947
- // src/tools/browser.ts
25948
26286
  var BROWSER_LAUNCH_TIMEOUT_MS = 15e3;
25949
26287
  var BrowserTool = class extends BaseTool {
25950
26288
  name = "browser";
25951
- description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Only available with multimodal models.";
26289
+ description = "Control a browser: navigate to URLs, click elements, fill forms, take screenshots. Screenshots are saved to a file for image_analyze to read.";
25952
26290
  inputSchema = {
25953
26291
  type: "object",
25954
26292
  properties: {
@@ -26015,7 +26353,14 @@ var BrowserTool = class extends BaseTool {
26015
26353
  }
26016
26354
  case "screenshot": {
26017
26355
  const buf = await page2.screenshot({ type: "png" });
26018
- return `data:image/png;base64,${buf.toString("base64")}`;
26356
+ const rel = path28.join(".cascade", "screenshots", `screenshot-${Date.now()}-${randomUUID().slice(0, 8)}.png`);
26357
+ const abs = resolveInWorkspace(this.workspaceRoot, rel);
26358
+ await fs11.mkdir(path28.dirname(abs), { recursive: true });
26359
+ await fs11.writeFile(abs, buf);
26360
+ return [
26361
+ `Screenshot saved to ${abs} (${Math.round(buf.byteLength / 1024)} KB).`,
26362
+ `Call image_analyze with that path to see what is on it \u2014 that needs a vision-capable model.`
26363
+ ].join(" ");
26019
26364
  }
26020
26365
  case "evaluate": {
26021
26366
  const result = await page2.evaluate(input["script"]);
@@ -26083,7 +26428,7 @@ var ImageAnalyzeTool = class extends BaseTool {
26083
26428
  };
26084
26429
  async function fileToImageAttachment(filePath) {
26085
26430
  const data = await fs11.readFile(filePath);
26086
- const ext = path27.extname(filePath).toLowerCase();
26431
+ const ext = path28.extname(filePath).toLowerCase();
26087
26432
  const mimeMap = {
26088
26433
  ".jpg": "image/jpeg",
26089
26434
  ".jpeg": "image/jpeg",
@@ -26117,14 +26462,14 @@ var PDFCreateTool = class extends BaseTool {
26117
26462
  const filePath = input["path"];
26118
26463
  const content = input["content"];
26119
26464
  const title = input["title"];
26120
- const dir = path27.dirname(filePath);
26121
- if (!fs26.existsSync(dir)) {
26122
- fs26.mkdirSync(dir, { recursive: true });
26465
+ const dir = path28.dirname(filePath);
26466
+ if (!fs27.existsSync(dir)) {
26467
+ fs27.mkdirSync(dir, { recursive: true });
26123
26468
  }
26124
26469
  return new Promise((resolve2, reject) => {
26125
26470
  try {
26126
26471
  const doc = new PDFDocument({ margin: 50 });
26127
- const stream = fs26.createWriteStream(filePath);
26472
+ const stream = fs27.createWriteStream(filePath);
26128
26473
  doc.pipe(stream);
26129
26474
  if (title) {
26130
26475
  doc.info["Title"] = title;
@@ -26202,22 +26547,22 @@ var CodeInterpreterTool = class extends BaseTool {
26202
26547
  }
26203
26548
  cmdPrefix = NODE_CMD;
26204
26549
  }
26205
- const tmpDir = path27.join(this.workspaceRoot, ".cascade", "tmp");
26206
- if (!fs26.existsSync(tmpDir)) {
26207
- fs26.mkdirSync(tmpDir, { recursive: true });
26550
+ const tmpDir = path28.join(this.workspaceRoot, ".cascade", "tmp");
26551
+ if (!fs27.existsSync(tmpDir)) {
26552
+ fs27.mkdirSync(tmpDir, { recursive: true });
26208
26553
  }
26209
26554
  const extension = language === "python" ? "py" : "js";
26210
26555
  const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
26211
- const filePath = path27.join(tmpDir, fileName);
26212
- fs26.writeFileSync(filePath, code, "utf-8");
26556
+ const filePath = path28.join(tmpDir, fileName);
26557
+ fs27.writeFileSync(filePath, code, "utf-8");
26213
26558
  const execArgs = [filePath, ...args];
26214
26559
  return new Promise((resolve2) => {
26215
26560
  const startMs = Date.now();
26216
26561
  execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
26217
26562
  const duration = Date.now() - startMs;
26218
26563
  try {
26219
- if (fs26.existsSync(filePath)) {
26220
- fs26.unlinkSync(filePath);
26564
+ if (fs27.existsSync(filePath)) {
26565
+ fs27.unlinkSync(filePath);
26221
26566
  }
26222
26567
  } catch (cleanupErr) {
26223
26568
  console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
@@ -26297,18 +26642,178 @@ ${formatted}`;
26297
26642
  return `Unknown action: ${action}`;
26298
26643
  }
26299
26644
  };
26645
+ var SsrfBlockedError = class extends Error {
26646
+ constructor(message) {
26647
+ super(message);
26648
+ this.name = "SsrfBlockedError";
26649
+ }
26650
+ };
26651
+ var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
26652
+ var MAX_REDIRECTS2 = 5;
26653
+ function allowLocal() {
26654
+ return process.env["CASCADE_ALLOW_LOCAL_FETCH"] === "1";
26655
+ }
26656
+ function isPrivateAddress(ip) {
26657
+ const type = net.isIP(ip);
26658
+ if (type === 4) return isPrivateIPv4(ip);
26659
+ if (type === 6) return isPrivateIPv6(ip);
26660
+ return true;
26661
+ }
26662
+ function isPrivateIPv4(ip) {
26663
+ const parts = ip.split(".").map((p) => Number(p));
26664
+ if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return true;
26665
+ const [a, b] = parts;
26666
+ if (a === 0) return true;
26667
+ if (a === 10) return true;
26668
+ if (a === 127) return true;
26669
+ if (a === 169 && b === 254) return true;
26670
+ if (a === 172 && b >= 16 && b <= 31) return true;
26671
+ if (a === 192 && b === 168) return true;
26672
+ if (a === 100 && b >= 64 && b <= 127) return true;
26673
+ if (a >= 224) return true;
26674
+ return false;
26675
+ }
26676
+ function ipv6Groups(ip) {
26677
+ let s = ip.toLowerCase().replace(/^\[|\]$/g, "");
26678
+ const zone = s.indexOf("%");
26679
+ if (zone !== -1) s = s.slice(0, zone);
26680
+ const dotted = /^(.*:)(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(s);
26681
+ if (dotted) {
26682
+ const o = [dotted[2], dotted[3], dotted[4], dotted[5]].map((p) => Number(p));
26683
+ if (o.some((n) => !Number.isInteger(n) || n < 0 || n > 255)) return null;
26684
+ s = `${dotted[1]}${(o[0] << 8 | o[1]).toString(16)}:${(o[2] << 8 | o[3]).toString(16)}`;
26685
+ }
26686
+ const halves = s.split("::");
26687
+ if (halves.length > 2) return null;
26688
+ const split = (part) => part ? part.split(":") : [];
26689
+ const head = split(halves[0] ?? "");
26690
+ const tail = halves.length === 2 ? split(halves[1] ?? "") : [];
26691
+ const zeros = halves.length === 2 ? 8 - head.length - tail.length : 0;
26692
+ if (zeros < 0) return null;
26693
+ const groups = [...head, ...Array(zeros).fill("0"), ...tail];
26694
+ if (groups.length !== 8) return null;
26695
+ const out = [];
26696
+ for (const g of groups) {
26697
+ if (!/^[0-9a-f]{1,4}$/.test(g)) return null;
26698
+ out.push(parseInt(g, 16));
26699
+ }
26700
+ return out;
26701
+ }
26702
+ function embeddedIPv4(g) {
26703
+ const quad = (hi, lo) => `${hi >> 8}.${hi & 255}.${lo >> 8}.${lo & 255}`;
26704
+ const leadingZero = (n) => g.slice(0, n).every((x) => x === 0);
26705
+ if (leadingZero(5) && g[5] === 65535) return quad(g[6], g[7]);
26706
+ if (leadingZero(6)) return quad(g[6], g[7]);
26707
+ if (g[0] === 100 && g[1] === 65435) {
26708
+ if (g[2] === 1) {
26709
+ return quad(g[3], (g[4] & 255) << 8 | g[5] >> 8);
26710
+ }
26711
+ if (g[2] === 0 && g[3] === 0 && g[4] === 0 && g[5] === 0) return quad(g[6], g[7]);
26712
+ return null;
26713
+ }
26714
+ if (g[0] === 8194) return quad(g[1], g[2]);
26715
+ return null;
26716
+ }
26717
+ function isPrivateIPv6(ip) {
26718
+ const g = ipv6Groups(ip);
26719
+ if (!g) return true;
26720
+ const v4 = embeddedIPv4(g);
26721
+ if (v4) return isPrivateIPv4(v4);
26722
+ if (g[0] === 100 && g[1] === 65435) return true;
26723
+ if (g.every((x) => x === 0)) return true;
26724
+ if (leading7Zero(g) && g[7] === 1) return true;
26725
+ if ((g[0] & 65472) === 65152) return true;
26726
+ if ((g[0] & 65024) === 64512) return true;
26727
+ if ((g[0] & 65280) === 65280) return true;
26728
+ return false;
26729
+ }
26730
+ function leading7Zero(g) {
26731
+ return g.slice(0, 7).every((x) => x === 0);
26732
+ }
26733
+ async function assertPublicUrl(rawUrl) {
26734
+ let url;
26735
+ try {
26736
+ url = new URL(rawUrl);
26737
+ } catch {
26738
+ throw new SsrfBlockedError(`Invalid URL: ${rawUrl}`);
26739
+ }
26740
+ if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
26741
+ throw new SsrfBlockedError(`Blocked URL scheme "${url.protocol}" \u2014 only http and https are allowed.`);
26742
+ }
26743
+ if (allowLocal()) return url;
26744
+ const host = url.hostname.replace(/^\[|\]$/g, "");
26745
+ if (net.isIP(host)) {
26746
+ if (isPrivateAddress(host)) {
26747
+ throw new SsrfBlockedError(`Blocked request to non-public address ${host}.`);
26748
+ }
26749
+ return url;
26750
+ }
26751
+ if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
26752
+ throw new SsrfBlockedError(`Blocked request to local hostname "${host}".`);
26753
+ }
26754
+ let addresses;
26755
+ try {
26756
+ const records = await dns2.lookup(host, { all: true });
26757
+ addresses = records.map((r) => r.address);
26758
+ } catch {
26759
+ throw new SsrfBlockedError(`Could not resolve host "${host}".`);
26760
+ }
26761
+ if (addresses.length === 0 || addresses.some((addr) => isPrivateAddress(addr))) {
26762
+ throw new SsrfBlockedError(`Blocked request to "${host}" \u2014 resolves to a non-public address.`);
26763
+ }
26764
+ return url;
26765
+ }
26766
+ var ssrfAgent = new Agent({
26767
+ connect: {
26768
+ lookup(hostname, options, callback) {
26769
+ if (allowLocal()) {
26770
+ dnsCallback.lookup(hostname, options, callback);
26771
+ return;
26772
+ }
26773
+ dnsCallback.lookup(hostname, { ...options, all: true }, (err, addresses) => {
26774
+ if (err) return callback(err, "", 0);
26775
+ const list = Array.isArray(addresses) ? addresses : [addresses];
26776
+ const bad = list.find((a) => isPrivateAddress(a.address));
26777
+ if (bad) {
26778
+ return callback(
26779
+ new SsrfBlockedError(`Blocked connection to "${hostname}" \u2014 resolved to non-public address ${bad.address}.`),
26780
+ "",
26781
+ 0
26782
+ );
26783
+ }
26784
+ if (options.all) return callback(null, list);
26785
+ const first = list[0];
26786
+ return callback(null, first.address, first.family);
26787
+ });
26788
+ }
26789
+ }
26790
+ });
26791
+ async function safeFetch(rawUrl, init = {}) {
26792
+ let currentUrl = (await assertPublicUrl(rawUrl)).toString();
26793
+ for (let i = 0; i <= MAX_REDIRECTS2; i++) {
26794
+ const resp = await fetch(currentUrl, { ...init, redirect: "manual", dispatcher: ssrfAgent });
26795
+ if (resp.status < 300 || resp.status >= 400) return resp;
26796
+ const location = resp.headers.get("location");
26797
+ if (!location) return resp;
26798
+ const next = new URL(location, currentUrl);
26799
+ await assertPublicUrl(next.toString());
26800
+ currentUrl = next.toString();
26801
+ }
26802
+ throw new SsrfBlockedError(`Too many redirects (>${MAX_REDIRECTS2}).`);
26803
+ }
26300
26804
 
26301
26805
  // src/tools/web-search.ts
26302
- async function searchSearXNG(query, baseUrl, maxResults) {
26806
+ async function searchSearXNG(query, baseUrl, maxResults, guardUrl = false) {
26303
26807
  const url = new URL("/search", baseUrl);
26304
26808
  url.searchParams.set("q", query);
26305
26809
  url.searchParams.set("format", "json");
26306
26810
  url.searchParams.set("categories", "general");
26307
26811
  url.searchParams.set("engines", "google,bing,duckduckgo");
26308
- const resp = await fetch(url.toString(), {
26812
+ const init = {
26309
26813
  headers: { "User-Agent": "Cascade-AI/1.0 WebSearchTool" },
26310
26814
  signal: AbortSignal.timeout(1e4)
26311
- });
26815
+ };
26816
+ const resp = guardUrl ? await safeFetch(url.toString(), init) : await fetch(url.toString(), init);
26312
26817
  if (!resp.ok) {
26313
26818
  throw new Error(`SearXNG returned HTTP ${resp.status}`);
26314
26819
  }
@@ -26437,7 +26942,11 @@ var WebSearchTool = class extends BaseTool {
26437
26942
  searxngUrl: config.searxngUrl ?? process.env["SEARXNG_URL"],
26438
26943
  braveApiKey: config.braveApiKey ?? process.env["BRAVE_SEARCH_API_KEY"],
26439
26944
  tavilyApiKey: config.tavilyApiKey ?? process.env["TAVILY_API_KEY"],
26440
- maxResults: config.maxResults ?? 5
26945
+ maxResults: config.maxResults ?? 5,
26946
+ // Note this reads only from `config`, never from the environment: the
26947
+ // guard tracks the trustworthiness of the URL the host passed in, and an
26948
+ // env var says nothing about that.
26949
+ guardSearxngUrl: config.guardSearxngUrl ?? false
26441
26950
  };
26442
26951
  }
26443
26952
  async execute(input, _options) {
@@ -26452,7 +26961,7 @@ var WebSearchTool = class extends BaseTool {
26452
26961
  let anyBackendReachable = false;
26453
26962
  if (this.config.searxngUrl) {
26454
26963
  try {
26455
- results = await searchSearXNG(query, this.config.searxngUrl, maxResults);
26964
+ results = await searchSearXNG(query, this.config.searxngUrl, maxResults, this.config.guardSearxngUrl);
26456
26965
  anyBackendReachable = true;
26457
26966
  if (results.length > 0) return this.formatResults(query, results);
26458
26967
  errors.push("SearXNG: returned 0 results");
@@ -26529,7 +27038,7 @@ var GlobTool = class extends BaseTool {
26529
27038
  };
26530
27039
  async execute(input, _options) {
26531
27040
  const pattern = input["pattern"];
26532
- const searchPath = input["path"] ? path27.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27041
+ const searchPath = input["path"] ? path28.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
26533
27042
  const matches = await glob(pattern, {
26534
27043
  cwd: searchPath,
26535
27044
  ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
@@ -26542,7 +27051,7 @@ var GlobTool = class extends BaseTool {
26542
27051
  const withMtime = await Promise.all(
26543
27052
  matches.map(async (rel) => {
26544
27053
  try {
26545
- const stat = await fs11.stat(path27.join(searchPath, rel));
27054
+ const stat = await fs11.stat(path28.join(searchPath, rel));
26546
27055
  return { rel, mtime: stat.mtimeMs };
26547
27056
  } catch {
26548
27057
  return { rel, mtime: 0 };
@@ -26591,7 +27100,7 @@ var GrepTool = class extends BaseTool {
26591
27100
  };
26592
27101
  async execute(input, _options) {
26593
27102
  const pattern = input["pattern"];
26594
- const searchPath = input["path"] ? path27.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
27103
+ const searchPath = input["path"] ? path28.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
26595
27104
  const globPattern = input["glob"];
26596
27105
  const outputMode = input["output_mode"] ?? "content";
26597
27106
  const context = input["context"] ?? 0;
@@ -26645,12 +27154,12 @@ var GrepTool = class extends BaseTool {
26645
27154
  nodir: true
26646
27155
  });
26647
27156
  } catch {
26648
- files = [path27.relative(searchPath, searchPath) || "."];
27157
+ files = [path28.relative(searchPath, searchPath) || "."];
26649
27158
  }
26650
27159
  const results = [];
26651
27160
  let totalCount = 0;
26652
27161
  for (const rel of files) {
26653
- const abs = path27.join(searchPath, rel);
27162
+ const abs = path28.join(searchPath, rel);
26654
27163
  let content;
26655
27164
  try {
26656
27165
  content = await fs11.readFile(abs, "utf-8");
@@ -26691,92 +27200,6 @@ Total: ${totalCount} matches`);
26691
27200
  return results.join("\n");
26692
27201
  }
26693
27202
  };
26694
- var SsrfBlockedError = class extends Error {
26695
- constructor(message) {
26696
- super(message);
26697
- this.name = "SsrfBlockedError";
26698
- }
26699
- };
26700
- var ALLOWED_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
26701
- var MAX_REDIRECTS2 = 5;
26702
- function allowLocal() {
26703
- return process.env["CASCADE_ALLOW_LOCAL_FETCH"] === "1";
26704
- }
26705
- function isPrivateAddress(ip) {
26706
- const type = net.isIP(ip);
26707
- if (type === 4) return isPrivateIPv4(ip);
26708
- if (type === 6) return isPrivateIPv6(ip);
26709
- return true;
26710
- }
26711
- function isPrivateIPv4(ip) {
26712
- const parts = ip.split(".").map((p) => Number(p));
26713
- if (parts.length !== 4 || parts.some((p) => Number.isNaN(p) || p < 0 || p > 255)) return true;
26714
- const [a, b] = parts;
26715
- if (a === 0) return true;
26716
- if (a === 10) return true;
26717
- if (a === 127) return true;
26718
- if (a === 169 && b === 254) return true;
26719
- if (a === 172 && b >= 16 && b <= 31) return true;
26720
- if (a === 192 && b === 168) return true;
26721
- if (a === 100 && b >= 64 && b <= 127) return true;
26722
- if (a >= 224) return true;
26723
- return false;
26724
- }
26725
- function isPrivateIPv6(ip) {
26726
- const lower = ip.toLowerCase().replace(/^\[|\]$/g, "");
26727
- if (lower === "::1" || lower === "::") return true;
26728
- if (lower.startsWith("fe80")) return true;
26729
- if (lower.startsWith("fc") || lower.startsWith("fd")) return true;
26730
- const mapped = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(lower);
26731
- if (mapped) return isPrivateIPv4(mapped[1]);
26732
- return false;
26733
- }
26734
- async function assertPublicUrl(rawUrl) {
26735
- let url;
26736
- try {
26737
- url = new URL(rawUrl);
26738
- } catch {
26739
- throw new SsrfBlockedError(`Invalid URL: ${rawUrl}`);
26740
- }
26741
- if (!ALLOWED_PROTOCOLS.has(url.protocol)) {
26742
- throw new SsrfBlockedError(`Blocked URL scheme "${url.protocol}" \u2014 only http and https are allowed.`);
26743
- }
26744
- if (allowLocal()) return url;
26745
- const host = url.hostname.replace(/^\[|\]$/g, "");
26746
- if (net.isIP(host)) {
26747
- if (isPrivateAddress(host)) {
26748
- throw new SsrfBlockedError(`Blocked request to non-public address ${host}.`);
26749
- }
26750
- return url;
26751
- }
26752
- if (host === "localhost" || host.endsWith(".localhost") || host.endsWith(".local")) {
26753
- throw new SsrfBlockedError(`Blocked request to local hostname "${host}".`);
26754
- }
26755
- let addresses;
26756
- try {
26757
- const records = await dns2.lookup(host, { all: true });
26758
- addresses = records.map((r) => r.address);
26759
- } catch {
26760
- throw new SsrfBlockedError(`Could not resolve host "${host}".`);
26761
- }
26762
- if (addresses.length === 0 || addresses.some((addr) => isPrivateAddress(addr))) {
26763
- throw new SsrfBlockedError(`Blocked request to "${host}" \u2014 resolves to a non-public address.`);
26764
- }
26765
- return url;
26766
- }
26767
- async function safeFetch(rawUrl, init = {}) {
26768
- let currentUrl = (await assertPublicUrl(rawUrl)).toString();
26769
- for (let i = 0; i <= MAX_REDIRECTS2; i++) {
26770
- const resp = await fetch(currentUrl, { ...init, redirect: "manual" });
26771
- if (resp.status < 300 || resp.status >= 400) return resp;
26772
- const location = resp.headers.get("location");
26773
- if (!location) return resp;
26774
- const next = new URL(location, currentUrl);
26775
- await assertPublicUrl(next.toString());
26776
- currentUrl = next.toString();
26777
- }
26778
- throw new SsrfBlockedError(`Too many redirects (>${MAX_REDIRECTS2}).`);
26779
- }
26780
27203
 
26781
27204
  // src/tools/web-fetch.ts
26782
27205
  var MAX_CHARS = 5e4;
@@ -27049,10 +27472,10 @@ var ToolRegistry = class extends EventEmitter {
27049
27472
  }
27050
27473
  isIgnored(filePath) {
27051
27474
  if (!filePath) return false;
27052
- const abs = path27.resolve(this.workspaceRoot, filePath);
27053
- const rel = path27.relative(this.workspaceRoot, abs);
27054
- if (!rel || rel.startsWith("..") || path27.isAbsolute(rel)) return true;
27055
- const posixRel = rel.split(path27.sep).join("/");
27475
+ const abs = path28.resolve(this.workspaceRoot, filePath);
27476
+ const rel = path28.relative(this.workspaceRoot, abs);
27477
+ if (!rel || rel.startsWith("..") || path28.isAbsolute(rel)) return true;
27478
+ const posixRel = rel.split(path28.sep).join("/");
27056
27479
  return this.ignoreMatcher.ignores(posixRel);
27057
27480
  }
27058
27481
  };
@@ -27286,22 +27709,22 @@ var FileMcpOAuthStore = class {
27286
27709
  filePath;
27287
27710
  load() {
27288
27711
  try {
27289
- return JSON.parse(fs26.readFileSync(this.filePath, "utf-8"));
27712
+ return JSON.parse(fs27.readFileSync(this.filePath, "utf-8"));
27290
27713
  } catch {
27291
27714
  return void 0;
27292
27715
  }
27293
27716
  }
27294
27717
  save(state) {
27295
- fs26.mkdirSync(path27.dirname(this.filePath), { recursive: true, mode: 448 });
27296
- fs26.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
27718
+ fs27.mkdirSync(path28.dirname(this.filePath), { recursive: true, mode: 448 });
27719
+ fs27.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
27297
27720
  try {
27298
- fs26.chmodSync(this.filePath, 384);
27721
+ fs27.chmodSync(this.filePath, 384);
27299
27722
  } catch {
27300
27723
  }
27301
27724
  }
27302
27725
  clear() {
27303
27726
  try {
27304
- fs26.rmSync(this.filePath);
27727
+ fs27.rmSync(this.filePath);
27305
27728
  } catch {
27306
27729
  }
27307
27730
  }
@@ -28139,7 +28562,7 @@ var TaskAnalyzer = class {
28139
28562
  analysisCache.clear();
28140
28563
  }
28141
28564
  };
28142
- var DEFAULT_STATS_FILE = path27.join(os6.homedir(), ".cascade", "model-perf.json");
28565
+ var DEFAULT_STATS_FILE = path28.join(os6.homedir(), ".cascade", "model-perf.json");
28143
28566
  var ModelPerformanceTracker = class _ModelPerformanceTracker {
28144
28567
  stats = /* @__PURE__ */ new Map();
28145
28568
  featureStats = /* @__PURE__ */ new Map();
@@ -28179,7 +28602,7 @@ var ModelPerformanceTracker = class _ModelPerformanceTracker {
28179
28602
  async save() {
28180
28603
  if (this.readOnly) return;
28181
28604
  try {
28182
- await fs11.mkdir(path27.dirname(this.statsFile), { recursive: true });
28605
+ await fs11.mkdir(path28.dirname(this.statsFile), { recursive: true });
28183
28606
  const modelsObj = {};
28184
28607
  const featuresObj = {};
28185
28608
  for (const [key, stat] of this.stats) modelsObj[key] = stat;
@@ -28812,7 +29235,7 @@ Required capability: ${description.slice(0, 300)}`;
28812
29235
  * any dangerous action, so a silently-reloaded tool can't act without approval. */
28813
29236
  async loadPersistedTools() {
28814
29237
  if (!this.workspacePath || !this.persistEnabled) return;
28815
- const file = path27.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
29238
+ const file = path28.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
28816
29239
  try {
28817
29240
  const raw = await fs11.readFile(file, "utf-8");
28818
29241
  const specs = JSON.parse(raw);
@@ -28836,8 +29259,8 @@ Required capability: ${description.slice(0, 300)}`;
28836
29259
  }
28837
29260
  async persist() {
28838
29261
  if (!this.workspacePath || !this.persistEnabled) return;
28839
- const dir = path27.join(this.workspacePath, ".cascade");
28840
- const file = path27.join(dir, DYNAMIC_TOOLS_FILE);
29262
+ const dir = path28.join(this.workspacePath, ".cascade");
29263
+ const file = path28.join(dir, DYNAMIC_TOOLS_FILE);
28841
29264
  try {
28842
29265
  await fs11.mkdir(dir, { recursive: true });
28843
29266
  await fs11.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
@@ -28857,12 +29280,12 @@ var WorldStateDB = class {
28857
29280
  constructor(workspacePath, debugMode = false) {
28858
29281
  this.workspacePath = workspacePath;
28859
29282
  this.debugMode = debugMode;
28860
- const cascadeDir = path27.join(workspacePath, ".cascade");
28861
- if (!fs26.existsSync(cascadeDir)) {
28862
- fs26.mkdirSync(cascadeDir, { recursive: true });
29283
+ const cascadeDir = path28.join(workspacePath, ".cascade");
29284
+ if (!fs27.existsSync(cascadeDir)) {
29285
+ fs27.mkdirSync(cascadeDir, { recursive: true });
28863
29286
  }
28864
- this.keyPath = path27.join(cascadeDir, "world_state.key");
28865
- this.dbPath = path27.join(cascadeDir, "world_state.db");
29287
+ this.keyPath = path28.join(cascadeDir, "world_state.key");
29288
+ this.dbPath = path28.join(cascadeDir, "world_state.db");
28866
29289
  this.initEncryptionKey();
28867
29290
  this.db = new Database2(this.dbPath);
28868
29291
  this.db.pragma("journal_mode = WAL");
@@ -28919,11 +29342,11 @@ var WorldStateDB = class {
28919
29342
  ).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
28920
29343
  }
28921
29344
  initEncryptionKey() {
28922
- if (fs26.existsSync(this.keyPath)) {
28923
- this.encryptionKey = fs26.readFileSync(this.keyPath);
29345
+ if (fs27.existsSync(this.keyPath)) {
29346
+ this.encryptionKey = fs27.readFileSync(this.keyPath);
28924
29347
  } else {
28925
29348
  this.encryptionKey = crypto.randomBytes(32);
28926
- fs26.writeFileSync(this.keyPath, this.encryptionKey);
29349
+ fs27.writeFileSync(this.keyPath, this.encryptionKey);
28927
29350
  }
28928
29351
  }
28929
29352
  encrypt(text) {
@@ -29209,9 +29632,9 @@ var WorldStateDB = class {
29209
29632
  dumpDebugIfNeeded() {
29210
29633
  if (!this.debugMode) return;
29211
29634
  try {
29212
- const dumpPath = path27.join(os6.tmpdir(), "cascade_world_state_debug.json");
29635
+ const dumpPath = path28.join(os6.tmpdir(), "cascade_world_state_debug.json");
29213
29636
  const entries = this.getAllEntries();
29214
- fs26.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
29637
+ fs27.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
29215
29638
  } catch (err) {
29216
29639
  console.error("Failed to dump debug world state", err);
29217
29640
  }
@@ -29268,7 +29691,7 @@ var ResumeStore = class {
29268
29691
  createdAt: checkpoint.createdAt ?? new Date(timestamp).toISOString()
29269
29692
  };
29270
29693
  await promises.mkdir(this.dir, { recursive: true });
29271
- const finalPath = path27.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
29694
+ const finalPath = path28.join(this.dir, `${timestamp}-${safeId(record.taskId)}.json`);
29272
29695
  const tempPath = `${finalPath}.${process.pid}.tmp`;
29273
29696
  await promises.writeFile(tempPath, JSON.stringify(record, null, 2), "utf-8");
29274
29697
  await promises.rename(tempPath, finalPath);
@@ -29291,7 +29714,7 @@ var ResumeStore = class {
29291
29714
  const checkpoints = [];
29292
29715
  for (const candidate of candidates) {
29293
29716
  try {
29294
- const raw = await promises.readFile(path27.join(this.dir, candidate.name), "utf-8");
29717
+ const raw = await promises.readFile(path28.join(this.dir, candidate.name), "utf-8");
29295
29718
  const parsed = JSON.parse(raw);
29296
29719
  if (isCheckpoint(parsed)) checkpoints.push(parsed);
29297
29720
  } catch {
@@ -29330,7 +29753,7 @@ var ResumeStore = class {
29330
29753
  const source = entries.find((name) => FILE_PATTERN.exec(name)?.[2] === wanted);
29331
29754
  if (!source) return null;
29332
29755
  const claimId = `${this.now()}-${safeId(target.taskId)}`;
29333
- await promises.rename(path27.join(this.dir, source), path27.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
29756
+ await promises.rename(path28.join(this.dir, source), path28.join(this.dir, `${claimId}${CLAIM_SUFFIX}`));
29334
29757
  return { checkpoint: target, claimId };
29335
29758
  } catch {
29336
29759
  return null;
@@ -29340,8 +29763,8 @@ var ResumeStore = class {
29340
29763
  async release(claimId) {
29341
29764
  try {
29342
29765
  await promises.rename(
29343
- path27.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
29344
- path27.join(this.dir, `${claimId}.json`)
29766
+ path28.join(this.dir, `${claimId}${CLAIM_SUFFIX}`),
29767
+ path28.join(this.dir, `${claimId}.json`)
29345
29768
  );
29346
29769
  } catch {
29347
29770
  }
@@ -29349,7 +29772,7 @@ var ResumeStore = class {
29349
29772
  /** Discard a claim for good — the resumed run has taken ownership. */
29350
29773
  async settle(claimId) {
29351
29774
  try {
29352
- await promises.rm(path27.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
29775
+ await promises.rm(path28.join(this.dir, `${claimId}${CLAIM_SUFFIX}`), { force: true });
29353
29776
  } catch {
29354
29777
  }
29355
29778
  }
@@ -29378,8 +29801,8 @@ var ResumeStore = class {
29378
29801
  const stamp2 = Number(/^(\d+)-/.exec(name)?.[1]);
29379
29802
  if (!Number.isFinite(stamp2) || stamp2 >= cutoff) continue;
29380
29803
  await promises.rename(
29381
- path27.join(this.dir, name),
29382
- path27.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
29804
+ path28.join(this.dir, name),
29805
+ path28.join(this.dir, `${name.slice(0, -CLAIM_SUFFIX.length)}.json`)
29383
29806
  );
29384
29807
  reclaimed++;
29385
29808
  }
@@ -29395,7 +29818,7 @@ var ResumeStore = class {
29395
29818
  await Promise.all(entries.map(async (name) => {
29396
29819
  const match = FILE_PATTERN.exec(name);
29397
29820
  if (match?.[2] === wanted) {
29398
- await promises.rm(path27.join(this.dir, name), { force: true });
29821
+ await promises.rm(path28.join(this.dir, name), { force: true });
29399
29822
  }
29400
29823
  }));
29401
29824
  } catch {
@@ -29410,7 +29833,7 @@ var ResumeStore = class {
29410
29833
  const doomed = files.filter((f, index) => f.timestamp < cutoff || index >= this.maxCheckpoints);
29411
29834
  const staleTemps = entries.filter((name) => name.endsWith(".tmp")).filter((name) => (Number(FILE_PATTERN.exec(name.replace(/\.\d+\.tmp$/, ".json"))?.[1]) || 0) < cutoff);
29412
29835
  await Promise.all([...doomed.map((f) => f.name), ...staleTemps].map(
29413
- (name) => promises.rm(path27.join(this.dir, name), { force: true })
29836
+ (name) => promises.rm(path28.join(this.dir, name), { force: true })
29414
29837
  ));
29415
29838
  } catch {
29416
29839
  }
@@ -29787,7 +30210,7 @@ var WorkspaceIndex = class {
29787
30210
  isIgnored;
29788
30211
  chunker;
29789
30212
  constructor(opts) {
29790
- this.root = path27.resolve(opts.root);
30213
+ this.root = path28.resolve(opts.root);
29791
30214
  this.db = opts.db;
29792
30215
  this.namespace = opts.namespace ?? "code";
29793
30216
  this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
@@ -29819,14 +30242,14 @@ var WorkspaceIndex = class {
29819
30242
  return;
29820
30243
  }
29821
30244
  for (const entry of entries) {
29822
- const abs = path27.join(dir, entry.name);
30245
+ const abs = path28.join(dir, entry.name);
29823
30246
  if (entry.isDirectory()) {
29824
30247
  if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
29825
30248
  await walk(abs);
29826
30249
  } else if (entry.isFile()) {
29827
- const ext = path27.extname(entry.name).slice(1).toLowerCase();
30250
+ const ext = path28.extname(entry.name).slice(1).toLowerCase();
29828
30251
  if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
29829
- out.push(path27.relative(this.root, abs));
30252
+ out.push(path28.relative(this.root, abs));
29830
30253
  }
29831
30254
  }
29832
30255
  };
@@ -29856,7 +30279,7 @@ var WorkspaceIndex = class {
29856
30279
  const entries = [];
29857
30280
  for (const rel of rels) {
29858
30281
  try {
29859
- const abs = path27.join(this.root, rel);
30282
+ const abs = path28.join(this.root, rel);
29860
30283
  const stat = await fs11.stat(abs);
29861
30284
  if (stat.size > this.maxFileBytes) continue;
29862
30285
  const buf = await fs11.readFile(abs);
@@ -30346,13 +30769,13 @@ var Cascade = class _Cascade extends EventEmitter {
30346
30769
  this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
30347
30770
  try {
30348
30771
  this.router.setDeadModelStore(new DeadModelStore(
30349
- fileDeadModelPersistence(path27.join(workspacePath, ".cascade", "dead-models.json"))
30772
+ fileDeadModelPersistence(path28.join(workspacePath, ".cascade", "dead-models.json"))
30350
30773
  ));
30351
30774
  } catch {
30352
30775
  }
30353
30776
  try {
30354
30777
  this.resumeStore = new ResumeStore({
30355
- dir: path27.join(workspacePath, ".cascade", "resume")
30778
+ dir: path28.join(workspacePath, ".cascade", "resume")
30356
30779
  });
30357
30780
  } catch {
30358
30781
  }
@@ -30440,8 +30863,8 @@ var Cascade = class _Cascade extends EventEmitter {
30440
30863
  this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
30441
30864
  return;
30442
30865
  }
30443
- const dbPath = ci.dbPath || path27.join(this.workspacePath, ".cascade", "code-index.db");
30444
- fs26.mkdirSync(path27.dirname(dbPath), { recursive: true });
30866
+ const dbPath = ci.dbPath || path28.join(this.workspacePath, ".cascade", "code-index.db");
30867
+ fs27.mkdirSync(path28.dirname(dbPath), { recursive: true });
30445
30868
  const db = new Database2(dbPath);
30446
30869
  db.pragma("journal_mode = WAL");
30447
30870
  const complete = chatCompleterFromProviders(this.config.providers);
@@ -32215,8 +32638,8 @@ function replaceInPlace(target, source) {
32215
32638
  }
32216
32639
  function writeConfigFile(configPath, config) {
32217
32640
  try {
32218
- fs26.mkdirSync(path27.dirname(configPath), { recursive: true });
32219
- fs26.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
32641
+ fs27.mkdirSync(path28.dirname(configPath), { recursive: true });
32642
+ fs27.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf-8");
32220
32643
  return { ok: true };
32221
32644
  } catch (err) {
32222
32645
  return { ok: false, error: err instanceof Error ? err.message : String(err) };
@@ -32626,7 +33049,7 @@ function aggregateCostStats(sessions, opts = {}) {
32626
33049
  }
32627
33050
 
32628
33051
  // src/dashboard/server.ts
32629
- var __dirname$1 = path27.dirname(fileURLToPath(import.meta.url));
33052
+ var __dirname$1 = path28.dirname(fileURLToPath(import.meta.url));
32630
33053
  var ORPHANED_ESCALATION_GRACE_MS = 3e4;
32631
33054
  var MASK = "***";
32632
33055
  function masked(key, value) {
@@ -32945,7 +33368,7 @@ var DashboardServer = class {
32945
33368
  * silently and still typecheck.
32946
33369
  */
32947
33370
  persistConfig(config) {
32948
- const result = writeConfigFile(path27.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
33371
+ const result = writeConfigFile(path28.join(this.workspacePath, CASCADE_CONFIG_FILE), config);
32949
33372
  if (!result.ok) console.warn(`[dashboard] Failed to persist config: ${result.error}`);
32950
33373
  return result;
32951
33374
  }
@@ -32964,7 +33387,7 @@ var DashboardServer = class {
32964
33387
  */
32965
33388
  syncGlobalCredentials() {
32966
33389
  try {
32967
- saveGlobalCredentials(path27.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
33390
+ saveGlobalCredentials(path28.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
32968
33391
  } catch (err) {
32969
33392
  console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
32970
33393
  }
@@ -32979,15 +33402,15 @@ var DashboardServer = class {
32979
33402
  resolveDashboardSecret() {
32980
33403
  const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
32981
33404
  if (fromConfig) return fromConfig;
32982
- const secretPath = path27.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
33405
+ const secretPath = path28.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
32983
33406
  try {
32984
- if (fs26.existsSync(secretPath)) {
32985
- const existing = fs26.readFileSync(secretPath, "utf-8").trim();
33407
+ if (fs27.existsSync(secretPath)) {
33408
+ const existing = fs27.readFileSync(secretPath, "utf-8").trim();
32986
33409
  if (existing.length >= 16) return existing;
32987
33410
  }
32988
33411
  const generated = randomUUID();
32989
- fs26.mkdirSync(path27.dirname(secretPath), { recursive: true });
32990
- fs26.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
33412
+ fs27.mkdirSync(path28.dirname(secretPath), { recursive: true });
33413
+ fs27.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
32991
33414
  if (this.config.dashboard.auth) {
32992
33415
  console.warn(
32993
33416
  `Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
@@ -33014,7 +33437,7 @@ var DashboardServer = class {
33014
33437
  // ── Setup ─────────────────────────────────────
33015
33438
  getGlobalStore() {
33016
33439
  if (!this.globalStore) {
33017
- const globalDbPath = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33440
+ const globalDbPath = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33018
33441
  this.globalStore = new MemoryStore(globalDbPath);
33019
33442
  }
33020
33443
  return this.globalStore;
@@ -33394,12 +33817,12 @@ ${prompt}`;
33394
33817
  }
33395
33818
  }
33396
33819
  watchRuntimeChanges() {
33397
- const workspaceDbPath = path27.join(this.workspacePath, CASCADE_DB_FILE);
33398
- const globalDbPath = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33820
+ const workspaceDbPath = path28.join(this.workspacePath, CASCADE_DB_FILE);
33821
+ const globalDbPath = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33399
33822
  const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
33400
33823
  for (const watchPath of watchPaths) {
33401
- if (!fs26.existsSync(watchPath)) continue;
33402
- fs26.watchFile(watchPath, { interval: 3e3 }, () => {
33824
+ if (!fs27.existsSync(watchPath)) continue;
33825
+ fs27.watchFile(watchPath, { interval: 3e3 }, () => {
33403
33826
  this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
33404
33827
  });
33405
33828
  }
@@ -33521,7 +33944,7 @@ ${prompt}`;
33521
33944
  const sessionId = req.params.id;
33522
33945
  this.store.deleteSession(sessionId);
33523
33946
  this.store.deleteRuntimeSession(sessionId);
33524
- const globalDbPath = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33947
+ const globalDbPath = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33525
33948
  const globalStore = new MemoryStore(globalDbPath);
33526
33949
  try {
33527
33950
  globalStore.deleteRuntimeSession(sessionId);
@@ -33614,7 +34037,7 @@ ${prompt}`;
33614
34037
  });
33615
34038
  this.app.delete("/api/sessions", auth2, (req, res) => {
33616
34039
  const body = req.body;
33617
- const globalDbPath = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
34040
+ const globalDbPath = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33618
34041
  if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
33619
34042
  const globalStore = new MemoryStore(globalDbPath);
33620
34043
  try {
@@ -33637,7 +34060,7 @@ ${prompt}`;
33637
34060
  });
33638
34061
  this.app.delete("/api/runtime", auth2, (_req, res) => {
33639
34062
  this.store.deleteAllRuntimeNodes();
33640
- const globalDbPath = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
34063
+ const globalDbPath = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33641
34064
  const globalStore = new MemoryStore(globalDbPath);
33642
34065
  try {
33643
34066
  globalStore.deleteAllRuntimeNodes();
@@ -33722,12 +34145,12 @@ ${prompt}`;
33722
34145
  if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
33723
34146
  if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
33724
34147
  try {
33725
- const configPath = path27.join(this.workspacePath, CASCADE_CONFIG_FILE);
33726
- const existing = fs26.existsSync(configPath) ? JSON.parse(fs26.readFileSync(configPath, "utf-8")) : {};
34148
+ const configPath = path28.join(this.workspacePath, CASCADE_CONFIG_FILE);
34149
+ const existing = fs27.existsSync(configPath) ? JSON.parse(fs27.readFileSync(configPath, "utf-8")) : {};
33727
34150
  const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
33728
34151
  const tmp = configPath + ".tmp";
33729
- fs26.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
33730
- fs26.renameSync(tmp, configPath);
34152
+ fs27.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
34153
+ fs27.renameSync(tmp, configPath);
33731
34154
  res.json({ ok: true });
33732
34155
  } catch (err) {
33733
34156
  res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
@@ -33755,7 +34178,7 @@ ${prompt}`;
33755
34178
  this.app.get("/api/runtime", auth2, (req, res) => {
33756
34179
  const scope = req.query["scope"] ?? "workspace";
33757
34180
  if (scope === "global") {
33758
- const globalDbPath = path27.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
34181
+ const globalDbPath = path28.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
33759
34182
  const globalStore = new MemoryStore(globalDbPath);
33760
34183
  try {
33761
34184
  res.json({
@@ -34096,13 +34519,13 @@ ${prompt}`;
34096
34519
  res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
34097
34520
  }
34098
34521
  });
34099
- const prodPath = path27.resolve(__dirname$1, "../web/dist");
34100
- const devPath = path27.resolve(__dirname$1, "../../web/dist");
34101
- const webDistPath = fs26.existsSync(prodPath) ? prodPath : devPath;
34102
- if (fs26.existsSync(webDistPath)) {
34522
+ const prodPath = path28.resolve(__dirname$1, "../web/dist");
34523
+ const devPath = path28.resolve(__dirname$1, "../../web/dist");
34524
+ const webDistPath = fs27.existsSync(prodPath) ? prodPath : devPath;
34525
+ if (fs27.existsSync(webDistPath)) {
34103
34526
  this.app.use(express.static(webDistPath));
34104
34527
  this.app.get("*", (_req, res) => {
34105
- res.sendFile(path27.join(webDistPath, "index.html"));
34528
+ res.sendFile(path28.join(webDistPath, "index.html"));
34106
34529
  });
34107
34530
  } else {
34108
34531
  this.app.get("/", (_req, res) => {
@@ -34289,14 +34712,14 @@ function planRetrieval(ctx) {
34289
34712
  }
34290
34713
  var CLOUD_SESSION_FILE = "cloud-session.json";
34291
34714
  function globalDir(homeDir = os6.homedir()) {
34292
- return path27.join(homeDir, GLOBAL_CONFIG_DIR);
34715
+ return path28.join(homeDir, GLOBAL_CONFIG_DIR);
34293
34716
  }
34294
34717
  function cloudSessionPath(dir = globalDir()) {
34295
- return path27.join(dir, CLOUD_SESSION_FILE);
34718
+ return path28.join(dir, CLOUD_SESSION_FILE);
34296
34719
  }
34297
34720
  function loadCloudSession(dir = globalDir()) {
34298
34721
  try {
34299
- const raw = fs26.readFileSync(cloudSessionPath(dir), "utf-8");
34722
+ const raw = fs27.readFileSync(cloudSessionPath(dir), "utf-8");
34300
34723
  const parsed = JSON.parse(raw);
34301
34724
  if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
34302
34725
  return parsed;
@@ -34305,17 +34728,17 @@ function loadCloudSession(dir = globalDir()) {
34305
34728
  }
34306
34729
  }
34307
34730
  function saveCloudSession(session, dir = globalDir()) {
34308
- fs26.mkdirSync(dir, { recursive: true, mode: 448 });
34731
+ fs27.mkdirSync(dir, { recursive: true, mode: 448 });
34309
34732
  const file = cloudSessionPath(dir);
34310
- fs26.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
34733
+ fs27.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
34311
34734
  try {
34312
- fs26.chmodSync(file, 384);
34735
+ fs27.chmodSync(file, 384);
34313
34736
  } catch {
34314
34737
  }
34315
34738
  }
34316
34739
  function clearCloudSession(dir = globalDir()) {
34317
34740
  try {
34318
- fs26.rmSync(cloudSessionPath(dir));
34741
+ fs27.rmSync(cloudSessionPath(dir));
34319
34742
  } catch {
34320
34743
  }
34321
34744
  }
@@ -34487,16 +34910,16 @@ var CloudClient = class _CloudClient {
34487
34910
  return (await this.refresh()).accessToken;
34488
34911
  }
34489
34912
  /** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
34490
- async authed(path29, init = {}) {
34491
- const call = (token) => fetch(this.url(path29), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
34913
+ async authed(path30, init = {}) {
34914
+ const call = (token) => fetch(this.url(path30), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
34492
34915
  let res = await call(await this.accessToken());
34493
34916
  if (res.status === 401) res = await call((await this.refresh()).accessToken);
34494
34917
  if (!res.ok) throw new Error(`Request failed (${res.status}).`);
34495
34918
  const text = await res.text();
34496
34919
  return text ? JSON.parse(text) : {};
34497
34920
  }
34498
- authedGet(path29) {
34499
- return this.authed(path29);
34921
+ authedGet(path30) {
34922
+ return this.authed(path30);
34500
34923
  }
34501
34924
  async fetchMe(token) {
34502
34925
  const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
@@ -34861,6 +35284,6 @@ function applySyncBundle(bundle, config, cleanup) {
34861
35284
  return next;
34862
35285
  }
34863
35286
 
34864
- export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN2 as CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applyEndpointEdit, applyProviderApiKey, applyProviderCredential, applySettingsCredentials, applySettingsPayload, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, commitSettings, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, credentialDispositionForEdit, credentialEndpointIdentity, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, endpointFromSettingsPayload, evaluateAcceptance, explainRefusal, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasDefaultEndpoint, hasProviderCredential, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, normalizeEndpoint, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, priorAzureRow, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sameAzureEndpoint, sameCredentialEndpoint, sameEndpoint, sessionWorthRemembering, settingsSnapshot, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
35287
+ export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN2 as CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeConfigSchema, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, DependencyScheduler, FileMcpOAuthStore, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, RESUME_SCHEMA_VERSION, ResumeStore, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskGraphValidationError, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applyEndpointEdit, applyProviderApiKey, applyProviderCredential, applySettingsCredentials, applySettingsPayload, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, commitSettings, compileSectionGraph, compileSubtaskGraph, compileTaskGraph, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, credentialDispositionForEdit, credentialEndpointIdentity, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, endpointFromSettingsPayload, evaluateAcceptance, explainRefusal, extractCharts, failures, fileExt, fileOAuthProvider, gatherSyncBundle, hasDefaultEndpoint, hasProviderCredential, hasUsableProvider, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, normalizeEndpoint, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, priorAzureRow, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sameAzureEndpoint, sameCredentialEndpoint, sameEndpoint, sessionWorthRemembering, settingsSnapshot, sniffImage, splitSlides, streamCascade, stripInline, summarizeCompleted, undecided, uniqueMcpServerName };
34865
35288
  //# sourceMappingURL=index.js.map
34866
35289
  //# sourceMappingURL=index.js.map