dist / messages.js
dist / messages.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toMessages = toMessages;
const images_1 = require("./images");
const preprocess_1 = require("./preprocess");
const imageSnapshots_1 = require("./imageSnapshots");
async function toMessages(history, client, signal, options = {}) {
const messages = [];
const snapshots = options.getSnapshotRoot ? new imageSnapshots_1.ImageSnapshots(options.getSnapshotRoot) : undefined;
const warn = options.warn ?? (() => { });
const entries = [...history];
const lastUserIndex = entries.reduce((last, message, index) => message.getRole() === "user" ? index : last, -1);
let total = 0;
for (const [index, message] of entries.entries()) {
signal.throwIfAborted();
const role = message.getRole();
const historical = index < lastUserIndex || entries.slice(index + 1).some(entry => entry.getRole() === "assistant");
const originalText = message.getText();
// Generator selection does not automatically enable the plugin's preprocessor.
// Handle explicit user commands here too, so no separate integration is required.
const command = role === "user" ? (0, preprocess_1.parseImageCommands)(originalText) : { text: originalText, paths: [] };
const text = command.paths.length ? command.text || "Describe the attached images." : originalText;
if (role === "tool") {
for (const result of message.getToolCallResults()) {
if (!result.toolCallId)
throw new Error("Tool result has no call ID; cannot forward it to Strata.");
messages.push({ role, tool_call_id: result.toolCallId, content: result.content });
}
continue;
}
const files = message.getFiles(client);
if (files.length && role !== "user")
throw new Error("Image attachments must be on a user message.");
const content = text ? [{ type: "text", text }] : [];
const load = async (identity, name, read) => {
try {
const bytes = await read();
if (snapshots)
await snapshots.remember(identity, bytes).catch(() => warn("Could not retain an image snapshot; the live image will still be sent."));
return bytes;
}
catch (error) {
signal.throwIfAborted();
if (!["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
throw error;
const cached = await snapshots?.recover(identity, signal).catch(() => undefined);
signal.throwIfAborted();
if (cached)
return cached;
if (!historical)
throw new Error(`Image attachment "${name}" is no longer available. Please attach it again.`, { cause: error });
warn(`An earlier image is no longer available: ${name}`);
content.push({ type: "text", text: `[Earlier image attachment unavailable: ${name}. Its image data is not included in this request. Do not claim to inspect it; ask for reattachment if its contents are needed.]` });
return;
}
};
for (const file of files) {
if (!file.isImage())
throw new Error(`Unsupported attachment: ${file.name}. This adapter forwards images only.`);
const bytes = await load(JSON.stringify([file.identifier, file.name, file.sizeBytes]), file.name, () => (0, images_1.readAttachment)(file, signal));
if (!bytes)
continue;
total += bytes.length;
if (total > images_1.TOTAL_IMAGE_LIMIT)
throw new Error("Conversation images exceed 64 MiB. Start a new chat or remove old images.");
content.push({ type: "image_url", image_url: { url: `data:${(0, images_1.imageMime)(bytes)};base64,${bytes.toString("base64")}` } });
}
for (const path of command.paths) {
const bytes = await load(`command:${path}`, path, () => (0, images_1.readImage)(path, signal));
if (!bytes)
continue;
total += bytes.length;
if (total > images_1.TOTAL_IMAGE_LIMIT)
throw new Error("Conversation images exceed 64 MiB. Start a new chat or remove old images.");
content.push({ type: "image_url", image_url: { url: `data:${(0, images_1.imageMime)(bytes)};base64,${bytes.toString("base64")}` } });
}
const calls = role === "assistant" ? message.getToolCallRequests().map(call => {
if (!call.id)
throw new Error("Tool call has no ID; cannot preserve tool history.");
return { id: call.id, type: "function", function: { name: call.name, arguments: JSON.stringify(call.arguments ?? {}) } };
}) : [];
messages.push({ role, content: files.length || command.paths.length ? content : text, ...(calls.length ? { tool_calls: calls } : {}) });
}
return messages;
}
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.toMessages = toMessages;
const images_1 = require("./images");
const preprocess_1 = require("./preprocess");
const imageSnapshots_1 = require("./imageSnapshots");
async function toMessages(history, client, signal, options = {}) {
const messages = [];
const snapshots = options.getSnapshotRoot ? new imageSnapshots_1.ImageSnapshots(options.getSnapshotRoot) : undefined;
const warn = options.warn ?? (() => { });
const entries = [...history];
const lastUserIndex = entries.reduce((last, message, index) => message.getRole() === "user" ? index : last, -1);
let total = 0;
for (const [index, message] of entries.entries()) {
signal.throwIfAborted();
const role = message.getRole();
const historical = index < lastUserIndex || entries.slice(index + 1).some(entry => entry.getRole() === "assistant");
const originalText = message.getText();
// Generator selection does not automatically enable the plugin's preprocessor.
// Handle explicit user commands here too, so no separate integration is required.
const command = role === "user" ? (0, preprocess_1.parseImageCommands)(originalText) : { text: originalText, paths: [] };
const text = command.paths.length ? command.text || "Describe the attached images." : originalText;
if (role === "tool") {
for (const result of message.getToolCallResults()) {
if (!result.toolCallId)
throw new Error("Tool result has no call ID; cannot forward it to Strata.");
messages.push({ role, tool_call_id: result.toolCallId, content: result.content });
}
continue;
}
const files = message.getFiles(client);
if (files.length && role !== "user")
throw new Error("Image attachments must be on a user message.");
const content = text ? [{ type: "text", text }] : [];
const load = async (identity, name, read) => {
try {
const bytes = await read();
if (snapshots)
await snapshots.remember(identity, bytes).catch(() => warn("Could not retain an image snapshot; the live image will still be sent."));
return bytes;
}
catch (error) {
signal.throwIfAborted();
if (!["ENOENT", "ENOTDIR"].includes(error.code ?? ""))
throw error;
const cached = await snapshots?.recover(identity, signal).catch(() => undefined);
signal.throwIfAborted();
if (cached)
return cached;
if (!historical)
throw new Error(`Image attachment "${name}" is no longer available. Please attach it again.`, { cause: error });
warn(`An earlier image is no longer available: ${name}`);
content.push({ type: "text", text: `[Earlier image attachment unavailable: ${name}. Its image data is not included in this request. Do not claim to inspect it; ask for reattachment if its contents are needed.]` });
return;
}
};
for (const file of files) {
if (!file.isImage())
throw new Error(`Unsupported attachment: ${file.name}. This adapter forwards images only.`);
const bytes = await load(JSON.stringify([file.identifier, file.name, file.sizeBytes]), file.name, () => (0, images_1.readAttachment)(file, signal));
if (!bytes)
continue;
total += bytes.length;
if (total > images_1.TOTAL_IMAGE_LIMIT)
throw new Error("Conversation images exceed 64 MiB. Start a new chat or remove old images.");
content.push({ type: "image_url", image_url: { url: `data:${(0, images_1.imageMime)(bytes)};base64,${bytes.toString("base64")}` } });
}
for (const path of command.paths) {
const bytes = await load(`command:${path}`, path, () => (0, images_1.readImage)(path, signal));
if (!bytes)
continue;
total += bytes.length;
if (total > images_1.TOTAL_IMAGE_LIMIT)
throw new Error("Conversation images exceed 64 MiB. Start a new chat or remove old images.");
content.push({ type: "image_url", image_url: { url: `data:${(0, images_1.imageMime)(bytes)};base64,${bytes.toString("base64")}` } });
}
const calls = role === "assistant" ? message.getToolCallRequests().map(call => {
if (!call.id)
throw new Error("Tool call has no ID; cannot preserve tool history.");
return { id: call.id, type: "function", function: { name: call.name, arguments: JSON.stringify(call.arguments ?? {}) } };
}) : [];
messages.push({ role, content: files.length || command.paths.length ? content : text, ...(calls.length ? { tool_calls: calls } : {}) });
}
return messages;
}