Files
Agent-Native/adapters/pitransport/testdata/result-v1.mjs
T
weichenandCursor e65c6ba604 Align execution errors with the shared catalog and add optional logs.
Contract codes now use the agent.* values, and runtime can emit structured logs without changing results when no logger is configured.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-29 14:24:11 +00:00

126 lines
4.3 KiB
JavaScript

import { createHash } from "node:crypto";
export const RESULT_V1_SCHEMA_VERSION = 1;
export const MAX_RESULT_MEMBERS = 32;
export const MAX_RESULT_MEMBER_BYTES = 2 * 1024 * 1024;
/** Soft cap for HTTP wire payloads; Go fail-closes at 8MB. */
export const MAX_RESULT_WIRE_BYTES = 6 * 1024 * 1024;
/** Tail window for execution.log patches; full log remains on shared volume. */
export const EXECUTION_LOG_PATCH_BYTES = 128 * 1024;
const RETRYABLE_CODES = new Set(["agent.timeout", "agent.canceled", "agent.transient"]);
const ALLOWED_CODES = new Set([
"agent.prompt_empty",
"agent.unauthorized",
"agent.timeout",
"agent.canceled",
"agent.transient",
"agent.missing_deliverable",
"agent.run_failed",
]);
export function typedErrorCode(payload) {
const raw = String(payload?.fallback_reason || payload?.error || "");
const t = raw.toLowerCase();
if (t.includes("prompt_empty")) return "agent.prompt_empty";
if (t.includes("unauthorized")) return "agent.unauthorized";
if (t.includes("timeout") || t.includes("timed out")) return "agent.timeout";
if (t.includes("cancel")) return "agent.canceled";
if (
t.includes("connection") ||
t.includes("terminated") ||
t.includes("econnreset") ||
t.includes("socket hang")
) {
return "agent.transient";
}
if (t.includes("missing target")) return "agent.missing_deliverable";
if (payload?.retryable) return "agent.transient";
if (!payload?.ok) return "agent.run_failed";
return "";
}
export function membersDigest(members) {
const rows = (Array.isArray(members) ? members : []).map((m) => ({
kind: m.kind,
path: m.path,
digest: m.digest,
size: m.size,
}));
return createHash("sha256").update(JSON.stringify(rows)).digest("hex");
}
/** Keep only the trailing bytes of execution.log for the HTTP patch wire. */
export function truncateExecutionLogContent(content, maxBytes = EXECUTION_LOG_PATCH_BYTES) {
const buf = Buffer.from(String(content || ""), "utf8");
if (buf.length <= maxBytes) {
return buf.toString("utf8");
}
const tail = buf.subarray(buf.length - maxBytes);
return `[truncated ${buf.length - maxBytes} bytes]\n` + tail.toString("utf8");
}
/**
* Cap patch bodies so members and patches stay aligned under wire limits.
* Oversized / overflow members are dropped from both lists (fail closed on wire size).
*/
export function capPatches(patches, opts = {}) {
const maxMember = opts.maxMemberBytes ?? MAX_RESULT_MEMBER_BYTES;
const maxTotal = opts.maxTotalBytes ?? MAX_RESULT_WIRE_BYTES;
const maxMembers = opts.maxMembers ?? MAX_RESULT_MEMBERS;
const out = [];
let total = 0;
for (const p of Array.isArray(patches) ? patches : []) {
const rel = String(p?.rel_path || "");
if (!rel || rel.includes("..") || rel.startsWith("/")) continue;
let content = String(p.content || "");
if (rel === "execution.log" || rel.endsWith("/execution.log")) {
content = truncateExecutionLogContent(content);
}
const buf = Buffer.from(content, "utf8");
if (buf.length > maxMember) continue;
if (out.length >= maxMembers) break;
if (total + buf.length > maxTotal) continue;
total += buf.length;
out.push({ rel_path: rel, content });
}
return out;
}
export function estimateWireBytes(payload) {
return Buffer.byteLength(JSON.stringify(payload), "utf8");
}
export function withResultV1(payload) {
const out = { ...payload };
delete out.stderr;
const capped = capPatches(Array.isArray(out.patches) ? out.patches : []);
out.patches = capped;
const members = [];
for (const p of capped) {
const rel = String(p?.rel_path || "");
const buf = Buffer.from(String(p.content || ""), "utf8");
members.push({
kind: "file",
path: rel,
digest: createHash("sha256").update(buf).digest("hex"),
size: buf.length,
});
}
const ok = !!out.ok;
const errorCode = ok ? "" : typedErrorCode(out);
const retryable = !ok && RETRYABLE_CODES.has(errorCode) && !!out.retryable;
out.capabilities = { typed_result: true };
out.runner_protocol = "typed-v1";
out.result_v1 = {
schema_version: RESULT_V1_SCHEMA_VERSION,
run_id: String(out.run_id || "unknown").slice(0, 128),
outcome: ok ? "success" : "error",
retryable,
error_code: ALLOWED_CODES.has(errorCode) ? errorCode : ok ? "" : "agent.run_failed",
members,
content_digest: membersDigest(members),
};
return out;
}