@sema-agent/core
Version:
Stateless, task-oriented AI agent core
185 lines (184 loc) • 7.38 kB
JavaScript
import { Value } from "typebox/value";
const MAX_ERROR_CHARS = 10_000;
const HALF = MAX_ERROR_CHARS / 2;
import { isHighSurrogate, isLowSurrogate, sliceHeadSafe, sliceTailSafe } from "./surrogate-safe-slice.js";
import { isErrorLike, readMember, thrownName, thrownText } from "./thrown-value.js";
export function truncateError(s) {
if (s.length <= MAX_ERROR_CHARS)
return s;
const head = sliceHeadSafe(s, HALF);
const tail = sliceTailSafe(s, HALF);
const removed = s.length - head.length - tail.length;
return `${head}\n\n... [${removed} characters truncated] ...\n\n${tail}`;
}
export const OUTPUT_BUDGET_MIN_CHARS = 512;
export function clipWithFilePointer(s, limit, fullOutputPath) {
if (s.length <= limit)
return s;
if (fullOutputPath !== undefined) {
const header = `[Truncated. Full output: ${fullOutputPath}]\n\n`;
const room = limit - header.length;
return room > 0 ? header + s.slice(-room) : header.trimEnd().slice(0, Math.min(limit, s.length));
}
const omittedCount = (kept) => s.length - kept;
const markerFor = (kept) => `\n…[${s.length} chars total, ${omittedCount(kept)} omitted from the middle]…\n`;
const budget = limit - markerFor(0).length;
if (budget < 2)
return s.slice(0, limit);
const half = Math.floor(budget / 2);
const headEnd = half > 0 && isHighSurrogate(s.charCodeAt(half - 1)) ? half - 1 : half;
const tailStart = half > 0 && isLowSurrogate(s.charCodeAt(s.length - half)) ? s.length - half + 1 : s.length - half;
const kept = headEnd + (s.length - tailStart);
return `${s.slice(0, headEnd)}${markerFor(kept)}${s.slice(tailStart)}`;
}
function isShellLike(e) {
try {
return typeof e === "object" && e !== null && ("exitCode" in e || "stderr" in e || "stdout" in e);
}
catch {
return false;
}
}
export function formatToolError(error) {
const parts = [];
const name = thrownName(error);
if (name === "AbortError")
return "Interrupted";
const message = readMember(error, "message");
const messageText = typeof message === "string" ? message : undefined;
if (isShellLike(error)) {
const exitCode = readMember(error, "exitCode");
const stderr = readMember(error, "stderr");
const stdout = readMember(error, "stdout");
if (typeof exitCode === "number")
parts.push(`Exit code ${exitCode}`);
if (isErrorLike(error) && messageText !== undefined && messageText !== "")
parts.push(messageText);
if (typeof stderr === "string" && stderr.trim())
parts.push(stderr);
if (typeof stdout === "string" && stdout.trim())
parts.push(stdout);
}
else if (isErrorLike(error)) {
parts.push((messageText ?? "") || (name ?? "") || "Error");
}
else {
parts.push(thrownText(error));
}
if (parts.length === 0)
parts.push(thrownText(error));
return truncateError(parts.join("\n").trim() || "Error");
}
const BRAIN_FLAT_CODES = new Set([
"auth",
"rate_limit",
"invalid_request",
"server",
"network",
"http",
"stream_torn",
"refusal",
"length_empty",
]);
const EXACT_CODE_CLASS = new Map([
["limits.max_tokens_exceeded", "budget"],
["limits.max_cost_exceeded", "budget"],
["limits.max_turns_exceeded", "limit"],
["limits.max_walltime_exceeded", "limit"],
["memory.admission_required", "config"],
["memory.admission_denied", "config"],
["memory.capture_optout_denied", "config"],
["env.lifetime_expired", "config"],
]);
const DOTTED_PREFIX_CLASS = [
["limits.", "limit"],
["output.", "output"],
["suspend.", "suspend"],
["review.", "review"],
["unexpected.", "unexpected"],
["oracle.", "oracle"],
["resume.", "resume"],
["config.", "config"],
["usage.", "config"],
];
export function errorClassOf(errorCode) {
if (!errorCode)
return "unknown";
const exact = EXACT_CODE_CLASS.get(errorCode);
if (exact !== undefined)
return exact;
for (const [prefix, cls] of DOTTED_PREFIX_CLASS) {
if (errorCode.startsWith(prefix))
return cls;
}
if (errorCode === "conflict")
return "conflict";
if (errorCode === "aborted")
return "aborted";
if (BRAIN_FLAT_CODES.has(errorCode))
return "brain";
return "unknown";
}
export function formatValidationPath(path) {
const segs = path.split("/").filter((s) => s.length > 0);
let out = "";
for (const seg of segs) {
if (/^\d+$/.test(seg))
out += `[${seg}]`;
else
out += out === "" ? seg : `.${seg}`;
}
return out;
}
function locateInvalidParam(schema, value, prefix) {
const s = schema;
if (s.type === "object" && s.properties && value !== null && typeof value === "object" && !Array.isArray(value)) {
const obj = value;
for (const name of s.required ?? []) {
if (!(name in obj))
return { accessor: prefix ? `${prefix}.${name}` : name, missing: true };
}
for (const [name, sub] of Object.entries(s.properties)) {
if (name in obj && !Value.Check(sub, obj[name])) {
return locateInvalidParam(sub, obj[name], prefix ? `${prefix}.${name}` : name);
}
}
}
else if (s.type === "array" && s.items && Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
if (!Value.Check(s.items, value[i]))
return locateInvalidParam(s.items, value[i], `${prefix}[${i}]`);
}
const a = s;
if (typeof a.minItems === "number" && value.length < a.minItems) {
const bound = typeof a.maxItems === "number" ? `between ${a.minItems} and ${a.maxItems}` : `at least ${a.minItems}`;
return { accessor: prefix, type: `an array with ${bound} items (got ${value.length})` };
}
if (typeof a.maxItems === "number" && value.length > a.maxItems) {
const bound = typeof a.minItems === "number" ? `between ${a.minItems} and ${a.maxItems}` : `at most ${a.maxItems}`;
return { accessor: prefix, type: `an array with ${bound} items (got ${value.length})` };
}
}
return { accessor: prefix, type: s.type };
}
export function formatZodValidationError(schema, value) {
const s = schema;
if (s.type === "object" && s.properties && typeof value === "object" && value !== null) {
const obj = value;
for (const name of s.required ?? []) {
if (!(name in obj))
return `The required parameter \`${name}\` is missing.`;
}
for (const [name, sub] of Object.entries(s.properties)) {
if (name in obj && !Value.Check(sub, obj[name])) {
const loc = locateInvalidParam(sub, obj[name], name);
if (loc.missing)
return `The required parameter \`${loc.accessor}\` is missing.`;
return `The parameter \`${loc.accessor}\` is invalid${loc.type ? `: expected ${loc.type}` : ""}.`;
}
}
}
const errs = [...Value.Errors(schema, value)];
const msg = errs[0]?.message?.trim();
return msg ? `Invalid arguments: ${msg}.` : "The arguments do not match the tool's parameter schema.";
}