@sema-agent/core
Version:
Stateless, task-oriented AI agent core
43 lines (42 loc) • 2.35 kB
JavaScript
import { createHash } from "node:crypto";
export const ARTIFACT_REJECT_BREAKER_THRESHOLD = 3;
export const ARTIFACT_REJECT_KEY_COMPONENT_MAX = 1024;
const HEAD = `IMPORTANT: Artifact calls for this target have now been rejected ${ARTIFACT_REJECT_BREAKER_THRESHOLD} or more times in this session for the same reason.`;
export const ARTIFACT_REJECT_BANNER_RETRYABLE = `${HEAD} Do not send the same call again: either make the specific change the error describes, or stop calling Artifact for this target and tell the user what is failing and why.`;
export const ARTIFACT_REJECT_BANNER_REFUSAL = `${HEAD} Stop calling Artifact for this target and tell the user the artifact could not be published and why — do not try to work around this refusal.`;
export const ARTIFACT_REJECT_REFUSAL_CODES = new Set(["artifact.force_refused", "artifact.forbidden"]);
const clip = (v) => (typeof v === "string" ? v.slice(0, ARTIFACT_REJECT_KEY_COMPONENT_MAX) : "");
export function artifactRejectKeyOf(args) {
if (args === null || typeof args !== "object")
return "publish\0\0";
const a = args;
return `${clip(a["action"]) || "publish"}\0${clip(a["file_path"])}\0${clip(a["url"])}`;
}
export function stripArtifactRejectBanner(text) {
for (const banner of [ARTIFACT_REJECT_BANNER_RETRYABLE, ARTIFACT_REJECT_BANNER_REFUSAL])
if (text.startsWith(`${banner}\n\n`))
return text.slice(banner.length + 2);
return text;
}
export function artifactRejectBannerFor(reason) {
return ARTIFACT_REJECT_REFUSAL_CODES.has(reason) ? ARTIFACT_REJECT_BANNER_REFUSAL : ARTIFACT_REJECT_BANNER_RETRYABLE;
}
export function createArtifactRejectBreaker() {
const table = new Map();
return {
hit(key, reason, text) {
const fingerprint = createHash("sha256").update(stripArtifactRejectBanner(text)).digest("hex");
const prior = table.get(key);
const count = prior !== undefined && prior.reason === reason && prior.fingerprint === fingerprint ? prior.count + 1 : 1;
table.set(key, { reason, fingerprint, count });
return count;
},
clear() {
table.clear();
},
streakOf(key) {
const s = table.get(key);
return s === undefined ? undefined : { reason: s.reason, count: s.count };
},
};
}