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.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
|
-
import
|
|
3
|
-
import
|
|
2
|
+
import path25, { dirname } from 'path';
|
|
3
|
+
import fs24, { mkdirSync, writeFileSync, existsSync, readFileSync } from 'fs';
|
|
4
4
|
import os6 from 'os';
|
|
5
5
|
import crypto4, { randomUUID, randomBytes, createHash, timingSafeEqual, webcrypto } from 'crypto';
|
|
6
6
|
import EventEmitter, { setMaxListeners } from 'events';
|
|
@@ -13,7 +13,7 @@ import http, { createServer } from 'http';
|
|
|
13
13
|
import https from 'https';
|
|
14
14
|
import zlib from 'zlib';
|
|
15
15
|
import { Readable } from 'stream';
|
|
16
|
-
import
|
|
16
|
+
import fs5 from 'fs/promises';
|
|
17
17
|
import * as ignoreFactory from 'ignore';
|
|
18
18
|
import ignoreFactory__default from 'ignore';
|
|
19
19
|
import { exec, execFile, execSync } from 'child_process';
|
|
@@ -61,12 +61,12 @@ var init_audit_logger = __esm({
|
|
|
61
61
|
constructor(workspacePath, debugMode = false) {
|
|
62
62
|
this.workspacePath = workspacePath;
|
|
63
63
|
this.debugMode = debugMode;
|
|
64
|
-
const cascadeDir =
|
|
65
|
-
if (!
|
|
66
|
-
|
|
64
|
+
const cascadeDir = path25.join(workspacePath, ".cascade");
|
|
65
|
+
if (!fs24.existsSync(cascadeDir)) {
|
|
66
|
+
fs24.mkdirSync(cascadeDir, { recursive: true });
|
|
67
67
|
}
|
|
68
|
-
this.keyPath =
|
|
69
|
-
this.dbPath =
|
|
68
|
+
this.keyPath = path25.join(cascadeDir, "audit_log.key");
|
|
69
|
+
this.dbPath = path25.join(cascadeDir, "audit_log.db");
|
|
70
70
|
this.initEncryptionKey();
|
|
71
71
|
this.db = new Database(this.dbPath);
|
|
72
72
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -96,11 +96,11 @@ var init_audit_logger = __esm({
|
|
|
96
96
|
return crypto4.createHash("sha256").update(`${prevHash}|${timestamp}|${eventType}|${tierId}|${encryptedPayload}`).digest("hex");
|
|
97
97
|
}
|
|
98
98
|
initEncryptionKey() {
|
|
99
|
-
if (
|
|
100
|
-
this.encryptionKey =
|
|
99
|
+
if (fs24.existsSync(this.keyPath)) {
|
|
100
|
+
this.encryptionKey = fs24.readFileSync(this.keyPath);
|
|
101
101
|
} else {
|
|
102
102
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
103
|
-
|
|
103
|
+
fs24.writeFileSync(this.keyPath, this.encryptionKey);
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
encrypt(text) {
|
|
@@ -178,9 +178,9 @@ var init_audit_logger = __esm({
|
|
|
178
178
|
dumpDebugIfNeeded() {
|
|
179
179
|
if (!this.debugMode) return;
|
|
180
180
|
try {
|
|
181
|
-
const dumpPath =
|
|
181
|
+
const dumpPath = path25.join(os6.tmpdir(), "cascade_audit_logs_debug.json");
|
|
182
182
|
const entries = this.getAllLogs();
|
|
183
|
-
|
|
183
|
+
fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
184
184
|
} catch (err) {
|
|
185
185
|
console.error("Failed to dump debug audit logs", err);
|
|
186
186
|
}
|
|
@@ -1862,7 +1862,7 @@ function reconcilePrice(model, live) {
|
|
|
1862
1862
|
}
|
|
1863
1863
|
|
|
1864
1864
|
// src/constants.ts
|
|
1865
|
-
var CASCADE_VERSION = "0.
|
|
1865
|
+
var CASCADE_VERSION = "0.67.0";
|
|
1866
1866
|
var CASCADE_CONFIG_DIR = ".cascade";
|
|
1867
1867
|
var CASCADE_MD_FILE = "CASCADE.md";
|
|
1868
1868
|
var CASCADE_IGNORE_FILE = ".cascadeignore";
|
|
@@ -4617,7 +4617,7 @@ function computeDelegationSavings(stats, t1Model) {
|
|
|
4617
4617
|
var DEFAULT_SNAPSHOT_URL = "https://raw.githubusercontent.com/Varun-SV/Cascade-AI/main/src/core/router/benchmark-data.json";
|
|
4618
4618
|
var OPENROUTER_MODELS_URL = "https://openrouter.ai/api/v1/models";
|
|
4619
4619
|
var FETCH_TIMEOUT_MS = 8e3;
|
|
4620
|
-
var DEFAULT_CACHE_FILE =
|
|
4620
|
+
var DEFAULT_CACHE_FILE = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, "benchmarks-cache.json");
|
|
4621
4621
|
function normalizeModelId2(id) {
|
|
4622
4622
|
let s = id.toLowerCase();
|
|
4623
4623
|
const slash = s.lastIndexOf("/");
|
|
@@ -4652,7 +4652,7 @@ var LiveDataProvider = class {
|
|
|
4652
4652
|
if (this.loaded) return;
|
|
4653
4653
|
this.loaded = true;
|
|
4654
4654
|
try {
|
|
4655
|
-
const raw = await
|
|
4655
|
+
const raw = await fs5.readFile(this.opts.cacheFile, "utf-8");
|
|
4656
4656
|
const cache = JSON.parse(raw);
|
|
4657
4657
|
if (cache.snapshot?.families) {
|
|
4658
4658
|
this.snapshot = cache.snapshot;
|
|
@@ -4764,14 +4764,14 @@ var LiveDataProvider = class {
|
|
|
4764
4764
|
}
|
|
4765
4765
|
async saveCache() {
|
|
4766
4766
|
try {
|
|
4767
|
-
await
|
|
4767
|
+
await fs5.mkdir(path25.dirname(this.opts.cacheFile), { recursive: true });
|
|
4768
4768
|
const cache = {
|
|
4769
4769
|
fetchedAt: this.fetchedAt,
|
|
4770
4770
|
snapshot: this.snapshot ?? void 0,
|
|
4771
4771
|
prices: Object.fromEntries(this.prices),
|
|
4772
4772
|
capabilities: Object.fromEntries(this.capabilities)
|
|
4773
4773
|
};
|
|
4774
|
-
await
|
|
4774
|
+
await fs5.writeFile(this.opts.cacheFile, JSON.stringify(cache, null, 2), "utf-8");
|
|
4775
4775
|
} catch {
|
|
4776
4776
|
}
|
|
4777
4777
|
}
|
|
@@ -6590,7 +6590,16 @@ var KNOWN_TOOLS = [
|
|
|
6590
6590
|
"glob",
|
|
6591
6591
|
"grep",
|
|
6592
6592
|
"web_fetch",
|
|
6593
|
-
|
|
6593
|
+
// Every media tool buildMediaTools can register, not just the image one.
|
|
6594
|
+
// Omitting three of the four meant a run whose ONLY tools were media ones
|
|
6595
|
+
// (a video-generation subtask on an account with no file/web tools) counted
|
|
6596
|
+
// as "no tools registered" and dropped all tool guidance — the worker was
|
|
6597
|
+
// then told nothing about using tools at all, and wrote the video as prose.
|
|
6598
|
+
"generate_image",
|
|
6599
|
+
"generate_video",
|
|
6600
|
+
"generate_speech",
|
|
6601
|
+
"transcribe_audio",
|
|
6602
|
+
"generate_document"
|
|
6594
6603
|
];
|
|
6595
6604
|
var PROVIDER_BACKED_TOOLS = /* @__PURE__ */ new Set([
|
|
6596
6605
|
"generate_image",
|
|
@@ -6607,19 +6616,55 @@ function buildWorkerRules(has) {
|
|
|
6607
6616
|
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.",
|
|
6608
6617
|
has("web_search") && '- Use the "web_search" tool to find current information, documentation, news, or general web data.',
|
|
6609
6618
|
has("pdf_create") && '- Use the "pdf_create" tool for PDF requests.',
|
|
6619
|
+
// A .docx/.pptx/.xlsx is a ZIP of OOXML parts, not text with a suffix.
|
|
6620
|
+
// file_write does exactly what it promises — writes the model's characters
|
|
6621
|
+
// verbatim — so without this rule the worker "wrote" a Word document that
|
|
6622
|
+
// Word correctly reported as corrupted. Stated as an explicit prohibition
|
|
6623
|
+
// on the tool it would otherwise reach for, because "use generate_document"
|
|
6624
|
+
// alone competes with a file_write habit the model already has.
|
|
6625
|
+
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.',
|
|
6626
|
+
// The alternative to this rule is not "a worse chart" — it is a paragraph
|
|
6627
|
+
// describing a chart that does not exist, which is what real decks came
|
|
6628
|
+
// back with. A `chart:` block is checkable, carries the exact numbers, and
|
|
6629
|
+
// becomes a genuine editable PowerPoint chart object.
|
|
6630
|
+
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.',
|
|
6610
6631
|
// Without this the model "writes the image" as prose — a bracketed
|
|
6611
6632
|
// description sitting in the deck where the picture should be — even with a
|
|
6612
6633
|
// working image model registered. Naming the reference syntax matters as
|
|
6613
6634
|
// much as naming the tool: a generated image nobody embeds is still a
|
|
6614
|
-
// missing image.
|
|
6635
|
+
// missing image. It also has to say the reference must stand ALONE on its
|
|
6636
|
+
// line, because only a standalone reference is parsed as a picture (see
|
|
6637
|
+
// core/documents/blocks.ts matchImageLine) — one embedded mid-sentence
|
|
6638
|
+
// silently degrades to caption text.
|
|
6615
6639
|
//
|
|
6616
|
-
// Scoped to PowerPoint/Word specifically: those are the only
|
|
6617
|
-
//
|
|
6618
|
-
//
|
|
6619
|
-
//
|
|
6620
|
-
//
|
|
6621
|
-
has("generate_image") &&
|
|
6622
|
-
|
|
6640
|
+
// Scoped to PowerPoint/Word specifically: those are the only renderers that
|
|
6641
|
+
// actually embed a Markdown image reference. PDF and plain-text/Markdown
|
|
6642
|
+
// deliverables flatten a reference straight to caption text — telling the
|
|
6643
|
+
// model to call generate_image for THOSE just pays for an image nobody
|
|
6644
|
+
// ever sees.
|
|
6645
|
+
has("generate_image") && // Supersedes the earlier wording of this same rule: it now names the
|
|
6646
|
+
// standalone-line parser constraint, says to generate BEFORE writing the
|
|
6647
|
+
// document, and points data-driven visuals at a ```chart: block rather
|
|
6648
|
+
// than a Markdown table, which is only the right advice now that chart
|
|
6649
|
+
// blocks render as real chart objects.
|
|
6650
|
+
'- 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.',
|
|
6651
|
+
// The other half of image reliability, and the half no amount of
|
|
6652
|
+
// instruction-tightening could fix: generate_image only exists when an
|
|
6653
|
+
// OpenAI or Gemini key is configured (multimodal/registry.ts CAPABILITIES).
|
|
6654
|
+
// With neither, the model was never given a choice — and a model that
|
|
6655
|
+
// doesn't know that writes "[illustration of a cat]" and moves on. Said out
|
|
6656
|
+
// loud, up front, so the fallback is a real chart or honest prose.
|
|
6657
|
+
!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.",
|
|
6658
|
+
// The video counterpart of the image rule above, and the fix for the
|
|
6659
|
+
// reported "it writes scripts forever and never makes the video" run. The
|
|
6660
|
+
// failure mode is identical — the model narrates the deliverable instead of
|
|
6661
|
+
// producing it — but the stakes are not: a video subtask that ends in prose
|
|
6662
|
+
// has burned the whole pre-production plan for nothing. Unlike the image
|
|
6663
|
+
// rule this is NOT scoped to a document format, because the clip itself is
|
|
6664
|
+
// the deliverable rather than an illustration inside one, and the reference
|
|
6665
|
+
// syntax is a plain link: Markdown image syntax does not embed a video.
|
|
6666
|
+
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.',
|
|
6667
|
+
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.`,
|
|
6623
6668
|
"- If you are not making meaningful progress, stop and escalate rather than looping or padding the response.",
|
|
6624
6669
|
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.',
|
|
6625
6670
|
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.",
|
|
@@ -6630,13 +6675,34 @@ function buildWorkerRules(has) {
|
|
|
6630
6675
|
Rules:
|
|
6631
6676
|
${rules.filter((r) => r !== false).join("\n")}`;
|
|
6632
6677
|
}
|
|
6633
|
-
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell"]);
|
|
6678
|
+
var ARTIFACT_TOOLS = /* @__PURE__ */ new Set(["file_write", "file_edit", "shell", "generate_document"]);
|
|
6679
|
+
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;
|
|
6634
6680
|
function shouldRequireArtifact(assignment, toolNames) {
|
|
6635
6681
|
if (!toolNames.some((n) => ARTIFACT_TOOLS.has(n))) return false;
|
|
6636
6682
|
if (assignment?.files?.length) return true;
|
|
6637
6683
|
const haystack = `${assignment?.description ?? ""}
|
|
6638
6684
|
${assignment?.expectedOutput ?? ""}`;
|
|
6639
|
-
return
|
|
6685
|
+
return ARTIFACT_FILE_RE.test(haystack) || /save (?:a|the)? file|create (?:a|the)? file|write (?:a|the)? file/i.test(haystack);
|
|
6686
|
+
}
|
|
6687
|
+
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;
|
|
6688
|
+
var IMAGE_REF_RE = /!\[[^\]]*\]\([^)]+\)/;
|
|
6689
|
+
var CHART_BLOCK_RE = /^\s*```+\s*chart\s*:/im;
|
|
6690
|
+
function missingVisualEvidence(assignment, output, calledTools, toolNames) {
|
|
6691
|
+
const canVisualize = toolNames.includes("generate_image") || toolNames.includes("generate_document");
|
|
6692
|
+
if (!canVisualize) return null;
|
|
6693
|
+
const haystack = `${assignment?.description ?? ""}
|
|
6694
|
+
${assignment?.expectedOutput ?? ""}`;
|
|
6695
|
+
if (!VISUAL_REQUEST_RE.test(haystack)) return null;
|
|
6696
|
+
if (calledTools.includes("generate_image") || calledTools.includes("generate_document")) return null;
|
|
6697
|
+
if (IMAGE_REF_RE.test(output) || CHART_BLOCK_RE.test(output)) return null;
|
|
6698
|
+
const options = [];
|
|
6699
|
+
if (toolNames.includes("generate_image")) {
|
|
6700
|
+
options.push('call "generate_image" and reference the result on its own line as ');
|
|
6701
|
+
}
|
|
6702
|
+
if (toolNames.includes("generate_document")) {
|
|
6703
|
+
options.push("emit a fenced ```chart:bar (or chart:line / chart:pie) block whose body is CSV, for anything data-driven");
|
|
6704
|
+
}
|
|
6705
|
+
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.`;
|
|
6640
6706
|
}
|
|
6641
6707
|
var T3Worker = class extends BaseTier {
|
|
6642
6708
|
router;
|
|
@@ -6817,6 +6883,18 @@ Now execute your subtask using this context where relevant.`
|
|
|
6817
6883
|
return this.buildResult("ESCALATED", output, { checksRun, passed, failed }, issues, correctionAttempts);
|
|
6818
6884
|
}
|
|
6819
6885
|
}
|
|
6886
|
+
const visualIssue = missingVisualEvidence(
|
|
6887
|
+
assignment,
|
|
6888
|
+
output,
|
|
6889
|
+
toolCalls.map((t) => t.name),
|
|
6890
|
+
this.tools.map((t) => t.name)
|
|
6891
|
+
);
|
|
6892
|
+
if (visualIssue) {
|
|
6893
|
+
correctionAttempts = 1;
|
|
6894
|
+
issues.push(visualIssue);
|
|
6895
|
+
this.sendStatusUpdate({ progressPct: 68, currentAction: "Verifying requested visuals", status: "IN_PROGRESS" });
|
|
6896
|
+
output = await this.correctOutput(output, [visualIssue]);
|
|
6897
|
+
}
|
|
6820
6898
|
this.sendStatusUpdate({ progressPct: 70, currentAction: "Self-testing output", status: "IN_PROGRESS" });
|
|
6821
6899
|
const testResult = await this.selfTest(assignment, output);
|
|
6822
6900
|
checksRun.push(...testResult.checksRun);
|
|
@@ -7131,8 +7209,8 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7131
7209
|
// Media generation can run for a minute; without this a cancelled run
|
|
7132
7210
|
// still pays for an image nobody will see.
|
|
7133
7211
|
...this.signal ? { signal: this.signal } : {},
|
|
7134
|
-
saveSnapshot: async (
|
|
7135
|
-
this.store?.addFileSnapshot(this.taskId,
|
|
7212
|
+
saveSnapshot: async (path27, content) => {
|
|
7213
|
+
this.store?.addFileSnapshot(this.taskId, path27, content);
|
|
7136
7214
|
},
|
|
7137
7215
|
sendPeerSync: (to, syncType, content) => {
|
|
7138
7216
|
this.peerBus?.send(this.id, to, syncType, this.assignment?.subtaskId ?? "", content);
|
|
@@ -7161,6 +7239,12 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7161
7239
|
if (isTaggedSystemic || isProviderBacked && classifyProviderError(err).systemic) {
|
|
7162
7240
|
throw new CriticalToolError(errMsg, tc.name);
|
|
7163
7241
|
}
|
|
7242
|
+
if (isProviderBacked) {
|
|
7243
|
+
throw new CriticalToolError(
|
|
7244
|
+
`${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}`,
|
|
7245
|
+
tc.name
|
|
7246
|
+
);
|
|
7247
|
+
}
|
|
7164
7248
|
return await this.adaptiveFallback(tc, `Tool error: ${errMsg}`);
|
|
7165
7249
|
}
|
|
7166
7250
|
}
|
|
@@ -7170,6 +7254,14 @@ HIERARCHY CONTEXT: ${this.hierarchyContext}` : "") + textToolSuffix,
|
|
|
7170
7254
|
* 1. Find a semantically similar registered tool and retry with same input
|
|
7171
7255
|
* 2. Synthesize a new tool via ToolCreator (if available) and run it
|
|
7172
7256
|
* 3. Return the original error so the agent loop can decide what to do next
|
|
7257
|
+
*
|
|
7258
|
+
* Scoped to CHEAP, task-scoped failures — the class of problem it was built
|
|
7259
|
+
* for is a wrong or missing tool name, where a name-similar sibling plausibly
|
|
7260
|
+
* does the same job for the same (near-zero) cost. PROVIDER_BACKED_TOOLS never
|
|
7261
|
+
* reach it: their siblings are keyword-similar but semantically different
|
|
7262
|
+
* (generate_image is not a stand-in for generate_video), and every attempt —
|
|
7263
|
+
* theirs, or a synthesized replacement's — is separately billed. See the
|
|
7264
|
+
* fast-fail branch in executeTool().
|
|
7173
7265
|
*/
|
|
7174
7266
|
async adaptiveFallback(tc, originalError) {
|
|
7175
7267
|
const altTool = this.findAlternativeTool(tc.name);
|
|
@@ -7294,7 +7386,7 @@ ${assignment.expectedOutput}`;
|
|
|
7294
7386
|
const declared = (assignment.files ?? []).map((f) => f.trim()).filter((f) => f.includes("."));
|
|
7295
7387
|
const haystack = `${assignment.description}
|
|
7296
7388
|
${assignment.expectedOutput}`;
|
|
7297
|
-
const matches = haystack.match(
|
|
7389
|
+
const matches = haystack.match(new RegExp(ARTIFACT_FILE_RE.source, "gi")) ?? [];
|
|
7298
7390
|
return [.../* @__PURE__ */ new Set([...declared, ...matches.map((m) => m.trim())])];
|
|
7299
7391
|
}
|
|
7300
7392
|
async verifyArtifacts(assignment) {
|
|
@@ -7305,9 +7397,9 @@ ${assignment.expectedOutput}`;
|
|
|
7305
7397
|
const { promisify: promisify4 } = await import('util');
|
|
7306
7398
|
const execAsync2 = promisify4(exec2);
|
|
7307
7399
|
for (const artifactPath of artifactPaths) {
|
|
7308
|
-
const absolutePath =
|
|
7400
|
+
const absolutePath = path25.resolve(process.cwd(), artifactPath);
|
|
7309
7401
|
try {
|
|
7310
|
-
const stat = await
|
|
7402
|
+
const stat = await fs5.stat(absolutePath);
|
|
7311
7403
|
if (!stat.isFile()) {
|
|
7312
7404
|
issues.push(`Expected artifact is not a file: ${artifactPath}`);
|
|
7313
7405
|
continue;
|
|
@@ -7316,8 +7408,22 @@ ${assignment.expectedOutput}`;
|
|
|
7316
7408
|
issues.push(`Artifact is empty: ${artifactPath}`);
|
|
7317
7409
|
continue;
|
|
7318
7410
|
}
|
|
7319
|
-
if (
|
|
7320
|
-
const
|
|
7411
|
+
if (/\.(docx|pptx|xlsx)$/i.test(artifactPath)) {
|
|
7412
|
+
const head = Buffer.alloc(4);
|
|
7413
|
+
const fh = await fs5.open(absolutePath, "r");
|
|
7414
|
+
try {
|
|
7415
|
+
await fh.read(head, 0, 4, 0);
|
|
7416
|
+
} finally {
|
|
7417
|
+
await fh.close();
|
|
7418
|
+
}
|
|
7419
|
+
if (head.toString("latin1") !== "PK") {
|
|
7420
|
+
issues.push(
|
|
7421
|
+
`${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.`
|
|
7422
|
+
);
|
|
7423
|
+
continue;
|
|
7424
|
+
}
|
|
7425
|
+
} else if (!/\.pdf$/i.test(artifactPath)) {
|
|
7426
|
+
const content = await fs5.readFile(absolutePath, "utf-8");
|
|
7321
7427
|
if (!content.trim()) {
|
|
7322
7428
|
issues.push(`Artifact content is empty: ${artifactPath}`);
|
|
7323
7429
|
continue;
|
|
@@ -7326,7 +7432,7 @@ ${assignment.expectedOutput}`;
|
|
|
7326
7432
|
issues.push(`PDF artifact looks too small to be valid: ${artifactPath}`);
|
|
7327
7433
|
continue;
|
|
7328
7434
|
}
|
|
7329
|
-
const ext =
|
|
7435
|
+
const ext = path25.extname(absolutePath).toLowerCase();
|
|
7330
7436
|
try {
|
|
7331
7437
|
if (ext === ".ts" || ext === ".tsx") {
|
|
7332
7438
|
await execAsync2(`npx tsc --noEmit ${absolutePath}`, { timeout: 1e4 });
|
|
@@ -7922,15 +8028,244 @@ _(incomplete: ${t2Result.issues.join("; ")})_`;
|
|
|
7922
8028
|
return output;
|
|
7923
8029
|
}
|
|
7924
8030
|
|
|
8031
|
+
// src/core/multimodal/registry.ts
|
|
8032
|
+
var CAPABILITIES = [
|
|
8033
|
+
// ── Image ──
|
|
8034
|
+
{
|
|
8035
|
+
modality: "image",
|
|
8036
|
+
provider: "openai",
|
|
8037
|
+
modelId: "dall-e-3",
|
|
8038
|
+
api: "openai-images"
|
|
8039
|
+
},
|
|
8040
|
+
{
|
|
8041
|
+
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
8042
|
+
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
8043
|
+
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
8044
|
+
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
8045
|
+
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
8046
|
+
// 'gemini-generate-content' rather than just the modelId moving.
|
|
8047
|
+
// https://ai.google.dev/gemini-api/docs/imagen
|
|
8048
|
+
modality: "image",
|
|
8049
|
+
provider: "gemini",
|
|
8050
|
+
modelId: "gemini-2.5-flash-image",
|
|
8051
|
+
api: "gemini-generate-content"
|
|
8052
|
+
},
|
|
8053
|
+
// ── Speech (text → audio) ──
|
|
8054
|
+
{
|
|
8055
|
+
modality: "speech",
|
|
8056
|
+
provider: "openai",
|
|
8057
|
+
modelId: "tts-1",
|
|
8058
|
+
api: "openai-speech"
|
|
8059
|
+
},
|
|
8060
|
+
{
|
|
8061
|
+
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
8062
|
+
// ships these as the same voice at two fidelities and charges double for
|
|
8063
|
+
// this one. That is the vendor's own ordering, not an inference.
|
|
8064
|
+
modality: "speech",
|
|
8065
|
+
provider: "openai",
|
|
8066
|
+
modelId: "tts-1-hd",
|
|
8067
|
+
api: "openai-speech",
|
|
8068
|
+
quality: 70,
|
|
8069
|
+
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
8070
|
+
},
|
|
8071
|
+
// ── Transcription (audio → text) ──
|
|
8072
|
+
{
|
|
8073
|
+
modality: "transcription",
|
|
8074
|
+
provider: "openai",
|
|
8075
|
+
modelId: "whisper-1",
|
|
8076
|
+
api: "openai-transcriptions"
|
|
8077
|
+
},
|
|
8078
|
+
// ── Video ──
|
|
8079
|
+
{
|
|
8080
|
+
// Long-running: submit returns an operation name, the video lands minutes
|
|
8081
|
+
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
8082
|
+
// that polling loop; the registry only records the shape via `api`.
|
|
8083
|
+
//
|
|
8084
|
+
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
8085
|
+
// not the Gemini Developer API's — this file calls
|
|
8086
|
+
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
8087
|
+
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
8088
|
+
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
8089
|
+
// there is `veo-3.1-generate-preview`.
|
|
8090
|
+
// https://ai.google.dev/gemini-api/docs/veo
|
|
8091
|
+
modality: "video",
|
|
8092
|
+
provider: "gemini",
|
|
8093
|
+
modelId: "veo-3.1-generate-preview",
|
|
8094
|
+
api: "gemini-predict-lro"
|
|
8095
|
+
}
|
|
8096
|
+
];
|
|
8097
|
+
function capabilityCost(cap) {
|
|
8098
|
+
const match = findPricing(cap.modelId, cap.provider, {
|
|
8099
|
+
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
8100
|
+
});
|
|
8101
|
+
const rate = match?.entry.rates?.[0];
|
|
8102
|
+
if (!rate) return null;
|
|
8103
|
+
const unitLabel = {
|
|
8104
|
+
per_image: "per image",
|
|
8105
|
+
per_second: "per second of video",
|
|
8106
|
+
per_character: "per character",
|
|
8107
|
+
per_audio_minute: "per audio minute"
|
|
8108
|
+
};
|
|
8109
|
+
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
8110
|
+
const amount = rate.amount;
|
|
8111
|
+
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
8112
|
+
return { label: `$${shown} ${unit}`, amount, unit };
|
|
8113
|
+
}
|
|
8114
|
+
function explainChoice(c, cost, modality, isBest) {
|
|
8115
|
+
const priced = cost ? ` ${cost.label}.` : "";
|
|
8116
|
+
if (c.quality !== void 0) {
|
|
8117
|
+
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}`;
|
|
8118
|
+
}
|
|
8119
|
+
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
8120
|
+
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;
|
|
8121
|
+
}
|
|
8122
|
+
var MultimodalRegistry = class {
|
|
8123
|
+
/** Provider types the user has actually configured a key for. */
|
|
8124
|
+
available;
|
|
8125
|
+
constructor(availableProviders) {
|
|
8126
|
+
this.available = new Set(availableProviders);
|
|
8127
|
+
}
|
|
8128
|
+
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
8129
|
+
usable(modality) {
|
|
8130
|
+
return CAPABILITIES.filter(
|
|
8131
|
+
(c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
|
|
8132
|
+
);
|
|
8133
|
+
}
|
|
8134
|
+
/** Modalities this account can actually generate right now. */
|
|
8135
|
+
availableModalities() {
|
|
8136
|
+
return [...new Set(this.usable().map((c) => c.modality))];
|
|
8137
|
+
}
|
|
8138
|
+
/**
|
|
8139
|
+
* Every usable capability for a modality, best first.
|
|
8140
|
+
*
|
|
8141
|
+
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
8142
|
+
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
8143
|
+
* the user naming a model is a stronger signal than any prior.
|
|
8144
|
+
*
|
|
8145
|
+
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
8146
|
+
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
8147
|
+
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
8148
|
+
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
8149
|
+
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
8150
|
+
*/
|
|
8151
|
+
rank(modality, opts = {}) {
|
|
8152
|
+
const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
8153
|
+
const qa = a.c.quality ?? -1;
|
|
8154
|
+
const qb = b.c.quality ?? -1;
|
|
8155
|
+
if (qa !== qb) return qb - qa;
|
|
8156
|
+
const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
8157
|
+
const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
8158
|
+
return pa - pb;
|
|
8159
|
+
});
|
|
8160
|
+
const results = scored.map(({ c, cost }, i) => ({
|
|
8161
|
+
capability: c,
|
|
8162
|
+
cost,
|
|
8163
|
+
reason: explainChoice(c, cost, modality, i === 0)
|
|
8164
|
+
}));
|
|
8165
|
+
if (opts.preferModel) {
|
|
8166
|
+
const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
|
|
8167
|
+
if (i > -1) {
|
|
8168
|
+
const [wanted] = results.splice(i, 1);
|
|
8169
|
+
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
8170
|
+
}
|
|
8171
|
+
}
|
|
8172
|
+
return results;
|
|
8173
|
+
}
|
|
8174
|
+
/**
|
|
8175
|
+
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
8176
|
+
*/
|
|
8177
|
+
select(modality, opts = {}) {
|
|
8178
|
+
return this.rank(modality, opts)[0] ?? null;
|
|
8179
|
+
}
|
|
8180
|
+
/**
|
|
8181
|
+
* A plain-language account of what this account can and cannot generate —
|
|
8182
|
+
* including the things it cannot, and why.
|
|
8183
|
+
*
|
|
8184
|
+
* This is the USER-facing inventory (model ids and unit prices), keyed on
|
|
8185
|
+
* which providers are configured. It is deliberately NOT what the planner
|
|
8186
|
+
* reads: see `describeGenerationForPlanner()` below for that, and the note
|
|
8187
|
+
* there for why the two cannot be the same string.
|
|
8188
|
+
*/
|
|
8189
|
+
describe() {
|
|
8190
|
+
const lines = [];
|
|
8191
|
+
const usable = this.usable();
|
|
8192
|
+
if (usable.length) {
|
|
8193
|
+
lines.push("Generation models available on your configured providers:");
|
|
8194
|
+
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
8195
|
+
const forModality = usable.filter((c) => c.modality === modality);
|
|
8196
|
+
if (!forModality.length) continue;
|
|
8197
|
+
const shown = forModality.map((c) => {
|
|
8198
|
+
const cost = capabilityCost(c);
|
|
8199
|
+
return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
8200
|
+
});
|
|
8201
|
+
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
8202
|
+
}
|
|
8203
|
+
} else {
|
|
8204
|
+
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
8205
|
+
}
|
|
8206
|
+
const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
|
|
8207
|
+
for (const c of blocked) {
|
|
8208
|
+
lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
|
|
8209
|
+
}
|
|
8210
|
+
lines.push(
|
|
8211
|
+
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
8212
|
+
);
|
|
8213
|
+
return lines.join("\n");
|
|
8214
|
+
}
|
|
8215
|
+
};
|
|
8216
|
+
var PLANNER_FACTS = [
|
|
8217
|
+
{ tool: "generate_image", modality: "image", shape: "one call returns one finished image" },
|
|
8218
|
+
{ tool: "generate_speech", modality: "speech", shape: "one call returns one finished audio file" },
|
|
8219
|
+
{
|
|
8220
|
+
tool: "generate_video",
|
|
8221
|
+
modality: "video",
|
|
8222
|
+
shape: "one call returns one finished clip, after rendering for 1-3 minutes (Cascade stops waiting at 8)"
|
|
8223
|
+
},
|
|
8224
|
+
{
|
|
8225
|
+
tool: "transcribe_audio",
|
|
8226
|
+
modality: "transcription",
|
|
8227
|
+
shape: "one call returns the transcript of one audio file"
|
|
8228
|
+
}
|
|
8229
|
+
];
|
|
8230
|
+
function plannerPriceNote(modality) {
|
|
8231
|
+
const costs = CAPABILITIES.filter((c) => c.modality === modality && !c.unsupported).map((c) => capabilityCost(c)).filter((c) => c !== null);
|
|
8232
|
+
if (!costs.length) return "";
|
|
8233
|
+
const labels = new Set(costs.map((c) => c.label));
|
|
8234
|
+
if (labels.size === 1) return ` Billed ${costs[0].label}.`;
|
|
8235
|
+
const units = new Set(costs.map((c) => c.unit));
|
|
8236
|
+
return units.size === 1 ? ` Billed ${[...units][0]}.` : "";
|
|
8237
|
+
}
|
|
8238
|
+
function describeGenerationForPlanner(has) {
|
|
8239
|
+
const present = PLANNER_FACTS.filter((f) => has(f.tool));
|
|
8240
|
+
if (!present.length) return "";
|
|
8241
|
+
const lines = ["MEDIA GENERATION \u2014 the only generation tools this run can call:"];
|
|
8242
|
+
for (const f of present) {
|
|
8243
|
+
lines.push(`- "${f.tool}" (${f.modality}): ${f.shape}.${plannerPriceNote(f.modality)}`);
|
|
8244
|
+
}
|
|
8245
|
+
lines.push(
|
|
8246
|
+
"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.",
|
|
8247
|
+
"No other modality has a tool here \u2014 never plan a step that generates music, 3D or anything not listed above."
|
|
8248
|
+
);
|
|
8249
|
+
if (has("generate_video")) {
|
|
8250
|
+
lines.push(
|
|
8251
|
+
'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.'
|
|
8252
|
+
);
|
|
8253
|
+
}
|
|
8254
|
+
return lines.join("\n");
|
|
8255
|
+
}
|
|
8256
|
+
|
|
7925
8257
|
// src/core/tiers/t2-manager.ts
|
|
7926
|
-
function buildT2SystemPrompt(
|
|
8258
|
+
function buildT2SystemPrompt(has) {
|
|
8259
|
+
const generation = describeGenerationForPlanner(has);
|
|
7927
8260
|
return [
|
|
7928
8261
|
"You are a T2 Manager agent in the Cascade AI system.",
|
|
7929
8262
|
"Your role is to analyze a section of a task and decompose it into 2-5 discrete subtasks for T3 Workers.",
|
|
7930
8263
|
'If subtasks have dependencies, you can specify "executionMode": "sequential" for the section.',
|
|
7931
|
-
|
|
7932
|
-
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text."
|
|
7933
|
-
|
|
8264
|
+
has("peer_message") && 'Provide "peerT3Ids" to subtasks so they can coordinate using the peer_message tool.',
|
|
8265
|
+
"Return ONLY valid JSON matching the T3 subtask array schema \u2014 no other text.",
|
|
8266
|
+
generation && `
|
|
8267
|
+
${generation}`
|
|
8268
|
+
].filter((l) => l !== false && l !== "").join("\n");
|
|
7934
8269
|
}
|
|
7935
8270
|
var T2Manager = class extends BaseTier {
|
|
7936
8271
|
router;
|
|
@@ -8227,7 +8562,7 @@ Return ONLY the JSON array.`;
|
|
|
8227
8562
|
const messages = [{ role: "user", content: prompt }];
|
|
8228
8563
|
const result = await this.router.generate("T2", {
|
|
8229
8564
|
messages,
|
|
8230
|
-
systemPrompt: this.systemPromptOverride + buildT2SystemPrompt(this.toolRegistry.hasTool(
|
|
8565
|
+
systemPrompt: this.systemPromptOverride + buildT2SystemPrompt((name) => this.toolRegistry.hasTool(name)) + (this.hierarchyContext ? `
|
|
8231
8566
|
|
|
8232
8567
|
HIERARCHY CONTEXT: ${this.hierarchyContext}` : ""),
|
|
8233
8568
|
maxTokens: 2e3,
|
|
@@ -8777,6 +9112,7 @@ function isStrongKeywordOverlap(a = [], b = []) {
|
|
|
8777
9112
|
}
|
|
8778
9113
|
function buildT1SystemPrompt(has) {
|
|
8779
9114
|
const canWriteArtifacts = has("file_write") || has("file_edit") || has("run_code") || has("pdf_create");
|
|
9115
|
+
const generation = describeGenerationForPlanner(has);
|
|
8780
9116
|
const rules = [
|
|
8781
9117
|
"- Simple \u2192 1 T3, Moderate \u2192 2-3 T2s, Complex \u2192 3-5 T2s, Highly Complex \u2192 5+ T2s",
|
|
8782
9118
|
"- Return ONLY valid JSON \u2014 no other text",
|
|
@@ -8811,7 +9147,9 @@ DEPENDENCY GUIDANCE:
|
|
|
8811
9147
|
QUALITY RULES:
|
|
8812
9148
|
- Each section must have a clear, testable "expectedOutput" so T2 knows when it is done.
|
|
8813
9149
|
- Do NOT create trivial sections that only move files or print summaries \u2014 fold those into adjacent sections.
|
|
8814
|
-
- If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2)
|
|
9150
|
+
- If the plan would naturally produce fewer than 2 independent sections, prefer Moderate routing (single T2).${generation ? `
|
|
9151
|
+
|
|
9152
|
+
${generation}` : ""}`;
|
|
8815
9153
|
}
|
|
8816
9154
|
var T1Administrator = class extends BaseTier {
|
|
8817
9155
|
router;
|
|
@@ -9472,219 +9810,37 @@ Instructions:
|
|
|
9472
9810
|
return result.content;
|
|
9473
9811
|
}
|
|
9474
9812
|
/**
|
|
9475
|
-
* T1-level permission evaluator.
|
|
9476
|
-
* Uses T1's model with full task context.
|
|
9477
|
-
* Returns null only when the model explicitly says UNSURE (triggers user prompt).
|
|
9478
|
-
*/
|
|
9479
|
-
async evaluatePermissionAtT1(req) {
|
|
9480
|
-
const prompt = `You are T1 Administrator. Overall task goal:
|
|
9481
|
-
${this.taskGoal}
|
|
9482
|
-
|
|
9483
|
-
A T3 Worker (inside section "${req.sectionContext}") wants to:
|
|
9484
|
-
Tool: ${req.toolName}
|
|
9485
|
-
Target: ${JSON.stringify(req.input)}
|
|
9486
|
-
Reason: ${req.subtaskContext}
|
|
9487
|
-
|
|
9488
|
-
T2 Manager was uncertain about this. Given the overall task goal, should this be allowed?
|
|
9489
|
-
Reply with exactly one word: YES, NO, or UNSURE.
|
|
9490
|
-
(UNSURE = escalate to the human user for a final decision.)`;
|
|
9491
|
-
try {
|
|
9492
|
-
const result = await this.router.generate("T1", {
|
|
9493
|
-
messages: [{ role: "user", content: prompt }],
|
|
9494
|
-
systemPrompt: this.systemPromptOverride + "You are a T1 Administrator evaluating permissions.",
|
|
9495
|
-
maxTokens: 10,
|
|
9496
|
-
temperature: 0
|
|
9497
|
-
});
|
|
9498
|
-
const answer = result.content.trim().toUpperCase();
|
|
9499
|
-
const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
|
|
9500
|
-
(req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
|
|
9501
|
-
return null;
|
|
9502
|
-
} catch {
|
|
9503
|
-
(req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
|
|
9504
|
-
return null;
|
|
9505
|
-
}
|
|
9506
|
-
}
|
|
9507
|
-
};
|
|
9508
|
-
|
|
9509
|
-
// src/core/multimodal/registry.ts
|
|
9510
|
-
var CAPABILITIES = [
|
|
9511
|
-
// ── Image ──
|
|
9512
|
-
{
|
|
9513
|
-
modality: "image",
|
|
9514
|
-
provider: "openai",
|
|
9515
|
-
modelId: "dall-e-3",
|
|
9516
|
-
api: "openai-images"
|
|
9517
|
-
},
|
|
9518
|
-
{
|
|
9519
|
-
// Was `imagen-4.0-generate-001` on `:predict`, which now 404s for new keys
|
|
9520
|
-
// ("no longer available to new users") — Imagen's :predict API is deprecated
|
|
9521
|
-
// and shuts down 2026-08-17. Google's own migration path is this model on
|
|
9522
|
-
// the ordinary generateContent endpoint, so the fix was never another Imagen
|
|
9523
|
-
// id: the whole request/response SHAPE changes, which is why `api` moved to
|
|
9524
|
-
// 'gemini-generate-content' rather than just the modelId moving.
|
|
9525
|
-
// https://ai.google.dev/gemini-api/docs/imagen
|
|
9526
|
-
modality: "image",
|
|
9527
|
-
provider: "gemini",
|
|
9528
|
-
modelId: "gemini-2.5-flash-image",
|
|
9529
|
-
api: "gemini-generate-content"
|
|
9530
|
-
},
|
|
9531
|
-
// ── Speech (text → audio) ──
|
|
9532
|
-
{
|
|
9533
|
-
modality: "speech",
|
|
9534
|
-
provider: "openai",
|
|
9535
|
-
modelId: "tts-1",
|
|
9536
|
-
api: "openai-speech"
|
|
9537
|
-
},
|
|
9538
|
-
{
|
|
9539
|
-
// The only quality claim in this table that needs no leaderboard: OpenAI
|
|
9540
|
-
// ships these as the same voice at two fidelities and charges double for
|
|
9541
|
-
// this one. That is the vendor's own ordering, not an inference.
|
|
9542
|
-
modality: "speech",
|
|
9543
|
-
provider: "openai",
|
|
9544
|
-
modelId: "tts-1-hd",
|
|
9545
|
-
api: "openai-speech",
|
|
9546
|
-
quality: 70,
|
|
9547
|
-
qualityBasis: "OpenAI's own hd variant of tts-1, priced 2x"
|
|
9548
|
-
},
|
|
9549
|
-
// ── Transcription (audio → text) ──
|
|
9550
|
-
{
|
|
9551
|
-
modality: "transcription",
|
|
9552
|
-
provider: "openai",
|
|
9553
|
-
modelId: "whisper-1",
|
|
9554
|
-
api: "openai-transcriptions"
|
|
9555
|
-
},
|
|
9556
|
-
// ── Video ──
|
|
9557
|
-
{
|
|
9558
|
-
// Long-running: submit returns an operation name, the video lands minutes
|
|
9559
|
-
// later, and the bytes come from a short-lived signed URL. generate.ts owns
|
|
9560
|
-
// that polling loop; the registry only records the shape via `api`.
|
|
9561
|
-
//
|
|
9562
|
-
// `veo-3.1-generate-001` (no "-preview") is VERTEX AI's id for this model,
|
|
9563
|
-
// not the Gemini Developer API's — this file calls
|
|
9564
|
-
// generativelanguage.googleapis.com (see generate.ts), which 404s on it:
|
|
9565
|
-
// "models/veo-3.1-generate-001 is not found for API version v1beta". On the
|
|
9566
|
-
// Gemini API specifically, Veo 3.1 is still preview-only, so the correct id
|
|
9567
|
-
// there is `veo-3.1-generate-preview`.
|
|
9568
|
-
// https://ai.google.dev/gemini-api/docs/veo
|
|
9569
|
-
modality: "video",
|
|
9570
|
-
provider: "gemini",
|
|
9571
|
-
modelId: "veo-3.1-generate-preview",
|
|
9572
|
-
api: "gemini-predict-lro"
|
|
9573
|
-
}
|
|
9574
|
-
];
|
|
9575
|
-
function capabilityCost(cap) {
|
|
9576
|
-
const match = findPricing(cap.modelId, cap.provider, {
|
|
9577
|
-
fallbackProviders: cap.provider === "gemini" ? ["vertex"] : []
|
|
9578
|
-
});
|
|
9579
|
-
const rate = match?.entry.rates?.[0];
|
|
9580
|
-
if (!rate) return null;
|
|
9581
|
-
const unitLabel = {
|
|
9582
|
-
per_image: "per image",
|
|
9583
|
-
per_second: "per second of video",
|
|
9584
|
-
per_character: "per character",
|
|
9585
|
-
per_audio_minute: "per audio minute"
|
|
9586
|
-
};
|
|
9587
|
-
const unit = unitLabel[rate.unit] ?? rate.unit;
|
|
9588
|
-
const amount = rate.amount;
|
|
9589
|
-
const shown = amount < 1e-3 ? amount.toExponential(2) : amount.toFixed(4);
|
|
9590
|
-
return { label: `$${shown} ${unit}`, amount, unit };
|
|
9591
|
-
}
|
|
9592
|
-
function explainChoice(c, cost, modality, isBest) {
|
|
9593
|
-
const priced = cost ? ` ${cost.label}.` : "";
|
|
9594
|
-
if (c.quality !== void 0) {
|
|
9595
|
-
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}`;
|
|
9596
|
-
}
|
|
9597
|
-
const noRanking = ". No published quality ranking is applied for this modality, so this is a cost choice, not a quality one.";
|
|
9598
|
-
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;
|
|
9599
|
-
}
|
|
9600
|
-
var MultimodalRegistry = class {
|
|
9601
|
-
/** Provider types the user has actually configured a key for. */
|
|
9602
|
-
available;
|
|
9603
|
-
constructor(availableProviders) {
|
|
9604
|
-
this.available = new Set(availableProviders);
|
|
9605
|
-
}
|
|
9606
|
-
/** Every capability whose provider is configured AND which Cascade can call. */
|
|
9607
|
-
usable(modality) {
|
|
9608
|
-
return CAPABILITIES.filter(
|
|
9609
|
-
(c) => !c.unsupported && this.available.has(c.provider) && (!modality || c.modality === modality)
|
|
9610
|
-
);
|
|
9611
|
-
}
|
|
9612
|
-
/** Modalities this account can actually generate right now. */
|
|
9613
|
-
availableModalities() {
|
|
9614
|
-
return [...new Set(this.usable().map((c) => c.modality))];
|
|
9615
|
-
}
|
|
9616
|
-
/**
|
|
9617
|
-
* Every usable capability for a modality, best first.
|
|
9618
|
-
*
|
|
9619
|
-
* Ranked models beat unranked ones, then higher quality, then lower unit
|
|
9620
|
-
* price. An explicit `preferModel` is moved to the head when it is usable —
|
|
9621
|
-
* the user naming a model is a stronger signal than any prior.
|
|
9622
|
-
*
|
|
9623
|
-
* `select()` is just the head of this list. The TAIL is the point: a systemic
|
|
9624
|
-
* provider failure (dead key, 404, exhausted quota) is scoped to one
|
|
9625
|
-
* provider+model, so "this model cannot draw" is not the same fact as "this
|
|
9626
|
-
* account cannot draw". A caller holding the ordered alternatives can try the
|
|
9627
|
-
* next provider instead of aborting the whole run — see generate-media.ts.
|
|
9628
|
-
*/
|
|
9629
|
-
rank(modality, opts = {}) {
|
|
9630
|
-
const scored = this.usable(modality).map((c) => ({ c, cost: capabilityCost(c) })).sort((a, b) => {
|
|
9631
|
-
const qa = a.c.quality ?? -1;
|
|
9632
|
-
const qb = b.c.quality ?? -1;
|
|
9633
|
-
if (qa !== qb) return qb - qa;
|
|
9634
|
-
const pa = a.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
9635
|
-
const pb = b.cost?.amount ?? Number.POSITIVE_INFINITY;
|
|
9636
|
-
return pa - pb;
|
|
9637
|
-
});
|
|
9638
|
-
const results = scored.map(({ c, cost }, i) => ({
|
|
9639
|
-
capability: c,
|
|
9640
|
-
cost,
|
|
9641
|
-
reason: explainChoice(c, cost, modality, i === 0)
|
|
9642
|
-
}));
|
|
9643
|
-
if (opts.preferModel) {
|
|
9644
|
-
const i = results.findIndex((r) => r.capability.modelId === opts.preferModel);
|
|
9645
|
-
if (i > -1) {
|
|
9646
|
-
const [wanted] = results.splice(i, 1);
|
|
9647
|
-
results.unshift({ ...wanted, reason: `Requested explicitly: ${wanted.capability.modelId}.` });
|
|
9648
|
-
}
|
|
9649
|
-
}
|
|
9650
|
-
return results;
|
|
9651
|
-
}
|
|
9652
|
-
/**
|
|
9653
|
-
* Pick a model for a modality — the best usable one, or null if there is none.
|
|
9654
|
-
*/
|
|
9655
|
-
select(modality, opts = {}) {
|
|
9656
|
-
return this.rank(modality, opts)[0] ?? null;
|
|
9657
|
-
}
|
|
9658
|
-
/**
|
|
9659
|
-
* A plain-language account of what this account can and cannot generate —
|
|
9660
|
-
* including the things it cannot, and why. The planner sees this, so being
|
|
9661
|
-
* candid here is what stops it inventing a `generate_music` call.
|
|
9662
|
-
*/
|
|
9663
|
-
describe() {
|
|
9664
|
-
const lines = [];
|
|
9665
|
-
const usable = this.usable();
|
|
9666
|
-
if (usable.length) {
|
|
9667
|
-
lines.push("Generation models available on your configured providers:");
|
|
9668
|
-
for (const modality of ["image", "speech", "transcription", "video"]) {
|
|
9669
|
-
const forModality = usable.filter((c) => c.modality === modality);
|
|
9670
|
-
if (!forModality.length) continue;
|
|
9671
|
-
const shown = forModality.map((c) => {
|
|
9672
|
-
const cost = capabilityCost(c);
|
|
9673
|
-
return `${c.modelId}${cost ? ` (${cost.label})` : ""}`;
|
|
9674
|
-
});
|
|
9675
|
-
lines.push(` ${modality}: ${shown.join(", ")}`);
|
|
9676
|
-
}
|
|
9677
|
-
} else {
|
|
9678
|
-
lines.push("No generation models are available \u2014 none of your configured providers offers one.");
|
|
9679
|
-
}
|
|
9680
|
-
const blocked = CAPABILITIES.filter((c) => c.unsupported && this.available.has(c.provider));
|
|
9681
|
-
for (const c of blocked) {
|
|
9682
|
-
lines.push(` ${c.modality}: ${c.modelId} exists on your account but is not callable. ${c.unsupported}`);
|
|
9813
|
+
* T1-level permission evaluator.
|
|
9814
|
+
* Uses T1's model with full task context.
|
|
9815
|
+
* Returns null only when the model explicitly says UNSURE (triggers user prompt).
|
|
9816
|
+
*/
|
|
9817
|
+
async evaluatePermissionAtT1(req) {
|
|
9818
|
+
const prompt = `You are T1 Administrator. Overall task goal:
|
|
9819
|
+
${this.taskGoal}
|
|
9820
|
+
|
|
9821
|
+
A T3 Worker (inside section "${req.sectionContext}") wants to:
|
|
9822
|
+
Tool: ${req.toolName}
|
|
9823
|
+
Target: ${JSON.stringify(req.input)}
|
|
9824
|
+
Reason: ${req.subtaskContext}
|
|
9825
|
+
|
|
9826
|
+
T2 Manager was uncertain about this. Given the overall task goal, should this be allowed?
|
|
9827
|
+
Reply with exactly one word: YES, NO, or UNSURE.
|
|
9828
|
+
(UNSURE = escalate to the human user for a final decision.)`;
|
|
9829
|
+
try {
|
|
9830
|
+
const result = await this.router.generate("T1", {
|
|
9831
|
+
messages: [{ role: "user", content: prompt }],
|
|
9832
|
+
systemPrompt: this.systemPromptOverride + "You are a T1 Administrator evaluating permissions.",
|
|
9833
|
+
maxTokens: 10,
|
|
9834
|
+
temperature: 0
|
|
9835
|
+
});
|
|
9836
|
+
const answer = result.content.trim().toUpperCase();
|
|
9837
|
+
const verdict = answer.includes("YES") ? "approve" : answer.includes("NO") ? "deny" : "unsure";
|
|
9838
|
+
(req.trail ??= []).push({ tier: "T1", verdict, reason: `T1: ${verdict === "approve" ? "consistent with overall task goal" : verdict === "deny" ? "not consistent with overall task goal" : "unsure"}` });
|
|
9839
|
+
return null;
|
|
9840
|
+
} catch {
|
|
9841
|
+
(req.trail ??= []).push({ tier: "T1", verdict: "unsure", reason: "T1 evaluation failed" });
|
|
9842
|
+
return null;
|
|
9683
9843
|
}
|
|
9684
|
-
lines.push(
|
|
9685
|
-
" music: no supported provider exposes a music-generation API, so Cascade cannot generate music."
|
|
9686
|
-
);
|
|
9687
|
-
return lines.join("\n");
|
|
9688
9844
|
}
|
|
9689
9845
|
};
|
|
9690
9846
|
|
|
@@ -9708,10 +9864,17 @@ var BaseTool = class {
|
|
|
9708
9864
|
|
|
9709
9865
|
// src/core/multimodal/generate.ts
|
|
9710
9866
|
var DEFAULT_TIMEOUT_MS = 12e4;
|
|
9867
|
+
var GenerationGaveUpError = class extends Error {
|
|
9868
|
+
constructor(message) {
|
|
9869
|
+
super(message);
|
|
9870
|
+
this.name = "GenerationGaveUpError";
|
|
9871
|
+
}
|
|
9872
|
+
};
|
|
9711
9873
|
async function callProvider(fn, modelId) {
|
|
9712
9874
|
try {
|
|
9713
9875
|
return await fn();
|
|
9714
9876
|
} catch (err) {
|
|
9877
|
+
if (err instanceof GenerationGaveUpError) throw err;
|
|
9715
9878
|
const classified = classifyProviderError(err);
|
|
9716
9879
|
throw new Error(describeProviderError(classified, modelId));
|
|
9717
9880
|
}
|
|
@@ -9909,8 +10072,8 @@ async function generateVideo(cap, cfg, req, signal, onProgress) {
|
|
|
9909
10072
|
onProgress?.("Still rendering\u2026");
|
|
9910
10073
|
}
|
|
9911
10074
|
if (!operation.done) {
|
|
9912
|
-
throw new
|
|
9913
|
-
|
|
10075
|
+
throw new GenerationGaveUpError(
|
|
10076
|
+
`${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.`
|
|
9914
10077
|
);
|
|
9915
10078
|
}
|
|
9916
10079
|
if (operation.error?.message) throw new Error(operation.error.message);
|
|
@@ -10216,6 +10379,705 @@ function buildMediaTools(deps, readFile) {
|
|
|
10216
10379
|
if (modalities.has("transcription") && readFile) tools.push(new TranscribeAudioTool(deps, readFile));
|
|
10217
10380
|
return tools;
|
|
10218
10381
|
}
|
|
10382
|
+
var WorkspaceSandboxError = class extends Error {
|
|
10383
|
+
constructor(attempted, workspaceRoot) {
|
|
10384
|
+
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10385
|
+
this.name = "WorkspaceSandboxError";
|
|
10386
|
+
}
|
|
10387
|
+
};
|
|
10388
|
+
function resolveInWorkspace(workspaceRoot, input) {
|
|
10389
|
+
if (typeof input !== "string" || input.length === 0) {
|
|
10390
|
+
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10391
|
+
}
|
|
10392
|
+
const root = path25.resolve(workspaceRoot);
|
|
10393
|
+
const abs = path25.isAbsolute(input) ? path25.resolve(input) : path25.resolve(root, input);
|
|
10394
|
+
const rel = path25.relative(root, abs);
|
|
10395
|
+
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path25.isAbsolute(rel)) {
|
|
10396
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10397
|
+
}
|
|
10398
|
+
try {
|
|
10399
|
+
const real = fs24.realpathSync(abs);
|
|
10400
|
+
const realRel = path25.relative(root, real);
|
|
10401
|
+
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path25.isAbsolute(realRel))) {
|
|
10402
|
+
throw new WorkspaceSandboxError(input, root);
|
|
10403
|
+
}
|
|
10404
|
+
} catch (e) {
|
|
10405
|
+
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10406
|
+
}
|
|
10407
|
+
return abs;
|
|
10408
|
+
}
|
|
10409
|
+
|
|
10410
|
+
// src/core/documents/blocks.ts
|
|
10411
|
+
function fileExt(name) {
|
|
10412
|
+
const base = name.split(/[\\/]/).pop() ?? name;
|
|
10413
|
+
const dot = base.lastIndexOf(".");
|
|
10414
|
+
return dot > 0 ? base.slice(dot + 1).toLowerCase() : "";
|
|
10415
|
+
}
|
|
10416
|
+
function parseDelimited(text, name = "") {
|
|
10417
|
+
const delim = fileExt(name) === "tsv" || text.indexOf(" ") !== -1 && text.indexOf(",") === -1 ? " " : ",";
|
|
10418
|
+
const rows = [];
|
|
10419
|
+
let row = [];
|
|
10420
|
+
let cell = "";
|
|
10421
|
+
let quoted = false;
|
|
10422
|
+
for (let i = 0; i < text.length; i++) {
|
|
10423
|
+
const ch = text[i];
|
|
10424
|
+
if (quoted) {
|
|
10425
|
+
if (ch === '"') {
|
|
10426
|
+
if (text[i + 1] === '"') {
|
|
10427
|
+
cell += '"';
|
|
10428
|
+
i++;
|
|
10429
|
+
} else quoted = false;
|
|
10430
|
+
} else cell += ch;
|
|
10431
|
+
} else if (ch === '"') {
|
|
10432
|
+
quoted = true;
|
|
10433
|
+
} else if (ch === delim) {
|
|
10434
|
+
row.push(cell);
|
|
10435
|
+
cell = "";
|
|
10436
|
+
} else if (ch === "\n") {
|
|
10437
|
+
row.push(cell);
|
|
10438
|
+
rows.push(row);
|
|
10439
|
+
row = [];
|
|
10440
|
+
cell = "";
|
|
10441
|
+
} else if (ch === "\r") ; else {
|
|
10442
|
+
cell += ch;
|
|
10443
|
+
}
|
|
10444
|
+
if (rows.length > 5e3) break;
|
|
10445
|
+
}
|
|
10446
|
+
if (cell !== "" || row.length) {
|
|
10447
|
+
row.push(cell);
|
|
10448
|
+
rows.push(row);
|
|
10449
|
+
}
|
|
10450
|
+
return rows.filter((r) => r.length > 1 || (r[0] ?? "") !== "");
|
|
10451
|
+
}
|
|
10452
|
+
var CHART_ALIASES = {
|
|
10453
|
+
bar: "bar",
|
|
10454
|
+
column: "bar",
|
|
10455
|
+
bars: "bar",
|
|
10456
|
+
barchart: "bar",
|
|
10457
|
+
horizontalbar: "bar",
|
|
10458
|
+
line: "line",
|
|
10459
|
+
lines: "line",
|
|
10460
|
+
linechart: "line",
|
|
10461
|
+
pie: "pie",
|
|
10462
|
+
piechart: "pie",
|
|
10463
|
+
doughnut: "doughnut",
|
|
10464
|
+
donut: "doughnut",
|
|
10465
|
+
area: "area",
|
|
10466
|
+
areachart: "area",
|
|
10467
|
+
scatter: "scatter",
|
|
10468
|
+
xy: "scatter"
|
|
10469
|
+
};
|
|
10470
|
+
function chartKind(raw) {
|
|
10471
|
+
return CHART_ALIASES[raw.trim().toLowerCase().replace(/[\s_-]/g, "")] ?? null;
|
|
10472
|
+
}
|
|
10473
|
+
function toNumber(cell) {
|
|
10474
|
+
const cleaned = cell.replace(/[$£€¥,\s%]/g, "").replace(/^\((.*)\)$/, "-$1");
|
|
10475
|
+
const n = Number.parseFloat(cleaned);
|
|
10476
|
+
return Number.isFinite(n) ? n : 0;
|
|
10477
|
+
}
|
|
10478
|
+
function parseChartSpec(kind, body) {
|
|
10479
|
+
const lines = body.replace(/\r\n/g, "\n").split("\n");
|
|
10480
|
+
let title;
|
|
10481
|
+
while (lines.length && !lines[0].trim()) lines.shift();
|
|
10482
|
+
const titleMatch = lines[0]?.match(/^\s*title\s*:\s*(.+)$/i);
|
|
10483
|
+
if (titleMatch) {
|
|
10484
|
+
title = titleMatch[1].trim();
|
|
10485
|
+
lines.shift();
|
|
10486
|
+
}
|
|
10487
|
+
const rows = parseDelimited(lines.join("\n").trim());
|
|
10488
|
+
if (rows.length < 2) return null;
|
|
10489
|
+
const header = rows[0].map((c) => c.trim());
|
|
10490
|
+
if (header.length < 2) return null;
|
|
10491
|
+
const dataRows = rows.slice(1).filter((r) => r.some((c) => c.trim() !== ""));
|
|
10492
|
+
if (!dataRows.length) return null;
|
|
10493
|
+
const categories = dataRows.map((r, i) => (r[0] ?? "").trim() || `Item ${i + 1}`);
|
|
10494
|
+
const series = header.slice(1).map((name, col) => ({
|
|
10495
|
+
name: name || `Series ${col + 1}`,
|
|
10496
|
+
values: dataRows.map((r) => toNumber((r[col + 1] ?? "").trim()))
|
|
10497
|
+
}));
|
|
10498
|
+
if (!series.length) return null;
|
|
10499
|
+
return {
|
|
10500
|
+
kind,
|
|
10501
|
+
...title ? { title } : {},
|
|
10502
|
+
categoryName: header[0] || "Category",
|
|
10503
|
+
categories,
|
|
10504
|
+
series
|
|
10505
|
+
};
|
|
10506
|
+
}
|
|
10507
|
+
function chartToTableRows(spec) {
|
|
10508
|
+
return [
|
|
10509
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10510
|
+
...spec.categories.map((cat, i) => [cat, ...spec.series.map((s) => String(s.values[i] ?? 0))])
|
|
10511
|
+
];
|
|
10512
|
+
}
|
|
10513
|
+
function parseBlocks(md) {
|
|
10514
|
+
const out = [];
|
|
10515
|
+
const lines = md.replace(/\r\n/g, "\n").split("\n");
|
|
10516
|
+
let i = 0;
|
|
10517
|
+
let ordinal = 0;
|
|
10518
|
+
const reset = () => {
|
|
10519
|
+
ordinal = 0;
|
|
10520
|
+
};
|
|
10521
|
+
while (i < lines.length) {
|
|
10522
|
+
const line = lines[i] ?? "";
|
|
10523
|
+
const fence = line.match(/^```+\s*(.*)$/);
|
|
10524
|
+
if (fence) {
|
|
10525
|
+
const info = (fence[1] ?? "").trim();
|
|
10526
|
+
i++;
|
|
10527
|
+
const code = [];
|
|
10528
|
+
while (i < lines.length && !/^```/.test(lines[i] ?? "")) {
|
|
10529
|
+
code.push(lines[i] ?? "");
|
|
10530
|
+
i++;
|
|
10531
|
+
}
|
|
10532
|
+
i++;
|
|
10533
|
+
const chartInfo = info.match(/^chart\s*:\s*(.+)$/i);
|
|
10534
|
+
const kind = chartInfo ? chartKind(chartInfo[1]) : null;
|
|
10535
|
+
const spec = kind ? parseChartSpec(kind, code.join("\n")) : null;
|
|
10536
|
+
out.push(spec ? { t: "chart", spec } : { t: "code", lines: code });
|
|
10537
|
+
reset();
|
|
10538
|
+
continue;
|
|
10539
|
+
}
|
|
10540
|
+
if (!line.trim()) {
|
|
10541
|
+
i++;
|
|
10542
|
+
reset();
|
|
10543
|
+
continue;
|
|
10544
|
+
}
|
|
10545
|
+
const h = line.match(/^(#{1,6})\s+(.*)$/);
|
|
10546
|
+
if (h) {
|
|
10547
|
+
out.push({ t: "heading", level: h[1].length, text: h[2] });
|
|
10548
|
+
i++;
|
|
10549
|
+
reset();
|
|
10550
|
+
continue;
|
|
10551
|
+
}
|
|
10552
|
+
if (/^\s*([-*_])\s*(\1\s*){2,}$/.test(line)) {
|
|
10553
|
+
out.push({ t: "hr" });
|
|
10554
|
+
i++;
|
|
10555
|
+
reset();
|
|
10556
|
+
continue;
|
|
10557
|
+
}
|
|
10558
|
+
const li = line.match(/^(\s*)([-*+]|\d+[.)])\s+(.*)$/);
|
|
10559
|
+
if (li) {
|
|
10560
|
+
const ordered = /\d/.test(li[2]);
|
|
10561
|
+
ordinal = ordered ? ordinal + 1 : 0;
|
|
10562
|
+
out.push({ t: "bullet", ordered, index: ordered ? ordinal : 0, text: li[3] });
|
|
10563
|
+
i++;
|
|
10564
|
+
continue;
|
|
10565
|
+
}
|
|
10566
|
+
if (/^>\s?/.test(line)) {
|
|
10567
|
+
out.push({ t: "quote", text: line.replace(/^>\s?/, "") });
|
|
10568
|
+
i++;
|
|
10569
|
+
reset();
|
|
10570
|
+
continue;
|
|
10571
|
+
}
|
|
10572
|
+
if (/^\|.*\|/.test(line)) {
|
|
10573
|
+
const rows = [];
|
|
10574
|
+
while (i < lines.length && /^\|.*\|/.test(lines[i] ?? "")) {
|
|
10575
|
+
const r = lines[i];
|
|
10576
|
+
if (!/^\|[\s:|-]+\|?\s*$/.test(r)) rows.push(r.replace(/^\||\|\s*$/g, "").split("|").map((c) => c.trim()));
|
|
10577
|
+
i++;
|
|
10578
|
+
}
|
|
10579
|
+
out.push({ t: "table", rows });
|
|
10580
|
+
reset();
|
|
10581
|
+
continue;
|
|
10582
|
+
}
|
|
10583
|
+
out.push({ t: "para", text: line });
|
|
10584
|
+
i++;
|
|
10585
|
+
reset();
|
|
10586
|
+
}
|
|
10587
|
+
return out;
|
|
10588
|
+
}
|
|
10589
|
+
var IMAGE_LINE = /^!\[([^\]]*)\]\(([^)]+)\)$/;
|
|
10590
|
+
function matchImageLine(s) {
|
|
10591
|
+
const m = s.trim().match(IMAGE_LINE);
|
|
10592
|
+
return m ? { alt: m[1].trim(), url: m[2].trim() } : null;
|
|
10593
|
+
}
|
|
10594
|
+
function sniffImage(b) {
|
|
10595
|
+
const dv = new DataView(b.buffer, b.byteOffset, b.byteLength);
|
|
10596
|
+
if (b.length > 24 && b[0] === 137 && b[1] === 80 && b[2] === 78 && b[3] === 71) {
|
|
10597
|
+
return { type: "png", mime: "image/png", width: dv.getUint32(16), height: dv.getUint32(20) };
|
|
10598
|
+
}
|
|
10599
|
+
if (b.length > 10 && b[0] === 71 && b[1] === 73 && b[2] === 70) {
|
|
10600
|
+
return { type: "gif", mime: "image/gif", width: dv.getUint16(6, true), height: dv.getUint16(8, true) };
|
|
10601
|
+
}
|
|
10602
|
+
if (b.length > 26 && b[0] === 66 && b[1] === 77) {
|
|
10603
|
+
return { type: "bmp", mime: "image/bmp", width: Math.abs(dv.getInt32(18, true)), height: Math.abs(dv.getInt32(22, true)) };
|
|
10604
|
+
}
|
|
10605
|
+
if (b.length > 4 && b[0] === 255 && b[1] === 216) {
|
|
10606
|
+
let i = 2;
|
|
10607
|
+
while (i + 9 < b.length) {
|
|
10608
|
+
if (b[i] !== 255) {
|
|
10609
|
+
i++;
|
|
10610
|
+
continue;
|
|
10611
|
+
}
|
|
10612
|
+
const marker = b[i + 1];
|
|
10613
|
+
if (marker >= 192 && marker <= 207 && marker !== 196 && marker !== 200 && marker !== 204) {
|
|
10614
|
+
return { type: "jpg", mime: "image/jpeg", height: dv.getUint16(i + 5), width: dv.getUint16(i + 7) };
|
|
10615
|
+
}
|
|
10616
|
+
i += 2 + dv.getUint16(i + 2);
|
|
10617
|
+
}
|
|
10618
|
+
}
|
|
10619
|
+
return null;
|
|
10620
|
+
}
|
|
10621
|
+
function bytesToBase64(bytes) {
|
|
10622
|
+
const g = globalThis;
|
|
10623
|
+
if (g.Buffer) return g.Buffer.from(bytes).toString("base64");
|
|
10624
|
+
const CHUNK = 32768;
|
|
10625
|
+
let binary = "";
|
|
10626
|
+
for (let i = 0; i < bytes.length; i += CHUNK) {
|
|
10627
|
+
binary += String.fromCharCode(...bytes.subarray(i, i + CHUNK));
|
|
10628
|
+
}
|
|
10629
|
+
return g.btoa(binary);
|
|
10630
|
+
}
|
|
10631
|
+
function stripInline(s) {
|
|
10632
|
+
return s.replace(/\*\*(.+?)\*\*/g, "$1").replace(/(^|[^*])\*(?!\s)(.+?)\*/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\[(.+?)\]\((.+?)\)/g, "$1 ($2)");
|
|
10633
|
+
}
|
|
10634
|
+
function inlineRuns(text) {
|
|
10635
|
+
const acc = [];
|
|
10636
|
+
const re = /(\*\*[^*]+\*\*|`[^`]+`|\[[^\]]{1,500}\]\([^)]{1,2000}\)|\*[^*\s][^*]*\*)/g;
|
|
10637
|
+
let last = 0;
|
|
10638
|
+
let m;
|
|
10639
|
+
while ((m = re.exec(text)) !== null) {
|
|
10640
|
+
if (m.index > last) acc.push({ text: text.slice(last, m.index) });
|
|
10641
|
+
const tok = m[0];
|
|
10642
|
+
if (tok.startsWith("**")) acc.push({ text: tok.slice(2, -2), bold: true });
|
|
10643
|
+
else if (tok.startsWith("`")) acc.push({ text: tok.slice(1, -1), code: true });
|
|
10644
|
+
else if (tok.startsWith("[")) {
|
|
10645
|
+
const l = tok.match(/\[([^\]]{1,500})\]\(([^)]{1,2000})\)/);
|
|
10646
|
+
acc.push({ text: l ? `${l[1]} (${l[2]})` : tok });
|
|
10647
|
+
} else acc.push({ text: tok.slice(1, -1), italic: true });
|
|
10648
|
+
last = re.lastIndex;
|
|
10649
|
+
}
|
|
10650
|
+
if (last < text.length) acc.push({ text: text.slice(last) });
|
|
10651
|
+
return acc.length ? acc : [{ text }];
|
|
10652
|
+
}
|
|
10653
|
+
function splitSlides(md) {
|
|
10654
|
+
const norm = md.replace(/\r\n/g, "\n").trim();
|
|
10655
|
+
let chunks = norm.split(/\n\s*-{3,}\s*\n/);
|
|
10656
|
+
if (chunks.length === 1) {
|
|
10657
|
+
const lines = norm.split("\n");
|
|
10658
|
+
const sections = [];
|
|
10659
|
+
let cur = [];
|
|
10660
|
+
for (const ln of lines) {
|
|
10661
|
+
if (/^#{1,2}\s+/.test(ln) && cur.some((l) => l.trim())) {
|
|
10662
|
+
sections.push(cur.join("\n"));
|
|
10663
|
+
cur = [];
|
|
10664
|
+
}
|
|
10665
|
+
cur.push(ln);
|
|
10666
|
+
}
|
|
10667
|
+
if (cur.length) sections.push(cur.join("\n"));
|
|
10668
|
+
if (sections.length > 1) chunks = sections;
|
|
10669
|
+
}
|
|
10670
|
+
return chunks.map(parseSlide).filter((s) => s.title || s.body.length || s.images.length || s.charts.length);
|
|
10671
|
+
}
|
|
10672
|
+
function parseSlide(chunk) {
|
|
10673
|
+
let title = "";
|
|
10674
|
+
const body = [];
|
|
10675
|
+
const images = [];
|
|
10676
|
+
const charts = [];
|
|
10677
|
+
const lines = chunk.split("\n");
|
|
10678
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10679
|
+
const raw = lines[i] ?? "";
|
|
10680
|
+
const fence = raw.match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
10681
|
+
if (fence) {
|
|
10682
|
+
const kind = chartKind(fence[1]);
|
|
10683
|
+
const bodyLines = [];
|
|
10684
|
+
i++;
|
|
10685
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
10686
|
+
bodyLines.push(lines[i] ?? "");
|
|
10687
|
+
i++;
|
|
10688
|
+
}
|
|
10689
|
+
const spec = kind ? parseChartSpec(kind, bodyLines.join("\n")) : null;
|
|
10690
|
+
if (spec) charts.push(spec);
|
|
10691
|
+
else for (const bl of bodyLines) if (bl.trim()) body.push(stripInline(bl.trim()));
|
|
10692
|
+
continue;
|
|
10693
|
+
}
|
|
10694
|
+
const line = raw.trim();
|
|
10695
|
+
if (!line) continue;
|
|
10696
|
+
const h = line.match(/^#{1,6}[ \t]+(.*)/);
|
|
10697
|
+
if (h) {
|
|
10698
|
+
if (!title) title = stripInline(h[1]);
|
|
10699
|
+
else body.push(stripInline(h[1]));
|
|
10700
|
+
continue;
|
|
10701
|
+
}
|
|
10702
|
+
const img = matchImageLine(line);
|
|
10703
|
+
if (img) {
|
|
10704
|
+
images.push(img);
|
|
10705
|
+
continue;
|
|
10706
|
+
}
|
|
10707
|
+
const li = line.match(/^([-*+]|\d+[.)])[ \t]+(.*)/);
|
|
10708
|
+
if (li) {
|
|
10709
|
+
const bulletImg = matchImageLine(li[2]);
|
|
10710
|
+
if (bulletImg) {
|
|
10711
|
+
images.push(bulletImg);
|
|
10712
|
+
continue;
|
|
10713
|
+
}
|
|
10714
|
+
body.push(stripInline(li[2]));
|
|
10715
|
+
continue;
|
|
10716
|
+
}
|
|
10717
|
+
if (/^>\s?/.test(line)) {
|
|
10718
|
+
body.push(stripInline(line.replace(/^>\s?/, "")));
|
|
10719
|
+
continue;
|
|
10720
|
+
}
|
|
10721
|
+
body.push(stripInline(line));
|
|
10722
|
+
}
|
|
10723
|
+
return { title, body, images, charts };
|
|
10724
|
+
}
|
|
10725
|
+
function extractCharts(source) {
|
|
10726
|
+
const lines = source.replace(/\r\n/g, "\n").split("\n");
|
|
10727
|
+
const charts = [];
|
|
10728
|
+
const rest = [];
|
|
10729
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10730
|
+
const fence = lines[i].match(/^[ \t]*```+[ \t]*chart[ \t]*:(.*)/i);
|
|
10731
|
+
if (!fence) {
|
|
10732
|
+
rest.push(lines[i]);
|
|
10733
|
+
continue;
|
|
10734
|
+
}
|
|
10735
|
+
const kind = chartKind(fence[1]);
|
|
10736
|
+
const body = [];
|
|
10737
|
+
i++;
|
|
10738
|
+
while (i < lines.length && !/^\s*```/.test(lines[i] ?? "")) {
|
|
10739
|
+
body.push(lines[i] ?? "");
|
|
10740
|
+
i++;
|
|
10741
|
+
}
|
|
10742
|
+
const spec = kind ? parseChartSpec(kind, body.join("\n")) : null;
|
|
10743
|
+
if (spec) charts.push(spec);
|
|
10744
|
+
else rest.push(...body);
|
|
10745
|
+
}
|
|
10746
|
+
return { charts, rest: rest.join("\n") };
|
|
10747
|
+
}
|
|
10748
|
+
|
|
10749
|
+
// src/core/documents/render.ts
|
|
10750
|
+
var DOCUMENT_MIME = {
|
|
10751
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
10752
|
+
pptx: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
10753
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
10754
|
+
};
|
|
10755
|
+
function isDocumentFormat(ext) {
|
|
10756
|
+
return ext === "docx" || ext === "pptx" || ext === "xlsx";
|
|
10757
|
+
}
|
|
10758
|
+
async function loadImage(ref, load) {
|
|
10759
|
+
if (!load) return null;
|
|
10760
|
+
try {
|
|
10761
|
+
const bytes = await load(ref.url);
|
|
10762
|
+
if (!bytes || !bytes.length) return null;
|
|
10763
|
+
const info = sniffImage(bytes);
|
|
10764
|
+
if (!info || info.width <= 0 || info.height <= 0) return null;
|
|
10765
|
+
return { ...info, bytes, alt: ref.alt };
|
|
10766
|
+
} catch {
|
|
10767
|
+
return null;
|
|
10768
|
+
}
|
|
10769
|
+
}
|
|
10770
|
+
async function renderXlsx(source, opts = {}) {
|
|
10771
|
+
const XLSX = await import('xlsx');
|
|
10772
|
+
const { charts, rest } = extractCharts(source);
|
|
10773
|
+
const name = opts.name ?? "";
|
|
10774
|
+
const rows = parseDelimited(rest, name.toLowerCase().endsWith(".tsv") ? "x.tsv" : "x.csv");
|
|
10775
|
+
const wb = XLSX.utils.book_new();
|
|
10776
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(rows.length ? rows : [[""]]), "Sheet1");
|
|
10777
|
+
const used = /* @__PURE__ */ new Set(["Sheet1"]);
|
|
10778
|
+
charts.forEach((spec, i) => {
|
|
10779
|
+
const base = (spec.title || `Chart ${i + 1}`).replace(/[:\\/?*[\]]/g, " ").trim().slice(0, 28) || `Chart ${i + 1}`;
|
|
10780
|
+
let sheetName = base;
|
|
10781
|
+
let n = 2;
|
|
10782
|
+
while (used.has(sheetName)) sheetName = `${base} ${n++}`.slice(0, 31);
|
|
10783
|
+
used.add(sheetName);
|
|
10784
|
+
const aoa = [
|
|
10785
|
+
[spec.categoryName, ...spec.series.map((s) => s.name)],
|
|
10786
|
+
...spec.categories.map((cat, r) => [cat, ...spec.series.map((s) => s.values[r] ?? 0)])
|
|
10787
|
+
];
|
|
10788
|
+
XLSX.utils.book_append_sheet(wb, XLSX.utils.aoa_to_sheet(aoa), sheetName);
|
|
10789
|
+
});
|
|
10790
|
+
return new Uint8Array(XLSX.write(wb, { type: "array", bookType: "xlsx" }));
|
|
10791
|
+
}
|
|
10792
|
+
var DOCX_MAX_W = 600;
|
|
10793
|
+
var DOCX_MAX_H = 864;
|
|
10794
|
+
async function renderDocx(md, opts = {}) {
|
|
10795
|
+
const d = await import('docx');
|
|
10796
|
+
const {
|
|
10797
|
+
Document,
|
|
10798
|
+
Packer,
|
|
10799
|
+
Paragraph,
|
|
10800
|
+
TextRun,
|
|
10801
|
+
ImageRun,
|
|
10802
|
+
HeadingLevel,
|
|
10803
|
+
Table,
|
|
10804
|
+
TableRow,
|
|
10805
|
+
TableCell,
|
|
10806
|
+
WidthType,
|
|
10807
|
+
BorderStyle,
|
|
10808
|
+
AlignmentType
|
|
10809
|
+
} = d;
|
|
10810
|
+
const HEADINGS = [
|
|
10811
|
+
HeadingLevel.HEADING_1,
|
|
10812
|
+
HeadingLevel.HEADING_2,
|
|
10813
|
+
HeadingLevel.HEADING_3,
|
|
10814
|
+
HeadingLevel.HEADING_4,
|
|
10815
|
+
HeadingLevel.HEADING_5,
|
|
10816
|
+
HeadingLevel.HEADING_6
|
|
10817
|
+
];
|
|
10818
|
+
const runs = (text) => inlineRuns(text).map((r) => new TextRun({
|
|
10819
|
+
text: r.text,
|
|
10820
|
+
...r.bold ? { bold: true } : {},
|
|
10821
|
+
...r.italic ? { italics: true } : {},
|
|
10822
|
+
...r.code ? { font: "Courier New" } : {}
|
|
10823
|
+
}));
|
|
10824
|
+
const tableOf = (rows) => new Table({
|
|
10825
|
+
width: { size: 100, type: WidthType.PERCENTAGE },
|
|
10826
|
+
rows: rows.map((r) => new TableRow({
|
|
10827
|
+
children: r.map((c) => new TableCell({ children: [new Paragraph({ children: runs(c) })] }))
|
|
10828
|
+
}))
|
|
10829
|
+
});
|
|
10830
|
+
const imageParagraph = (img) => {
|
|
10831
|
+
const scale = Math.min(1, DOCX_MAX_W / img.width, DOCX_MAX_H / img.height);
|
|
10832
|
+
return new Paragraph({
|
|
10833
|
+
children: [new ImageRun({
|
|
10834
|
+
type: img.type,
|
|
10835
|
+
data: img.bytes,
|
|
10836
|
+
transformation: { width: Math.round(img.width * scale), height: Math.round(img.height * scale) },
|
|
10837
|
+
...img.alt ? { altText: { name: img.alt, description: img.alt, title: img.alt } } : {}
|
|
10838
|
+
})]
|
|
10839
|
+
});
|
|
10840
|
+
};
|
|
10841
|
+
const asImage = async (text) => {
|
|
10842
|
+
const ref = matchImageLine(text);
|
|
10843
|
+
return ref ? await loadImage(ref, opts.loadImageBytes) : null;
|
|
10844
|
+
};
|
|
10845
|
+
const children = [];
|
|
10846
|
+
for (const b of parseBlocks(md)) {
|
|
10847
|
+
switch (b.t) {
|
|
10848
|
+
case "heading":
|
|
10849
|
+
children.push(new Paragraph({ heading: HEADINGS[Math.min(b.level, 6) - 1], children: runs(b.text) }));
|
|
10850
|
+
break;
|
|
10851
|
+
case "para": {
|
|
10852
|
+
const img = await asImage(b.text);
|
|
10853
|
+
children.push(img ? imageParagraph(img) : new Paragraph({ children: runs(b.text) }));
|
|
10854
|
+
break;
|
|
10855
|
+
}
|
|
10856
|
+
case "bullet": {
|
|
10857
|
+
const img = await asImage(b.text);
|
|
10858
|
+
children.push(img ? imageParagraph(img) : new Paragraph({
|
|
10859
|
+
children: runs(b.text),
|
|
10860
|
+
...b.ordered ? { numbering: { reference: "ol", level: 0 } } : { bullet: { level: 0 } }
|
|
10861
|
+
}));
|
|
10862
|
+
break;
|
|
10863
|
+
}
|
|
10864
|
+
case "code":
|
|
10865
|
+
for (const cl of b.lines) {
|
|
10866
|
+
children.push(new Paragraph({ children: [new TextRun({ text: cl || " ", font: "Courier New", size: 18 })] }));
|
|
10867
|
+
}
|
|
10868
|
+
break;
|
|
10869
|
+
case "quote":
|
|
10870
|
+
children.push(new Paragraph({ indent: { left: 360 }, children: [new TextRun({ text: stripInline(b.text), italics: true })] }));
|
|
10871
|
+
break;
|
|
10872
|
+
case "table":
|
|
10873
|
+
children.push(tableOf(b.rows));
|
|
10874
|
+
break;
|
|
10875
|
+
case "chart":
|
|
10876
|
+
if (b.spec.title) {
|
|
10877
|
+
children.push(new Paragraph({ children: [new TextRun({ text: b.spec.title, bold: true })] }));
|
|
10878
|
+
}
|
|
10879
|
+
children.push(tableOf(chartToTableRows(b.spec)));
|
|
10880
|
+
break;
|
|
10881
|
+
case "hr":
|
|
10882
|
+
children.push(new Paragraph({
|
|
10883
|
+
children: [],
|
|
10884
|
+
border: { bottom: { color: "CCCCCC", space: 1, style: BorderStyle.SINGLE, size: 6 } }
|
|
10885
|
+
}));
|
|
10886
|
+
break;
|
|
10887
|
+
}
|
|
10888
|
+
}
|
|
10889
|
+
const doc = new Document({
|
|
10890
|
+
numbering: {
|
|
10891
|
+
config: [{ reference: "ol", levels: [{ level: 0, format: "decimal", text: "%1.", alignment: AlignmentType.START }] }]
|
|
10892
|
+
},
|
|
10893
|
+
sections: [{ children: children.length ? children : [new Paragraph({ children: [new TextRun("")] })] }]
|
|
10894
|
+
});
|
|
10895
|
+
return new Uint8Array(await Packer.toArrayBuffer(doc));
|
|
10896
|
+
}
|
|
10897
|
+
function pptxConstructor(mod) {
|
|
10898
|
+
let c = mod;
|
|
10899
|
+
for (let i = 0; i < 3 && c && typeof c !== "function"; i++) c = c.default;
|
|
10900
|
+
if (typeof c !== "function") throw new Error("pptxgenjs did not export a constructor.");
|
|
10901
|
+
return c;
|
|
10902
|
+
}
|
|
10903
|
+
var SLIDE_H = 7.5;
|
|
10904
|
+
var CONTENT_X = 0.6;
|
|
10905
|
+
var CONTENT_W = 12.1;
|
|
10906
|
+
var CONTENT_BOTTOM = 0.4;
|
|
10907
|
+
async function renderPptx(md, opts = {}) {
|
|
10908
|
+
const PptxGen = pptxConstructor(await import('pptxgenjs'));
|
|
10909
|
+
const pptx = new PptxGen();
|
|
10910
|
+
pptx.layout = "LAYOUT_WIDE";
|
|
10911
|
+
const CHART_TYPES = {
|
|
10912
|
+
bar: pptx.ChartType["bar"],
|
|
10913
|
+
line: pptx.ChartType["line"],
|
|
10914
|
+
pie: pptx.ChartType["pie"],
|
|
10915
|
+
doughnut: pptx.ChartType["doughnut"],
|
|
10916
|
+
area: pptx.ChartType["area"],
|
|
10917
|
+
scatter: pptx.ChartType["scatter"]
|
|
10918
|
+
};
|
|
10919
|
+
const slides = splitSlides(md);
|
|
10920
|
+
const blank = { title: "", body: [""], images: [], charts: [] };
|
|
10921
|
+
for (const s of slides.length ? slides : [blank]) {
|
|
10922
|
+
const slide = pptx.addSlide();
|
|
10923
|
+
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" });
|
|
10924
|
+
const top = s.title ? 1.5 : 0.5;
|
|
10925
|
+
const pics = (await Promise.all(s.images.map((r) => loadImage(r, opts.loadImageBytes)))).filter((p) => p !== null);
|
|
10926
|
+
const visuals = [
|
|
10927
|
+
...s.charts.map((spec) => ({ kind: "chart", spec })),
|
|
10928
|
+
...pics.map((img) => ({ kind: "image", img }))
|
|
10929
|
+
];
|
|
10930
|
+
const bodyH = visuals.length ? 2.2 : 5.4;
|
|
10931
|
+
if (s.body.length) {
|
|
10932
|
+
slide.addText(
|
|
10933
|
+
s.body.map((t) => ({ text: t, options: { bullet: true, breakLine: true } })),
|
|
10934
|
+
{ x: CONTENT_X, y: top, w: CONTENT_W, h: bodyH, fontSize: 16, color: "374151", valign: "top" }
|
|
10935
|
+
);
|
|
10936
|
+
}
|
|
10937
|
+
if (visuals.length) {
|
|
10938
|
+
const y = top + (s.body.length ? bodyH + 0.2 : 0);
|
|
10939
|
+
const h = Math.max(1, SLIDE_H - CONTENT_BOTTOM - y);
|
|
10940
|
+
const gap = 0.3;
|
|
10941
|
+
const w = (CONTENT_W - gap * (visuals.length - 1)) / visuals.length;
|
|
10942
|
+
visuals.forEach((v, i) => {
|
|
10943
|
+
const x = CONTENT_X + i * (w + gap);
|
|
10944
|
+
if (v.kind === "image") {
|
|
10945
|
+
slide.addImage({
|
|
10946
|
+
data: `data:${v.img.mime};base64,${bytesToBase64(v.img.bytes)}`,
|
|
10947
|
+
x,
|
|
10948
|
+
y,
|
|
10949
|
+
w,
|
|
10950
|
+
h,
|
|
10951
|
+
// 'contain' keeps the aspect ratio inside the box — a stretched
|
|
10952
|
+
// render looks worse than a smaller correct one.
|
|
10953
|
+
sizing: { type: "contain", w, h },
|
|
10954
|
+
...v.img.alt ? { altText: v.img.alt } : {}
|
|
10955
|
+
});
|
|
10956
|
+
} else {
|
|
10957
|
+
slide.addChart(CHART_TYPES[v.spec.kind], chartData(v.spec), {
|
|
10958
|
+
x,
|
|
10959
|
+
y,
|
|
10960
|
+
w,
|
|
10961
|
+
h,
|
|
10962
|
+
showLegend: v.spec.series.length > 1 || v.spec.kind === "pie" || v.spec.kind === "doughnut",
|
|
10963
|
+
legendPos: "b",
|
|
10964
|
+
showTitle: !!v.spec.title,
|
|
10965
|
+
...v.spec.title ? { title: v.spec.title } : {},
|
|
10966
|
+
showValue: v.spec.kind === "pie" || v.spec.kind === "doughnut"
|
|
10967
|
+
});
|
|
10968
|
+
}
|
|
10969
|
+
});
|
|
10970
|
+
}
|
|
10971
|
+
}
|
|
10972
|
+
return new Uint8Array(await pptx.write({ outputType: "arraybuffer" }));
|
|
10973
|
+
}
|
|
10974
|
+
function chartData(spec) {
|
|
10975
|
+
const series = spec.kind === "pie" || spec.kind === "doughnut" ? spec.series.slice(0, 1) : spec.series;
|
|
10976
|
+
return series.map((s) => ({ name: s.name, labels: spec.categories, values: s.values }));
|
|
10977
|
+
}
|
|
10978
|
+
async function renderDocument(format, source, opts = {}) {
|
|
10979
|
+
if (format === "docx") return renderDocx(source, opts);
|
|
10980
|
+
if (format === "pptx") return renderPptx(source, opts);
|
|
10981
|
+
return renderXlsx(source, opts);
|
|
10982
|
+
}
|
|
10983
|
+
|
|
10984
|
+
// src/tools/generate-document.ts
|
|
10985
|
+
var SOURCE_HINT = {
|
|
10986
|
+
docx: "Markdown",
|
|
10987
|
+
pptx: "Markdown slides (separate slides with a --- rule; each slide starts with a heading)",
|
|
10988
|
+
xlsx: "CSV (one header row, then data rows)"
|
|
10989
|
+
};
|
|
10990
|
+
var GenerateDocumentTool = class extends BaseTool {
|
|
10991
|
+
/**
|
|
10992
|
+
* `readFile` is injected so the image loader is testable and so a host with a
|
|
10993
|
+
* different workspace (not plain disk) can supply its own reader.
|
|
10994
|
+
*/
|
|
10995
|
+
constructor(readFile = async (p) => new Uint8Array(await fs5.readFile(p))) {
|
|
10996
|
+
super();
|
|
10997
|
+
this.readFile = readFile;
|
|
10998
|
+
}
|
|
10999
|
+
readFile;
|
|
11000
|
+
name = "generate_document";
|
|
11001
|
+
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.";
|
|
11002
|
+
inputSchema = {
|
|
11003
|
+
type: "object",
|
|
11004
|
+
properties: {
|
|
11005
|
+
path: {
|
|
11006
|
+
type: "string",
|
|
11007
|
+
description: "Where to write the file, relative to the workspace root. The extension decides the format: .docx, .pptx or .xlsx."
|
|
11008
|
+
},
|
|
11009
|
+
content: {
|
|
11010
|
+
type: "string",
|
|
11011
|
+
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.'
|
|
11012
|
+
},
|
|
11013
|
+
format: {
|
|
11014
|
+
type: "string",
|
|
11015
|
+
description: 'Optional override: "docx", "pptx" or "xlsx". Normally inferred from the path extension.'
|
|
11016
|
+
}
|
|
11017
|
+
},
|
|
11018
|
+
required: ["path", "content"]
|
|
11019
|
+
};
|
|
11020
|
+
isDangerous() {
|
|
11021
|
+
return true;
|
|
11022
|
+
}
|
|
11023
|
+
async execute(input, options) {
|
|
11024
|
+
const filePath = String(input["path"] ?? "").trim();
|
|
11025
|
+
if (!filePath) return 'Provide a "path" for the document, e.g. "report.docx".';
|
|
11026
|
+
const content = typeof input["content"] === "string" ? input["content"] : "";
|
|
11027
|
+
if (!content.trim()) return 'Provide "content" \u2014 the Markdown (docx/pptx) or CSV (xlsx) source for the document.';
|
|
11028
|
+
const requested = String(input["format"] ?? "").trim().toLowerCase().replace(/^\./, "");
|
|
11029
|
+
const ext = requested || fileExt(filePath);
|
|
11030
|
+
if (!isDocumentFormat(ext)) {
|
|
11031
|
+
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.`;
|
|
11032
|
+
}
|
|
11033
|
+
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
11034
|
+
const embedded = [];
|
|
11035
|
+
const skipped = [];
|
|
11036
|
+
const loadImageBytes = async (url) => {
|
|
11037
|
+
if (/^(https?|ftp):\/\//i.test(url)) {
|
|
11038
|
+
skipped.push(`${url} (remote URL \u2014 download it into the workspace first)`);
|
|
11039
|
+
return null;
|
|
11040
|
+
}
|
|
11041
|
+
const local = url.replace(/^file:\/\//i, "").split("?")[0].split("#")[0];
|
|
11042
|
+
try {
|
|
11043
|
+
const bytes2 = await this.readFile(resolveInWorkspace(this.workspaceRoot, decodeURIComponent(local)));
|
|
11044
|
+
embedded.push(local);
|
|
11045
|
+
return bytes2;
|
|
11046
|
+
} catch {
|
|
11047
|
+
skipped.push(`${local} (not found in the workspace)`);
|
|
11048
|
+
return null;
|
|
11049
|
+
}
|
|
11050
|
+
};
|
|
11051
|
+
if (options.saveSnapshot) {
|
|
11052
|
+
try {
|
|
11053
|
+
const old = await fs5.readFile(absPath, "utf-8");
|
|
11054
|
+
await options.saveSnapshot(absPath, old);
|
|
11055
|
+
} catch {
|
|
11056
|
+
}
|
|
11057
|
+
}
|
|
11058
|
+
const bytes = await renderDocument(ext, content, { name: filePath, loadImageBytes });
|
|
11059
|
+
await fs5.mkdir(path25.dirname(absPath), { recursive: true });
|
|
11060
|
+
await fs5.writeFile(absPath, bytes);
|
|
11061
|
+
const charts = countCharts(content);
|
|
11062
|
+
return [
|
|
11063
|
+
`Wrote a real ${LABEL[ext]} file to ${filePath} (${bytes.length} bytes) from ${SOURCE_HINT[ext]}.`,
|
|
11064
|
+
embedded.length ? `Embedded ${embedded.length} image(s): ${embedded.join(", ")}.` : "",
|
|
11065
|
+
// Named, not counted: a dropped image is the failure the user actually
|
|
11066
|
+
// notices, and the model can only fix it if it is told which reference died.
|
|
11067
|
+
skipped.length ? `Could NOT embed ${skipped.length} image reference(s): ${skipped.join("; ")}.` : "",
|
|
11068
|
+
charts && ext === "pptx" ? `Rendered ${charts} chart block(s) as real, editable PowerPoint charts.` : "",
|
|
11069
|
+
charts && ext === "docx" ? `Rendered ${charts} chart block(s) as data tables \u2014 Word documents cannot hold a native chart object.` : "",
|
|
11070
|
+
charts && ext === "xlsx" ? `Wrote ${charts} chart block(s) onto their own worksheets as real rows the user can chart in Excel.` : ""
|
|
11071
|
+
].filter(Boolean).join("\n");
|
|
11072
|
+
}
|
|
11073
|
+
};
|
|
11074
|
+
var LABEL = { docx: "Word", pptx: "PowerPoint", xlsx: "Excel" };
|
|
11075
|
+
function countCharts(source) {
|
|
11076
|
+
return (source.match(/^\s*```+\s*chart\s*:/gim) ?? []).length;
|
|
11077
|
+
}
|
|
11078
|
+
function buildDocumentTools(readFile) {
|
|
11079
|
+
return readFile ? [new GenerateDocumentTool(readFile)] : [];
|
|
11080
|
+
}
|
|
10219
11081
|
|
|
10220
11082
|
// src/core/run-breaker.ts
|
|
10221
11083
|
var DEFAULT_FAILURE_THRESHOLD = 3;
|
|
@@ -10538,35 +11400,6 @@ ${[e.stdout, e.stderr].filter(Boolean).join("\n").trim()}`;
|
|
|
10538
11400
|
}
|
|
10539
11401
|
}
|
|
10540
11402
|
};
|
|
10541
|
-
var WorkspaceSandboxError = class extends Error {
|
|
10542
|
-
constructor(attempted, workspaceRoot) {
|
|
10543
|
-
super(`Refusing access to "${attempted}" \u2014 outside workspace root "${workspaceRoot}".`);
|
|
10544
|
-
this.name = "WorkspaceSandboxError";
|
|
10545
|
-
}
|
|
10546
|
-
};
|
|
10547
|
-
function resolveInWorkspace(workspaceRoot, input) {
|
|
10548
|
-
if (typeof input !== "string" || input.length === 0) {
|
|
10549
|
-
throw new WorkspaceSandboxError(String(input), workspaceRoot);
|
|
10550
|
-
}
|
|
10551
|
-
const root = path24.resolve(workspaceRoot);
|
|
10552
|
-
const abs = path24.isAbsolute(input) ? path24.resolve(input) : path24.resolve(root, input);
|
|
10553
|
-
const rel = path24.relative(root, abs);
|
|
10554
|
-
if (rel === "" || rel === ".") ; else if (rel.startsWith("..") || path24.isAbsolute(rel)) {
|
|
10555
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10556
|
-
}
|
|
10557
|
-
try {
|
|
10558
|
-
const real = fs23.realpathSync(abs);
|
|
10559
|
-
const realRel = path24.relative(root, real);
|
|
10560
|
-
if (realRel !== "" && realRel !== "." && (realRel.startsWith("..") || path24.isAbsolute(realRel))) {
|
|
10561
|
-
throw new WorkspaceSandboxError(input, root);
|
|
10562
|
-
}
|
|
10563
|
-
} catch (e) {
|
|
10564
|
-
if (e instanceof WorkspaceSandboxError) throw e;
|
|
10565
|
-
}
|
|
10566
|
-
return abs;
|
|
10567
|
-
}
|
|
10568
|
-
|
|
10569
|
-
// src/tools/file.ts
|
|
10570
11403
|
var FileReadTool = class extends BaseTool {
|
|
10571
11404
|
name = "file_read";
|
|
10572
11405
|
description = "Read the contents of a file. Returns the file content as a string.";
|
|
@@ -10584,7 +11417,7 @@ var FileReadTool = class extends BaseTool {
|
|
|
10584
11417
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10585
11418
|
const offset = input["offset"] ?? 1;
|
|
10586
11419
|
const limit = input["limit"];
|
|
10587
|
-
const content = await
|
|
11420
|
+
const content = await fs5.readFile(absPath, "utf-8");
|
|
10588
11421
|
const lines = content.split("\n");
|
|
10589
11422
|
const start = Math.max(0, offset - 1);
|
|
10590
11423
|
const end = limit ? start + limit : lines.length;
|
|
@@ -10613,13 +11446,13 @@ var FileWriteTool = class extends BaseTool {
|
|
|
10613
11446
|
const content = input["content"];
|
|
10614
11447
|
if (options.saveSnapshot) {
|
|
10615
11448
|
try {
|
|
10616
|
-
const oldContent = await
|
|
11449
|
+
const oldContent = await fs5.readFile(absPath, "utf-8");
|
|
10617
11450
|
await options.saveSnapshot(absPath, oldContent);
|
|
10618
11451
|
} catch {
|
|
10619
11452
|
}
|
|
10620
11453
|
}
|
|
10621
|
-
await
|
|
10622
|
-
await
|
|
11454
|
+
await fs5.mkdir(path25.dirname(absPath), { recursive: true });
|
|
11455
|
+
await fs5.writeFile(absPath, content, "utf-8");
|
|
10623
11456
|
return `Written ${content.length} characters to ${filePath}`;
|
|
10624
11457
|
}
|
|
10625
11458
|
};
|
|
@@ -10645,7 +11478,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10645
11478
|
const oldString = input["old_string"];
|
|
10646
11479
|
const newString = input["new_string"];
|
|
10647
11480
|
const replaceAll = input["replace_all"] ?? false;
|
|
10648
|
-
const rawContent = await
|
|
11481
|
+
const rawContent = await fs5.readFile(absPath, "utf-8");
|
|
10649
11482
|
if (options.saveSnapshot) {
|
|
10650
11483
|
await options.saveSnapshot(absPath, rawContent);
|
|
10651
11484
|
}
|
|
@@ -10657,7 +11490,7 @@ var FileEditTool = class extends BaseTool {
|
|
|
10657
11490
|
);
|
|
10658
11491
|
}
|
|
10659
11492
|
const updated = replaceAll ? content.split(normalizedOld).join(newString) : content.replace(normalizedOld, newString);
|
|
10660
|
-
await
|
|
11493
|
+
await fs5.writeFile(absPath, updated, "utf-8");
|
|
10661
11494
|
const count = replaceAll ? content.split(normalizedOld).length - 1 : 1;
|
|
10662
11495
|
return `Replaced ${count} occurrence(s) in ${filePath}`;
|
|
10663
11496
|
}
|
|
@@ -10680,12 +11513,12 @@ var FileDeleteTool = class extends BaseTool {
|
|
|
10680
11513
|
const absPath = resolveInWorkspace(this.workspaceRoot, filePath);
|
|
10681
11514
|
if (options.saveSnapshot) {
|
|
10682
11515
|
try {
|
|
10683
|
-
const oldContent = await
|
|
11516
|
+
const oldContent = await fs5.readFile(absPath, "utf-8");
|
|
10684
11517
|
await options.saveSnapshot(absPath, oldContent);
|
|
10685
11518
|
} catch {
|
|
10686
11519
|
}
|
|
10687
11520
|
}
|
|
10688
|
-
await
|
|
11521
|
+
await fs5.rm(absPath, { recursive: false });
|
|
10689
11522
|
return `Deleted ${filePath}`;
|
|
10690
11523
|
}
|
|
10691
11524
|
};
|
|
@@ -10702,7 +11535,7 @@ var FileListTool = class extends BaseTool {
|
|
|
10702
11535
|
async execute(input, _options) {
|
|
10703
11536
|
const inputPath = input["path"] || ".";
|
|
10704
11537
|
const absPath = resolveInWorkspace(this.workspaceRoot, inputPath);
|
|
10705
|
-
const entries = await
|
|
11538
|
+
const entries = await fs5.readdir(absPath, { withFileTypes: true });
|
|
10706
11539
|
return entries.map((e) => `${e.isDirectory() ? "[DIR] " : " "}${e.name}`).join("\n") || "(empty directory)";
|
|
10707
11540
|
}
|
|
10708
11541
|
};
|
|
@@ -11115,8 +11948,8 @@ var ImageAnalyzeTool = class extends BaseTool {
|
|
|
11115
11948
|
}
|
|
11116
11949
|
};
|
|
11117
11950
|
async function fileToImageAttachment(filePath) {
|
|
11118
|
-
const data = await
|
|
11119
|
-
const ext =
|
|
11951
|
+
const data = await fs5.readFile(filePath);
|
|
11952
|
+
const ext = path25.extname(filePath).toLowerCase();
|
|
11120
11953
|
const mimeMap = {
|
|
11121
11954
|
".jpg": "image/jpeg",
|
|
11122
11955
|
".jpeg": "image/jpeg",
|
|
@@ -11150,14 +11983,14 @@ var PDFCreateTool = class extends BaseTool {
|
|
|
11150
11983
|
const filePath = input["path"];
|
|
11151
11984
|
const content = input["content"];
|
|
11152
11985
|
const title = input["title"];
|
|
11153
|
-
const dir =
|
|
11154
|
-
if (!
|
|
11155
|
-
|
|
11986
|
+
const dir = path25.dirname(filePath);
|
|
11987
|
+
if (!fs24.existsSync(dir)) {
|
|
11988
|
+
fs24.mkdirSync(dir, { recursive: true });
|
|
11156
11989
|
}
|
|
11157
11990
|
return new Promise((resolve2, reject) => {
|
|
11158
11991
|
try {
|
|
11159
11992
|
const doc = new PDFDocument({ margin: 50 });
|
|
11160
|
-
const stream =
|
|
11993
|
+
const stream = fs24.createWriteStream(filePath);
|
|
11161
11994
|
doc.pipe(stream);
|
|
11162
11995
|
if (title) {
|
|
11163
11996
|
doc.info["Title"] = title;
|
|
@@ -11235,22 +12068,22 @@ var CodeInterpreterTool = class extends BaseTool {
|
|
|
11235
12068
|
}
|
|
11236
12069
|
cmdPrefix = NODE_CMD;
|
|
11237
12070
|
}
|
|
11238
|
-
const tmpDir =
|
|
11239
|
-
if (!
|
|
11240
|
-
|
|
12071
|
+
const tmpDir = path25.join(this.workspaceRoot, ".cascade", "tmp");
|
|
12072
|
+
if (!fs24.existsSync(tmpDir)) {
|
|
12073
|
+
fs24.mkdirSync(tmpDir, { recursive: true });
|
|
11241
12074
|
}
|
|
11242
12075
|
const extension = language === "python" ? "py" : "js";
|
|
11243
12076
|
const fileName = `intp_${randomUUID().slice(0, 8)}.${extension}`;
|
|
11244
|
-
const filePath =
|
|
11245
|
-
|
|
12077
|
+
const filePath = path25.join(tmpDir, fileName);
|
|
12078
|
+
fs24.writeFileSync(filePath, code, "utf-8");
|
|
11246
12079
|
const execArgs = [filePath, ...args];
|
|
11247
12080
|
return new Promise((resolve2) => {
|
|
11248
12081
|
const startMs = Date.now();
|
|
11249
12082
|
execFile(cmdPrefix, execArgs, { cwd: this.workspaceRoot, timeout: 3e4 }, (error, stdout, stderr) => {
|
|
11250
12083
|
const duration = Date.now() - startMs;
|
|
11251
12084
|
try {
|
|
11252
|
-
if (
|
|
11253
|
-
|
|
12085
|
+
if (fs24.existsSync(filePath)) {
|
|
12086
|
+
fs24.unlinkSync(filePath);
|
|
11254
12087
|
}
|
|
11255
12088
|
} catch (cleanupErr) {
|
|
11256
12089
|
console.error(`Failed to cleanup interpreter script ${filePath}:`, cleanupErr);
|
|
@@ -11562,7 +12395,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11562
12395
|
};
|
|
11563
12396
|
async execute(input, _options) {
|
|
11564
12397
|
const pattern = input["pattern"];
|
|
11565
|
-
const searchPath = input["path"] ?
|
|
12398
|
+
const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11566
12399
|
const matches = await glob(pattern, {
|
|
11567
12400
|
cwd: searchPath,
|
|
11568
12401
|
ignore: ["node_modules/**", ".git/**", "dist/**", "build/**"],
|
|
@@ -11575,7 +12408,7 @@ var GlobTool = class extends BaseTool {
|
|
|
11575
12408
|
const withMtime = await Promise.all(
|
|
11576
12409
|
matches.map(async (rel) => {
|
|
11577
12410
|
try {
|
|
11578
|
-
const stat = await
|
|
12411
|
+
const stat = await fs5.stat(path25.join(searchPath, rel));
|
|
11579
12412
|
return { rel, mtime: stat.mtimeMs };
|
|
11580
12413
|
} catch {
|
|
11581
12414
|
return { rel, mtime: 0 };
|
|
@@ -11624,7 +12457,7 @@ var GrepTool = class extends BaseTool {
|
|
|
11624
12457
|
};
|
|
11625
12458
|
async execute(input, _options) {
|
|
11626
12459
|
const pattern = input["pattern"];
|
|
11627
|
-
const searchPath = input["path"] ?
|
|
12460
|
+
const searchPath = input["path"] ? path25.resolve(this.workspaceRoot, input["path"]) : this.workspaceRoot;
|
|
11628
12461
|
const globPattern = input["glob"];
|
|
11629
12462
|
const outputMode = input["output_mode"] ?? "content";
|
|
11630
12463
|
const context = input["context"] ?? 0;
|
|
@@ -11678,15 +12511,15 @@ var GrepTool = class extends BaseTool {
|
|
|
11678
12511
|
nodir: true
|
|
11679
12512
|
});
|
|
11680
12513
|
} catch {
|
|
11681
|
-
files = [
|
|
12514
|
+
files = [path25.relative(searchPath, searchPath) || "."];
|
|
11682
12515
|
}
|
|
11683
12516
|
const results = [];
|
|
11684
12517
|
let totalCount = 0;
|
|
11685
12518
|
for (const rel of files) {
|
|
11686
|
-
const abs =
|
|
12519
|
+
const abs = path25.join(searchPath, rel);
|
|
11687
12520
|
let content;
|
|
11688
12521
|
try {
|
|
11689
|
-
content = await
|
|
12522
|
+
content = await fs5.readFile(abs, "utf-8");
|
|
11690
12523
|
} catch {
|
|
11691
12524
|
continue;
|
|
11692
12525
|
}
|
|
@@ -12070,10 +12903,10 @@ var ToolRegistry = class extends EventEmitter {
|
|
|
12070
12903
|
}
|
|
12071
12904
|
isIgnored(filePath) {
|
|
12072
12905
|
if (!filePath) return false;
|
|
12073
|
-
const abs =
|
|
12074
|
-
const rel =
|
|
12075
|
-
if (!rel || rel.startsWith("..") ||
|
|
12076
|
-
const posixRel = rel.split(
|
|
12906
|
+
const abs = path25.resolve(this.workspaceRoot, filePath);
|
|
12907
|
+
const rel = path25.relative(this.workspaceRoot, abs);
|
|
12908
|
+
if (!rel || rel.startsWith("..") || path25.isAbsolute(rel)) return true;
|
|
12909
|
+
const posixRel = rel.split(path25.sep).join("/");
|
|
12077
12910
|
return this.ignoreMatcher.ignores(posixRel);
|
|
12078
12911
|
}
|
|
12079
12912
|
};
|
|
@@ -12307,22 +13140,22 @@ var FileMcpOAuthStore = class {
|
|
|
12307
13140
|
filePath;
|
|
12308
13141
|
load() {
|
|
12309
13142
|
try {
|
|
12310
|
-
return JSON.parse(
|
|
13143
|
+
return JSON.parse(fs24.readFileSync(this.filePath, "utf-8"));
|
|
12311
13144
|
} catch {
|
|
12312
13145
|
return void 0;
|
|
12313
13146
|
}
|
|
12314
13147
|
}
|
|
12315
13148
|
save(state) {
|
|
12316
|
-
|
|
12317
|
-
|
|
13149
|
+
fs24.mkdirSync(path25.dirname(this.filePath), { recursive: true, mode: 448 });
|
|
13150
|
+
fs24.writeFileSync(this.filePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
12318
13151
|
try {
|
|
12319
|
-
|
|
13152
|
+
fs24.chmodSync(this.filePath, 384);
|
|
12320
13153
|
} catch {
|
|
12321
13154
|
}
|
|
12322
13155
|
}
|
|
12323
13156
|
clear() {
|
|
12324
13157
|
try {
|
|
12325
|
-
|
|
13158
|
+
fs24.rmSync(this.filePath);
|
|
12326
13159
|
} catch {
|
|
12327
13160
|
}
|
|
12328
13161
|
}
|
|
@@ -13361,7 +14194,7 @@ var TaskAnalyzer = class {
|
|
|
13361
14194
|
analysisCache.clear();
|
|
13362
14195
|
}
|
|
13363
14196
|
};
|
|
13364
|
-
var DEFAULT_STATS_FILE =
|
|
14197
|
+
var DEFAULT_STATS_FILE = path25.join(os6.homedir(), ".cascade", "model-perf.json");
|
|
13365
14198
|
var ModelPerformanceTracker = class {
|
|
13366
14199
|
stats = /* @__PURE__ */ new Map();
|
|
13367
14200
|
featureStats = /* @__PURE__ */ new Map();
|
|
@@ -13381,7 +14214,7 @@ var ModelPerformanceTracker = class {
|
|
|
13381
14214
|
if (this.loaded) return;
|
|
13382
14215
|
this.loaded = true;
|
|
13383
14216
|
try {
|
|
13384
|
-
const raw = await
|
|
14217
|
+
const raw = await fs5.readFile(this.statsFile, "utf-8");
|
|
13385
14218
|
const parsed = JSON.parse(raw);
|
|
13386
14219
|
if (parsed.models) {
|
|
13387
14220
|
for (const [key, stat] of Object.entries(parsed.models)) this.stats.set(key, stat);
|
|
@@ -13401,15 +14234,15 @@ var ModelPerformanceTracker = class {
|
|
|
13401
14234
|
async save() {
|
|
13402
14235
|
if (this.readOnly) return;
|
|
13403
14236
|
try {
|
|
13404
|
-
await
|
|
14237
|
+
await fs5.mkdir(path25.dirname(this.statsFile), { recursive: true });
|
|
13405
14238
|
const modelsObj = {};
|
|
13406
14239
|
const featuresObj = {};
|
|
13407
14240
|
for (const [key, stat] of this.stats) modelsObj[key] = stat;
|
|
13408
14241
|
for (const [key, stat] of this.featureStats) featuresObj[key] = stat;
|
|
13409
14242
|
const json = JSON.stringify({ models: modelsObj, features: featuresObj }, null, 2);
|
|
13410
14243
|
const tmp = `${this.statsFile}.${process.pid}.${Date.now()}.tmp`;
|
|
13411
|
-
await
|
|
13412
|
-
await
|
|
14244
|
+
await fs5.writeFile(tmp, json, "utf-8");
|
|
14245
|
+
await fs5.rename(tmp, this.statsFile);
|
|
13413
14246
|
} catch {
|
|
13414
14247
|
}
|
|
13415
14248
|
}
|
|
@@ -13949,9 +14782,9 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
13949
14782
|
* any dangerous action, so a silently-reloaded tool can't act without approval. */
|
|
13950
14783
|
async loadPersistedTools() {
|
|
13951
14784
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
13952
|
-
const file =
|
|
14785
|
+
const file = path25.join(this.workspacePath, ".cascade", DYNAMIC_TOOLS_FILE);
|
|
13953
14786
|
try {
|
|
13954
|
-
const raw = await
|
|
14787
|
+
const raw = await fs5.readFile(file, "utf-8");
|
|
13955
14788
|
const specs = JSON.parse(raw);
|
|
13956
14789
|
if (!Array.isArray(specs)) return;
|
|
13957
14790
|
let loaded = 0;
|
|
@@ -13973,11 +14806,11 @@ Required capability: ${description.slice(0, 300)}`;
|
|
|
13973
14806
|
}
|
|
13974
14807
|
async persist() {
|
|
13975
14808
|
if (!this.workspacePath || !this.persistEnabled) return;
|
|
13976
|
-
const dir =
|
|
13977
|
-
const file =
|
|
14809
|
+
const dir = path25.join(this.workspacePath, ".cascade");
|
|
14810
|
+
const file = path25.join(dir, DYNAMIC_TOOLS_FILE);
|
|
13978
14811
|
try {
|
|
13979
|
-
await
|
|
13980
|
-
await
|
|
14812
|
+
await fs5.mkdir(dir, { recursive: true });
|
|
14813
|
+
await fs5.writeFile(file, JSON.stringify(Array.from(this.specs.values()), null, 2), "utf-8");
|
|
13981
14814
|
} catch (err) {
|
|
13982
14815
|
this.log(`[tool-creator] Failed to persist tools: ${err instanceof Error ? err.message : String(err)}`);
|
|
13983
14816
|
}
|
|
@@ -13994,12 +14827,12 @@ var WorldStateDB = class {
|
|
|
13994
14827
|
constructor(workspacePath, debugMode = false) {
|
|
13995
14828
|
this.workspacePath = workspacePath;
|
|
13996
14829
|
this.debugMode = debugMode;
|
|
13997
|
-
const cascadeDir =
|
|
13998
|
-
if (!
|
|
13999
|
-
|
|
14830
|
+
const cascadeDir = path25.join(workspacePath, ".cascade");
|
|
14831
|
+
if (!fs24.existsSync(cascadeDir)) {
|
|
14832
|
+
fs24.mkdirSync(cascadeDir, { recursive: true });
|
|
14000
14833
|
}
|
|
14001
|
-
this.keyPath =
|
|
14002
|
-
this.dbPath =
|
|
14834
|
+
this.keyPath = path25.join(cascadeDir, "world_state.key");
|
|
14835
|
+
this.dbPath = path25.join(cascadeDir, "world_state.db");
|
|
14003
14836
|
this.initEncryptionKey();
|
|
14004
14837
|
this.db = new Database(this.dbPath);
|
|
14005
14838
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -14056,11 +14889,11 @@ var WorldStateDB = class {
|
|
|
14056
14889
|
).run(e, r, row.encrypted_value, row.source_worker, row.timestamp, validTo, change);
|
|
14057
14890
|
}
|
|
14058
14891
|
initEncryptionKey() {
|
|
14059
|
-
if (
|
|
14060
|
-
this.encryptionKey =
|
|
14892
|
+
if (fs24.existsSync(this.keyPath)) {
|
|
14893
|
+
this.encryptionKey = fs24.readFileSync(this.keyPath);
|
|
14061
14894
|
} else {
|
|
14062
14895
|
this.encryptionKey = crypto4.randomBytes(32);
|
|
14063
|
-
|
|
14896
|
+
fs24.writeFileSync(this.keyPath, this.encryptionKey);
|
|
14064
14897
|
}
|
|
14065
14898
|
}
|
|
14066
14899
|
encrypt(text) {
|
|
@@ -14346,9 +15179,9 @@ var WorldStateDB = class {
|
|
|
14346
15179
|
dumpDebugIfNeeded() {
|
|
14347
15180
|
if (!this.debugMode) return;
|
|
14348
15181
|
try {
|
|
14349
|
-
const dumpPath =
|
|
15182
|
+
const dumpPath = path25.join(os6.tmpdir(), "cascade_world_state_debug.json");
|
|
14350
15183
|
const entries = this.getAllEntries();
|
|
14351
|
-
|
|
15184
|
+
fs24.writeFileSync(dumpPath, JSON.stringify(entries, null, 2), "utf-8");
|
|
14352
15185
|
} catch (err) {
|
|
14353
15186
|
console.error("Failed to dump debug world state", err);
|
|
14354
15187
|
}
|
|
@@ -14403,9 +15236,9 @@ var CascadeIgnore = class {
|
|
|
14403
15236
|
]);
|
|
14404
15237
|
}
|
|
14405
15238
|
async load(workspacePath) {
|
|
14406
|
-
const filePath =
|
|
15239
|
+
const filePath = path25.join(workspacePath, ".cascadeignore");
|
|
14407
15240
|
try {
|
|
14408
|
-
const content = await
|
|
15241
|
+
const content = await fs5.readFile(filePath, "utf-8");
|
|
14409
15242
|
const lines = content.split("\n").filter((l) => l.trim() && !l.startsWith("#"));
|
|
14410
15243
|
this.ig.add(lines);
|
|
14411
15244
|
this.loaded = true;
|
|
@@ -14414,7 +15247,7 @@ var CascadeIgnore = class {
|
|
|
14414
15247
|
}
|
|
14415
15248
|
isIgnored(filePath, workspacePath) {
|
|
14416
15249
|
try {
|
|
14417
|
-
const relative = workspacePath ?
|
|
15250
|
+
const relative = workspacePath ? path25.relative(workspacePath, filePath) : filePath;
|
|
14418
15251
|
return this.ig.ignores(relative);
|
|
14419
15252
|
} catch {
|
|
14420
15253
|
return false;
|
|
@@ -14757,7 +15590,7 @@ var WorkspaceIndex = class {
|
|
|
14757
15590
|
isIgnored;
|
|
14758
15591
|
chunker;
|
|
14759
15592
|
constructor(opts) {
|
|
14760
|
-
this.root =
|
|
15593
|
+
this.root = path25.resolve(opts.root);
|
|
14761
15594
|
this.db = opts.db;
|
|
14762
15595
|
this.namespace = opts.namespace ?? "code";
|
|
14763
15596
|
this.extensions = new Set((opts.extensions ?? DEFAULT_EXTENSIONS).map((e) => e.replace(/^\./, "").toLowerCase()));
|
|
@@ -14784,19 +15617,19 @@ var WorkspaceIndex = class {
|
|
|
14784
15617
|
const walk = async (dir) => {
|
|
14785
15618
|
let entries;
|
|
14786
15619
|
try {
|
|
14787
|
-
entries = await
|
|
15620
|
+
entries = await fs5.readdir(dir, { withFileTypes: true });
|
|
14788
15621
|
} catch {
|
|
14789
15622
|
return;
|
|
14790
15623
|
}
|
|
14791
15624
|
for (const entry of entries) {
|
|
14792
|
-
const abs =
|
|
15625
|
+
const abs = path25.join(dir, entry.name);
|
|
14793
15626
|
if (entry.isDirectory()) {
|
|
14794
15627
|
if (HARD_SKIP_DIRS.has(entry.name) || this.isIgnored(abs)) continue;
|
|
14795
15628
|
await walk(abs);
|
|
14796
15629
|
} else if (entry.isFile()) {
|
|
14797
|
-
const ext =
|
|
15630
|
+
const ext = path25.extname(entry.name).slice(1).toLowerCase();
|
|
14798
15631
|
if (!this.extensions.has(ext) || this.isIgnored(abs)) continue;
|
|
14799
|
-
out.push(
|
|
15632
|
+
out.push(path25.relative(this.root, abs));
|
|
14800
15633
|
}
|
|
14801
15634
|
}
|
|
14802
15635
|
};
|
|
@@ -14826,10 +15659,10 @@ var WorkspaceIndex = class {
|
|
|
14826
15659
|
const entries = [];
|
|
14827
15660
|
for (const rel of rels) {
|
|
14828
15661
|
try {
|
|
14829
|
-
const abs =
|
|
14830
|
-
const stat = await
|
|
15662
|
+
const abs = path25.join(this.root, rel);
|
|
15663
|
+
const stat = await fs5.stat(abs);
|
|
14831
15664
|
if (stat.size > this.maxFileBytes) continue;
|
|
14832
|
-
const buf = await
|
|
15665
|
+
const buf = await fs5.readFile(abs);
|
|
14833
15666
|
if (buf.includes(0)) continue;
|
|
14834
15667
|
entries.push({ path: rel, hash: hashContent(buf), text: buf.toString("utf8") });
|
|
14835
15668
|
} catch {
|
|
@@ -15398,7 +16231,7 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15398
16231
|
this.toolRegistry = new ToolRegistry(this.config.tools, workspacePath);
|
|
15399
16232
|
try {
|
|
15400
16233
|
this.router.setDeadModelStore(new DeadModelStore(
|
|
15401
|
-
fileDeadModelPersistence(
|
|
16234
|
+
fileDeadModelPersistence(path25.join(workspacePath, ".cascade", "dead-models.json"))
|
|
15402
16235
|
));
|
|
15403
16236
|
} catch {
|
|
15404
16237
|
}
|
|
@@ -15406,6 +16239,7 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15406
16239
|
(this.config.providers ?? []).map((p) => p.type)
|
|
15407
16240
|
);
|
|
15408
16241
|
this.registerMediaTools(workspacePath);
|
|
16242
|
+
this.registerDocumentTools(workspacePath);
|
|
15409
16243
|
this.telemetry = config.telemetry?.enabled ? new Telemetry(config.telemetry, config.telemetry.distinctId ?? "anonymous") : noopTelemetry;
|
|
15410
16244
|
this.worldStateDB = new WorldStateDB(this.workspacePath, this.config.workspace?.debugWorldState ?? false);
|
|
15411
16245
|
this.router.setWorldStateDB(this.worldStateDB);
|
|
@@ -15485,8 +16319,8 @@ var Cascade = class _Cascade extends EventEmitter {
|
|
|
15485
16319
|
this.emit("log", { level: "warn", message: "Code index enabled but no embeddings-capable provider is configured \u2014 skipping." });
|
|
15486
16320
|
return;
|
|
15487
16321
|
}
|
|
15488
|
-
const dbPath = ci.dbPath ||
|
|
15489
|
-
|
|
16322
|
+
const dbPath = ci.dbPath || path25.join(this.workspacePath, ".cascade", "code-index.db");
|
|
16323
|
+
fs24.mkdirSync(path25.dirname(dbPath), { recursive: true });
|
|
15490
16324
|
const db = new Database(dbPath);
|
|
15491
16325
|
db.pragma("journal_mode = WAL");
|
|
15492
16326
|
const complete = chatCompleterFromProviders(this.config.providers);
|
|
@@ -15826,6 +16660,35 @@ ${input}` }],
|
|
|
15826
16660
|
this.toolRegistry.register(tool);
|
|
15827
16661
|
}
|
|
15828
16662
|
}
|
|
16663
|
+
/**
|
|
16664
|
+
* Register `generate_document` where there is a real workspace to write into.
|
|
16665
|
+
*
|
|
16666
|
+
* A `.docx`/`.pptx`/`.xlsx` is a ZIP of OOXML parts, so `file_write` — which
|
|
16667
|
+
* does exactly what it says and writes the model's text verbatim — produced
|
|
16668
|
+
* files Word/Excel/PowerPoint correctly reported as corrupted. This tool owns
|
|
16669
|
+
* the conversion (see tools/generate-document.ts for why it isn't folded into
|
|
16670
|
+
* file_write).
|
|
16671
|
+
*
|
|
16672
|
+
* Registered OUTSIDE `enabledTools` for the same reason the media tools are:
|
|
16673
|
+
* that allowlist is a blast-radius control for tools that reach the machine
|
|
16674
|
+
* or the network, and this one only writes the artifact the user asked for,
|
|
16675
|
+
* into the workspace the run already owns. `disabledTools` still removes it.
|
|
16676
|
+
* A host with no filesystem (the cloud) has its own browser-side exporter and
|
|
16677
|
+
* simply never reaches this path — `buildDocumentTools` returns nothing
|
|
16678
|
+
* without a file reader, mirroring `transcribe_audio`.
|
|
16679
|
+
*/
|
|
16680
|
+
registerDocumentTools(workspacePath) {
|
|
16681
|
+
const denied = new Set(this.config.tools?.disabledTools ?? []);
|
|
16682
|
+
const tools = buildDocumentTools(async (p) => {
|
|
16683
|
+
const fsp = await import('fs/promises');
|
|
16684
|
+
return new Uint8Array(await fsp.readFile(p));
|
|
16685
|
+
});
|
|
16686
|
+
for (const tool of tools) {
|
|
16687
|
+
if (denied.has(tool.name)) continue;
|
|
16688
|
+
tool.setWorkspaceRoot(workspacePath);
|
|
16689
|
+
this.toolRegistry.register(tool);
|
|
16690
|
+
}
|
|
16691
|
+
}
|
|
15829
16692
|
/**
|
|
15830
16693
|
* Supply per-model thumbs-up/down counts so Auto routing can weigh how models
|
|
15831
16694
|
* have actually performed for this user, on top of public benchmarks.
|
|
@@ -16774,7 +17637,7 @@ var Keystore = class {
|
|
|
16774
17637
|
const creds = await this.keytar.findCredentials(KEYTAR_SERVICE);
|
|
16775
17638
|
this.cache = Object.fromEntries(creds.map((c) => [c.account, c.password]));
|
|
16776
17639
|
this.backend = "keytar";
|
|
16777
|
-
if (password &&
|
|
17640
|
+
if (password && fs24.existsSync(this.storePath)) {
|
|
16778
17641
|
try {
|
|
16779
17642
|
const fileEntries = this.decryptFile(password);
|
|
16780
17643
|
for (const [k, v] of Object.entries(fileEntries)) {
|
|
@@ -16793,7 +17656,7 @@ var Keystore = class {
|
|
|
16793
17656
|
"Keystore unlock requires a password because the OS keychain (keytar) is not available on this system."
|
|
16794
17657
|
);
|
|
16795
17658
|
}
|
|
16796
|
-
if (!
|
|
17659
|
+
if (!fs24.existsSync(this.storePath)) {
|
|
16797
17660
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
16798
17661
|
this.masterKey = this.deriveKey(password, salt);
|
|
16799
17662
|
this.writeWithSalt({}, salt);
|
|
@@ -16807,7 +17670,7 @@ var Keystore = class {
|
|
|
16807
17670
|
}
|
|
16808
17671
|
/** Synchronous legacy unlock kept for AES-only environments. */
|
|
16809
17672
|
unlockSync(password) {
|
|
16810
|
-
if (!
|
|
17673
|
+
if (!fs24.existsSync(this.storePath)) {
|
|
16811
17674
|
const salt = crypto4.randomBytes(SALT_LEN);
|
|
16812
17675
|
this.masterKey = this.deriveKey(password, salt);
|
|
16813
17676
|
this.writeWithSalt({}, salt);
|
|
@@ -16865,7 +17728,7 @@ var Keystore = class {
|
|
|
16865
17728
|
}
|
|
16866
17729
|
}
|
|
16867
17730
|
decryptFile(password, knownSalt) {
|
|
16868
|
-
if (!
|
|
17731
|
+
if (!fs24.existsSync(this.storePath)) return {};
|
|
16869
17732
|
try {
|
|
16870
17733
|
const { salt, ciphertext, iv, tag } = this.readRaw();
|
|
16871
17734
|
const useSalt = knownSalt ?? salt;
|
|
@@ -16887,8 +17750,8 @@ var Keystore = class {
|
|
|
16887
17750
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16888
17751
|
const tag = cipher.getAuthTag();
|
|
16889
17752
|
const out = Buffer.concat([raw.salt, iv, tag, ciphertext]);
|
|
16890
|
-
|
|
16891
|
-
|
|
17753
|
+
fs24.mkdirSync(path25.dirname(this.storePath), { recursive: true });
|
|
17754
|
+
fs24.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16892
17755
|
}
|
|
16893
17756
|
writeWithSalt(data, salt) {
|
|
16894
17757
|
if (!this.masterKey) throw new Error("writeWithSalt called before masterKey was set");
|
|
@@ -16898,11 +17761,11 @@ var Keystore = class {
|
|
|
16898
17761
|
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
16899
17762
|
const tag = cipher.getAuthTag();
|
|
16900
17763
|
const out = Buffer.concat([salt, iv, tag, ciphertext]);
|
|
16901
|
-
|
|
16902
|
-
|
|
17764
|
+
fs24.mkdirSync(path25.dirname(this.storePath), { recursive: true });
|
|
17765
|
+
fs24.writeFileSync(this.storePath, out, { mode: 384 });
|
|
16903
17766
|
}
|
|
16904
17767
|
readRaw() {
|
|
16905
|
-
const buf =
|
|
17768
|
+
const buf = fs24.readFileSync(this.storePath);
|
|
16906
17769
|
let offset = 0;
|
|
16907
17770
|
const salt = buf.subarray(offset, offset + SALT_LEN);
|
|
16908
17771
|
offset += SALT_LEN;
|
|
@@ -16918,9 +17781,9 @@ var Keystore = class {
|
|
|
16918
17781
|
}
|
|
16919
17782
|
};
|
|
16920
17783
|
async function loadCascadeMd(workspacePath) {
|
|
16921
|
-
const filePath =
|
|
17784
|
+
const filePath = path25.join(workspacePath, "CASCADE.md");
|
|
16922
17785
|
try {
|
|
16923
|
-
const raw = await
|
|
17786
|
+
const raw = await fs5.readFile(filePath, "utf-8");
|
|
16924
17787
|
return parseCascadeMd(raw);
|
|
16925
17788
|
} catch {
|
|
16926
17789
|
return null;
|
|
@@ -16949,7 +17812,7 @@ ${raw.trim()}`;
|
|
|
16949
17812
|
var MemoryStore = class _MemoryStore {
|
|
16950
17813
|
db;
|
|
16951
17814
|
constructor(dbPath) {
|
|
16952
|
-
|
|
17815
|
+
fs24.mkdirSync(path25.dirname(dbPath), { recursive: true });
|
|
16953
17816
|
try {
|
|
16954
17817
|
this.db = new Database(dbPath, { timeout: 5e3 });
|
|
16955
17818
|
this.db.pragma("journal_mode = WAL");
|
|
@@ -17785,7 +18648,7 @@ Original error: ${err.message}`
|
|
|
17785
18648
|
}
|
|
17786
18649
|
};
|
|
17787
18650
|
function credentialsPath(globalDir2) {
|
|
17788
|
-
return
|
|
18651
|
+
return path25.join(globalDir2, GLOBAL_CREDENTIALS_FILE);
|
|
17789
18652
|
}
|
|
17790
18653
|
function providerKey(p) {
|
|
17791
18654
|
if (p.type === "azure") {
|
|
@@ -17798,7 +18661,7 @@ function isPersistable(p) {
|
|
|
17798
18661
|
}
|
|
17799
18662
|
function loadGlobalCredentials(globalDir2) {
|
|
17800
18663
|
try {
|
|
17801
|
-
const raw =
|
|
18664
|
+
const raw = fs24.readFileSync(credentialsPath(globalDir2), "utf-8");
|
|
17802
18665
|
const parsed = JSON.parse(raw);
|
|
17803
18666
|
if (!Array.isArray(parsed.providers)) return [];
|
|
17804
18667
|
return parsed.providers.filter(
|
|
@@ -17810,13 +18673,13 @@ function loadGlobalCredentials(globalDir2) {
|
|
|
17810
18673
|
}
|
|
17811
18674
|
function saveGlobalCredentials(globalDir2, providers) {
|
|
17812
18675
|
const filePath = credentialsPath(globalDir2);
|
|
17813
|
-
|
|
18676
|
+
fs24.mkdirSync(globalDir2, { recursive: true, mode: 448 });
|
|
17814
18677
|
const body = { version: 1, providers: providers.filter(isPersistable) };
|
|
17815
18678
|
const tmp = `${filePath}.tmp`;
|
|
17816
|
-
|
|
17817
|
-
|
|
18679
|
+
fs24.writeFileSync(tmp, JSON.stringify(body, null, 2), { encoding: "utf-8", mode: 384 });
|
|
18680
|
+
fs24.renameSync(tmp, filePath);
|
|
17818
18681
|
try {
|
|
17819
|
-
|
|
18682
|
+
fs24.chmodSync(filePath, 384);
|
|
17820
18683
|
} catch {
|
|
17821
18684
|
}
|
|
17822
18685
|
}
|
|
@@ -17851,7 +18714,7 @@ var ConfigManager = class {
|
|
|
17851
18714
|
/** `globalDirOverride` exists for tests — never point it at the real home dir there. */
|
|
17852
18715
|
constructor(workspacePath = process.cwd(), globalDirOverride) {
|
|
17853
18716
|
this.workspacePath = workspacePath;
|
|
17854
|
-
this.globalDir = globalDirOverride ??
|
|
18717
|
+
this.globalDir = globalDirOverride ?? path25.join(os6.homedir(), GLOBAL_CONFIG_DIR);
|
|
17855
18718
|
}
|
|
17856
18719
|
async load() {
|
|
17857
18720
|
this.config = await this.loadConfig();
|
|
@@ -17865,8 +18728,8 @@ var ConfigManager = class {
|
|
|
17865
18728
|
this.ignore = new CascadeIgnore();
|
|
17866
18729
|
await this.ignore.load(this.workspacePath);
|
|
17867
18730
|
this.cascadeMd = await loadCascadeMd(this.workspacePath);
|
|
17868
|
-
this.keystore = new Keystore(
|
|
17869
|
-
this.store = new MemoryStore(
|
|
18731
|
+
this.keystore = new Keystore(path25.join(this.globalDir, GLOBAL_KEYSTORE_FILE));
|
|
18732
|
+
this.store = new MemoryStore(path25.join(this.workspacePath, CASCADE_DB_FILE));
|
|
17870
18733
|
await this.injectEnvKeys();
|
|
17871
18734
|
this.config.providers = mergeGlobalCredentials(this.config.providers, loadGlobalCredentials(this.globalDir));
|
|
17872
18735
|
await this.ensureDefaultIdentity();
|
|
@@ -17891,9 +18754,9 @@ var ConfigManager = class {
|
|
|
17891
18754
|
return this.workspacePath;
|
|
17892
18755
|
}
|
|
17893
18756
|
async save() {
|
|
17894
|
-
const configPath =
|
|
17895
|
-
await
|
|
17896
|
-
await
|
|
18757
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
18758
|
+
await fs5.mkdir(path25.dirname(configPath), { recursive: true });
|
|
18759
|
+
await fs5.writeFile(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
17897
18760
|
try {
|
|
17898
18761
|
saveGlobalCredentials(this.globalDir, this.config.providers);
|
|
17899
18762
|
} catch (err) {
|
|
@@ -17928,9 +18791,9 @@ var ConfigManager = class {
|
|
|
17928
18791
|
return configProvider?.apiKey;
|
|
17929
18792
|
}
|
|
17930
18793
|
async loadConfig() {
|
|
17931
|
-
const configPath =
|
|
18794
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
17932
18795
|
try {
|
|
17933
|
-
const raw = await
|
|
18796
|
+
const raw = await fs5.readFile(configPath, "utf-8");
|
|
17934
18797
|
return validateConfig(JSON.parse(raw));
|
|
17935
18798
|
} catch (err) {
|
|
17936
18799
|
if (err.code === "ENOENT") {
|
|
@@ -18460,7 +19323,7 @@ function aggregateCostStats(sessions, opts = {}) {
|
|
|
18460
19323
|
}
|
|
18461
19324
|
|
|
18462
19325
|
// src/dashboard/server.ts
|
|
18463
|
-
var __dirname$1 =
|
|
19326
|
+
var __dirname$1 = path25.dirname(fileURLToPath(import.meta.url));
|
|
18464
19327
|
var ORPHANED_ESCALATION_GRACE_MS = 3e4;
|
|
18465
19328
|
var DashboardServer = class {
|
|
18466
19329
|
app;
|
|
@@ -18740,14 +19603,14 @@ var DashboardServer = class {
|
|
|
18740
19603
|
*/
|
|
18741
19604
|
persistConfig() {
|
|
18742
19605
|
try {
|
|
18743
|
-
const configPath =
|
|
18744
|
-
|
|
18745
|
-
|
|
19606
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
19607
|
+
fs24.mkdirSync(path25.dirname(configPath), { recursive: true });
|
|
19608
|
+
fs24.writeFileSync(configPath, JSON.stringify(this.config, null, 2), "utf-8");
|
|
18746
19609
|
} catch (err) {
|
|
18747
19610
|
console.warn(`[dashboard] Failed to persist config: ${err instanceof Error ? err.message : String(err)}`);
|
|
18748
19611
|
}
|
|
18749
19612
|
try {
|
|
18750
|
-
saveGlobalCredentials(
|
|
19613
|
+
saveGlobalCredentials(path25.join(os6.homedir(), GLOBAL_CONFIG_DIR), this.config.providers ?? []);
|
|
18751
19614
|
} catch (err) {
|
|
18752
19615
|
console.warn(`[dashboard] Failed to sync global credentials: ${err instanceof Error ? err.message : String(err)}`);
|
|
18753
19616
|
}
|
|
@@ -18762,15 +19625,15 @@ var DashboardServer = class {
|
|
|
18762
19625
|
resolveDashboardSecret() {
|
|
18763
19626
|
const fromConfig = this.config.dashboard.secret ?? process.env["CASCADE_DASHBOARD_SECRET"];
|
|
18764
19627
|
if (fromConfig) return fromConfig;
|
|
18765
|
-
const secretPath =
|
|
19628
|
+
const secretPath = path25.join(this.workspacePath, CASCADE_DASHBOARD_SECRET_FILE);
|
|
18766
19629
|
try {
|
|
18767
|
-
if (
|
|
18768
|
-
const existing =
|
|
19630
|
+
if (fs24.existsSync(secretPath)) {
|
|
19631
|
+
const existing = fs24.readFileSync(secretPath, "utf-8").trim();
|
|
18769
19632
|
if (existing.length >= 16) return existing;
|
|
18770
19633
|
}
|
|
18771
19634
|
const generated = randomUUID();
|
|
18772
|
-
|
|
18773
|
-
|
|
19635
|
+
fs24.mkdirSync(path25.dirname(secretPath), { recursive: true });
|
|
19636
|
+
fs24.writeFileSync(secretPath, generated, { encoding: "utf-8", mode: 384 });
|
|
18774
19637
|
if (this.config.dashboard.auth) {
|
|
18775
19638
|
console.warn(
|
|
18776
19639
|
`Dashboard auth enabled with no secret configured; persisted a generated secret to ${secretPath}. Set CASCADE_DASHBOARD_SECRET or config.dashboard.secret to override.`
|
|
@@ -18797,7 +19660,7 @@ var DashboardServer = class {
|
|
|
18797
19660
|
// ── Setup ─────────────────────────────────────
|
|
18798
19661
|
getGlobalStore() {
|
|
18799
19662
|
if (!this.globalStore) {
|
|
18800
|
-
const globalDbPath =
|
|
19663
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
18801
19664
|
this.globalStore = new MemoryStore(globalDbPath);
|
|
18802
19665
|
}
|
|
18803
19666
|
return this.globalStore;
|
|
@@ -19177,12 +20040,12 @@ ${prompt}`;
|
|
|
19177
20040
|
}
|
|
19178
20041
|
}
|
|
19179
20042
|
watchRuntimeChanges() {
|
|
19180
|
-
const workspaceDbPath =
|
|
19181
|
-
const globalDbPath =
|
|
20043
|
+
const workspaceDbPath = path25.join(this.workspacePath, CASCADE_DB_FILE);
|
|
20044
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19182
20045
|
const watchPaths = [workspaceDbPath, globalDbPath].filter((p, index, arr) => arr.indexOf(p) === index);
|
|
19183
20046
|
for (const watchPath of watchPaths) {
|
|
19184
|
-
if (!
|
|
19185
|
-
|
|
20047
|
+
if (!fs24.existsSync(watchPath)) continue;
|
|
20048
|
+
fs24.watchFile(watchPath, { interval: 3e3 }, () => {
|
|
19186
20049
|
this.throttledBroadcast(watchPath === globalDbPath ? "global" : "workspace");
|
|
19187
20050
|
});
|
|
19188
20051
|
}
|
|
@@ -19304,7 +20167,7 @@ ${prompt}`;
|
|
|
19304
20167
|
const sessionId = req.params.id;
|
|
19305
20168
|
this.store.deleteSession(sessionId);
|
|
19306
20169
|
this.store.deleteRuntimeSession(sessionId);
|
|
19307
|
-
const globalDbPath =
|
|
20170
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19308
20171
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19309
20172
|
try {
|
|
19310
20173
|
globalStore.deleteRuntimeSession(sessionId);
|
|
@@ -19397,7 +20260,7 @@ ${prompt}`;
|
|
|
19397
20260
|
});
|
|
19398
20261
|
this.app.delete("/api/sessions", auth2, (req, res) => {
|
|
19399
20262
|
const body = req.body;
|
|
19400
|
-
const globalDbPath =
|
|
20263
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19401
20264
|
if (body?.ids && Array.isArray(body.ids) && body.ids.length > 0) {
|
|
19402
20265
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19403
20266
|
try {
|
|
@@ -19420,7 +20283,7 @@ ${prompt}`;
|
|
|
19420
20283
|
});
|
|
19421
20284
|
this.app.delete("/api/runtime", auth2, (_req, res) => {
|
|
19422
20285
|
this.store.deleteAllRuntimeNodes();
|
|
19423
|
-
const globalDbPath =
|
|
20286
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19424
20287
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19425
20288
|
try {
|
|
19426
20289
|
globalStore.deleteAllRuntimeNodes();
|
|
@@ -19506,12 +20369,12 @@ ${prompt}`;
|
|
|
19506
20369
|
if (body["tierLimits"]) this.config.tierLimits = { ...this.config.tierLimits, ...body["tierLimits"] };
|
|
19507
20370
|
if (body["budget"]) this.config.budget = { ...this.config.budget, ...body["budget"] };
|
|
19508
20371
|
try {
|
|
19509
|
-
const configPath =
|
|
19510
|
-
const existing =
|
|
20372
|
+
const configPath = path25.join(this.workspacePath, CASCADE_CONFIG_FILE);
|
|
20373
|
+
const existing = fs24.existsSync(configPath) ? JSON.parse(fs24.readFileSync(configPath, "utf-8")) : {};
|
|
19511
20374
|
const updated = { ...existing, tierLimits: this.config.tierLimits, budget: this.config.budget };
|
|
19512
20375
|
const tmp = configPath + ".tmp";
|
|
19513
|
-
|
|
19514
|
-
|
|
20376
|
+
fs24.writeFileSync(tmp, JSON.stringify(updated, null, 2), "utf-8");
|
|
20377
|
+
fs24.renameSync(tmp, configPath);
|
|
19515
20378
|
res.json({ ok: true });
|
|
19516
20379
|
} catch (err) {
|
|
19517
20380
|
res.status(500).json({ error: `Failed to save config: ${err instanceof Error ? err.message : String(err)}` });
|
|
@@ -19539,7 +20402,7 @@ ${prompt}`;
|
|
|
19539
20402
|
this.app.get("/api/runtime", auth2, (req, res) => {
|
|
19540
20403
|
const scope = req.query["scope"] ?? "workspace";
|
|
19541
20404
|
if (scope === "global") {
|
|
19542
|
-
const globalDbPath =
|
|
20405
|
+
const globalDbPath = path25.join(os6.homedir(), GLOBAL_CONFIG_DIR, GLOBAL_RUNTIME_DB_FILE);
|
|
19543
20406
|
const globalStore = new MemoryStore(globalDbPath);
|
|
19544
20407
|
try {
|
|
19545
20408
|
res.json({
|
|
@@ -19880,13 +20743,13 @@ ${prompt}`;
|
|
|
19880
20743
|
res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
|
|
19881
20744
|
}
|
|
19882
20745
|
});
|
|
19883
|
-
const prodPath =
|
|
19884
|
-
const devPath =
|
|
19885
|
-
const webDistPath =
|
|
19886
|
-
if (
|
|
20746
|
+
const prodPath = path25.resolve(__dirname$1, "../web/dist");
|
|
20747
|
+
const devPath = path25.resolve(__dirname$1, "../../web/dist");
|
|
20748
|
+
const webDistPath = fs24.existsSync(prodPath) ? prodPath : devPath;
|
|
20749
|
+
if (fs24.existsSync(webDistPath)) {
|
|
19887
20750
|
this.app.use(express.static(webDistPath));
|
|
19888
20751
|
this.app.get("*", (_req, res) => {
|
|
19889
|
-
res.sendFile(
|
|
20752
|
+
res.sendFile(path25.join(webDistPath, "index.html"));
|
|
19890
20753
|
});
|
|
19891
20754
|
} else {
|
|
19892
20755
|
this.app.get("/", (_req, res) => {
|
|
@@ -20073,14 +20936,14 @@ function planRetrieval(ctx) {
|
|
|
20073
20936
|
}
|
|
20074
20937
|
var CLOUD_SESSION_FILE = "cloud-session.json";
|
|
20075
20938
|
function globalDir(homeDir = os6.homedir()) {
|
|
20076
|
-
return
|
|
20939
|
+
return path25.join(homeDir, GLOBAL_CONFIG_DIR);
|
|
20077
20940
|
}
|
|
20078
20941
|
function cloudSessionPath(dir = globalDir()) {
|
|
20079
|
-
return
|
|
20942
|
+
return path25.join(dir, CLOUD_SESSION_FILE);
|
|
20080
20943
|
}
|
|
20081
20944
|
function loadCloudSession(dir = globalDir()) {
|
|
20082
20945
|
try {
|
|
20083
|
-
const raw =
|
|
20946
|
+
const raw = fs24.readFileSync(cloudSessionPath(dir), "utf-8");
|
|
20084
20947
|
const parsed = JSON.parse(raw);
|
|
20085
20948
|
if (!parsed?.refreshToken || !parsed?.serverUrl) return null;
|
|
20086
20949
|
return parsed;
|
|
@@ -20089,17 +20952,17 @@ function loadCloudSession(dir = globalDir()) {
|
|
|
20089
20952
|
}
|
|
20090
20953
|
}
|
|
20091
20954
|
function saveCloudSession(session, dir = globalDir()) {
|
|
20092
|
-
|
|
20955
|
+
fs24.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
20093
20956
|
const file = cloudSessionPath(dir);
|
|
20094
|
-
|
|
20957
|
+
fs24.writeFileSync(file, JSON.stringify(session, null, 2), { mode: 384 });
|
|
20095
20958
|
try {
|
|
20096
|
-
|
|
20959
|
+
fs24.chmodSync(file, 384);
|
|
20097
20960
|
} catch {
|
|
20098
20961
|
}
|
|
20099
20962
|
}
|
|
20100
20963
|
function clearCloudSession(dir = globalDir()) {
|
|
20101
20964
|
try {
|
|
20102
|
-
|
|
20965
|
+
fs24.rmSync(cloudSessionPath(dir));
|
|
20103
20966
|
} catch {
|
|
20104
20967
|
}
|
|
20105
20968
|
}
|
|
@@ -20271,16 +21134,16 @@ var CloudClient = class _CloudClient {
|
|
|
20271
21134
|
return (await this.refresh()).accessToken;
|
|
20272
21135
|
}
|
|
20273
21136
|
/** Authenticated request that refreshes once on a 401 (e.g. server-side revoke). */
|
|
20274
|
-
async authed(
|
|
20275
|
-
const call = (token) => fetch(this.url(
|
|
21137
|
+
async authed(path27, init = {}) {
|
|
21138
|
+
const call = (token) => fetch(this.url(path27), { ...init, headers: { ...init.headers ?? {}, Authorization: `Bearer ${token}` } });
|
|
20276
21139
|
let res = await call(await this.accessToken());
|
|
20277
21140
|
if (res.status === 401) res = await call((await this.refresh()).accessToken);
|
|
20278
21141
|
if (!res.ok) throw new Error(`Request failed (${res.status}).`);
|
|
20279
21142
|
const text = await res.text();
|
|
20280
21143
|
return text ? JSON.parse(text) : {};
|
|
20281
21144
|
}
|
|
20282
|
-
authedGet(
|
|
20283
|
-
return this.authed(
|
|
21145
|
+
authedGet(path27) {
|
|
21146
|
+
return this.authed(path27);
|
|
20284
21147
|
}
|
|
20285
21148
|
async fetchMe(token) {
|
|
20286
21149
|
const res = await fetch(this.url("/api/me"), { headers: { Authorization: `Bearer ${token}` } });
|
|
@@ -20617,6 +21480,6 @@ function applySyncBundle(bundle, config) {
|
|
|
20617
21480
|
return next;
|
|
20618
21481
|
}
|
|
20619
21482
|
|
|
20620
|
-
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildManifest, buildSessionTranscript, cagCharBudget, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, isMcpToolName, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseRankOrder, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, runCascade, sessionWorthRemembering, streamCascade, uniqueMcpServerName };
|
|
21483
|
+
export { AZURE_BASE_MODELS, AZURE_BASE_URL_TEMPLATE, AuditLogger, CASCADE_AUDIT_FILE, CASCADE_CONFIG_DIR, CASCADE_CONFIG_FILE, CASCADE_DASHBOARD_SECRET_FILE, CASCADE_DB_FILE, CASCADE_IGNORE_FILE, CASCADE_KEYSTORE_FILE, CASCADE_MD_FILE, CASCADE_VERSION, CHARS_PER_TOKEN, COMPLEXITY_T2_COUNT, Cascade, CascadeCancelledError, CascadeIgnore, CascadeRouter, CascadeToolError, CloudClient, CodeSearchTool, ConfigManager, CurrentPageTool, DEFAULT_API_PORT, DEFAULT_APPROVAL_REQUIRED, DEFAULT_AUTO_SUMMARIZE_AT, DEFAULT_CLOUD_URL, DEFAULT_CONTEXT_LIMIT, DEFAULT_DASHBOARD_PORT, DEFAULT_MAX_SESSION_MESSAGES, DEFAULT_RETENTION_DAYS, DEFAULT_THEME, DOCUMENT_MIME, DashboardServer, FileMcpOAuthStore, GITHUB_MODELS_API_VERSION, GITHUB_MODELS_CATALOG_URL, GITHUB_MODELS_INFERENCE_URL, GLOBAL_CONFIG_DIR, GLOBAL_CREDENTIALS_FILE, GLOBAL_DB_FILE, GLOBAL_KEYSTORE_FILE, GLOBAL_RUNTIME_DB_FILE, GenerateDocumentTool, GraphRetriever, GraphSearchTool, HooksRunner, Keystore, LLMReranker, LM_STUDIO_BASE_URL, MAX_PAGE_TEXT_CHARS, MCP_TOOL_PREFIX, MODELS, McpClient, McpOAuthProvider, MemoryStore, OLLAMA_BASE_URL, OpenAICompatibleEmbedder, PROVIDER_DISPLAY_NAMES, Retriever, SqliteVectorStore, T1Administrator, T1_MODEL_PRIORITY, T2Manager, T2_MODEL_PRIORITY, T3Worker, T3_MODEL_PRIORITY, THEME_NAMES, TOOL_NAMES, TaskScheduler, Telemetry, ToolRegistry, VISION_MODEL_PRIORITY, WorkspaceIndex, applySyncBundle, azureModelForDeployment, beginMcpOAuth, buildDocumentTools, buildManifest, buildSessionTranscript, bytesToBase64, cagCharBudget, chartKind, chartToTableRows, chatCompleterFromProviders, chunkCode, chunkText2 as chunkText, completeMcpOAuth, connectMcpWithLoopbackOAuth, createCascade, decryptJSON as decryptSyncBlob, diffManifest, disambiguateMcpServerNames, discoverMcpAuthServer, discoverMcpTools, distillSessionFacts, embedderFromProviders, encryptJSON as encryptSyncBlob, extractCharts, fileExt, fileOAuthProvider, gatherSyncBundle, hashContent, heuristicCodeChunker, inferAzureBaseModel, inlineRuns, isDocumentFormat, isMcpToolName, matchImageLine, mcpServerPrefix, mcpToolName, nodeHttpFetch, parseBlocks, parseChartSpec, parseDelimited, parseRankOrder, parseSlide, planRetrieval, preferIpv4Host, reciprocalRankFusion, refreshMcpToken, removeMcpServerDenials, renderDocument, renderDocx, renderPptx, renderXlsx, runCascade, sessionWorthRemembering, sniffImage, splitSlides, streamCascade, stripInline, uniqueMcpServerName };
|
|
20621
21484
|
//# sourceMappingURL=index.js.map
|
|
20622
21485
|
//# sourceMappingURL=index.js.map
|