@sema-agent/core
Version:
Stateless, task-oriented AI agent core
222 lines (221 loc) • 8.39 kB
JavaScript
import { isErrorLike, readMember, thrownText } from "../core/thrown-value.js";
import { emitBrainTelemetry } from "./status-sink.js";
import { isProviderEchoShape } from "./provider-echo-shape.js";
export const ZERO_USAGE = {
input: 0,
output: 0,
cacheRead: 0,
cacheWrite: 0,
totalTokens: 0,
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
};
const BRAIN_ERROR_CODES = ["auth", "rate_limit", "invalid_request", "server", "network", "http", "stream_torn", "refusal", "length_empty"];
export const ALL_BRAIN_ERROR_CODES = BRAIN_ERROR_CODES;
export const IN_BAND_OUTPUT_CODES = new Set(["stream_torn", "refusal", "length_empty"]);
export class BrainError extends Error {
code;
status;
inputTooLong;
inputTooLongRuledOut;
apiError;
providerCode;
constructor(code, detail, status, opts) {
super(`[${code}] ${detail}`);
this.name = "BrainError";
this.code = code;
this.status = status;
if (opts?.inputTooLong === true)
this.inputTooLong = true;
else if (opts?.inputTooLongRuledOut === true)
this.inputTooLongRuledOut = true;
if (opts?.apiError === true)
this.apiError = true;
if (opts?.providerCode !== undefined)
this.providerCode = opts.providerCode;
}
}
export function providerErrorObject(body) {
const trimmed = body.trim();
if (!trimmed.startsWith("{"))
return undefined;
let parsed;
try {
parsed = JSON.parse(trimmed);
}
catch {
return undefined;
}
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
return undefined;
const err = parsed.error;
if (typeof err !== "object" || err === null || Array.isArray(err))
return undefined;
return err;
}
export function readProviderErrorCode(body) {
const err = providerErrorObject(body);
const word = typeof err?.code === "string" ? err.code : typeof err?.type === "string" ? err.type : undefined;
const reading = providerEchoReading(word);
if (reading === "carried")
return word;
if (reading === "refused")
emitBrainTelemetry({ kind: "echo_dropped", field: "apiErrorCode" });
return undefined;
}
const REQUEST_ID_HEADERS = ["request-id", "x-request-id"];
export { isProviderEchoShape, PROVIDER_ECHO_SHAPE } from "./provider-echo-shape.js";
export function providerEchoReading(value) {
if (typeof value !== "string" || value.length === 0)
return "absent";
return isProviderEchoShape(value) ? "carried" : "refused";
}
export function readProviderRequestId(res) {
let refused = false;
try {
const get = res?.headers?.get;
if (typeof get !== "function")
return undefined;
for (const name of REQUEST_ID_HEADERS) {
const raw = get.call(res.headers, name);
const reading = providerEchoReading(raw);
if (reading === "carried")
return raw;
if (reading === "refused")
refused = true;
}
}
catch {
}
if (refused)
emitBrainTelemetry({ kind: "echo_dropped", field: "requestId" });
return undefined;
}
export function namesTheFailure(status) {
return status !== undefined && status >= 100 && !(status >= 200 && status < 300);
}
export function refusedTheRequest(status) {
return status !== undefined && status >= 400 && status < 500;
}
export function classifyHttp(status) {
if (status === 401 || status === 403)
return "auth";
if (status === 429)
return "rate_limit";
if (status === 400 || status === 422)
return "invalid_request";
if (status === 408 || status === 409)
return "server";
if (status >= 500)
return "server";
return "http";
}
const CODE_ALTERNATION = BRAIN_ERROR_CODES.join("|");
const CODE_RE = new RegExp(`^\\[(${CODE_ALTERNATION})\\]`);
const CODE_PREFIX_RE = new RegExp(`^\\[(?:${CODE_ALTERNATION})\\]\\s*`);
export function extractErrorCode(errorMessage) {
const m = errorMessage ? CODE_RE.exec(errorMessage) : null;
return m ? m[1] : undefined;
}
export function stripErrorCodePrefix(errorMessage) {
return errorMessage.replace(CODE_PREFIX_RE, "");
}
const STALE_CONNECTION_CODES = new Set([
"ECONNRESET",
"EPIPE",
"ConnectionClosed",
"ETIMEDOUT",
"ECONNABORTED",
"ERR_SOCKET_CLOSED",
"StreamSuspended",
]);
function hasStaleConnectionCode(e) {
let cur = e;
for (let depth = 0; depth < 5 && cur !== undefined && cur !== null; depth++) {
const code = readMember(cur, "code");
if (typeof code === "string" && STALE_CONNECTION_CODES.has(code))
return true;
cur = isErrorLike(cur) ? readMember(cur, "cause") : undefined;
}
return false;
}
const DETERMINISTIC_CONNECT_CODES = new Set(["ECONNREFUSED", "ENOTFOUND"]);
const CONNECT_GRAPH_NODE_BUDGET = 16;
function walkConnectFailure(node, budget) {
if (budget.left <= 0)
return "indeterminate";
budget.left--;
if (node === undefined || node === null)
return "silent";
let verdict = "silent";
const code = readMember(node, "code");
if (typeof code === "string") {
if (!DETERMINISTIC_CONNECT_CODES.has(code))
return "indeterminate";
verdict = "deterministic";
}
if (!isErrorLike(node))
return verdict;
const members = readMember(node, "errors");
const legCount = readMember(members, "length");
if (members !== undefined) {
if (typeof legCount !== "number" || !Number.isInteger(legCount) || legCount <= 0)
return "indeterminate";
for (let i = 0; i < legCount; i++) {
if (walkConnectFailure(readMember(members, String(i)), budget) !== "deterministic")
return "indeterminate";
}
verdict = "deterministic";
}
const cause = readMember(node, "cause");
if (cause !== undefined && cause !== null) {
const below = walkConnectFailure(cause, budget);
if (below === "indeterminate")
return "indeterminate";
if (below === "deterministic")
verdict = "deterministic";
}
return verdict;
}
export function classifyConnectFailure(e) {
return walkConnectFailure(e, { left: CONNECT_GRAPH_NODE_BUDGET }) === "deterministic" ? "deterministic" : "indeterminate";
}
const FETCH_TRANSPORT_SHELLS = new Set(["fetch failed", "terminated"]);
const REQUEST_ARGUMENT_CODES = new Set(["ERR_INVALID_URL"]);
export function classifyFetchThrow(e) {
if (readMember(e, "name") !== "TypeError")
return "transport";
const message = readMember(e, "message");
if (typeof message === "string" && FETCH_TRANSPORT_SHELLS.has(message))
return "transport";
const cause = readMember(e, "cause");
if (cause === undefined || cause === null)
return "request_unbuildable";
const code = readMember(cause, "code");
return typeof code === "string" && REQUEST_ARGUMENT_CODES.has(code) ? "request_unbuildable" : "transport";
}
export function abortLikeName(err) {
if (!isErrorLike(err))
return undefined;
const name = readMember(err, "name");
return name === "AbortError" || name === "TimeoutError" ? name : undefined;
}
export function brainErrorCodeOf(e) {
const code = readMember(e, "code");
return typeof code === "string" && BRAIN_ERROR_CODES.includes(code) ? code : undefined;
}
export function describeNetworkError(e) {
const top = thrownText(e);
const parts = [];
let cur = isErrorLike(e) ? readMember(e, "cause") : undefined;
for (let depth = 0; depth < 4 && cur !== undefined && cur !== null; depth++) {
const rawCode = readMember(cur, "code");
const code = typeof rawCode === "string" ? rawCode : undefined;
const msg = thrownText(cur);
const piece = code !== undefined && !msg.includes(code) ? `${code}: ${msg}` : msg;
if (piece.length > 0 && piece !== top && !parts.includes(piece))
parts.push(piece);
cur = isErrorLike(cur) ? readMember(cur, "cause") : undefined;
}
const body = parts.length > 0 ? `${top} (${parts.join(" ← ")})` : top;
return hasStaleConnectionCode(e) ? `${body} — possibly a stale connection; a fresh one may succeed` : body;
}