cascade-ai 0.66.1 → 0.67.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/README.md +8 -0
- package/dist/cli.cjs +1342 -484
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1335 -477
- package/dist/cli.js.map +1 -1
- package/dist/desktop-core.cjs +70407 -1980
- package/dist/index.cjs +1315 -430
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +278 -1
- package/dist/index.d.ts +278 -1
- package/dist/index.js +1287 -424
- package/dist/index.js.map +1 -1
- package/package.json +5 -1
package/dist/index.cjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
var Database = require('better-sqlite3');
|
|
4
|
-
var
|
|
5
|
-
var
|
|
4
|
+
var path25 = require('path');
|
|
5
|
+
var fs24 = require('fs');
|
|
6
6
|
var os6 = require('os');
|
|
7
7
|
var crypto4 = require('crypto');
|
|
8
8
|
var EventEmitter = require('events');
|
|
@@ -15,7 +15,7 @@ var http = require('http');
|
|
|
15
15
|
var https = require('https');
|
|
16
16
|
var zlib = require('zlib');
|
|
17
17
|
var stream = require('stream');
|
|
18
|
-
var
|
|
18
|
+
var fs5 = require('fs/promises');
|
|
19
19
|
var ignoreFactory = require('ignore');
|
|
20
20
|
var child_process = require('child_process');
|
|
21
21
|
var util = require('util');
|
|
@@ -61,8 +61,8 @@ function _interopNamespace(e) {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
var Database__default = /*#__PURE__*/_interopDefault(Database);
|
|
64
|
-
var
|
|
65
|
-
var
|
|
64
|
+
var path25__default = /*#__PURE__*/_interopDefault(path25);
|
|
65
|
+
var fs24__default = /*#__PURE__*/_interopDefault(fs24);
|
|
66
66
|
var os6__default = /*#__PURE__*/_interopDefault(os6);
|
|
67
67
|
var crypto4__default = /*#__PURE__*/_interopDefault(crypto4);
|
|
68
68
|
var EventEmitter__default = /*#__PURE__*/_interopDefault(EventEmitter);
|
|
@@ -72,7 +72,7 @@ var dns__default = /*#__PURE__*/_interopDefault(dns);
|
|
|
72
72
|
var http__default = /*#__PURE__*/_interopDefault(http);
|
|
73
73
|
var https__default = /*#__PURE__*/_interopDefault(https);
|
|
74
74
|
var zlib__default = /*#__PURE__*/_interopDefault(zlib);
|
|
75
|
-
var
|
|
75
|
+
var fs5__default = /*#__PURE__*/_interopDefault(fs5);
|
|
76
76
|
var ignoreFactory__namespace = /*#__PURE__*/_interopNamespace(ignoreFactory);
|
|
77
77
|
var PDFDocument__default = /*#__PURE__*/_interopDefault(PDFDocument);
|
|
78
78
|
var dns2__default = /*#__PURE__*/_interopDefault(dns2);
|
|
@@ -107,12 +107,12 @@ var init_audit_logger = __esm({
|
|
|
107
107
|
constructor(workspacePath, debugMode = false) {
|
|
108
108
|
this.workspacePath = workspacePath;
|
|
109
109
|
this.debugMode = debugMode;
|
|
110
|
-
const cascadeDir =
|
|
111
|
-
if (!
|
|
112
|
-
|
|
110
|
+
const cascadeDir = path25__default.default.join(workspacePath, ".cascade");
|
|
111
|
+
if (!fs24__default.default.existsSync(cascadeDir)) {
|
|
112
|
+
fs24__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
113
113
|
}
|
|
114
|
-
this.keyPath =
|
|
115
|
-
this.dbPath =
|
|
114
|
+
this.keyPath = path25__default.default.join(cascadeDir, "audit_log.key");
|
|
115
|
+
this.dbPath = path25__default.default.join(cascadeDir, "audit_log.db");
|
|
116
116
|
this.initEncryptionKey();
|
|
117
117
|
this.db = new Database__default.default(this.dbPath);
|
|
118
118
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -142,11 +142,11 @@ var init_audit_logger = __esm({
|
|
|
142
142
|
return crypto4__default.default.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
143
143
|
}
|
|
144
144
|
initEncryptionKey() {
|
|
145
|
-
if (
|
|
146
|
-
this.encryptionKey =
|
|
145
|
+
if (fs24__default.default.existsSync(this.keyPath)) {
|
|
146
|
+
this.encryptionKey = fs24__default.default.readFileSync(this.keyPath);
|
|
147
147
|
} else {
|
|
148
148
|
this.encryptionKey = crypto4__default.default.randomBytes(32);
|
|
149
|
-
|
|
149
|
+
fs24__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
encrypt(text) {
|
|
@@ -224,9 +224,9 @@ var init_audit_logger = __esm({
|
|
|
224
224
|
dumpDebugIfNeeded() {
|
|
225
225
|
if (!this.debugMode) return;
|
|
226
226
|
try {
|
|
227
|
-
const dumpPath =
|
|
227
|
+
const dumpPath = path25__default.default.join(os6__default.default.tmpdir(), "cascade_audit_logs_debug.json");
|
|
228
228
|
const entries = this.getAllLogs();
|
|
229
|
-
|
|
229
|
+
fs24__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
230
230
|
} catch (err) {
|
|
231
231
|
console.error("Failed to dump debug audit logs", err);
|
|
232
232
|
}
|
|
@@ -1908,7 +1908,7 @@ function reconcilePrice(model, live) {
|
|
|
1908
1908
|
}
|
|
1909
1909
|
|
|
1910
1910
|
// src/constants.ts
|
|
1911
|
-
var CASCADE_VERSION = "0.
|
|
1911
|
+
var CASCADE_VERSION = "0.67.0";
|
|
1912
1912
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1913
1913
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1914
1914
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4494,13 +4494,13 @@ var DeadModelStore = class {
|
|
|
4494
4494
|
function fileDeadModelPersistence(filePath) {
|
|
4495
4495
|
return {
|
|
4496
4496
|
load() {
|
|
4497
|
-
if (!
|
|
4498
|
-
const raw = JSON.parse(
|
|
4497
|
+
if (!fs24.existsSync(filePath)) return [];
|
|
4498
|
+
const raw = JSON.parse(fs24.readFileSync(filePath, "utf-8"));
|
|
4499
4499
|
return Array.isArray(raw) ? raw : [];
|
|
4500
4500
|
},
|
|
4501
4501
|
save(records) {
|
|
4502
|
-
|
|
4503
|
-
|
|
4502
|
+
fs24.mkdirSync(path25.dirname(filePath), { recursive: true });
|
|
4503
|
+
fs24.writeFileSync(filePath, JSON.stringify(records, null, 2));
|
|
4504
4504
|
}
|
|
4505
4505
|
};
|
|
4506
4506
|
}
|
|
@@ -4663,7 +4663,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4663
4663
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4664
4664
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4665
4665
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4666
|
-
var DEFAULT_CACHE_FILE =
|
|
4666
|
+
var DEFAULT_CACHE_FILE = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4667
4667
|
function normalizeModelId2(id) {
|
|
4668
4668
|
let s = id.toLowerCase();
|
|
4669
4669
|
const slash = s.lastIndexOf("/");
|
|
@@ -4698,7 +4698,7 @@ var LiveDataProvider = class {
|
|
|
4698
4698
|
if (this.loaded) return;
|
|
4699
4699
|
this.loaded = true;
|
|
4700
4700
|
try {
|
|
4701
|
-
const raw = await
|
|
4701
|
+
const raw = await fs5__default.default.readFile(this.opts.cacheFile, "utf-8");
|
|
4702
4702
|
const cache = JSON.parse(raw);
|
|
4703
4703
|
if (cache.snapshot?.families) {
|
|
4704
4704
|
this.snapshot = cache.snapshot;
|
|
@@ -4810,14 +4810,14 @@ var LiveDataProvider = class {
|
|
|
4810
4810
|
}
|
|
4811
4811
|
async saveCache() {
|
|
4812
4812
|
try {
|
|
4813
|
-
await
|
|
4813
|
+
await fs5__default.default.mkdir(path25__default.default.dirname(this.opts.cacheFile), { recursive: true });
|
|
4814
4814
|
const cache = {
|
|
4815
4815
|
fetchedAt: this.fetchedAt,
|
|
4816
4816
|
snapshot: this.snapshot ?? void 0,
|
|
4817
4817
|
prices: Object.fromEntries(this.prices),
|
|
4818
4818
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4819
4819
|
};
|
|
4820
|
-
await
|
|
4820
|
+
await fs5__default.default.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4821
4821
|
} catch {
|
|
4822
4822
|
}
|
|
4823
4823
|
}
|
|
@@ -6636,7 +6636,16 @@ var KNOWN_TOOLS = [
|
|
|
6636
6636
|
"glob",
|
|
6637
6637
|
"grep",
|
|
6638
6638
|
"web_fetch",
|
|
6639
|
-
|
|
6639
|
+
// Every media tool buildMediaTools can register, not just the image one.
|
|
6640
|
+
// Omitting three of the four meant a run whose ONLY tools were media ones
|
|
6641
|
+
// (a video-generation subtask on an account with no file/web tools) counted
|
|
6642
|
+
// as "no tools registered" and dropped all tool guidance — the worker was
|
|
6643
|
+
// then told nothing about using tools at all, and wrote the video as prose.
|
|
6644
|
+
"generate_image",
|
|
6645
|
+
"generate_video",
|
|
6646
|
+
"generate_speech",
|
|
6647
|
+
"transcribe_audio",
|
|
6648
|
+
"generate_document"
|
|
6640
6649
|
];
|
|
6641
6650
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
6642
6651
|
"generate_image",
|
|
@@ -6653,19 +6662,55 @@ function buildWorkerRules(has) {
|
|
|
6653
6662
|
canWriteFiles && "- If the task asks for a file or artifact, you must actually create it in the workspace, verify that it exists, and inspect it before claiming success.",
|
|
6654
6663
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
6655
6664
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
6665
|
+
// A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
|
|
6666
|
+
// file_write does exactly what it promises — writes the model's characters
|
|
6667
|
+
// verbatim — so without this rule the worker "wrote" a Word document that
|
|
6668
|
+
// Word correctly reported as corrupted. Stated as an explicit prohibition
|
|
6669
|
+
// on the tool it would otherwise reach for, because "use generate_document"
|
|
6670
|
+
// alone competes with a file_write habit the model already has.
|
|
6671
|
+
has("generate_document") && '- For a Word (.docx), PowerPoint (.pptx) or Excel (.xlsx) deliverable you MUST call the "generate_document" tool and NEVER "file_write" or "run_code" \u2014 those formats are ZIP archives of XML, so text written straight to the path opens as a corrupted file. Pass the target path plus the source: Markdown for .docx, Markdown slides separated by --- rules for .pptx (each slide starts with a heading), CSV for .xlsx.',
|
|
6672
|
+
// The alternative to this rule is not "a worse chart" — it is a paragraph
|
|
6673
|
+
// describing a chart that does not exist, which is what real decks came
|
|
6674
|
+
// back with. A `chart:` block is checkable, carries the exact numbers, and
|
|
6675
|
+
// becomes a genuine editable PowerPoint chart object.
|
|
6676
|
+
has("generate_document") && '- For any data-driven visualization (a chart, graph, trend, breakdown or comparison of numbers), emit a fenced ```chart:bar block \u2014 also chart:line, chart:pie, chart:doughnut, chart:area, chart:scatter \u2014 whose body is an optional "title: ..." line followed by CSV: a header row of "<category label>,<series name>,<series name>", then one row per category. In a PowerPoint deliverable that becomes a REAL, editable chart carrying your exact numbers; in Word and Excel the same block keeps every value as a table or worksheet. Never write prose describing a chart in place of emitting one.',
|
|
6656
6677
|
// Without this the model "writes the image" as prose — a bracketed
|
|
6657
6678
|
// description sitting in the deck where the picture should be — even with a
|
|
6658
6679
|
// working image model registered. Naming the reference syntax matters as
|
|
6659
6680
|
// much as naming the tool: a generated image nobody embeds is still a
|
|
6660
|
-
// missing image.
|
|
6681
|
+
// missing image. It also has to say the reference must stand ALONE on its
|
|
6682
|
+
// line, because only a standalone reference is parsed as a picture (see
|
|
6683
|
+
// core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
|
|
6684
|
+
// silently degrades to caption text.
|
|
6661
6685
|
//
|
|
6662
|
-
// Scoped to PowerPoint/Word specifically: those are the only
|
|
6663
|
-
//
|
|
6664
|
-
//
|
|
6665
|
-
//
|
|
6666
|
-
//
|
|
6667
|
-
has("generate_image") &&
|
|
6668
|
-
|
|
6686
|
+
// Scoped to PowerPoint/Word specifically: those are the only renderers that
|
|
6687
|
+
// actually embed a Markdown image reference. PDF and plain-text/Markdown
|
|
6688
|
+
// deliverables flatten a reference straight to caption text — telling the
|
|
6689
|
+
// model to call generate_image for THOSE just pays for an image nobody
|
|
6690
|
+
// ever sees.
|
|
6691
|
+
has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
|
|
6692
|
+
// standalone-line parser constraint, says to generate BEFORE writing the
|
|
6693
|
+
// document, and points data-driven visuals at a ```chart: block rather
|
|
6694
|
+
// than a Markdown table, which is only the right advice now that chart
|
|
6695
|
+
// blocks render as real chart objects.
|
|
6696
|
+
'- When a slide deck (PowerPoint) or Word document deliverable calls for a decorative image, photo or illustration, you MUST call the "generate_image" tool \u2014 once per image the deliverable needs, BEFORE you write the document \u2014 and then put each result on a line of its OWN using Markdown image syntax: , where "location" is exactly the string the tool reported back. A reference inside a sentence stays prose and no picture appears. NEVER write a text placeholder or a bracketed description such as [image: a cat] in its place, and never state that an image is included when you did not generate one. Do NOT use "generate_image" for a chart, graph or diagram that must show exact data (numbers, axes, labels) \u2014 an image model cannot guarantee those values are correct; emit a ```chart: block instead. For any OTHER deliverable format (PDF, plain text, code, etc.) do NOT call "generate_image" \u2014 those cannot embed the result, so describe the visual in words instead.',
|
|
6697
|
+
// The other half of image reliability, and the half no amount of
|
|
6698
|
+
// instruction-tightening could fix: generate_image only exists when an
|
|
6699
|
+
// OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
|
|
6700
|
+
// With neither, the model was never given a choice — and a model that
|
|
6701
|
+
// doesn't know that writes "[illustration of a cat]" and moves on. Said out
|
|
6702
|
+
// loud, up front, so the fallback is a real chart or honest prose.
|
|
6703
|
+
!has("generate_image") && (has("generate_document") || canWriteFiles) && "- No image-generation model is available on this run, so there is NO tool that can draw a picture. Do not emit a Markdown image reference, a bracketed placeholder such as [image: a cat], or any claim that an illustration is included. If the request needs a data visualization use a ```chart: block, which needs no image model; otherwise describe the visual in words and say plainly that no image could be generated.",
|
|
6704
|
+
// The video counterpart of the image rule above, and the fix for the
|
|
6705
|
+
// reported "it writes scripts forever and never makes the video" run. The
|
|
6706
|
+
// failure mode is identical — the model narrates the deliverable instead of
|
|
6707
|
+
// producing it — but the stakes are not: a video subtask that ends in prose
|
|
6708
|
+
// has burned the whole pre-production plan for nothing. Unlike the image
|
|
6709
|
+
// rule this is NOT scoped to a document format, because the clip itself is
|
|
6710
|
+
// the deliverable rather than an illustration inside one, and the reference
|
|
6711
|
+
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
6712
|
+
has("generate_video") && '- When the subtask deliverable is a video, you MUST call the "generate_video" tool \u2014 that call IS the deliverable, and the subtask is not done until it has returned. NEVER deliver the video as prose, a script, a storyboard, or a bracketed placeholder such as [video: a cat skating], and NEVER claim a video exists unless the tool reported a location for it. Call it exactly ONCE: it is billed per second of output and renders for minutes, so a second call charges the user again. Then report the location string the tool returned VERBATIM as your result, referencing it as [description](location) if it goes inside a document. If the tool reports a failure or a timeout, report that failure verbatim and stop \u2014 do NOT call it again, and do NOT substitute a written description of a video that was never made.',
|
|
6713
|
+
has("run_code") && `- Use the "run_code" tool for data processing, archives, and file formats not covered by a dedicated tool. ${has("generate_document") ? 'Do NOT use it to build a .docx, .pptx or .xlsx \u2014 "generate_document" already produces those correctly. ' : ""}${has("pdf_create") ? 'Do NOT use it to build a PDF \u2014 "pdf_create" does that. ' : ""}Always cleanup after code execution.`,
|
|
6669
6714
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
6670
6715
|
has("peer_message") && '- Use the "peer_message" tool to communicate with other T3 workers if your tasks have dependencies or shared state. You can send updates or wait for signals.',
|
|
6671
6716
|
hasAnyTool && "- Only use tools directly relevant to THIS subtask. Do not reach for an unrelated connected-service action (e.g. creating, deleting, or modifying a repository, issue, or PR; sending a message) unless the subtask explicitly calls for it.",
|
|
@@ -6676,13 +6721,34 @@ function buildWorkerRules(has) {
|
|
|
6676
6721
|
Rules:
|
|
6677
6722
|
${rules.filter((r) => r !== false).join("\n")}`;
|
|
6678
6723
|
}
|
|
6679
|
-
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
|
|
6724
|
+
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
|
|
6725
|
+
var ARTIFACT_FILE_RE = /\b[\w./-]+\.(pdf|md|html|txt|json|csv|py|js|ts|tsx|jsx|docx?|pptx?|xlsx?|png|jpg|jpeg|svg|gif)\b/i;
|
|
6680
6726
|
function shouldRequireArtifact(assignment, toolNames) {
|
|
6681
6727
|
if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
|
|
6682
6728
|
if (assignment?.files?.length) return true;
|
|
6683
6729
|
const haystack = `${assignment?.description ?? ""}
|
|
6684
6730
|
${assignment?.expectedOutput ?? ""}`;
|
|
6685
|
-
return
|
|
6731
|
+
return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
6732
|
+
}
|
|
6733
|
+
var VISUAL_REQUEST_RE = /\b(image|images|picture|pictures|photo|photos|illustration|illustrations|visual|visuals|visualisation|visualization|visualizations|chart|charts|graph|graphs|plot|plots|diagram|diagrams|infographic)\b/i;
|
|
6734
|
+
var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
|
|
6735
|
+
var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
|
|
6736
|
+
function missingVisualEvidence(assignment, output, calledTools, toolNames) {
|
|
6737
|
+
const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
|
|
6738
|
+
if (!canVisualize) return null;
|
|
6739
|
+
const haystack = `${assignment?.description ?? ""}
|
|
6740
|
+
${assignment?.expectedOutput ?? ""}`;
|
|
6741
|
+
if (!VISUAL_REQUEST_RE.test(haystack)) return null;
|
|
6742
|
+
if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
|
|
6743
|
+
if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
|
|
6744
|
+
const options = [];
|
|
6745
|
+
if (toolNames.includes("generate_image")) {
|
|
6746
|
+
options.push('call "generate_image" and reference the result on its own line as ');
|
|
6747
|
+
}
|
|
6748
|
+
if (toolNames.includes("generate_document")) {
|
|
6749
|
+
options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
|
|
6750
|
+
}
|
|
6751
|
+
return `The subtask asks for a visual, but the output contains no embedded image and no chart block \u2014 only prose. ${options.join(", or ")}. Describing a picture is not producing one.`;
|
|
6686
6752
|
}
|
|
6687
6753
|
var T3Worker = class extends BaseTier {
|
|
6688
6754
|
router;
|
|
@@ -6863,6 +6929,18 @@ Now execute your subtask using this context where relevant.`
|
|
|
6863
6929
|
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6864
6930
|
}
|
|
6865
6931
|
}
|
|
6932
|
+
const visualIssue = missingVisualEvidence(
|
|
6933
|
+
assignment,
|
|
6934
|
+
output,
|
|
6935
|
+
toolCalls.map((t) => t.name),
|
|
6936
|
+
this.tools.map((t) => t.name)
|
|
6937
|
+
);
|
|
6938
|
+
if (visualIssue) {
|
|
6939
|
+
correctionAttempts = 1;
|
|
6940
|
+
issues.push(visualIssue);
|
|
6941
|
+
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
6942
|
+
output = await this.correctOutput(output, [visualIssue]);
|
|
6943
|
+
}
|
|
6866
6944
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6867
6945
|
const testResult = await this.selfTest(assignment, output);
|
|
6868
6946
|
checksRun.push(...testResult.checksRun);
|
|
@@ -7177,8 +7255,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7177
7255
|
// Media generation can run for a minute; without this a cancelled run
|
|
7178
7256
|
// still pays for an image nobody will see.
|
|
7179
7257
|
...this.signal ? { signal: this.signal } : {},
|
|
7180
|
-
saveSnapshot: async (
|
|
7181
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7258
|
+
saveSnapshot: async (path27, content) => {
|
|
7259
|
+
this.store?.addFileSnapshot(this.taskId, path27, content);
|
|
7182
7260
|
},
|
|
7183
7261
|
sendPeerSync: (to, syncType, content) => {
|
|
7184
7262
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7207,6 +7285,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7207
7285
|
if (isTaggedSystemic || isProviderBacked && classifyProviderError(err).systemic) {
|
|
7208
7286
|
throw new CriticalToolError(errMsg, tc.name);
|
|
7209
7287
|
}
|
|
7288
|
+
if (isProviderBacked) {
|
|
7289
|
+
throw new CriticalToolError(
|
|
7290
|
+
`${tc.name} failed and was not retried \u2014 media generation is billed per attempt, so Cascade stops on the first failure instead of paying for a second. Reason: ${errMsg}`,
|
|
7291
|
+
tc.name
|
|
7292
|
+
);
|
|
7293
|
+
}
|
|
7210
7294
|
return await this.adaptiveFallback(tc, `Tool error: ${errMsg}`);
|
|
7211
7295
|
}
|
|
7212
7296
|
}
|
|
@@ -7216,6 +7300,14 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7216
7300
|
* 1. Find a semantically similar registered tool and retry with same input
|
|
7217
7301
|
* 2. Synthesize a new tool via ToolCreator (if available) and run it
|
|
7218
7302
|
* 3. Return the original error so the agent loop can decide what to do next
|
|
7303
|
+
*
|
|
7304
|
+
* Scoped to CHEAP, task-scoped failures — the class of problem it was built
|
|
7305
|
+
* for is a wrong or missing tool name, where a name-similar sibling plausibly
|
|
7306
|
+
* does the same job for the same (near-zero) cost. PROVIDER_BACKED_TOOLS never
|
|
7307
|
+
* reach it: their siblings are keyword-similar but semantically different
|
|
7308
|
+
* (generate_image is not a stand-in for generate_video), and every attempt —
|
|
7309
|
+
* theirs, or a synthesized replacement's — is separately billed. See the
|
|
7310
|
+
* fast-fail branch in executeTool().
|
|
7219
7311
|
*/
|
|
7220
7312
|
async adaptiveFallback(tc, originalError) {
|
|
7221
7313
|
const altTool = this.findAlternativeTool(tc.name);
|
|
@@ -7340,7 +7432,7 @@ ${assignment.expectedOutput}`;
|
|
|
7340
7432
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7341
7433
|
const haystack = `${assignment.description}
|
|
7342
7434
|
${assignment.expectedOutput}`;
|
|
7343
|
-
const matches = haystack.match(
|
|
7435
|
+
const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
|
|
7344
7436
|
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
7345
7437
|
}
|
|
7346
7438
|
async verifyArtifacts(assignment) {
|
|
@@ -7351,9 +7443,9 @@ ${assignment.expectedOutput}`;
|
|
|
7351
7443
|
const { promisify: promisify4 } = await import('util');
|
|
7352
7444
|
const execAsync2 = promisify4(exec2);
|
|
7353
7445
|
for (const artifactPath of artifactPaths) {
|
|
7354
|
-
const absolutePath =
|
|
7446
|
+
const absolutePath = path25__default.default.resolve(process.cwd(), artifactPath);
|
|
7355
7447
|
try {
|
|
7356
|
-
const stat = await
|
|
7448
|
+
const stat = await fs5__default.default.stat(absolutePath);
|
|
7357
7449
|
if (!stat.isFile()) {
|
|
7358
7450
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
7359
7451
|
continue;
|
|
@@ -7362,8 +7454,22 @@ ${assignment.expectedOutput}`;
|
|
|
7362
7454
|
issues.push(`Artifact is empty: ${artifactPath}`);
|
|
7363
7455
|
continue;
|
|
7364
7456
|
}
|
|
7365
|
-
if (
|
|
7366
|
-
const
|
|
7457
|
+
if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
|
|
7458
|
+
const head = Buffer.alloc(4);
|
|
7459
|
+
const fh = await fs5__default.default.open(absolutePath, "r");
|
|
7460
|
+
try {
|
|
7461
|
+
await fh.read(head, 0, 4, 0);
|
|
7462
|
+
} finally {
|
|
7463
|
+
await fh.close();
|
|
7464
|
+
}
|
|
7465
|
+
if (head.toString("latin1") !== "PK") {
|
|
7466
|
+
issues.push(
|
|
7467
|
+
`${artifactPath} is not a valid Office file \u2014 it must be written with the generate_document tool, which renders the real OOXML archive. Plain text saved under that extension opens as corrupted.`
|
|
7468
|
+
);
|
|
7469
|
+
continue;
|
|
7470
|
+
}
|
|
7471
|
+
} else if (!/\.pdf$/i.test(artifactPath)) {
|
|
7472
|
+
const content = await fs5__default.default.readFile(absolutePath, "utf-8");
|
|
7367
7473
|
if (!content.trim()) {
|
|
7368
7474
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
7369
7475
|
continue;
|
|
@@ -7372,7 +7478,7 @@ ${assignment.expectedOutput}`;
|
|
|
7372
7478
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7373
7479
|
continue;
|
|
7374
7480
|
}
|
|
7375
|
-
const ext =
|
|
7481
|
+
const ext = path25__default.default.extname(absolutePath).toLowerCase();
|
|
7376
7482
|
try {
|
|
7377
7483
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7378
7484
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -7968,15 +8074,244 @@ _(incomplete: ${t2Result.issues.join("; ")})_`;
|
|
|
7968
8074
|
return output;
|
|
7969
8075
|
}
|
|
7970
8076
|
|
|
8077
|
+
// src/core/multimodal/registry.ts
|
|
8078
|
+
var CAPABILITIES = [
|
|
8079
|
+
// ── Image ──
|
|
8080
|
+
{
|
|
8081
|
+
modality: "image",
|
|
8082
|
+
provider: "openai",
|
|
8083
|
+
modelId: "dall-e-3",
|
|
8084
|
+
api: "openai-images"
|
|
8085
|
+
},
|
|
8086
|
+
{
|
|
8087
|
+
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
8088
|
+
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
8089
|
+
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
8090
|
+
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
8091
|
+
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
8092
|
+
// 'gemini-generate-content' rather than just the modelId moving.
|
|
8093
|
+
// https://ai.google.dev/gemini-api/docs/imagen
|
|
8094
|
+
modality: "image",
|
|
8095
|
+
provider: "gemini",
|
|
8096
|
+
modelId: "gemini-2.5-flash-image",
|
|
8097
|
+
api: "gemini-generate-content"
|
|
8098
|
+
},
|
|
8099
|
+
// ── Speech (text → audio) ──
|
|
8100
|
+
{
|
|
8101
|
+
modality: "speech",
|
|
8102
|
+
provider: "openai",
|
|
8103
|
+
modelId: "tts-1",
|
|
8104
|
+
api: "openai-speech"
|
|
8105
|
+
},
|
|
8106
|
+
{
|
|
8107
|
+
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
8108
|
+
// ships these as the same voice at two fidelities and charges double for
|
|
8109
|
+
// this one. That is the vendor's own ordering, not an inference.
|
|
8110
|
+
modality: "speech",
|
|
8111
|
+
provider: "openai",
|
|
8112
|
+
modelId: "tts-1-hd",
|
|
8113
|
+
api: "openai-speech",
|
|
8114
|
+
quality: 70,
|
|
8115
|
+
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
8116
|
+
},
|
|
8117
|
+
// ── Transcription (audio → text) ──
|
|
8118
|
+
{
|
|
8119
|
+
modality: "transcription",
|
|
8120
|
+
provider: "openai",
|
|
8121
|
+
modelId: "whisper-1",
|
|
8122
|
+
api: "openai-transcriptions"
|
|
8123
|
+
},
|
|
8124
|
+
// ── Video ──
|
|
8125
|
+
{
|
|
8126
|
+
// Long-running: submit returns an operation name, the video lands minutes
|
|
8127
|
+
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
8128
|
+
// that polling loop; the registry only records the shape via `api`.
|
|
8129
|
+
//
|
|
8130
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
8131
|
+
// not the Gemini Developer API's — this file calls
|
|
8132
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
8133
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
8134
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
8135
|
+
// there is `veo-3.1-generate-preview`.
|
|
8136
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
8137
|
+
modality: "video",
|
|
8138
|
+
provider: "gemini",
|
|
8139
|
+
modelId: "veo-3.1-generate-preview",
|
|
8140
|
+
api: "gemini-predict-lro"
|
|
8141
|
+
}
|
|
8142
|
+
];
|
|
8143
|
+
function capabilityCost(cap) {
|
|
8144
|
+
const match = findPricing(cap.modelId, cap.provider, {
|
|
8145
|
+
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
8146
|
+
});
|
|
8147
|
+
const rate = match?.entry.rates?.[0];
|
|
8148
|
+
if (!rate) return null;
|
|
8149
|
+
const unitLabel = {
|
|
8150
|
+
per_image: "per image",
|
|
8151
|
+
per_second: "per second of video",
|
|
8152
|
+
per_character: "per character",
|
|
8153
|
+
per_audio_minute: "per audio minute"
|
|
8154
|
+
};
|
|
8155
|
+
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
8156
|
+
const amount = rate.amount;
|
|
8157
|
+
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
8158
|
+
return { label: `$${shown} ${unit}`, amount, unit };
|
|
8159
|
+
}
|
|
8160
|
+
function explainChoice(c, cost, modality, isBest) {
|
|
8161
|
+
const priced = cost ? ` ${cost.label}.` : "";
|
|
8162
|
+
if (c.quality !== void 0) {
|
|
8163
|
+
return isBest ? `${c.modelId} (${c.provider}) \u2014 ranked highest for ${modality}: ${c.qualityBasis}.${priced}` : `${c.modelId} (${c.provider}) \u2014 ranked alternative for ${modality}: ${c.qualityBasis}.${priced}`;
|
|
8164
|
+
}
|
|
8165
|
+
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
8166
|
+
return isBest ? `${c.modelId} (${c.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c.modelId} (${c.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
|
|
8167
|
+
}
|
|
8168
|
+
var MultimodalRegistry = class {
|
|
8169
|
+
/** Provider types the user has actually configured a key for. */
|
|
8170
|
+
available;
|
|
8171
|
+
constructor(availableProviders) {
|
|
8172
|
+
this.available = new Set(availableProviders);
|
|
8173
|
+
}
|
|
8174
|
+
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
8175
|
+
usable(modality) {
|
|
8176
|
+
return CAPABILITIES.filter(
|
|
8177
|
+
(c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
|
|
8178
|
+
);
|
|
8179
|
+
}
|
|
8180
|
+
/** Modalities this account can actually generate right now. */
|
|
8181
|
+
availableModalities() {
|
|
8182
|
+
return [...new Set(this.usable().map((c) => c.modality))];
|
|
8183
|
+
}
|
|
8184
|
+
/**
|
|
8185
|
+
* Every usable capability for a modality, best first.
|
|
8186
|
+
*
|
|
8187
|
+
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
8188
|
+
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
8189
|
+
* the user naming a model is a stronger signal than any prior.
|
|
8190
|
+
*
|
|
8191
|
+
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
8192
|
+
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
8193
|
+
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
8194
|
+
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
8195
|
+
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
8196
|
+
*/
|
|
8197
|
+
rank(modality, opts = {}) {
|
|
8198
|
+
const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
8199
|
+
const qa = a.c.quality ?? -1;
|
|
8200
|
+
const qb = b.c.quality ?? -1;
|
|
8201
|
+
if (qa !== qb) return qb - qa;
|
|
8202
|
+
const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
8203
|
+
const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
8204
|
+
return pa - pb;
|
|
8205
|
+
});
|
|
8206
|
+
const results = scored.map(({ c, cost }, i) => ({
|
|
8207
|
+
capability: c,
|
|
8208
|
+
cost,
|
|
8209
|
+
reason: explainChoice(c, cost, modality, i === 0)
|
|
8210
|
+
}));
|
|
8211
|
+
if (opts.preferModel) {
|
|
8212
|
+
const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
|
|
8213
|
+
if (i > -1) {
|
|
8214
|
+
const [wanted] = results.splice(i, 1);
|
|
8215
|
+
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
8216
|
+
}
|
|
8217
|
+
}
|
|
8218
|
+
return results;
|
|
8219
|
+
}
|
|
8220
|
+
/**
|
|
8221
|
+
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
8222
|
+
*/
|
|
8223
|
+
select(modality, opts = {}) {
|
|
8224
|
+
return this.rank(modality, opts)[0] ?? null;
|
|
8225
|
+
}
|
|
8226
|
+
/**
|
|
8227
|
+
* A plain-language account of what this account can and cannot generate —
|
|
8228
|
+
* including the things it cannot, and why.
|
|
8229
|
+
*
|
|
8230
|
+
* This is the USER-facing inventory (model ids and unit prices), keyed on
|
|
8231
|
+
* which providers are configured. It is deliberately NOT what the planner
|
|
8232
|
+
* reads: see `describeGenerationForPlanner()` below for that, and the note
|
|
8233
|
+
* there for why the two cannot be the same string.
|
|
8234
|
+
*/
|
|
8235
|
+
describe() {
|
|
8236
|
+
const lines = [];
|
|
8237
|
+
const usable = this.usable();
|
|
8238
|
+
if (usable.length) {
|
|
8239
|
+
lines.push("Generation models available on your configured providers:");
|
|
8240
|
+
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
8241
|
+
const forModality = usable.filter((c) => c.modality === modality);
|
|
8242
|
+
if (!forModality.length) continue;
|
|
8243
|
+
const shown = forModality.map((c) => {
|
|
8244
|
+
const cost = capabilityCost(c);
|
|
8245
|
+
return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
8246
|
+
});
|
|
8247
|
+
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
8248
|
+
}
|
|
8249
|
+
} else {
|
|
8250
|
+
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
8251
|
+
}
|
|
8252
|
+
const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
|
|
8253
|
+
for (const c of blocked) {
|
|
8254
|
+
lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
|
|
8255
|
+
}
|
|
8256
|
+
lines.push(
|
|
8257
|
+
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
8258
|
+
);
|
|
8259
|
+
return lines.join("\n");
|
|
8260
|
+
}
|
|
8261
|
+
};
|
|
8262
|
+
var PLANNER_FACTS = [
|
|
8263
|
+
{ tool: "generate_image", modality: "image", shape: "one call returns one finished image" },
|
|
8264
|
+
{ tool: "generate_speech", modality: "speech", shape: "one call returns one finished audio file" },
|
|
8265
|
+
{
|
|
8266
|
+
tool: "generate_video",
|
|
8267
|
+
modality: "video",
|
|
8268
|
+
shape: "one call returns one finished clip, after rendering for 1-3 minutes (Cascade stops waiting at 8)"
|
|
8269
|
+
},
|
|
8270
|
+
{
|
|
8271
|
+
tool: "transcribe_audio",
|
|
8272
|
+
modality: "transcription",
|
|
8273
|
+
shape: "one call returns the transcript of one audio file"
|
|
8274
|
+
}
|
|
8275
|
+
];
|
|
8276
|
+
function plannerPriceNote(modality) {
|
|
8277
|
+
const costs = CAPABILITIES.filter((c) => c.modality === modality && !c.unsupported).map((c) => capabilityCost(c)).filter((c) => c !== null);
|
|
8278
|
+
if (!costs.length) return "";
|
|
8279
|
+
const labels = new Set(costs.map((c) => c.label));
|
|
8280
|
+
if (labels.size === 1) return ` Billed ${costs[0].label}.`;
|
|
8281
|
+
const units = new Set(costs.map((c) => c.unit));
|
|
8282
|
+
return units.size === 1 ? ` Billed ${[...units][0]}.` : "";
|
|
8283
|
+
}
|
|
8284
|
+
function describeGenerationForPlanner(has) {
|
|
8285
|
+
const present = PLANNER_FACTS.filter((f) => has(f.tool));
|
|
8286
|
+
if (!present.length) return "";
|
|
8287
|
+
const lines = ["MEDIA GENERATION \u2014 the only generation tools this run can call:"];
|
|
8288
|
+
for (const f of present) {
|
|
8289
|
+
lines.push(`- "${f.tool}" (${f.modality}): ${f.shape}.${plannerPriceNote(f.modality)}`);
|
|
8290
|
+
}
|
|
8291
|
+
lines.push(
|
|
8292
|
+
"Each one is a single ATOMIC tool call, made by ONE T3 worker, that produces the finished file. It is never a multi-step pipeline, and no worker can satisfy it by describing the result in words.",
|
|
8293
|
+
"No other modality has a tool here \u2014 never plan a step that generates music, 3D or anything not listed above."
|
|
8294
|
+
);
|
|
8295
|
+
if (has("generate_video")) {
|
|
8296
|
+
lines.push(
|
|
8297
|
+
'VIDEO PLANS MUST END IN THE TOOL CALL. Creative pre-production \u2014 a script, a shot list, direction notes \u2014 is expected and worth planning, but it is NOT the video. Any plan that involves video MUST contain exactly ONE subtask whose deliverable is the "generate_video" call itself; it MUST be the last subtask on that path (dependsOn the pre-production ones), and nothing after it may be required to produce the clip. Never end a video plan on a script, a storyboard or a review step. Never split one clip across several "generate_video" subtasks, and never plan a render \u2192 critique \u2192 re-render loop: every render is billed again and takes minutes.'
|
|
8298
|
+
);
|
|
8299
|
+
}
|
|
8300
|
+
return lines.join("\n");
|
|
8301
|
+
}
|
|
8302
|
+
|
|
7971
8303
|
// src/core/tiers/t2-manager.ts
|
|
7972
|
-
function buildT2SystemPrompt(
|
|
8304
|
+
function buildT2SystemPrompt(has) {
|
|
8305
|
+
const generation = describeGenerationForPlanner(has);
|
|
7973
8306
|
return [
|
|
7974
8307
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
7975
8308
|
"Your role is to analyze a section of a task and decompose it into 2-5 discrete subtasks for T3 Workers.",
|
|
7976
8309
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
7977
|
-
|
|
7978
|
-
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text."
|
|
7979
|
-
|
|
8310
|
+
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
8311
|
+
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
8312
|
+
generation && `
|
|
8313
|
+
${generation}`
|
|
8314
|
+
].filter((l) => l !== false && l !== "").join("\n");
|
|
7980
8315
|
}
|
|
7981
8316
|
var T2Manager = class extends BaseTier {
|
|
7982
8317
|
router;
|
|
@@ -8273,7 +8608,7 @@ Return ONLY the JSON array.`;
|
|
|
8273
8608
|
const messages = [{ role: "user", content: prompt }];
|
|
8274
8609
|
const result = await this.router.generate("T2", {
|
|
8275
8610
|
messages,
|
|
8276
|
-
systemPrompt: this.systemPromptOverride + buildT2SystemPrompt(this.toolRegistry.hasTool(
|
|
8611
|
+
systemPrompt: this.systemPromptOverride + buildT2SystemPrompt((name) => this.toolRegistry.hasTool(name)) + (this.hierarchyContext ? `
|
|
8277
8612
|
|
|
8278
8613
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
8279
8614
|
maxTokens: 2e3,
|
|
@@ -8823,6 +9158,7 @@ function isStrongKeywordOverlap(a = [], b = []) {
|
|
|
8823
9158
|
}
|
|
8824
9159
|
function buildT1SystemPrompt(has) {
|
|
8825
9160
|
const canWriteArtifacts = has("file_write") || has("file_edit") || has("run_code") || has("pdf_create");
|
|
9161
|
+
const generation = describeGenerationForPlanner(has);
|
|
8826
9162
|
const rules = [
|
|
8827
9163
|
"- Simple \u2192 1 T3, Moderate \u2192 2-3 T2s, Complex \u2192 3-5 T2s, Highly Complex \u2192 5+ T2s",
|
|
8828
9164
|
"- Return ONLY valid JSON \u2014 no other text",
|
|
@@ -8857,7 +9193,9 @@ DEPENDENCY GUIDANCE:
|
|
|
8857
9193
|
QUALITY RULES:
|
|
8858
9194
|
- Each section must have a clear, testable "expectedOutput" so T2 knows when it is done.
|
|
8859
9195
|
- Do NOT create trivial sections that only move files or print summaries \u2014 fold those into adjacent sections.
|
|
8860
|
-
- If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2)
|
|
9196
|
+
- If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2).${generation ? `
|
|
9197
|
+
|
|
9198
|
+
${generation}` : ""}`;
|
|
8861
9199
|
}
|
|
8862
9200
|
var T1Administrator = class extends BaseTier {
|
|
8863
9201
|
router;
|
|
@@ -9518,219 +9856,37 @@ Instructions:
|
|
|
9518
9856
|
return result.content;
|
|
9519
9857
|
}
|
|
9520
9858
|
/**
|
|
9521
|
-
* T1-level permission evaluator.
|
|
9522
|
-
* Uses T1's model with full task context.
|
|
9523
|
-
* Returns null only when the model explicitly says UNSURE (triggers user prompt).
|
|
9524
|
-
*/
|
|
9525
|
-
async evaluatePermissionAtT1(req) {
|
|
9526
|
-
const prompt = `You are T1 Administrator. Overall task goal:
|
|
9527
|
-
${this.taskGoal}
|
|
9528
|
-
|
|
9529
|
-
A T3 Worker (inside section "${req.sectionContext}") wants to:
|
|
9530
|
-
Tool: ${req.toolName}
|
|
9531
|
-
Target: ${JSON.stringify(req.input)}
|
|
9532
|
-
Reason: ${req.subtaskContext}
|
|
9533
|
-
|
|
9534
|
-
T2 Manager was uncertain about this. Given the overall task goal, should this be allowed?
|
|
9535
|
-
Reply with exactly one word: YES, NO, or UNSURE.
|
|
9536
|
-
(UNSURE = escalate to the human user for a final decision.)`;
|
|
9537
|
-
try {
|
|
9538
|
-
const result = await this.router.generate("T1", {
|
|
9539
|
-
messages: [{ role: "user", content: prompt }],
|
|
9540
|
-
systemPrompt: this.systemPromptOverride + "You are a T1 Administrator evaluating permissions.",
|
|
9541
|
-
maxTokens: 10,
|
|
9542
|
-
temperature: 0
|
|
9543
|
-
});
|
|
9544
|
-
const answer = result.content.trim().toUpperCase();
|
|
9545
|
-
const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
|
|
9546
|
-
(req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
|
|
9547
|
-
return null;
|
|
9548
|
-
} catch {
|
|
9549
|
-
(req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
|
|
9550
|
-
return null;
|
|
9551
|
-
}
|
|
9552
|
-
}
|
|
9553
|
-
};
|
|
9554
|
-
|
|
9555
|
-
// src/core/multimodal/registry.ts
|
|
9556
|
-
var CAPABILITIES = [
|
|
9557
|
-
// ── Image ──
|
|
9558
|
-
{
|
|
9559
|
-
modality: "image",
|
|
9560
|
-
provider: "openai",
|
|
9561
|
-
modelId: "dall-e-3",
|
|
9562
|
-
api: "openai-images"
|
|
9563
|
-
},
|
|
9564
|
-
{
|
|
9565
|
-
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
9566
|
-
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
9567
|
-
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
9568
|
-
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
9569
|
-
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
9570
|
-
// 'gemini-generate-content' rather than just the modelId moving.
|
|
9571
|
-
// https://ai.google.dev/gemini-api/docs/imagen
|
|
9572
|
-
modality: "image",
|
|
9573
|
-
provider: "gemini",
|
|
9574
|
-
modelId: "gemini-2.5-flash-image",
|
|
9575
|
-
api: "gemini-generate-content"
|
|
9576
|
-
},
|
|
9577
|
-
// ── Speech (text → audio) ──
|
|
9578
|
-
{
|
|
9579
|
-
modality: "speech",
|
|
9580
|
-
provider: "openai",
|
|
9581
|
-
modelId: "tts-1",
|
|
9582
|
-
api: "openai-speech"
|
|
9583
|
-
},
|
|
9584
|
-
{
|
|
9585
|
-
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
9586
|
-
// ships these as the same voice at two fidelities and charges double for
|
|
9587
|
-
// this one. That is the vendor's own ordering, not an inference.
|
|
9588
|
-
modality: "speech",
|
|
9589
|
-
provider: "openai",
|
|
9590
|
-
modelId: "tts-1-hd",
|
|
9591
|
-
api: "openai-speech",
|
|
9592
|
-
quality: 70,
|
|
9593
|
-
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
9594
|
-
},
|
|
9595
|
-
// ── Transcription (audio → text) ──
|
|
9596
|
-
{
|
|
9597
|
-
modality: "transcription",
|
|
9598
|
-
provider: "openai",
|
|
9599
|
-
modelId: "whisper-1",
|
|
9600
|
-
api: "openai-transcriptions"
|
|
9601
|
-
},
|
|
9602
|
-
// ── Video ──
|
|
9603
|
-
{
|
|
9604
|
-
// Long-running: submit returns an operation name, the video lands minutes
|
|
9605
|
-
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
9606
|
-
// that polling loop; the registry only records the shape via `api`.
|
|
9607
|
-
//
|
|
9608
|
-
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
9609
|
-
// not the Gemini Developer API's — this file calls
|
|
9610
|
-
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
9611
|
-
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
9612
|
-
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
9613
|
-
// there is `veo-3.1-generate-preview`.
|
|
9614
|
-
// https://ai.google.dev/gemini-api/docs/veo
|
|
9615
|
-
modality: "video",
|
|
9616
|
-
provider: "gemini",
|
|
9617
|
-
modelId: "veo-3.1-generate-preview",
|
|
9618
|
-
api: "gemini-predict-lro"
|
|
9619
|
-
}
|
|
9620
|
-
];
|
|
9621
|
-
function capabilityCost(cap) {
|
|
9622
|
-
const match = findPricing(cap.modelId, cap.provider, {
|
|
9623
|
-
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
9624
|
-
});
|
|
9625
|
-
const rate = match?.entry.rates?.[0];
|
|
9626
|
-
if (!rate) return null;
|
|
9627
|
-
const unitLabel = {
|
|
9628
|
-
per_image: "per image",
|
|
9629
|
-
per_second: "per second of video",
|
|
9630
|
-
per_character: "per character",
|
|
9631
|
-
per_audio_minute: "per audio minute"
|
|
9632
|
-
};
|
|
9633
|
-
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
9634
|
-
const amount = rate.amount;
|
|
9635
|
-
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
9636
|
-
return { label: `$${shown} ${unit}`, amount, unit };
|
|
9637
|
-
}
|
|
9638
|
-
function explainChoice(c, cost, modality, isBest) {
|
|
9639
|
-
const priced = cost ? ` ${cost.label}.` : "";
|
|
9640
|
-
if (c.quality !== void 0) {
|
|
9641
|
-
return isBest ? `${c.modelId} (${c.provider}) \u2014 ranked highest for ${modality}: ${c.qualityBasis}.${priced}` : `${c.modelId} (${c.provider}) \u2014 ranked alternative for ${modality}: ${c.qualityBasis}.${priced}`;
|
|
9642
|
-
}
|
|
9643
|
-
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
9644
|
-
return isBest ? `${c.modelId} (${c.provider}) \u2014 cheapest usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking : `${c.modelId} (${c.provider}) \u2014 alternative usable ${modality} model` + (cost ? ` at ${cost.label}` : "") + noRanking;
|
|
9645
|
-
}
|
|
9646
|
-
var MultimodalRegistry = class {
|
|
9647
|
-
/** Provider types the user has actually configured a key for. */
|
|
9648
|
-
available;
|
|
9649
|
-
constructor(availableProviders) {
|
|
9650
|
-
this.available = new Set(availableProviders);
|
|
9651
|
-
}
|
|
9652
|
-
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
9653
|
-
usable(modality) {
|
|
9654
|
-
return CAPABILITIES.filter(
|
|
9655
|
-
(c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
|
|
9656
|
-
);
|
|
9657
|
-
}
|
|
9658
|
-
/** Modalities this account can actually generate right now. */
|
|
9659
|
-
availableModalities() {
|
|
9660
|
-
return [...new Set(this.usable().map((c) => c.modality))];
|
|
9661
|
-
}
|
|
9662
|
-
/**
|
|
9663
|
-
* Every usable capability for a modality, best first.
|
|
9664
|
-
*
|
|
9665
|
-
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
9666
|
-
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
9667
|
-
* the user naming a model is a stronger signal than any prior.
|
|
9668
|
-
*
|
|
9669
|
-
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
9670
|
-
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
9671
|
-
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
9672
|
-
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
9673
|
-
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
9674
|
-
*/
|
|
9675
|
-
rank(modality, opts = {}) {
|
|
9676
|
-
const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
9677
|
-
const qa = a.c.quality ?? -1;
|
|
9678
|
-
const qb = b.c.quality ?? -1;
|
|
9679
|
-
if (qa !== qb) return qb - qa;
|
|
9680
|
-
const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
9681
|
-
const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
9682
|
-
return pa - pb;
|
|
9683
|
-
});
|
|
9684
|
-
const results = scored.map(({ c, cost }, i) => ({
|
|
9685
|
-
capability: c,
|
|
9686
|
-
cost,
|
|
9687
|
-
reason: explainChoice(c, cost, modality, i === 0)
|
|
9688
|
-
}));
|
|
9689
|
-
if (opts.preferModel) {
|
|
9690
|
-
const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
|
|
9691
|
-
if (i > -1) {
|
|
9692
|
-
const [wanted] = results.splice(i, 1);
|
|
9693
|
-
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
9694
|
-
}
|
|
9695
|
-
}
|
|
9696
|
-
return results;
|
|
9697
|
-
}
|
|
9698
|
-
/**
|
|
9699
|
-
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
9700
|
-
*/
|
|
9701
|
-
select(modality, opts = {}) {
|
|
9702
|
-
return this.rank(modality, opts)[0] ?? null;
|
|
9703
|
-
}
|
|
9704
|
-
/**
|
|
9705
|
-
* A plain-language account of what this account can and cannot generate —
|
|
9706
|
-
* including the things it cannot, and why. The planner sees this, so being
|
|
9707
|
-
* candid here is what stops it inventing a `generate_music` call.
|
|
9708
|
-
*/
|
|
9709
|
-
describe() {
|
|
9710
|
-
const lines = [];
|
|
9711
|
-
const usable = this.usable();
|
|
9712
|
-
if (usable.length) {
|
|
9713
|
-
lines.push("Generation models available on your configured providers:");
|
|
9714
|
-
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
9715
|
-
const forModality = usable.filter((c) => c.modality === modality);
|
|
9716
|
-
if (!forModality.length) continue;
|
|
9717
|
-
const shown = forModality.map((c) => {
|
|
9718
|
-
const cost = capabilityCost(c);
|
|
9719
|
-
return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
9720
|
-
});
|
|
9721
|
-
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
9722
|
-
}
|
|
9723
|
-
} else {
|
|
9724
|
-
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
9725
|
-
}
|
|
9726
|
-
const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
|
|
9727
|
-
for (const c of blocked) {
|
|
9728
|
-
lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
|
|
9859
|
+
* T1-level permission evaluator.
|
|
9860
|
+
* Uses T1's model with full task context.
|
|
9861
|
+
* Returns null only when the model explicitly says UNSURE (triggers user prompt).
|
|
9862
|
+
*/
|
|
9863
|
+
async evaluatePermissionAtT1(req) {
|
|
9864
|
+
const prompt = `You are T1 Administrator. Overall task goal:
|
|
9865
|
+
${this.taskGoal}
|
|
9866
|
+
|
|
9867
|
+
A T3 Worker (inside section "${req.sectionContext}") wants to:
|
|
9868
|
+
Tool: ${req.toolName}
|
|
9869
|
+
Target: ${JSON.stringify(req.input)}
|
|
9870
|
+
Reason: ${req.subtaskContext}
|
|
9871
|
+
|
|
9872
|
+
T2 Manager was uncertain about this. Given the overall task goal, should this be allowed?
|
|
9873
|
+
Reply with exactly one word: YES, NO, or UNSURE.
|
|
9874
|
+
(UNSURE = escalate to the human user for a final decision.)`;
|
|
9875
|
+
try {
|
|
9876
|
+
const result = await this.router.generate("T1", {
|
|
9877
|
+
messages: [{ role: "user", content: prompt }],
|
|
9878
|
+
systemPrompt: this.systemPromptOverride + "You are a T1 Administrator evaluating permissions.",
|
|
9879
|
+
maxTokens: 10,
|
|
9880
|
+
temperature: 0
|
|
9881
|
+
});
|
|
9882
|
+
const answer = result.content.trim().toUpperCase();
|
|
9883
|
+
const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
|
|
9884
|
+
(req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
|
|
9885
|
+
return null;
|
|
9886
|
+
} catch {
|
|
9887
|
+
(req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
|
|
9888
|
+
return null;
|
|
9729
9889
|
}
|
|
9730
|
-
lines.push(
|
|
9731
|
-
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
9732
|
-
);
|
|
9733
|
-
return lines.join("\n");
|
|
9734
9890
|
}
|
|
9735
9891
|
};
|
|
9736
9892
|
|
|
@@ -9754,10 +9910,17 @@ var BaseTool = class {
|
|
|
9754
9910
|
|
|
9755
9911
|
// src/core/multimodal/generate.ts
|
|
9756
9912
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
9913
|
+
var GenerationGaveUpError = class extends Error {
|
|
9914
|
+
constructor(message) {
|
|
9915
|
+
super(message);
|
|
9916
|
+
this.name = "GenerationGaveUpError";
|
|
9917
|
+
}
|
|
9918
|
+
};
|
|
9757
9919
|
async function callProvider(fn, modelId) {
|
|
9758
9920
|
try {
|
|
9759
9921
|
return await fn();
|
|
9760
9922
|
} catch (err) {
|
|
9923
|
+
if (err instanceof GenerationGaveUpError) throw err;
|
|
9761
9924
|
const classified = classifyProviderError(err);
|
|
9762
9925
|
throw new Error(describeProviderError(classified, modelId));
|
|
9763
9926
|
}
|
|
@@ -9955,8 +10118,8 @@ async function generateVideo(cap, cfg, req, signal, onProgress) {
|
|
|
9955
10118
|
onProgress?.("Still rendering\u2026");
|
|
9956
10119
|
}
|
|
9957
10120
|
if (!operation.done) {
|
|
9958
|
-
throw new
|
|
9959
|
-
|
|
10121
|
+
throw new GenerationGaveUpError(
|
|
10122
|
+
`${cap.modelId} timed out after ${Math.round(VIDEO_TIMEOUT_MS / 6e4)} minutes \u2014 no video was produced. Cascade stopped waiting; the render may still finish on the provider side, but nothing was saved. Try again with a shorter clip or a simpler prompt.`
|
|
9960
10123
|
);
|
|
9961
10124
|
}
|
|
9962
10125
|
if (operation.error?.message) throw new Error(operation.error.message);
|
|
@@ -10262,6 +10425,705 @@ function buildMediaTools(deps, readFile) {
|
|
|
10262
10425
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
10263
10426
|
return tools;
|
|
10264
10427
|
}
|
|
10428
|
+
var WorkspaceSandboxError = class extends Error {
|
|
10429
|
+
constructor(attempted, workspaceRoot) {
|
|
10430
|
+
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10431
|
+
this.name = "WorkspaceSandboxError";
|
|
10432
|
+
}
|
|
10433
|
+
};
|
|
10434
|
+
function resolveInWorkspace(workspaceRoot, input) {
|
|
10435
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
10436
|
+
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10437
|
+
}
|
|
10438
|
+
const root = path25__default.default.resolve(workspaceRoot);
|
|
10439
|
+
const abs = path25__default.default.isAbsolute(input) ? path25__default.default.resolve(input) : path25__default.default.resolve(root, input);
|
|
10440
|
+
const rel = path25__default.default.relative(root, abs);
|
|
10441
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path25__default.default.isAbsolute(rel)) {
|
|
10442
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10443
|
+
}
|
|
10444
|
+
try {
|
|
10445
|
+
const real = fs24__default.default.realpathSync(abs);
|
|
10446
|
+
const realRel = path25__default.default.relative(root, real);
|
|
10447
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path25__default.default.isAbsolute(realRel))) {
|
|
10448
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10449
|
+
}
|
|
10450
|
+
} catch (e) {
|
|
10451
|
+
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10452
|
+
}
|
|
10453
|
+
return abs;
|
|
10454
|
+
}
|
|
10455
|
+
|
|
10456
|
+
// src/core/documents/blocks.ts
|
|
10457
|
+
function fileExt(name) {
|
|
10458
|
+
const base = name.split(/[\\/]/).pop() ?? name;
|
|
10459
|
+
const dot = base.lastIndexOf(".");
|
|
10460
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
10461
|
+
}
|
|
10462
|
+
function parseDelimited(text, name = "") {
|
|
10463
|
+
const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
|
|
10464
|
+
const rows = [];
|
|
10465
|
+
let row = [];
|
|
10466
|
+
let cell = "";
|
|
10467
|
+
let quoted = false;
|
|
10468
|
+
for (let i = 0; i < text.length; i++) {
|
|
10469
|
+
const ch = text[i];
|
|
10470
|
+
if (quoted) {
|
|
10471
|
+
if (ch === '"') {
|
|
10472
|
+
if (text[i + 1] === '"') {
|
|
10473
|
+
cell += '"';
|
|
10474
|
+
i++;
|
|
10475
|
+
} else quoted = false;
|
|
10476
|
+
} else cell += ch;
|
|
10477
|
+
} else if (ch === '"') {
|
|
10478
|
+
quoted = true;
|
|
10479
|
+
} else if (ch === delim) {
|
|
10480
|
+
row.push(cell);
|
|
10481
|
+
cell = "";
|
|
10482
|
+
} else if (ch === "\n") {
|
|
10483
|
+
row.push(cell);
|
|
10484
|
+
rows.push(row);
|
|
10485
|
+
row = [];
|
|
10486
|
+
cell = "";
|
|
10487
|
+
} else if (ch === "\r") ; else {
|
|
10488
|
+
cell += ch;
|
|
10489
|
+
}
|
|
10490
|
+
if (rows.length > 5e3) break;
|
|
10491
|
+
}
|
|
10492
|
+
if (cell !== "" || row.length) {
|
|
10493
|
+
row.push(cell);
|
|
10494
|
+
rows.push(row);
|
|
10495
|
+
}
|
|
10496
|
+
return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
|
|
10497
|
+
}
|
|
10498
|
+
var CHART_ALIASES = {
|
|
10499
|
+
bar: "bar",
|
|
10500
|
+
column: "bar",
|
|
10501
|
+
bars: "bar",
|
|
10502
|
+
barchart: "bar",
|
|
10503
|
+
horizontalbar: "bar",
|
|
10504
|
+
line: "line",
|
|
10505
|
+
lines: "line",
|
|
10506
|
+
linechart: "line",
|
|
10507
|
+
pie: "pie",
|
|
10508
|
+
piechart: "pie",
|
|
10509
|
+
doughnut: "doughnut",
|
|
10510
|
+
donut: "doughnut",
|
|
10511
|
+
area: "area",
|
|
10512
|
+
areachart: "area",
|
|
10513
|
+
scatter: "scatter",
|
|
10514
|
+
xy: "scatter"
|
|
10515
|
+
};
|
|
10516
|
+
function chartKind(raw) {
|
|
10517
|
+
return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
|
|
10518
|
+
}
|
|
10519
|
+
function toNumber(cell) {
|
|
10520
|
+
const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
|
|
10521
|
+
const n = Number.parseFloat(cleaned);
|
|
10522
|
+
return Number.isFinite(n) ? n : 0;
|
|
10523
|
+
}
|
|
10524
|
+
function parseChartSpec(kind, body) {
|
|
10525
|
+
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
|
10526
|
+
let title;
|
|
10527
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
10528
|
+
const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
|
|
10529
|
+
if (titleMatch) {
|
|
10530
|
+
title = titleMatch[1].trim();
|
|
10531
|
+
lines.shift();
|
|
10532
|
+
}
|
|
10533
|
+
const rows = parseDelimited(lines.join("\n").trim());
|
|
10534
|
+
if (rows.length < 2) return null;
|
|
10535
|
+
const header = rows[0].map((c) => c.trim());
|
|
10536
|
+
if (header.length < 2) return null;
|
|
10537
|
+
const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
10538
|
+
if (!dataRows.length) return null;
|
|
10539
|
+
const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
|
|
10540
|
+
const series = header.slice(1).map((name, col) => ({
|
|
10541
|
+
name: name || `Series ${col + 1}`,
|
|
10542
|
+
values: dataRows.map((r) => toNumber((r[col + 1] ?? "").trim()))
|
|
10543
|
+
}));
|
|
10544
|
+
if (!series.length) return null;
|
|
10545
|
+
return {
|
|
10546
|
+
kind,
|
|
10547
|
+
...title ? { title } : {},
|
|
10548
|
+
categoryName: header[0] || "Category",
|
|
10549
|
+
categories,
|
|
10550
|
+
series
|
|
10551
|
+
};
|
|
10552
|
+
}
|
|
10553
|
+
function chartToTableRows(spec) {
|
|
10554
|
+
return [
|
|
10555
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10556
|
+
...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
|
|
10557
|
+
];
|
|
10558
|
+
}
|
|
10559
|
+
function parseBlocks(md) {
|
|
10560
|
+
const out = [];
|
|
10561
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
10562
|
+
let i = 0;
|
|
10563
|
+
let ordinal = 0;
|
|
10564
|
+
const reset = () => {
|
|
10565
|
+
ordinal = 0;
|
|
10566
|
+
};
|
|
10567
|
+
while (i < lines.length) {
|
|
10568
|
+
const line = lines[i] ?? "";
|
|
10569
|
+
const fence = line.match(/^```+\s*(.*)$/);
|
|
10570
|
+
if (fence) {
|
|
10571
|
+
const info = (fence[1] ?? "").trim();
|
|
10572
|
+
i++;
|
|
10573
|
+
const code = [];
|
|
10574
|
+
while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
|
|
10575
|
+
code.push(lines[i] ?? "");
|
|
10576
|
+
i++;
|
|
10577
|
+
}
|
|
10578
|
+
i++;
|
|
10579
|
+
const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
|
|
10580
|
+
const kind = chartInfo ? chartKind(chartInfo[1]) : null;
|
|
10581
|
+
const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
|
|
10582
|
+
out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
|
|
10583
|
+
reset();
|
|
10584
|
+
continue;
|
|
10585
|
+
}
|
|
10586
|
+
if (!line.trim()) {
|
|
10587
|
+
i++;
|
|
10588
|
+
reset();
|
|
10589
|
+
continue;
|
|
10590
|
+
}
|
|
10591
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
10592
|
+
if (h) {
|
|
10593
|
+
out.push({ t: "heading", level: h[1].length, text: h[2] });
|
|
10594
|
+
i++;
|
|
10595
|
+
reset();
|
|
10596
|
+
continue;
|
|
10597
|
+
}
|
|
10598
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
10599
|
+
out.push({ t: "hr" });
|
|
10600
|
+
i++;
|
|
10601
|
+
reset();
|
|
10602
|
+
continue;
|
|
10603
|
+
}
|
|
10604
|
+
const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
|
|
10605
|
+
if (li) {
|
|
10606
|
+
const ordered = /\d/.test(li[2]);
|
|
10607
|
+
ordinal = ordered ? ordinal + 1 : 0;
|
|
10608
|
+
out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
|
|
10609
|
+
i++;
|
|
10610
|
+
continue;
|
|
10611
|
+
}
|
|
10612
|
+
if (/^>\s?/.test(line)) {
|
|
10613
|
+
out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
|
|
10614
|
+
i++;
|
|
10615
|
+
reset();
|
|
10616
|
+
continue;
|
|
10617
|
+
}
|
|
10618
|
+
if (/^\|.*\|/.test(line)) {
|
|
10619
|
+
const rows = [];
|
|
10620
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
|
|
10621
|
+
const r = lines[i];
|
|
10622
|
+
if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
|
|
10623
|
+
i++;
|
|
10624
|
+
}
|
|
10625
|
+
out.push({ t: "table", rows });
|
|
10626
|
+
reset();
|
|
10627
|
+
continue;
|
|
10628
|
+
}
|
|
10629
|
+
out.push({ t: "para", text: line });
|
|
10630
|
+
i++;
|
|
10631
|
+
reset();
|
|
10632
|
+
}
|
|
10633
|
+
return out;
|
|
10634
|
+
}
|
|
10635
|
+
var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
10636
|
+
function matchImageLine(s) {
|
|
10637
|
+
const m = s.trim().match(IMAGE_LINE);
|
|
10638
|
+
return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
|
|
10639
|
+
}
|
|
10640
|
+
function sniffImage(b) {
|
|
10641
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
10642
|
+
if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
10643
|
+
return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
10644
|
+
}
|
|
10645
|
+
if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
10646
|
+
return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
|
|
10647
|
+
}
|
|
10648
|
+
if (b.length > 26 && b[0] === 66 && b[1] === 77) {
|
|
10649
|
+
return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
|
|
10650
|
+
}
|
|
10651
|
+
if (b.length > 4 && b[0] === 255 && b[1] === 216) {
|
|
10652
|
+
let i = 2;
|
|
10653
|
+
while (i + 9 < b.length) {
|
|
10654
|
+
if (b[i] !== 255) {
|
|
10655
|
+
i++;
|
|
10656
|
+
continue;
|
|
10657
|
+
}
|
|
10658
|
+
const marker = b[i + 1];
|
|
10659
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
10660
|
+
return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
|
|
10661
|
+
}
|
|
10662
|
+
i += 2 + dv.getUint16(i + 2);
|
|
10663
|
+
}
|
|
10664
|
+
}
|
|
10665
|
+
return null;
|
|
10666
|
+
}
|
|
10667
|
+
function bytesToBase64(bytes) {
|
|
10668
|
+
const g = globalThis;
|
|
10669
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
10670
|
+
const CHUNK = 32768;
|
|
10671
|
+
let binary = "";
|
|
10672
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
10673
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
10674
|
+
}
|
|
10675
|
+
return g.btoa(binary);
|
|
10676
|
+
}
|
|
10677
|
+
function stripInline(s) {
|
|
10678
|
+
return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
|
|
10679
|
+
}
|
|
10680
|
+
function inlineRuns(text) {
|
|
10681
|
+
const acc = [];
|
|
10682
|
+
const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
|
|
10683
|
+
let last = 0;
|
|
10684
|
+
let m;
|
|
10685
|
+
while ((m = re.exec(text)) !== null) {
|
|
10686
|
+
if (m.index > last) acc.push({ text: text.slice(last, m.index) });
|
|
10687
|
+
const tok = m[0];
|
|
10688
|
+
if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
|
|
10689
|
+
else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
|
|
10690
|
+
else if (tok.startsWith("[")) {
|
|
10691
|
+
const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
|
|
10692
|
+
acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
|
|
10693
|
+
} else acc.push({ text: tok.slice(1, -1), italic: true });
|
|
10694
|
+
last = re.lastIndex;
|
|
10695
|
+
}
|
|
10696
|
+
if (last < text.length) acc.push({ text: text.slice(last) });
|
|
10697
|
+
return acc.length ? acc : [{ text }];
|
|
10698
|
+
}
|
|
10699
|
+
function splitSlides(md) {
|
|
10700
|
+
const norm = md.replace(/\r\n/g, "\n").trim();
|
|
10701
|
+
let chunks = norm.split(/\n\s*-{3,}\s*\n/);
|
|
10702
|
+
if (chunks.length === 1) {
|
|
10703
|
+
const lines = norm.split("\n");
|
|
10704
|
+
const sections = [];
|
|
10705
|
+
let cur = [];
|
|
10706
|
+
for (const ln of lines) {
|
|
10707
|
+
if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
|
|
10708
|
+
sections.push(cur.join("\n"));
|
|
10709
|
+
cur = [];
|
|
10710
|
+
}
|
|
10711
|
+
cur.push(ln);
|
|
10712
|
+
}
|
|
10713
|
+
if (cur.length) sections.push(cur.join("\n"));
|
|
10714
|
+
if (sections.length > 1) chunks = sections;
|
|
10715
|
+
}
|
|
10716
|
+
return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
|
|
10717
|
+
}
|
|
10718
|
+
function parseSlide(chunk) {
|
|
10719
|
+
let title = "";
|
|
10720
|
+
const body = [];
|
|
10721
|
+
const images = [];
|
|
10722
|
+
const charts = [];
|
|
10723
|
+
const lines = chunk.split("\n");
|
|
10724
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10725
|
+
const raw = lines[i] ?? "";
|
|
10726
|
+
const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
10727
|
+
if (fence) {
|
|
10728
|
+
const kind = chartKind(fence[1]);
|
|
10729
|
+
const bodyLines = [];
|
|
10730
|
+
i++;
|
|
10731
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
10732
|
+
bodyLines.push(lines[i] ?? "");
|
|
10733
|
+
i++;
|
|
10734
|
+
}
|
|
10735
|
+
const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
|
|
10736
|
+
if (spec) charts.push(spec);
|
|
10737
|
+
else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
|
|
10738
|
+
continue;
|
|
10739
|
+
}
|
|
10740
|
+
const line = raw.trim();
|
|
10741
|
+
if (!line) continue;
|
|
10742
|
+
const h = line.match(/^#{1,6}[ \t]+(.*)/);
|
|
10743
|
+
if (h) {
|
|
10744
|
+
if (!title) title = stripInline(h[1]);
|
|
10745
|
+
else body.push(stripInline(h[1]));
|
|
10746
|
+
continue;
|
|
10747
|
+
}
|
|
10748
|
+
const img = matchImageLine(line);
|
|
10749
|
+
if (img) {
|
|
10750
|
+
images.push(img);
|
|
10751
|
+
continue;
|
|
10752
|
+
}
|
|
10753
|
+
const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
|
|
10754
|
+
if (li) {
|
|
10755
|
+
const bulletImg = matchImageLine(li[2]);
|
|
10756
|
+
if (bulletImg) {
|
|
10757
|
+
images.push(bulletImg);
|
|
10758
|
+
continue;
|
|
10759
|
+
}
|
|
10760
|
+
body.push(stripInline(li[2]));
|
|
10761
|
+
continue;
|
|
10762
|
+
}
|
|
10763
|
+
if (/^>\s?/.test(line)) {
|
|
10764
|
+
body.push(stripInline(line.replace(/^>\s?/, "")));
|
|
10765
|
+
continue;
|
|
10766
|
+
}
|
|
10767
|
+
body.push(stripInline(line));
|
|
10768
|
+
}
|
|
10769
|
+
return { title, body, images, charts };
|
|
10770
|
+
}
|
|
10771
|
+
function extractCharts(source) {
|
|
10772
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
10773
|
+
const charts = [];
|
|
10774
|
+
const rest = [];
|
|
10775
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10776
|
+
const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
10777
|
+
if (!fence) {
|
|
10778
|
+
rest.push(lines[i]);
|
|
10779
|
+
continue;
|
|
10780
|
+
}
|
|
10781
|
+
const kind = chartKind(fence[1]);
|
|
10782
|
+
const body = [];
|
|
10783
|
+
i++;
|
|
10784
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
10785
|
+
body.push(lines[i] ?? "");
|
|
10786
|
+
i++;
|
|
10787
|
+
}
|
|
10788
|
+
const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
|
|
10789
|
+
if (spec) charts.push(spec);
|
|
10790
|
+
else rest.push(...body);
|
|
10791
|
+
}
|
|
10792
|
+
return { charts, rest: rest.join("\n") };
|
|
10793
|
+
}
|
|
10794
|
+
|
|
10795
|
+
// src/core/documents/render.ts
|
|
10796
|
+
var DOCUMENT_MIME = {
|
|
10797
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
10798
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
10799
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
10800
|
+
};
|
|
10801
|
+
function isDocumentFormat(ext) {
|
|
10802
|
+
return ext === "docx" || ext === "pptx" || ext === "xlsx";
|
|
10803
|
+
}
|
|
10804
|
+
async function loadImage(ref, load) {
|
|
10805
|
+
if (!load) return null;
|
|
10806
|
+
try {
|
|
10807
|
+
const bytes = await load(ref.url);
|
|
10808
|
+
if (!bytes || !bytes.length) return null;
|
|
10809
|
+
const info = sniffImage(bytes);
|
|
10810
|
+
if (!info || info.width <= 0 || info.height <= 0) return null;
|
|
10811
|
+
return { ...info, bytes, alt: ref.alt };
|
|
10812
|
+
} catch {
|
|
10813
|
+
return null;
|
|
10814
|
+
}
|
|
10815
|
+
}
|
|
10816
|
+
async function renderXlsx(source, opts = {}) {
|
|
10817
|
+
const XLSX = await import('xlsx');
|
|
10818
|
+
const { charts, rest } = extractCharts(source);
|
|
10819
|
+
const name = opts.name ?? "";
|
|
10820
|
+
const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
|
|
10821
|
+
const wb = XLSX.utils.book_new();
|
|
10822
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
|
|
10823
|
+
const used = /* @__PURE__ */ new Set(["Sheet1"]);
|
|
10824
|
+
charts.forEach((spec, i) => {
|
|
10825
|
+
const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
|
|
10826
|
+
let sheetName = base;
|
|
10827
|
+
let n = 2;
|
|
10828
|
+
while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
|
|
10829
|
+
used.add(sheetName);
|
|
10830
|
+
const aoa = [
|
|
10831
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10832
|
+
...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
|
|
10833
|
+
];
|
|
10834
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
|
|
10835
|
+
});
|
|
10836
|
+
return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
|
|
10837
|
+
}
|
|
10838
|
+
var DOCX_MAX_W = 600;
|
|
10839
|
+
var DOCX_MAX_H = 864;
|
|
10840
|
+
async function renderDocx(md, opts = {}) {
|
|
10841
|
+
const d = await import('docx');
|
|
10842
|
+
const {
|
|
10843
|
+
Document,
|
|
10844
|
+
Packer,
|
|
10845
|
+
Paragraph,
|
|
10846
|
+
TextRun,
|
|
10847
|
+
ImageRun,
|
|
10848
|
+
HeadingLevel,
|
|
10849
|
+
Table,
|
|
10850
|
+
TableRow,
|
|
10851
|
+
TableCell,
|
|
10852
|
+
WidthType,
|
|
10853
|
+
BorderStyle,
|
|
10854
|
+
AlignmentType
|
|
10855
|
+
} = d;
|
|
10856
|
+
const HEADINGS = [
|
|
10857
|
+
HeadingLevel.HEADING_1,
|
|
10858
|
+
HeadingLevel.HEADING_2,
|
|
10859
|
+
HeadingLevel.HEADING_3,
|
|
10860
|
+
HeadingLevel.HEADING_4,
|
|
10861
|
+
HeadingLevel.HEADING_5,
|
|
10862
|
+
HeadingLevel.HEADING_6
|
|
10863
|
+
];
|
|
10864
|
+
const runs = (text) => inlineRuns(text).map((r) => new TextRun({
|
|
10865
|
+
text: r.text,
|
|
10866
|
+
...r.bold ? { bold: true } : {},
|
|
10867
|
+
...r.italic ? { italics: true } : {},
|
|
10868
|
+
...r.code ? { font: "Courier New" } : {}
|
|
10869
|
+
}));
|
|
10870
|
+
const tableOf = (rows) => new Table({
|
|
10871
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
10872
|
+
rows: rows.map((r) => new TableRow({
|
|
10873
|
+
children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
|
|
10874
|
+
}))
|
|
10875
|
+
});
|
|
10876
|
+
const imageParagraph = (img) => {
|
|
10877
|
+
const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
|
|
10878
|
+
return new Paragraph({
|
|
10879
|
+
children: [new ImageRun({
|
|
10880
|
+
type: img.type,
|
|
10881
|
+
data: img.bytes,
|
|
10882
|
+
transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
|
|
10883
|
+
...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
|
|
10884
|
+
})]
|
|
10885
|
+
});
|
|
10886
|
+
};
|
|
10887
|
+
const asImage = async (text) => {
|
|
10888
|
+
const ref = matchImageLine(text);
|
|
10889
|
+
return ref ? await loadImage(ref, opts.loadImageBytes) : null;
|
|
10890
|
+
};
|
|
10891
|
+
const children = [];
|
|
10892
|
+
for (const b of parseBlocks(md)) {
|
|
10893
|
+
switch (b.t) {
|
|
10894
|
+
case "heading":
|
|
10895
|
+
children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
|
|
10896
|
+
break;
|
|
10897
|
+
case "para": {
|
|
10898
|
+
const img = await asImage(b.text);
|
|
10899
|
+
children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
|
|
10900
|
+
break;
|
|
10901
|
+
}
|
|
10902
|
+
case "bullet": {
|
|
10903
|
+
const img = await asImage(b.text);
|
|
10904
|
+
children.push(img ? imageParagraph(img) : new Paragraph({
|
|
10905
|
+
children: runs(b.text),
|
|
10906
|
+
...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
|
|
10907
|
+
}));
|
|
10908
|
+
break;
|
|
10909
|
+
}
|
|
10910
|
+
case "code":
|
|
10911
|
+
for (const cl of b.lines) {
|
|
10912
|
+
children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
|
|
10913
|
+
}
|
|
10914
|
+
break;
|
|
10915
|
+
case "quote":
|
|
10916
|
+
children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
|
|
10917
|
+
break;
|
|
10918
|
+
case "table":
|
|
10919
|
+
children.push(tableOf(b.rows));
|
|
10920
|
+
break;
|
|
10921
|
+
case "chart":
|
|
10922
|
+
if (b.spec.title) {
|
|
10923
|
+
children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
|
|
10924
|
+
}
|
|
10925
|
+
children.push(tableOf(chartToTableRows(b.spec)));
|
|
10926
|
+
break;
|
|
10927
|
+
case "hr":
|
|
10928
|
+
children.push(new Paragraph({
|
|
10929
|
+
children: [],
|
|
10930
|
+
border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
|
|
10931
|
+
}));
|
|
10932
|
+
break;
|
|
10933
|
+
}
|
|
10934
|
+
}
|
|
10935
|
+
const doc = new Document({
|
|
10936
|
+
numbering: {
|
|
10937
|
+
config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
|
|
10938
|
+
},
|
|
10939
|
+
sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
|
|
10940
|
+
});
|
|
10941
|
+
return new Uint8Array(await Packer.toArrayBuffer(doc));
|
|
10942
|
+
}
|
|
10943
|
+
function pptxConstructor(mod) {
|
|
10944
|
+
let c = mod;
|
|
10945
|
+
for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
|
|
10946
|
+
if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
|
|
10947
|
+
return c;
|
|
10948
|
+
}
|
|
10949
|
+
var SLIDE_H = 7.5;
|
|
10950
|
+
var CONTENT_X = 0.6;
|
|
10951
|
+
var CONTENT_W = 12.1;
|
|
10952
|
+
var CONTENT_BOTTOM = 0.4;
|
|
10953
|
+
async function renderPptx(md, opts = {}) {
|
|
10954
|
+
const PptxGen = pptxConstructor(await import('pptxgenjs'));
|
|
10955
|
+
const pptx = new PptxGen();
|
|
10956
|
+
pptx.layout = "LAYOUT_WIDE";
|
|
10957
|
+
const CHART_TYPES = {
|
|
10958
|
+
bar: pptx.ChartType["bar"],
|
|
10959
|
+
line: pptx.ChartType["line"],
|
|
10960
|
+
pie: pptx.ChartType["pie"],
|
|
10961
|
+
doughnut: pptx.ChartType["doughnut"],
|
|
10962
|
+
area: pptx.ChartType["area"],
|
|
10963
|
+
scatter: pptx.ChartType["scatter"]
|
|
10964
|
+
};
|
|
10965
|
+
const slides = splitSlides(md);
|
|
10966
|
+
const blank = { title: "", body: [""], images: [], charts: [] };
|
|
10967
|
+
for (const s of slides.length ? slides : [blank]) {
|
|
10968
|
+
const slide = pptx.addSlide();
|
|
10969
|
+
if (s.title) slide.addText(s.title, { x: 0.5, y: 0.35, w: 12.3, h: 0.9, fontSize: 28, bold: true, color: "1F2937" });
|
|
10970
|
+
const top = s.title ? 1.5 : 0.5;
|
|
10971
|
+
const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
|
|
10972
|
+
const visuals = [
|
|
10973
|
+
...s.charts.map((spec) => ({ kind: "chart", spec })),
|
|
10974
|
+
...pics.map((img) => ({ kind: "image", img }))
|
|
10975
|
+
];
|
|
10976
|
+
const bodyH = visuals.length ? 2.2 : 5.4;
|
|
10977
|
+
if (s.body.length) {
|
|
10978
|
+
slide.addText(
|
|
10979
|
+
s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
|
|
10980
|
+
{ x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
|
|
10981
|
+
);
|
|
10982
|
+
}
|
|
10983
|
+
if (visuals.length) {
|
|
10984
|
+
const y = top + (s.body.length ? bodyH + 0.2 : 0);
|
|
10985
|
+
const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
|
|
10986
|
+
const gap = 0.3;
|
|
10987
|
+
const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
|
|
10988
|
+
visuals.forEach((v, i) => {
|
|
10989
|
+
const x = CONTENT_X + i * (w + gap);
|
|
10990
|
+
if (v.kind === "image") {
|
|
10991
|
+
slide.addImage({
|
|
10992
|
+
data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
|
|
10993
|
+
x,
|
|
10994
|
+
y,
|
|
10995
|
+
w,
|
|
10996
|
+
h,
|
|
10997
|
+
// 'contain' keeps the aspect ratio inside the box — a stretched
|
|
10998
|
+
// render looks worse than a smaller correct one.
|
|
10999
|
+
sizing: { type: "contain", w, h },
|
|
11000
|
+
...v.img.alt ? { altText: v.img.alt } : {}
|
|
11001
|
+
});
|
|
11002
|
+
} else {
|
|
11003
|
+
slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
|
|
11004
|
+
x,
|
|
11005
|
+
y,
|
|
11006
|
+
w,
|
|
11007
|
+
h,
|
|
11008
|
+
showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
|
|
11009
|
+
legendPos: "b",
|
|
11010
|
+
showTitle: !!v.spec.title,
|
|
11011
|
+
...v.spec.title ? { title: v.spec.title } : {},
|
|
11012
|
+
showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
|
|
11013
|
+
});
|
|
11014
|
+
}
|
|
11015
|
+
});
|
|
11016
|
+
}
|
|
11017
|
+
}
|
|
11018
|
+
return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
|
|
11019
|
+
}
|
|
11020
|
+
function chartData(spec) {
|
|
11021
|
+
const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
|
|
11022
|
+
return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
|
|
11023
|
+
}
|
|
11024
|
+
async function renderDocument(format, source, opts = {}) {
|
|
11025
|
+
if (format === "docx") return renderDocx(source, opts);
|
|
11026
|
+
if (format === "pptx") return renderPptx(source, opts);
|
|
11027
|
+
return renderXlsx(source, opts);
|
|
11028
|
+
}
|
|
11029
|
+
|
|
11030
|
+
// src/tools/generate-document.ts
|
|
11031
|
+
var SOURCE_HINT = {
|
|
11032
|
+
docx: "Markdown",
|
|
11033
|
+
pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
|
|
11034
|
+
xlsx: "CSV (one header row, then data rows)"
|
|
11035
|
+
};
|
|
11036
|
+
var GenerateDocumentTool = class extends BaseTool {
|
|
11037
|
+
/**
|
|
11038
|
+
* `readFile` is injected so the image loader is testable and so a host with a
|
|
11039
|
+
* different workspace (not plain disk) can supply its own reader.
|
|
11040
|
+
*/
|
|
11041
|
+
constructor(readFile = async (p) => new Uint8Array(await fs5__default.default.readFile(p))) {
|
|
11042
|
+
super();
|
|
11043
|
+
this.readFile = readFile;
|
|
11044
|
+
}
|
|
11045
|
+
readFile;
|
|
11046
|
+
name = "generate_document";
|
|
11047
|
+
description = "Create a REAL Microsoft Office file (.docx Word, .pptx PowerPoint, .xlsx Excel) in the workspace. You write the SOURCE and this tool renders the actual binary: Markdown for .docx, Markdown slides (separated by --- rules) for .pptx, CSV for .xlsx. ALWAYS use this instead of file_write for those three extensions \u2014 file_write stores your text verbatim, which Office reports as a corrupted file. Supports embedded images (reference a generated image as  on its own line) and real, data-accurate charts (a ```chart:bar / chart:line / chart:pie fenced block whose body is CSV). Use pdf_create for PDFs.";
|
|
11048
|
+
inputSchema = {
|
|
11049
|
+
type: "object",
|
|
11050
|
+
properties: {
|
|
11051
|
+
path: {
|
|
11052
|
+
type: "string",
|
|
11053
|
+
description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
|
|
11054
|
+
},
|
|
11055
|
+
content: {
|
|
11056
|
+
type: "string",
|
|
11057
|
+
description: 'The document source. Markdown for .docx; Markdown slides separated by --- rules for .pptx (each slide starts with a heading); CSV for .xlsx. A standalone  line embeds that image. A fenced ```chart:bar (or chart:line, chart:pie, chart:area, chart:doughnut, chart:scatter) block whose body is CSV \u2014 an optional "title: \u2026" line, then a header row of "<category>,<series1>,<series2>", then data rows \u2014 becomes a real chart.'
|
|
11058
|
+
},
|
|
11059
|
+
format: {
|
|
11060
|
+
type: "string",
|
|
11061
|
+
description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
|
|
11062
|
+
}
|
|
11063
|
+
},
|
|
11064
|
+
required: ["path", "content"]
|
|
11065
|
+
};
|
|
11066
|
+
isDangerous() {
|
|
11067
|
+
return true;
|
|
11068
|
+
}
|
|
11069
|
+
async execute(input, options) {
|
|
11070
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
11071
|
+
if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
|
|
11072
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
11073
|
+
if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
|
|
11074
|
+
const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
|
|
11075
|
+
const ext = requested || fileExt(filePath);
|
|
11076
|
+
if (!isDocumentFormat(ext)) {
|
|
11077
|
+
return `generate_document renders .docx, .pptx and .xlsx. "${filePath}" is not one of those \u2014 use pdf_create for a PDF, or file_write for a text file.`;
|
|
11078
|
+
}
|
|
11079
|
+
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
11080
|
+
const embedded = [];
|
|
11081
|
+
const skipped = [];
|
|
11082
|
+
const loadImageBytes = async (url) => {
|
|
11083
|
+
if (/^(https?|ftp):\/\//i.test(url)) {
|
|
11084
|
+
skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
|
|
11085
|
+
return null;
|
|
11086
|
+
}
|
|
11087
|
+
const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
|
|
11088
|
+
try {
|
|
11089
|
+
const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
|
|
11090
|
+
embedded.push(local);
|
|
11091
|
+
return bytes2;
|
|
11092
|
+
} catch {
|
|
11093
|
+
skipped.push(`${local} (not found in the workspace)`);
|
|
11094
|
+
return null;
|
|
11095
|
+
}
|
|
11096
|
+
};
|
|
11097
|
+
if (options.saveSnapshot) {
|
|
11098
|
+
try {
|
|
11099
|
+
const old = await fs5__default.default.readFile(absPath, "utf-8");
|
|
11100
|
+
await options.saveSnapshot(absPath, old);
|
|
11101
|
+
} catch {
|
|
11102
|
+
}
|
|
11103
|
+
}
|
|
11104
|
+
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11105
|
+
await fs5__default.default.mkdir(path25__default.default.dirname(absPath), { recursive: true });
|
|
11106
|
+
await fs5__default.default.writeFile(absPath, bytes);
|
|
11107
|
+
const charts = countCharts(content);
|
|
11108
|
+
return [
|
|
11109
|
+
`Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
|
|
11110
|
+
embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
|
|
11111
|
+
// Named, not counted: a dropped image is the failure the user actually
|
|
11112
|
+
// notices, and the model can only fix it if it is told which reference died.
|
|
11113
|
+
skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
|
|
11114
|
+
charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
|
|
11115
|
+
charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
|
|
11116
|
+
charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
|
|
11117
|
+
].filter(Boolean).join("\n");
|
|
11118
|
+
}
|
|
11119
|
+
};
|
|
11120
|
+
var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
|
|
11121
|
+
function countCharts(source) {
|
|
11122
|
+
return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
|
|
11123
|
+
}
|
|
11124
|
+
function buildDocumentTools(readFile) {
|
|
11125
|
+
return readFile ? [new GenerateDocumentTool(readFile)] : [];
|
|
11126
|
+
}
|
|
10265
11127
|
|
|
10266
11128
|
// src/core/run-breaker.ts
|
|
10267
11129
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
@@ -10584,35 +11446,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
|
|
|
10584
11446
|
}
|
|
10585
11447
|
}
|
|
10586
11448
|
};
|
|
10587
|
-
var WorkspaceSandboxError = class extends Error {
|
|
10588
|
-
constructor(attempted, workspaceRoot) {
|
|
10589
|
-
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10590
|
-
this.name = "WorkspaceSandboxError";
|
|
10591
|
-
}
|
|
10592
|
-
};
|
|
10593
|
-
function resolveInWorkspace(workspaceRoot, input) {
|
|
10594
|
-
if (typeof input !== "string" || input.length === 0) {
|
|
10595
|
-
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10596
|
-
}
|
|
10597
|
-
const root = path24__default.default.resolve(workspaceRoot);
|
|
10598
|
-
const abs = path24__default.default.isAbsolute(input) ? path24__default.default.resolve(input) : path24__default.default.resolve(root, input);
|
|
10599
|
-
const rel = path24__default.default.relative(root, abs);
|
|
10600
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path24__default.default.isAbsolute(rel)) {
|
|
10601
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10602
|
-
}
|
|
10603
|
-
try {
|
|
10604
|
-
const real = fs23__default.default.realpathSync(abs);
|
|
10605
|
-
const realRel = path24__default.default.relative(root, real);
|
|
10606
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path24__default.default.isAbsolute(realRel))) {
|
|
10607
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10608
|
-
}
|
|
10609
|
-
} catch (e) {
|
|
10610
|
-
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10611
|
-
}
|
|
10612
|
-
return abs;
|
|
10613
|
-
}
|
|
10614
|
-
|
|
10615
|
-
// src/tools/file.ts
|
|
10616
11449
|
var FileReadTool = class extends BaseTool {
|
|
10617
11450
|
name = "file_read";
|
|
10618
11451
|
description = "Read the contents of a file. Returns the file content as a string.";
|
|
@@ -10630,7 +11463,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
10630
11463
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10631
11464
|
const offset = input["offset"] ?? 1;
|
|
10632
11465
|
const limit = input["limit"];
|
|
10633
|
-
const content = await
|
|
11466
|
+
const content = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10634
11467
|
const lines = content.split("\n");
|
|
10635
11468
|
const start = Math.max(0, offset - 1);
|
|
10636
11469
|
const end = limit ? start + limit : lines.length;
|
|
@@ -10659,13 +11492,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
10659
11492
|
const content = input["content"];
|
|
10660
11493
|
if (options.saveSnapshot) {
|
|
10661
11494
|
try {
|
|
10662
|
-
const oldContent = await
|
|
11495
|
+
const oldContent = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10663
11496
|
await options.saveSnapshot(absPath, oldContent);
|
|
10664
11497
|
} catch {
|
|
10665
11498
|
}
|
|
10666
11499
|
}
|
|
10667
|
-
await
|
|
10668
|
-
await
|
|
11500
|
+
await fs5__default.default.mkdir(path25__default.default.dirname(absPath), { recursive: true });
|
|
11501
|
+
await fs5__default.default.writeFile(absPath, content, "utf-8");
|
|
10669
11502
|
return `Written ${content.length} characters to ${filePath}`;
|
|
10670
11503
|
}
|
|
10671
11504
|
};
|
|
@@ -10691,7 +11524,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10691
11524
|
const oldString = input["old_string"];
|
|
10692
11525
|
const newString = input["new_string"];
|
|
10693
11526
|
const replaceAll = input["replace_all"] ?? false;
|
|
10694
|
-
const rawContent = await
|
|
11527
|
+
const rawContent = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10695
11528
|
if (options.saveSnapshot) {
|
|
10696
11529
|
await options.saveSnapshot(absPath, rawContent);
|
|
10697
11530
|
}
|
|
@@ -10703,7 +11536,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10703
11536
|
);
|
|
10704
11537
|
}
|
|
10705
11538
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
10706
|
-
await
|
|
11539
|
+
await fs5__default.default.writeFile(absPath, updated, "utf-8");
|
|
10707
11540
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
10708
11541
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
10709
11542
|
}
|
|
@@ -10726,12 +11559,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
10726
11559
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10727
11560
|
if (options.saveSnapshot) {
|
|
10728
11561
|
try {
|
|
10729
|
-
const oldContent = await
|
|
11562
|
+
const oldContent = await fs5__default.default.readFile(absPath, "utf-8");
|
|
10730
11563
|
await options.saveSnapshot(absPath, oldContent);
|
|
10731
11564
|
} catch {
|
|
10732
11565
|
}
|
|
10733
11566
|
}
|
|
10734
|
-
await
|
|
11567
|
+
await fs5__default.default.rm(absPath, { recursive: false });
|
|
10735
11568
|
return `Deleted ${filePath}`;
|
|
10736
11569
|
}
|
|
10737
11570
|
};
|
|
@@ -10748,7 +11581,7 @@ var FileListTool = class extends BaseTool {
|
|
|
10748
11581
|
async execute(input, _options) {
|
|
10749
11582
|
const inputPath = input["path"] || ".";
|
|
10750
11583
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
10751
|
-
const entries = await
|
|
11584
|
+
const entries = await fs5__default.default.readdir(absPath, { withFileTypes: true });
|
|
10752
11585
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
10753
11586
|
}
|
|
10754
11587
|
};
|
|
@@ -11161,8 +11994,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11161
11994
|
}
|
|
11162
11995
|
};
|
|
11163
11996
|
async function fileToImageAttachment(filePath) {
|
|
11164
|
-
const data = await
|
|
11165
|
-
const ext =
|
|
11997
|
+
const data = await fs5__default.default.readFile(filePath);
|
|
11998
|
+
const ext = path25__default.default.extname(filePath).toLowerCase();
|
|
11166
11999
|
const mimeMap = {
|
|
11167
12000
|
".jpg": "image/jpeg",
|
|
11168
12001
|
".jpeg": "image/jpeg",
|
|
@@ -11196,14 +12029,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11196
12029
|
const filePath = input["path"];
|
|
11197
12030
|
const content = input["content"];
|
|
11198
12031
|
const title = input["title"];
|
|
11199
|
-
const dir =
|
|
11200
|
-
if (!
|
|
11201
|
-
|
|
12032
|
+
const dir = path25__default.default.dirname(filePath);
|
|
12033
|
+
if (!fs24__default.default.existsSync(dir)) {
|
|
12034
|
+
fs24__default.default.mkdirSync(dir, { recursive: true });
|
|
11202
12035
|
}
|
|
11203
12036
|
return new Promise((resolve2, reject) => {
|
|
11204
12037
|
try {
|
|
11205
12038
|
const doc = new PDFDocument__default.default({ margin: 50 });
|
|
11206
|
-
const stream =
|
|
12039
|
+
const stream = fs24__default.default.createWriteStream(filePath);
|
|
11207
12040
|
doc.pipe(stream);
|
|
11208
12041
|
if (title) {
|
|
11209
12042
|
doc.info["Title"] = title;
|
|
@@ -11281,22 +12114,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11281
12114
|
}
|
|
11282
12115
|
cmdPrefix = NODE_CMD;
|
|
11283
12116
|
}
|
|
11284
|
-
const tmpDir =
|
|
11285
|
-
if (!
|
|
11286
|
-
|
|
12117
|
+
const tmpDir = path25__default.default.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12118
|
+
if (!fs24__default.default.existsSync(tmpDir)) {
|
|
12119
|
+
fs24__default.default.mkdirSync(tmpDir, { recursive: true });
|
|
11287
12120
|
}
|
|
11288
12121
|
const extension = language === "python" ? "py" : "js";
|
|
11289
12122
|
const fileName = `intp_${crypto4.randomUUID().slice(0, 8)}.${extension}`;
|
|
11290
|
-
const filePath =
|
|
11291
|
-
|
|
12123
|
+
const filePath = path25__default.default.join(tmpDir, fileName);
|
|
12124
|
+
fs24__default.default.writeFileSync(filePath, code, "utf-8");
|
|
11292
12125
|
const execArgs = [filePath, ...args];
|
|
11293
12126
|
return new Promise((resolve2) => {
|
|
11294
12127
|
const startMs = Date.now();
|
|
11295
12128
|
child_process.execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
11296
12129
|
const duration = Date.now() - startMs;
|
|
11297
12130
|
try {
|
|
11298
|
-
if (
|
|
11299
|
-
|
|
12131
|
+
if (fs24__default.default.existsSync(filePath)) {
|
|
12132
|
+
fs24__default.default.unlinkSync(filePath);
|
|
11300
12133
|
}
|
|
11301
12134
|
} catch (cleanupErr) {
|
|
11302
12135
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -11608,7 +12441,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11608
12441
|
};
|
|
11609
12442
|
async execute(input, _options) {
|
|
11610
12443
|
const pattern = input["pattern"];
|
|
11611
|
-
const searchPath = input["path"] ?
|
|
12444
|
+
const searchPath = input["path"] ? path25__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11612
12445
|
const matches = await glob.glob(pattern, {
|
|
11613
12446
|
cwd: searchPath,
|
|
11614
12447
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -11621,7 +12454,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11621
12454
|
const withMtime = await Promise.all(
|
|
11622
12455
|
matches.map(async (rel) => {
|
|
11623
12456
|
try {
|
|
11624
|
-
const stat = await
|
|
12457
|
+
const stat = await fs5__default.default.stat(path25__default.default.join(searchPath, rel));
|
|
11625
12458
|
return { rel, mtime: stat.mtimeMs };
|
|
11626
12459
|
} catch {
|
|
11627
12460
|
return { rel, mtime: 0 };
|
|
@@ -11670,7 +12503,7 @@ var GrepTool = class extends BaseTool {
|
|
|
11670
12503
|
};
|
|
11671
12504
|
async execute(input, _options) {
|
|
11672
12505
|
const pattern = input["pattern"];
|
|
11673
|
-
const searchPath = input["path"] ?
|
|
12506
|
+
const searchPath = input["path"] ? path25__default.default.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11674
12507
|
const globPattern = input["glob"];
|
|
11675
12508
|
const outputMode = input["output_mode"] ?? "content";
|
|
11676
12509
|
const context = input["context"] ?? 0;
|
|
@@ -11724,15 +12557,15 @@ var GrepTool = class extends BaseTool {
|
|
|
11724
12557
|
nodir: true
|
|
11725
12558
|
});
|
|
11726
12559
|
} catch {
|
|
11727
|
-
files = [
|
|
12560
|
+
files = [path25__default.default.relative(searchPath, searchPath) || "."];
|
|
11728
12561
|
}
|
|
11729
12562
|
const results = [];
|
|
11730
12563
|
let totalCount = 0;
|
|
11731
12564
|
for (const rel of files) {
|
|
11732
|
-
const abs =
|
|
12565
|
+
const abs = path25__default.default.join(searchPath, rel);
|
|
11733
12566
|
let content;
|
|
11734
12567
|
try {
|
|
11735
|
-
content = await
|
|
12568
|
+
content = await fs5__default.default.readFile(abs, "utf-8");
|
|
11736
12569
|
} catch {
|
|
11737
12570
|
continue;
|
|
11738
12571
|
}
|
|
@@ -12116,10 +12949,10 @@ var ToolRegistry = class extends EventEmitter__default.default {
|
|
|
12116
12949
|
}
|
|
12117
12950
|
isIgnored(filePath) {
|
|
12118
12951
|
if (!filePath) return false;
|
|
12119
|
-
const abs =
|
|
12120
|
-
const rel =
|
|
12121
|
-
if (!rel || rel.startsWith("..") ||
|
|
12122
|
-
const posixRel = rel.split(
|
|
12952
|
+
const abs = path25__default.default.resolve(this.workspaceRoot, filePath);
|
|
12953
|
+
const rel = path25__default.default.relative(this.workspaceRoot, abs);
|
|
12954
|
+
if (!rel || rel.startsWith("..") || path25__default.default.isAbsolute(rel)) return true;
|
|
12955
|
+
const posixRel = rel.split(path25__default.default.sep).join("/");
|
|
12123
12956
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12124
12957
|
}
|
|
12125
12958
|
};
|
|
@@ -12353,22 +13186,22 @@ var FileMcpOAuthStore = class {
|
|
|
12353
13186
|
filePath;
|
|
12354
13187
|
load() {
|
|
12355
13188
|
try {
|
|
12356
|
-
return JSON.parse(
|
|
13189
|
+
return JSON.parse(fs24__default.default.readFileSync(this.filePath, "utf-8"));
|
|
12357
13190
|
} catch {
|
|
12358
13191
|
return void 0;
|
|
12359
13192
|
}
|
|
12360
13193
|
}
|
|
12361
13194
|
save(state) {
|
|
12362
|
-
|
|
12363
|
-
|
|
13195
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13196
|
+
fs24__default.default.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
12364
13197
|
try {
|
|
12365
|
-
|
|
13198
|
+
fs24__default.default.chmodSync(this.filePath, 384);
|
|
12366
13199
|
} catch {
|
|
12367
13200
|
}
|
|
12368
13201
|
}
|
|
12369
13202
|
clear() {
|
|
12370
13203
|
try {
|
|
12371
|
-
|
|
13204
|
+
fs24__default.default.rmSync(this.filePath);
|
|
12372
13205
|
} catch {
|
|
12373
13206
|
}
|
|
12374
13207
|
}
|
|
@@ -13407,7 +14240,7 @@ var TaskAnalyzer = class {
|
|
|
13407
14240
|
analysisCache.clear();
|
|
13408
14241
|
}
|
|
13409
14242
|
};
|
|
13410
|
-
var DEFAULT_STATS_FILE =
|
|
14243
|
+
var DEFAULT_STATS_FILE = path25__default.default.join(os6__default.default.homedir(), ".cascade", "model-perf.json");
|
|
13411
14244
|
var ModelPerformanceTracker = class {
|
|
13412
14245
|
stats = /* @__PURE__ */ new Map();
|
|
13413
14246
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -13427,7 +14260,7 @@ var ModelPerformanceTracker = class {
|
|
|
13427
14260
|
if (this.loaded) return;
|
|
13428
14261
|
this.loaded = true;
|
|
13429
14262
|
try {
|
|
13430
|
-
const raw = await
|
|
14263
|
+
const raw = await fs5__default.default.readFile(this.statsFile, "utf-8");
|
|
13431
14264
|
const parsed = JSON.parse(raw);
|
|
13432
14265
|
if (parsed.models) {
|
|
13433
14266
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -13447,15 +14280,15 @@ var ModelPerformanceTracker = class {
|
|
|
13447
14280
|
async save() {
|
|
13448
14281
|
if (this.readOnly) return;
|
|
13449
14282
|
try {
|
|
13450
|
-
await
|
|
14283
|
+
await fs5__default.default.mkdir(path25__default.default.dirname(this.statsFile), { recursive: true });
|
|
13451
14284
|
const modelsObj = {};
|
|
13452
14285
|
const featuresObj = {};
|
|
13453
14286
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
13454
14287
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
13455
14288
|
const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
|
|
13456
14289
|
const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
|
|
13457
|
-
await
|
|
13458
|
-
await
|
|
14290
|
+
await fs5__default.default.writeFile(tmp, json, "utf-8");
|
|
14291
|
+
await fs5__default.default.rename(tmp, this.statsFile);
|
|
13459
14292
|
} catch {
|
|
13460
14293
|
}
|
|
13461
14294
|
}
|
|
@@ -13995,9 +14828,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
13995
14828
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
13996
14829
|
async loadPersistedTools() {
|
|
13997
14830
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
13998
|
-
const file =
|
|
14831
|
+
const file = path25__default.default.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
13999
14832
|
try {
|
|
14000
|
-
const raw = await
|
|
14833
|
+
const raw = await fs5__default.default.readFile(file, "utf-8");
|
|
14001
14834
|
const specs = JSON.parse(raw);
|
|
14002
14835
|
if (!Array.isArray(specs)) return;
|
|
14003
14836
|
let loaded = 0;
|
|
@@ -14019,11 +14852,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
14019
14852
|
}
|
|
14020
14853
|
async persist() {
|
|
14021
14854
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
14022
|
-
const dir =
|
|
14023
|
-
const file =
|
|
14855
|
+
const dir = path25__default.default.join(this.workspacePath, ".cascade");
|
|
14856
|
+
const file = path25__default.default.join(dir, DYNAMIC_TOOLS_FILE);
|
|
14024
14857
|
try {
|
|
14025
|
-
await
|
|
14026
|
-
await
|
|
14858
|
+
await fs5__default.default.mkdir(dir, { recursive: true });
|
|
14859
|
+
await fs5__default.default.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
14027
14860
|
} catch (err) {
|
|
14028
14861
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
14029
14862
|
}
|
|
@@ -14040,12 +14873,12 @@ var WorldStateDB = class {
|
|
|
14040
14873
|
constructor(workspacePath, debugMode = false) {
|
|
14041
14874
|
this.workspacePath = workspacePath;
|
|
14042
14875
|
this.debugMode = debugMode;
|
|
14043
|
-
const cascadeDir =
|
|
14044
|
-
if (!
|
|
14045
|
-
|
|
14876
|
+
const cascadeDir = path25__default.default.join(workspacePath, ".cascade");
|
|
14877
|
+
if (!fs24__default.default.existsSync(cascadeDir)) {
|
|
14878
|
+
fs24__default.default.mkdirSync(cascadeDir, { recursive: true });
|
|
14046
14879
|
}
|
|
14047
|
-
this.keyPath =
|
|
14048
|
-
this.dbPath =
|
|
14880
|
+
this.keyPath = path25__default.default.join(cascadeDir, "world_state.key");
|
|
14881
|
+
this.dbPath = path25__default.default.join(cascadeDir, "world_state.db");
|
|
14049
14882
|
this.initEncryptionKey();
|
|
14050
14883
|
this.db = new Database__default.default(this.dbPath);
|
|
14051
14884
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14102,11 +14935,11 @@ var WorldStateDB = class {
|
|
|
14102
14935
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14103
14936
|
}
|
|
14104
14937
|
initEncryptionKey() {
|
|
14105
|
-
if (
|
|
14106
|
-
this.encryptionKey =
|
|
14938
|
+
if (fs24__default.default.existsSync(this.keyPath)) {
|
|
14939
|
+
this.encryptionKey = fs24__default.default.readFileSync(this.keyPath);
|
|
14107
14940
|
} else {
|
|
14108
14941
|
this.encryptionKey = crypto4__default.default.randomBytes(32);
|
|
14109
|
-
|
|
14942
|
+
fs24__default.default.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14110
14943
|
}
|
|
14111
14944
|
}
|
|
14112
14945
|
encrypt(text) {
|
|
@@ -14392,9 +15225,9 @@ var WorldStateDB = class {
|
|
|
14392
15225
|
dumpDebugIfNeeded() {
|
|
14393
15226
|
if (!this.debugMode) return;
|
|
14394
15227
|
try {
|
|
14395
|
-
const dumpPath =
|
|
15228
|
+
const dumpPath = path25__default.default.join(os6__default.default.tmpdir(), "cascade_world_state_debug.json");
|
|
14396
15229
|
const entries = this.getAllEntries();
|
|
14397
|
-
|
|
15230
|
+
fs24__default.default.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
14398
15231
|
} catch (err) {
|
|
14399
15232
|
console.error("Failed to dump debug world state", err);
|
|
14400
15233
|
}
|
|
@@ -14449,9 +15282,9 @@ var CascadeIgnore = class {
|
|
|
14449
15282
|
]);
|
|
14450
15283
|
}
|
|
14451
15284
|
async load(workspacePath) {
|
|
14452
|
-
const filePath =
|
|
15285
|
+
const filePath = path25__default.default.join(workspacePath, ".cascadeignore");
|
|
14453
15286
|
try {
|
|
14454
|
-
const content = await
|
|
15287
|
+
const content = await fs5__default.default.readFile(filePath, "utf-8");
|
|
14455
15288
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
14456
15289
|
this.ig.add(lines);
|
|
14457
15290
|
this.loaded = true;
|
|
@@ -14460,7 +15293,7 @@ var CascadeIgnore = class {
|
|
|
14460
15293
|
}
|
|
14461
15294
|
isIgnored(filePath, workspacePath) {
|
|
14462
15295
|
try {
|
|
14463
|
-
const relative = workspacePath ?
|
|
15296
|
+
const relative = workspacePath ? path25__default.default.relative(workspacePath, filePath) : filePath;
|
|
14464
15297
|
return this.ig.ignores(relative);
|
|
14465
15298
|
} catch {
|
|
14466
15299
|
return false;
|
|
@@ -14803,7 +15636,7 @@ var WorkspaceIndex = class {
|
|
|
14803
15636
|
isIgnored;
|
|
14804
15637
|
chunker;
|
|
14805
15638
|
constructor(opts) {
|
|
14806
|
-
this.root =
|
|
15639
|
+
this.root = path25__default.default.resolve(opts.root);
|
|
14807
15640
|
this.db = opts.db;
|
|
14808
15641
|
this.namespace = opts.namespace ?? "code";
|
|
14809
15642
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -14830,19 +15663,19 @@ var WorkspaceIndex = class {
|
|
|
14830
15663
|
const walk = async (dir) => {
|
|
14831
15664
|
let entries;
|
|
14832
15665
|
try {
|
|
14833
|
-
entries = await
|
|
15666
|
+
entries = await fs5__default.default.readdir(dir, { withFileTypes: true });
|
|
14834
15667
|
} catch {
|
|
14835
15668
|
return;
|
|
14836
15669
|
}
|
|
14837
15670
|
for (const entry of entries) {
|
|
14838
|
-
const abs =
|
|
15671
|
+
const abs = path25__default.default.join(dir, entry.name);
|
|
14839
15672
|
if (entry.isDirectory()) {
|
|
14840
15673
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
14841
15674
|
await walk(abs);
|
|
14842
15675
|
} else if (entry.isFile()) {
|
|
14843
|
-
const ext =
|
|
15676
|
+
const ext = path25__default.default.extname(entry.name).slice(1).toLowerCase();
|
|
14844
15677
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
14845
|
-
out.push(
|
|
15678
|
+
out.push(path25__default.default.relative(this.root, abs));
|
|
14846
15679
|
}
|
|
14847
15680
|
}
|
|
14848
15681
|
};
|
|
@@ -14872,10 +15705,10 @@ var WorkspaceIndex = class {
|
|
|
14872
15705
|
const entries = [];
|
|
14873
15706
|
for (const rel of rels) {
|
|
14874
15707
|
try {
|
|
14875
|
-
const abs =
|
|
14876
|
-
const stat = await
|
|
15708
|
+
const abs = path25__default.default.join(this.root, rel);
|
|
15709
|
+
const stat = await fs5__default.default.stat(abs);
|
|
14877
15710
|
if (stat.size > this.maxFileBytes) continue;
|
|
14878
|
-
const buf = await
|
|
15711
|
+
const buf = await fs5__default.default.readFile(abs);
|
|
14879
15712
|
if (buf.includes(0)) continue;
|
|
14880
15713
|
entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
|
|
14881
15714
|
} catch {
|
|
@@ -15444,7 +16277,7 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15444
16277
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
15445
16278
|
try {
|
|
15446
16279
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
15447
|
-
fileDeadModelPersistence(
|
|
16280
|
+
fileDeadModelPersistence(path25__default.default.join(workspacePath, ".cascade", "dead-models.json"))
|
|
15448
16281
|
));
|
|
15449
16282
|
} catch {
|
|
15450
16283
|
}
|
|
@@ -15452,6 +16285,7 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15452
16285
|
(this.config.providers ?? []).map((p) => p.type)
|
|
15453
16286
|
);
|
|
15454
16287
|
this.registerMediaTools(workspacePath);
|
|
16288
|
+
this.registerDocumentTools(workspacePath);
|
|
15455
16289
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
15456
16290
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
15457
16291
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -15531,8 +16365,8 @@ var Cascade = class _Cascade extends EventEmitter__default.default {
|
|
|
15531
16365
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
15532
16366
|
return;
|
|
15533
16367
|
}
|
|
15534
|
-
const dbPath = ci.dbPath ||
|
|
15535
|
-
|
|
16368
|
+
const dbPath = ci.dbPath || path25__default.default.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16369
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(dbPath), { recursive: true });
|
|
15536
16370
|
const db = new Database__default.default(dbPath);
|
|
15537
16371
|
db.pragma("journal_mode = WAL");
|
|
15538
16372
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -15872,6 +16706,35 @@ ${input}` }],
|
|
|
15872
16706
|
this.toolRegistry.register(tool);
|
|
15873
16707
|
}
|
|
15874
16708
|
}
|
|
16709
|
+
/**
|
|
16710
|
+
* Register `generate_document` where there is a real workspace to write into.
|
|
16711
|
+
*
|
|
16712
|
+
* A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
|
|
16713
|
+
* does exactly what it says and writes the model's text verbatim — produced
|
|
16714
|
+
* files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
|
|
16715
|
+
* the conversion (see tools/generate-document.ts for why it isn't folded into
|
|
16716
|
+
* file_write).
|
|
16717
|
+
*
|
|
16718
|
+
* Registered OUTSIDE `enabledTools` for the same reason the media tools are:
|
|
16719
|
+
* that allowlist is a blast-radius control for tools that reach the machine
|
|
16720
|
+
* or the network, and this one only writes the artifact the user asked for,
|
|
16721
|
+
* into the workspace the run already owns. `disabledTools` still removes it.
|
|
16722
|
+
* A host with no filesystem (the cloud) has its own browser-side exporter and
|
|
16723
|
+
* simply never reaches this path — `buildDocumentTools` returns nothing
|
|
16724
|
+
* without a file reader, mirroring `transcribe_audio`.
|
|
16725
|
+
*/
|
|
16726
|
+
registerDocumentTools(workspacePath) {
|
|
16727
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
16728
|
+
const tools = buildDocumentTools(async (p) => {
|
|
16729
|
+
const fsp = await import('fs/promises');
|
|
16730
|
+
return new Uint8Array(await fsp.readFile(p));
|
|
16731
|
+
});
|
|
16732
|
+
for (const tool of tools) {
|
|
16733
|
+
if (denied.has(tool.name)) continue;
|
|
16734
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
16735
|
+
this.toolRegistry.register(tool);
|
|
16736
|
+
}
|
|
16737
|
+
}
|
|
15875
16738
|
/**
|
|
15876
16739
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
15877
16740
|
* have actually performed for this user, on top of public benchmarks.
|
|
@@ -16820,7 +17683,7 @@ var Keystore = class {
|
|
|
16820
17683
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
16821
17684
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
16822
17685
|
this.backend = "keytar";
|
|
16823
|
-
if (password &&
|
|
17686
|
+
if (password && fs24__default.default.existsSync(this.storePath)) {
|
|
16824
17687
|
try {
|
|
16825
17688
|
const fileEntries = this.decryptFile(password);
|
|
16826
17689
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -16839,7 +17702,7 @@ var Keystore = class {
|
|
|
16839
17702
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
16840
17703
|
);
|
|
16841
17704
|
}
|
|
16842
|
-
if (!
|
|
17705
|
+
if (!fs24__default.default.existsSync(this.storePath)) {
|
|
16843
17706
|
const salt = crypto4__default.default.randomBytes(SALT_LEN);
|
|
16844
17707
|
this.masterKey = this.deriveKey(password, salt);
|
|
16845
17708
|
this.writeWithSalt({}, salt);
|
|
@@ -16853,7 +17716,7 @@ var Keystore = class {
|
|
|
16853
17716
|
}
|
|
16854
17717
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
16855
17718
|
unlockSync(password) {
|
|
16856
|
-
if (!
|
|
17719
|
+
if (!fs24__default.default.existsSync(this.storePath)) {
|
|
16857
17720
|
const salt = crypto4__default.default.randomBytes(SALT_LEN);
|
|
16858
17721
|
this.masterKey = this.deriveKey(password, salt);
|
|
16859
17722
|
this.writeWithSalt({}, salt);
|
|
@@ -16911,7 +17774,7 @@ var Keystore = class {
|
|
|
16911
17774
|
}
|
|
16912
17775
|
}
|
|
16913
17776
|
decryptFile(password, knownSalt) {
|
|
16914
|
-
if (!
|
|
17777
|
+
if (!fs24__default.default.existsSync(this.storePath)) return {};
|
|
16915
17778
|
try {
|
|
16916
17779
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
16917
17780
|
const useSalt = knownSalt ?? salt;
|
|
@@ -16933,8 +17796,8 @@ var Keystore = class {
|
|
|
16933
17796
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16934
17797
|
const tag = cipher.getAuthTag();
|
|
16935
17798
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
16936
|
-
|
|
16937
|
-
|
|
17799
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(this.storePath), { recursive: true });
|
|
17800
|
+
fs24__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16938
17801
|
}
|
|
16939
17802
|
writeWithSalt(data, salt) {
|
|
16940
17803
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -16944,11 +17807,11 @@ var Keystore = class {
|
|
|
16944
17807
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16945
17808
|
const tag = cipher.getAuthTag();
|
|
16946
17809
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
16947
|
-
|
|
16948
|
-
|
|
17810
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(this.storePath), { recursive: true });
|
|
17811
|
+
fs24__default.default.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16949
17812
|
}
|
|
16950
17813
|
readRaw() {
|
|
16951
|
-
const buf =
|
|
17814
|
+
const buf = fs24__default.default.readFileSync(this.storePath);
|
|
16952
17815
|
let offset = 0;
|
|
16953
17816
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
16954
17817
|
offset += SALT_LEN;
|
|
@@ -16964,9 +17827,9 @@ var Keystore = class {
|
|
|
16964
17827
|
}
|
|
16965
17828
|
};
|
|
16966
17829
|
async function loadCascadeMd(workspacePath) {
|
|
16967
|
-
const filePath =
|
|
17830
|
+
const filePath = path25__default.default.join(workspacePath, "CASCADE.md");
|
|
16968
17831
|
try {
|
|
16969
|
-
const raw = await
|
|
17832
|
+
const raw = await fs5__default.default.readFile(filePath, "utf-8");
|
|
16970
17833
|
return parseCascadeMd(raw);
|
|
16971
17834
|
} catch {
|
|
16972
17835
|
return null;
|
|
@@ -16995,7 +17858,7 @@ ${raw.trim()}`;
|
|
|
16995
17858
|
var MemoryStore = class _MemoryStore {
|
|
16996
17859
|
db;
|
|
16997
17860
|
constructor(dbPath) {
|
|
16998
|
-
|
|
17861
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(dbPath), { recursive: true });
|
|
16999
17862
|
try {
|
|
17000
17863
|
this.db = new Database__default.default(dbPath, { timeout: 5e3 });
|
|
17001
17864
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17831,7 +18694,7 @@ Original error: ${err.message}`
|
|
|
17831
18694
|
}
|
|
17832
18695
|
};
|
|
17833
18696
|
function credentialsPath(globalDir2) {
|
|
17834
|
-
return
|
|
18697
|
+
return path25__default.default.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
17835
18698
|
}
|
|
17836
18699
|
function providerKey(p) {
|
|
17837
18700
|
if (p.type === "azure") {
|
|
@@ -17844,7 +18707,7 @@ function isPersistable(p) {
|
|
|
17844
18707
|
}
|
|
17845
18708
|
function loadGlobalCredentials(globalDir2) {
|
|
17846
18709
|
try {
|
|
17847
|
-
const raw =
|
|
18710
|
+
const raw = fs24__default.default.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
17848
18711
|
const parsed = JSON.parse(raw);
|
|
17849
18712
|
if (!Array.isArray(parsed.providers)) return [];
|
|
17850
18713
|
return parsed.providers.filter(
|
|
@@ -17856,13 +18719,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
17856
18719
|
}
|
|
17857
18720
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
17858
18721
|
const filePath = credentialsPath(globalDir2);
|
|
17859
|
-
|
|
18722
|
+
fs24__default.default.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
17860
18723
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
17861
18724
|
const tmp = `${filePath}.tmp`;
|
|
17862
|
-
|
|
17863
|
-
|
|
18725
|
+
fs24__default.default.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
18726
|
+
fs24__default.default.renameSync(tmp, filePath);
|
|
17864
18727
|
try {
|
|
17865
|
-
|
|
18728
|
+
fs24__default.default.chmodSync(filePath, 384);
|
|
17866
18729
|
} catch {
|
|
17867
18730
|
}
|
|
17868
18731
|
}
|
|
@@ -17897,7 +18760,7 @@ var ConfigManager = class {
|
|
|
17897
18760
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
17898
18761
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
17899
18762
|
this.workspacePath = workspacePath;
|
|
17900
|
-
this.globalDir = globalDirOverride ??
|
|
18763
|
+
this.globalDir = globalDirOverride ?? path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR);
|
|
17901
18764
|
}
|
|
17902
18765
|
async load() {
|
|
17903
18766
|
this.config = await this.loadConfig();
|
|
@@ -17911,8 +18774,8 @@ var ConfigManager = class {
|
|
|
17911
18774
|
this.ignore = new CascadeIgnore();
|
|
17912
18775
|
await this.ignore.load(this.workspacePath);
|
|
17913
18776
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
17914
|
-
this.keystore = new Keystore(
|
|
17915
|
-
this.store = new MemoryStore(
|
|
18777
|
+
this.keystore = new Keystore(path25__default.default.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
18778
|
+
this.store = new MemoryStore(path25__default.default.join(this.workspacePath, CASCADE_DB_FILE));
|
|
17916
18779
|
await this.injectEnvKeys();
|
|
17917
18780
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
17918
18781
|
await this.ensureDefaultIdentity();
|
|
@@ -17937,9 +18800,9 @@ var ConfigManager = class {
|
|
|
17937
18800
|
return this.workspacePath;
|
|
17938
18801
|
}
|
|
17939
18802
|
async save() {
|
|
17940
|
-
const configPath =
|
|
17941
|
-
await
|
|
17942
|
-
await
|
|
18803
|
+
const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18804
|
+
await fs5__default.default.mkdir(path25__default.default.dirname(configPath), { recursive: true });
|
|
18805
|
+
await fs5__default.default.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
17943
18806
|
try {
|
|
17944
18807
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
17945
18808
|
} catch (err) {
|
|
@@ -17974,9 +18837,9 @@ var ConfigManager = class {
|
|
|
17974
18837
|
return configProvider?.apiKey;
|
|
17975
18838
|
}
|
|
17976
18839
|
async loadConfig() {
|
|
17977
|
-
const configPath =
|
|
18840
|
+
const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
17978
18841
|
try {
|
|
17979
|
-
const raw = await
|
|
18842
|
+
const raw = await fs5__default.default.readFile(configPath, "utf-8");
|
|
17980
18843
|
return validateConfig(JSON.parse(raw));
|
|
17981
18844
|
} catch (err) {
|
|
17982
18845
|
if (err.code === "ENOENT") {
|
|
@@ -18506,7 +19369,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
18506
19369
|
}
|
|
18507
19370
|
|
|
18508
19371
|
// src/dashboard/server.ts
|
|
18509
|
-
var __dirname$1 =
|
|
19372
|
+
var __dirname$1 = path25__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
|
|
18510
19373
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
18511
19374
|
var DashboardServer = class {
|
|
18512
19375
|
app;
|
|
@@ -18786,14 +19649,14 @@ var DashboardServer = class {
|
|
|
18786
19649
|
*/
|
|
18787
19650
|
persistConfig() {
|
|
18788
19651
|
try {
|
|
18789
|
-
const configPath =
|
|
18790
|
-
|
|
18791
|
-
|
|
19652
|
+
const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19653
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(configPath), { recursive: true });
|
|
19654
|
+
fs24__default.default.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18792
19655
|
} catch (err) {
|
|
18793
19656
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
18794
19657
|
}
|
|
18795
19658
|
try {
|
|
18796
|
-
saveGlobalCredentials(
|
|
19659
|
+
saveGlobalCredentials(path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
18797
19660
|
} catch (err) {
|
|
18798
19661
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
18799
19662
|
}
|
|
@@ -18808,15 +19671,15 @@ var DashboardServer = class {
|
|
|
18808
19671
|
resolveDashboardSecret() {
|
|
18809
19672
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
18810
19673
|
if (fromConfig) return fromConfig;
|
|
18811
|
-
const secretPath =
|
|
19674
|
+
const secretPath = path25__default.default.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
18812
19675
|
try {
|
|
18813
|
-
if (
|
|
18814
|
-
const existing =
|
|
19676
|
+
if (fs24__default.default.existsSync(secretPath)) {
|
|
19677
|
+
const existing = fs24__default.default.readFileSync(secretPath, "utf-8").trim();
|
|
18815
19678
|
if (existing.length >= 16) return existing;
|
|
18816
19679
|
}
|
|
18817
19680
|
const generated = crypto4.randomUUID();
|
|
18818
|
-
|
|
18819
|
-
|
|
19681
|
+
fs24__default.default.mkdirSync(path25__default.default.dirname(secretPath), { recursive: true });
|
|
19682
|
+
fs24__default.default.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
18820
19683
|
if (this.config.dashboard.auth) {
|
|
18821
19684
|
console.warn(
|
|
18822
19685
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -18843,7 +19706,7 @@ var DashboardServer = class {
|
|
|
18843
19706
|
// ── Setup ─────────────────────────────────────
|
|
18844
19707
|
getGlobalStore() {
|
|
18845
19708
|
if (!this.globalStore) {
|
|
18846
|
-
const globalDbPath =
|
|
19709
|
+
const globalDbPath = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
18847
19710
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
18848
19711
|
}
|
|
18849
19712
|
return this.globalStore;
|
|
@@ -19223,12 +20086,12 @@ ${prompt}`;
|
|
|
19223
20086
|
}
|
|
19224
20087
|
}
|
|
19225
20088
|
watchRuntimeChanges() {
|
|
19226
|
-
const workspaceDbPath =
|
|
19227
|
-
const globalDbPath =
|
|
20089
|
+
const workspaceDbPath = path25__default.default.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20090
|
+
const globalDbPath = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19228
20091
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
19229
20092
|
for (const watchPath of watchPaths) {
|
|
19230
|
-
if (!
|
|
19231
|
-
|
|
20093
|
+
if (!fs24__default.default.existsSync(watchPath)) continue;
|
|
20094
|
+
fs24__default.default.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
19232
20095
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
19233
20096
|
});
|
|
19234
20097
|
}
|
|
@@ -19350,7 +20213,7 @@ ${prompt}`;
|
|
|
19350
20213
|
const sessionId = req.params.id;
|
|
19351
20214
|
this.store.deleteSession(sessionId);
|
|
19352
20215
|
this.store.deleteRuntimeSession(sessionId);
|
|
19353
|
-
const globalDbPath =
|
|
20216
|
+
const globalDbPath = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19354
20217
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19355
20218
|
try {
|
|
19356
20219
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -19443,7 +20306,7 @@ ${prompt}`;
|
|
|
19443
20306
|
});
|
|
19444
20307
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
19445
20308
|
const body = req.body;
|
|
19446
|
-
const globalDbPath =
|
|
20309
|
+
const globalDbPath = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19447
20310
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
19448
20311
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19449
20312
|
try {
|
|
@@ -19466,7 +20329,7 @@ ${prompt}`;
|
|
|
19466
20329
|
});
|
|
19467
20330
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
19468
20331
|
this.store.deleteAllRuntimeNodes();
|
|
19469
|
-
const globalDbPath =
|
|
20332
|
+
const globalDbPath = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19470
20333
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19471
20334
|
try {
|
|
19472
20335
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -19552,12 +20415,12 @@ ${prompt}`;
|
|
|
19552
20415
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
19553
20416
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
19554
20417
|
try {
|
|
19555
|
-
const configPath =
|
|
19556
|
-
const existing =
|
|
20418
|
+
const configPath = path25__default.default.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20419
|
+
const existing = fs24__default.default.existsSync(configPath) ? JSON.parse(fs24__default.default.readFileSync(configPath, "utf-8")) : {};
|
|
19557
20420
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
19558
20421
|
const tmp = configPath + ".tmp";
|
|
19559
|
-
|
|
19560
|
-
|
|
20422
|
+
fs24__default.default.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20423
|
+
fs24__default.default.renameSync(tmp, configPath);
|
|
19561
20424
|
res.json({ ok: true });
|
|
19562
20425
|
} catch (err) {
|
|
19563
20426
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -19585,7 +20448,7 @@ ${prompt}`;
|
|
|
19585
20448
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
19586
20449
|
const scope = req.query["scope"] ?? "workspace";
|
|
19587
20450
|
if (scope === "global") {
|
|
19588
|
-
const globalDbPath =
|
|
20451
|
+
const globalDbPath = path25__default.default.join(os6__default.default.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19589
20452
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19590
20453
|
try {
|
|
19591
20454
|
res.json({
|
|
@@ -19926,13 +20789,13 @@ ${prompt}`;
|
|
|
19926
20789
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
19927
20790
|
}
|
|
19928
20791
|
});
|
|
19929
|
-
const prodPath =
|
|
19930
|
-
const devPath =
|
|
19931
|
-
const webDistPath =
|
|
19932
|
-
if (
|
|
20792
|
+
const prodPath = path25__default.default.resolve(__dirname$1, "../web/dist");
|
|
20793
|
+
const devPath = path25__default.default.resolve(__dirname$1, "../../web/dist");
|
|
20794
|
+
const webDistPath = fs24__default.default.existsSync(prodPath) ? prodPath : devPath;
|
|
20795
|
+
if (fs24__default.default.existsSync(webDistPath)) {
|
|
19933
20796
|
this.app.use(express__default.default.static(webDistPath));
|
|
19934
20797
|
this.app.get("*", (_req, res) => {
|
|
19935
|
-
res.sendFile(
|
|
20798
|
+
res.sendFile(path25__default.default.join(webDistPath, "index.html"));
|
|
19936
20799
|
});
|
|
19937
20800
|
} else {
|
|
19938
20801
|
this.app.get("/", (_req, res) => {
|
|
@@ -20119,14 +20982,14 @@ function planRetrieval(ctx) {
|
|
|
20119
20982
|
}
|
|
20120
20983
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20121
20984
|
function globalDir(homeDir = os6__default.default.homedir()) {
|
|
20122
|
-
return
|
|
20985
|
+
return path25__default.default.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20123
20986
|
}
|
|
20124
20987
|
function cloudSessionPath(dir = globalDir()) {
|
|
20125
|
-
return
|
|
20988
|
+
return path25__default.default.join(dir, CLOUD_SESSION_FILE);
|
|
20126
20989
|
}
|
|
20127
20990
|
function loadCloudSession(dir = globalDir()) {
|
|
20128
20991
|
try {
|
|
20129
|
-
const raw =
|
|
20992
|
+
const raw = fs24__default.default.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20130
20993
|
const parsed = JSON.parse(raw);
|
|
20131
20994
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20132
20995
|
return parsed;
|
|
@@ -20135,17 +20998,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20135
20998
|
}
|
|
20136
20999
|
}
|
|
20137
21000
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20138
|
-
|
|
21001
|
+
fs24__default.default.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20139
21002
|
const file = cloudSessionPath(dir);
|
|
20140
|
-
|
|
21003
|
+
fs24__default.default.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20141
21004
|
try {
|
|
20142
|
-
|
|
21005
|
+
fs24__default.default.chmodSync(file, 384);
|
|
20143
21006
|
} catch {
|
|
20144
21007
|
}
|
|
20145
21008
|
}
|
|
20146
21009
|
function clearCloudSession(dir = globalDir()) {
|
|
20147
21010
|
try {
|
|
20148
|
-
|
|
21011
|
+
fs24__default.default.rmSync(cloudSessionPath(dir));
|
|
20149
21012
|
} catch {
|
|
20150
21013
|
}
|
|
20151
21014
|
}
|
|
@@ -20317,16 +21180,16 @@ var CloudClient = class _CloudClient {
|
|
|
20317
21180
|
return (await this.refresh()).accessToken;
|
|
20318
21181
|
}
|
|
20319
21182
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
20320
|
-
async authed(
|
|
20321
|
-
const call = (token) => fetch(this.url(
|
|
21183
|
+
async authed(path27, init = {}) {
|
|
21184
|
+
const call = (token) => fetch(this.url(path27), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
20322
21185
|
let res = await call(await this.accessToken());
|
|
20323
21186
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
20324
21187
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
20325
21188
|
const text = await res.text();
|
|
20326
21189
|
return text ? JSON.parse(text) : {};
|
|
20327
21190
|
}
|
|
20328
|
-
authedGet(
|
|
20329
|
-
return this.authed(
|
|
21191
|
+
authedGet(path27) {
|
|
21192
|
+
return this.authed(path27);
|
|
20330
21193
|
}
|
|
20331
21194
|
async fetchMe(token) {
|
|
20332
21195
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -20695,6 +21558,7 @@ exports.DEFAULT_DASHBOARD_PORT = DEFAULT_DASHBOARD_PORT;
|
|
|
20695
21558
|
exports.DEFAULT_MAX_SESSION_MESSAGES = DEFAULT_MAX_SESSION_MESSAGES;
|
|
20696
21559
|
exports.DEFAULT_RETENTION_DAYS = DEFAULT_RETENTION_DAYS;
|
|
20697
21560
|
exports.DEFAULT_THEME = DEFAULT_THEME;
|
|
21561
|
+
exports.DOCUMENT_MIME = DOCUMENT_MIME;
|
|
20698
21562
|
exports.DashboardServer = DashboardServer;
|
|
20699
21563
|
exports.FileMcpOAuthStore = FileMcpOAuthStore;
|
|
20700
21564
|
exports.GITHUB_MODELS_API_VERSION = GITHUB_MODELS_API_VERSION;
|
|
@@ -20705,6 +21569,7 @@ exports.GLOBAL_CREDENTIALS_FILE = GLOBAL_CREDENTIALS_FILE;
|
|
|
20705
21569
|
exports.GLOBAL_DB_FILE = GLOBAL_DB_FILE;
|
|
20706
21570
|
exports.GLOBAL_KEYSTORE_FILE = GLOBAL_KEYSTORE_FILE;
|
|
20707
21571
|
exports.GLOBAL_RUNTIME_DB_FILE = GLOBAL_RUNTIME_DB_FILE;
|
|
21572
|
+
exports.GenerateDocumentTool = GenerateDocumentTool;
|
|
20708
21573
|
exports.GraphRetriever = GraphRetriever;
|
|
20709
21574
|
exports.GraphSearchTool = GraphSearchTool;
|
|
20710
21575
|
exports.HooksRunner = HooksRunner;
|
|
@@ -20738,9 +21603,13 @@ exports.WorkspaceIndex = WorkspaceIndex;
|
|
|
20738
21603
|
exports.applySyncBundle = applySyncBundle;
|
|
20739
21604
|
exports.azureModelForDeployment = azureModelForDeployment;
|
|
20740
21605
|
exports.beginMcpOAuth = beginMcpOAuth;
|
|
21606
|
+
exports.buildDocumentTools = buildDocumentTools;
|
|
20741
21607
|
exports.buildManifest = buildManifest;
|
|
20742
21608
|
exports.buildSessionTranscript = buildSessionTranscript;
|
|
21609
|
+
exports.bytesToBase64 = bytesToBase64;
|
|
20743
21610
|
exports.cagCharBudget = cagCharBudget;
|
|
21611
|
+
exports.chartKind = chartKind;
|
|
21612
|
+
exports.chartToTableRows = chartToTableRows;
|
|
20744
21613
|
exports.chatCompleterFromProviders = chatCompleterFromProviders;
|
|
20745
21614
|
exports.chunkCode = chunkCode;
|
|
20746
21615
|
exports.chunkText = chunkText2;
|
|
@@ -20755,24 +21624,40 @@ exports.discoverMcpTools = discoverMcpTools;
|
|
|
20755
21624
|
exports.distillSessionFacts = distillSessionFacts;
|
|
20756
21625
|
exports.embedderFromProviders = embedderFromProviders;
|
|
20757
21626
|
exports.encryptSyncBlob = encryptJSON;
|
|
21627
|
+
exports.extractCharts = extractCharts;
|
|
21628
|
+
exports.fileExt = fileExt;
|
|
20758
21629
|
exports.fileOAuthProvider = fileOAuthProvider;
|
|
20759
21630
|
exports.gatherSyncBundle = gatherSyncBundle;
|
|
20760
21631
|
exports.hashContent = hashContent;
|
|
20761
21632
|
exports.heuristicCodeChunker = heuristicCodeChunker;
|
|
20762
21633
|
exports.inferAzureBaseModel = inferAzureBaseModel;
|
|
21634
|
+
exports.inlineRuns = inlineRuns;
|
|
21635
|
+
exports.isDocumentFormat = isDocumentFormat;
|
|
20763
21636
|
exports.isMcpToolName = isMcpToolName;
|
|
21637
|
+
exports.matchImageLine = matchImageLine;
|
|
20764
21638
|
exports.mcpServerPrefix = mcpServerPrefix;
|
|
20765
21639
|
exports.mcpToolName = mcpToolName;
|
|
20766
21640
|
exports.nodeHttpFetch = nodeHttpFetch;
|
|
21641
|
+
exports.parseBlocks = parseBlocks;
|
|
21642
|
+
exports.parseChartSpec = parseChartSpec;
|
|
21643
|
+
exports.parseDelimited = parseDelimited;
|
|
20767
21644
|
exports.parseRankOrder = parseRankOrder;
|
|
21645
|
+
exports.parseSlide = parseSlide;
|
|
20768
21646
|
exports.planRetrieval = planRetrieval;
|
|
20769
21647
|
exports.preferIpv4Host = preferIpv4Host;
|
|
20770
21648
|
exports.reciprocalRankFusion = reciprocalRankFusion;
|
|
20771
21649
|
exports.refreshMcpToken = refreshMcpToken;
|
|
20772
21650
|
exports.removeMcpServerDenials = removeMcpServerDenials;
|
|
21651
|
+
exports.renderDocument = renderDocument;
|
|
21652
|
+
exports.renderDocx = renderDocx;
|
|
21653
|
+
exports.renderPptx = renderPptx;
|
|
21654
|
+
exports.renderXlsx = renderXlsx;
|
|
20773
21655
|
exports.runCascade = runCascade;
|
|
20774
21656
|
exports.sessionWorthRemembering = sessionWorthRemembering;
|
|
21657
|
+
exports.sniffImage = sniffImage;
|
|
21658
|
+
exports.splitSlides = splitSlides;
|
|
20775
21659
|
exports.streamCascade = streamCascade;
|
|
21660
|
+
exports.stripInline = stripInline;
|
|
20776
21661
|
exports.uniqueMcpServerName = uniqueMcpServerName;
|
|
20777
21662
|
//# sourceMappingURL=index.cjs.map
|
|
20778
21663
|
//# sourceMappingURL=index.cjs.map
|