dist / analysisHandoff.js
dist / analysisHandoff.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.completeDeferredAnalyses = completeDeferredAnalyses;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const images_1 = require("./images");
const digest = (bytes) => (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex");
async function completeDeferredAnalyses(history, messages, getRoot, signal, analyze) {
let calls = new Set();
let pending = [];
for (const message of history) {
// Only the most recent tool round is eligible. Older results must not keep
// reattaching images after the assistant has already analyzed them.
if (message.getRole() === "user" || message.getRole() === "assistant") {
pending = [];
calls = new Set(message.getToolCallRequests().filter(call => call.name.replace(/[^a-z]/gi, "").toLowerCase().endsWith("analyzelocalimageadvanced")).map(call => call.id).filter((id) => Boolean(id)));
}
if (message.getRole() !== "tool")
continue;
for (const result of message.getToolCallResults()) {
if (!result.toolCallId || !calls.has(result.toolCallId))
continue;
let value;
try {
value = JSON.parse(result.content);
}
catch {
continue;
}
if (value.analysisDeferred !== true || value._aiaStrataAnalysis?.version !== 1)
continue;
pending.push({ value, toolCallId: result.toolCallId });
}
}
if (!pending.length)
return messages;
const root = await (0, promises_1.realpath)(getRoot());
const rootHash = digest(Buffer.from(root));
let total = 0;
const completed = new Map();
for (const { value, toolCallId } of pending) {
signal.throwIfAborted();
const candidate = value._aiaImageCandidate;
const prompt = value._aiaStrataAnalysis.prompt;
if (candidate?.version !== 1 || candidate.rootHash !== rootHash || typeof candidate.relativePath !== "string"
|| typeof prompt !== "string" || !prompt.trim() || prompt.length > 128 * 1024
|| !/^[a-f0-9]{64}$/.test(candidate.sha256) || !Number.isFinite(candidate.createdAt)
|| Date.now() - candidate.createdAt > 24 * 60 * 60 * 1000 || candidate.createdAt > Date.now() + 60_000
|| (0, node_path_1.isAbsolute)(candidate.relativePath))
throw new Error("Invalid deferred image-analysis receipt.");
const target = await (0, promises_1.realpath)((0, node_path_1.resolve)(root, candidate.relativePath));
const rel = (0, node_path_1.relative)(root, target);
if (!rel || (0, node_path_1.isAbsolute)(rel) || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\"))
throw new Error("Deferred image escapes the chat working directory.");
const bytes = await (0, images_1.readImage)(target, signal);
if (digest(bytes) !== candidate.sha256)
throw new Error("Deferred image changed after tool verification. Run Analyze again.");
total += bytes.length;
if (total > images_1.TOTAL_IMAGE_LIMIT)
throw new Error("Deferred analysis images exceed 64 MiB.");
// Complete the analysis in an isolated request. Its output is evidence for
// this exact tool call, never a new user instruction in the main dialogue.
const analysis = await analyze(`data:${(0, images_1.imageMime)(bytes)};base64,${bytes.toString("base64")}`, prompt);
if (!analysis.trim())
throw new Error("Strata returned an empty image analysis.");
completed.set(toolCallId, JSON.stringify({ analysis, _aiaImageCandidate: candidate }));
}
return messages.map(message => message.role === "tool" && message.tool_call_id && completed.has(message.tool_call_id)
? { ...message, content: completed.get(message.tool_call_id) } : message);
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.completeDeferredAnalyses = completeDeferredAnalyses;
const node_crypto_1 = require("node:crypto");
const promises_1 = require("node:fs/promises");
const node_path_1 = require("node:path");
const images_1 = require("./images");
const digest = (bytes) => (0, node_crypto_1.createHash)("sha256").update(bytes).digest("hex");
async function completeDeferredAnalyses(history, messages, getRoot, signal, analyze) {
let calls = new Set();
let pending = [];
for (const message of history) {
// Only the most recent tool round is eligible. Older results must not keep
// reattaching images after the assistant has already analyzed them.
if (message.getRole() === "user" || message.getRole() === "assistant") {
pending = [];
calls = new Set(message.getToolCallRequests().filter(call => call.name.replace(/[^a-z]/gi, "").toLowerCase().endsWith("analyzelocalimageadvanced")).map(call => call.id).filter((id) => Boolean(id)));
}
if (message.getRole() !== "tool")
continue;
for (const result of message.getToolCallResults()) {
if (!result.toolCallId || !calls.has(result.toolCallId))
continue;
let value;
try {
value = JSON.parse(result.content);
}
catch {
continue;
}
if (value.analysisDeferred !== true || value._aiaStrataAnalysis?.version !== 1)
continue;
pending.push({ value, toolCallId: result.toolCallId });
}
}
if (!pending.length)
return messages;
const root = await (0, promises_1.realpath)(getRoot());
const rootHash = digest(Buffer.from(root));
let total = 0;
const completed = new Map();
for (const { value, toolCallId } of pending) {
signal.throwIfAborted();
const candidate = value._aiaImageCandidate;
const prompt = value._aiaStrataAnalysis.prompt;
if (candidate?.version !== 1 || candidate.rootHash !== rootHash || typeof candidate.relativePath !== "string"
|| typeof prompt !== "string" || !prompt.trim() || prompt.length > 128 * 1024
|| !/^[a-f0-9]{64}$/.test(candidate.sha256) || !Number.isFinite(candidate.createdAt)
|| Date.now() - candidate.createdAt > 24 * 60 * 60 * 1000 || candidate.createdAt > Date.now() + 60_000
|| (0, node_path_1.isAbsolute)(candidate.relativePath))
throw new Error("Invalid deferred image-analysis receipt.");
const target = await (0, promises_1.realpath)((0, node_path_1.resolve)(root, candidate.relativePath));
const rel = (0, node_path_1.relative)(root, target);
if (!rel || (0, node_path_1.isAbsolute)(rel) || rel === ".." || rel.startsWith("../") || rel.startsWith("..\\"))
throw new Error("Deferred image escapes the chat working directory.");
const bytes = await (0, images_1.readImage)(target, signal);
if (digest(bytes) !== candidate.sha256)
throw new Error("Deferred image changed after tool verification. Run Analyze again.");
total += bytes.length;
if (total > images_1.TOTAL_IMAGE_LIMIT)
throw new Error("Deferred analysis images exceed 64 MiB.");
// Complete the analysis in an isolated request. Its output is evidence for
// this exact tool call, never a new user instruction in the main dialogue.
const analysis = await analyze(`data:${(0, images_1.imageMime)(bytes)};base64,${bytes.toString("base64")}`, prompt);
if (!analysis.trim())
throw new Error("Strata returned an empty image analysis.");
completed.set(toolCallId, JSON.stringify({ analysis, _aiaImageCandidate: candidate }));
}
return messages.map(message => message.role === "tool" && message.tool_call_id && completed.has(message.tool_call_id)
? { ...message, content: completed.get(message.tool_call_id) } : message);
}