@sema-agent/core
Version:
Stateless, task-oriented AI agent core
534 lines (533 loc) • 25.6 kB
JavaScript
import { createHash } from "node:crypto";
import { BrainError } from "./errors.js";
import { reportCredentialOriginRefusal } from "./status-sink.js";
import { receiptRefusal, receiveOwnProperties } from "../core/own-property-receipt.js";
import { credentialHeaderValues, holdOwnCredentialValues, isCredentialHeaderName, isSchemedCredentialHeader } from "../core/secret-env.js";
const REDACTED_VALUE = "«redacted»";
const REDACTED_CREDENTIALS = "[redacted-credentials]";
function servableParse(raw) {
let u;
try {
u = new URL(raw);
}
catch {
return undefined;
}
return u.protocol === "http:" || u.protocol === "https:" ? u : undefined;
}
function composableParse(raw) {
let u;
try {
u = new URL(raw);
}
catch {
return undefined;
}
return u.host !== "" || u.pathname.startsWith("/") ? u : undefined;
}
export function routeKeyOf(url) {
if (url === undefined)
return "";
if (typeof url !== "string") {
throw new TypeError(`route pairing: baseUrl must be a string (got ${typeof url})`);
}
const raw = url.trim();
if (raw === "")
return "";
const u = servableParse(raw);
if (u === undefined)
return raw.replace(/\/+$/, "");
return canonicalRootSpelling(u);
}
function canonicalRootSpelling(u) {
return `${u.protocol}//${u.host}${u.pathname.replace(/\/+$/, "")}${u.search}`;
}
const QUERY_PURE_TEXT = /^[A-Za-z0-9._~[\]\-+,*!'()$ ]*$/;
const QUERY_DECODE_ROUNDS = 3;
function decodedToFixedPoint(text) {
let cur = text;
for (let round = 0; round < QUERY_DECODE_ROUNDS; round++) {
if (!cur.includes("%"))
return cur;
let next;
try {
next = decodeURIComponent(cur);
}
catch {
return cur;
}
if (next === cur)
return cur;
cur = next;
}
return cur;
}
function readQuery(search) {
if (search === "" || search === "?")
return { pure: true, segments: [] };
const segments = search.replace(/^\?/, "").split("&").map((raw) => {
const eq = raw.indexOf("=");
return eq === -1 ? { raw } : { raw, name: raw.slice(0, eq), value: raw.slice(eq + 1) };
});
const pure = segments.every((sg) => {
if (sg.name === undefined)
return QUERY_PURE_TEXT.test(decodedToFixedPoint(sg.raw));
const name = decodedToFixedPoint(sg.name);
return name !== "" && QUERY_PURE_TEXT.test(name) && QUERY_PURE_TEXT.test(decodedToFixedPoint(sg.value ?? ""));
});
return { pure, segments };
}
export function displayUrlOf(url) {
const key = routeKeyOf(url);
if (key === "")
return "";
const u = composableParse(key);
if (u === undefined) {
const cut = key.search(/[?#]/);
const head = cut === -1 ? key : key.slice(0, cut);
const at = head.lastIndexOf("@");
const shown = at === -1 ? head : `${REDACTED_VALUE}@${head.slice(at + 1)}`;
return cut === -1 ? shown : `${shown}${key[cut]}${REDACTED_VALUE}`;
}
const read = readQuery(u.search);
const query = read.segments.length === 0
? ""
: !read.pure
? `?${REDACTED_VALUE}`
: `?${read.segments.map((sg) => (sg.raw === "" ? "" : sg.name === undefined ? REDACTED_VALUE : `${sg.name}=${REDACTED_VALUE}`)).join("&")}`;
return `${u.protocol}//${u.host}${u.pathname}${query}`.replace(/\/+$/, "");
}
function queryDifferenceNote(requestKey, declaredKey) {
if (displayUrlOf(requestKey) !== displayUrlOf(declaredKey))
return "";
const names = new Set();
const ua = composableParse(requestKey);
const ub = composableParse(declaredKey);
if (ua !== undefined && ub !== undefined) {
const ra = readQuery(ua.search);
const rb = readQuery(ub.search);
if (!ra.pure || !rb.pure) {
if (ra.segments.length !== rb.segments.length) {
return ` (same rendering — the query differs in segment count (${ra.segments.length} vs ${rb.segments.length}); a value here is not plain text, so this line withholds every name)`;
}
const at = ra.segments.flatMap((sg, i) => (sg.raw === rb.segments[i]?.raw ? [] : [i + 1]));
return at.length === 0
? ` (same rendering — the difference is in a part this line withholds or folds)`
: ` (same rendering — the query differs at segment${at.length > 1 ? "s" : ""} ${at.join(", ")} of ${ra.segments.length}; a value here is not plain text, so this line withholds every name)`;
}
const a = ra.segments;
const b = rb.segments;
const valuesOf = (segs, n) => segs.filter((sg) => sg.name === n).map((sg) => sg.value).join("\u0000");
for (const n of new Set([...a, ...b].flatMap((sg) => (sg.name === undefined ? [] : [sg.name])))) {
if (valuesOf(a, n) !== valuesOf(b, n))
names.add(n);
}
}
return names.size > 0
? ` (same rendering — the query values differ under: ${[...names].sort().join(", ")})`
: ` (same rendering — the difference is in a part this line withholds or folds)`;
}
export function assertServableRequestRoot(url, seat) {
return judgeRequestRoot(url, seat)?.trimmed;
}
function judgeRequestRoot(url, seat) {
if (typeof url !== "string" || url.trim() === "")
return undefined;
const trimmed = url.trim();
const parsed = URL.canParse(trimmed) ? new URL(trimmed) : undefined;
if (parsed === undefined || (parsed.protocol !== "http:" && parsed.protocol !== "https:")) {
const named = parsed === undefined ? "it did not parse as a URL" : displayUrlOf(trimmed);
throw new BrainError("invalid_request", `${seat} is not a servable request root (${named}) — it must be an ABSOLUTE http(s) URL. Another scheme, ` +
`or a spelling that is not a URL at all, fails at the transport in a shape indistinguishable from an ` +
`outage, so it would be retried for the whole backoff ladder instead of refused here.`);
}
if (parsed.username !== "" || parsed.password !== "") {
throw new BrainError("invalid_request", `${seat} carries credentials in its authority (${parsed.protocol}//${REDACTED_CREDENTIALS}@${parsed.host}${parsed.pathname}) — ` +
`a request cannot be built from a URL with userinfo, so every call through this brain would fail: put the ` +
`credential on the apiKey knob or an auth header and leave the URL bare`);
}
if (parsed.hash !== "") {
throw new BrainError("invalid_request", `${seat} carries a URL fragment (${displayUrlOf(trimmed)}#…) — a fragment is never sent on the wire, so the ` +
`request would silently target the root's own path and the endpoint suffix would land inside the discarded ` +
`part: drop everything from the "#" on. A per-deployment QUERY is kept and rides every request ` +
`(a gateway's \`?api-version=…\` is supported); a fragment cannot be.`);
}
return { trimmed, parsed };
}
export function joinRequestUrl(root, suffix) {
if (typeof suffix !== "string" || /[?#]/.test(suffix)) {
throw new TypeError(`request URL: the endpoint suffix must be a path (got ${typeof suffix === "string" ? JSON.stringify(suffix) : typeof suffix}) — ` +
`a request's query belongs to its ROOT, which declares it once for every request`);
}
const judged = judgeRequestRoot(root, "the request root");
if (judged === undefined) {
throw new BrainError("invalid_request", `no request root is declared, so no request URL can be built for "${suffix}" — set config.baseUrl or Model.baseUrl`);
}
const { parsed } = judged;
const tail = suffix.replace(/^\/+/, "");
if (tail === "")
return canonicalRootSpelling(parsed);
const base = parsed.pathname.replace(/\/+$/, "");
const composed = `${parsed.protocol}//${parsed.host}${base}/${tail}${parsed.search}`;
const reread = servableParse(composed);
if (reread === undefined || !reread.pathname.startsWith(base === "" ? "/" : `${base}/`)) {
throw new TypeError(`request URL: the endpoint suffix ${JSON.stringify(suffix)} does not stay under the request root's path ` +
`(${base === "" ? "/" : base}) — a suffix names an endpoint of that root, and one that walks out of it would ` +
`send the request, and the credential paired with that root, somewhere the pairing law never judged`);
}
return composed;
}
export function stripUrlCredentials(text) {
return text.replace(/([A-Za-z][A-Za-z0-9+.\-]*:\/\/)[^/\s"'@]*@/g, `$1${REDACTED_CREDENTIALS}@`);
}
export function resolveRequestRoot(modelBaseUrl, configBaseUrl, laneDefault = "") {
return resolveRequestRootOf(modelBaseUrl, configBaseUrl, laneDefault).root;
}
export function resolveRequestRootOf(modelBaseUrl, configBaseUrl, laneDefault = "") {
const entry = judgeRequestRoot(modelBaseUrl, "Model.baseUrl");
const declared = judgeRequestRoot(configBaseUrl, "config.baseUrl");
const origin = entry !== undefined ? "model" : declared !== undefined ? "deployment" : "lane-default";
const chosen = (entry ?? declared)?.parsed ?? servableParse(laneDefault.trim());
return { root: chosen === undefined ? laneDefault : canonicalRootSpelling(chosen), origin };
}
export function hasAuthCarrier(headers) {
if (!headers)
return false;
return Object.keys(headers).some((name) => isCredentialHeaderName(name));
}
function routeOriginOf(rootKey) {
if (rootKey === "")
return "";
const u = servableParse(rootKey);
return u === undefined ? rootKey : `${u.protocol}//${u.host}`;
}
const CREDENTIAL_ORIGIN_BINDINGS_MAX = 16_384;
const CREDENTIAL_ORIGIN_TABLE = Symbol.for("sema.core.credentialOriginTable");
function credentialOriginTable() {
const held = Reflect.get(globalThis, CREDENTIAL_ORIGIN_TABLE);
if (held instanceof Map)
return held;
const table = new Map();
Reflect.set(globalThis, CREDENTIAL_ORIGIN_TABLE, table);
return table;
}
export function __resetCredentialOriginBindings() {
credentialOriginTable().clear();
}
const resolvedCredentialFacts = new WeakMap();
const HEADER_VALUE_ENDS = /^[\t\n\r ]+|[\t\n\r ]+$/g;
const AUTH_SCHEME_CREDENTIALS = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+[\t ]+(\S[\s\S]*)$/;
function perModelCredential(credential, apiKey, bag, conflict) {
const headers = {};
for (const [name, value] of Object.entries(bag ?? {})) {
if (isCredentialHeaderName(name) && typeof value === "string")
headers[name] = value;
}
const headerValues = Object.entries(headers).map(([name, value]) => {
const wire = value.replace(HEADER_VALUE_ENDS, "");
return isSchemedCredentialHeader(name) ? (AUTH_SCHEME_CREDENTIALS.exec(wire)?.[1] ?? wire) : wire;
});
const values = apiKey !== undefined ? [apiKey, ...headerValues] : headerValues;
const secrets = [...new Set(values.map((v) => v.replace(HEADER_VALUE_ENDS, "")).filter((v) => v !== ""))];
if (secrets.length === 0 && Object.keys(headers).length === 0 && conflict === undefined)
return credential;
const proof = Object.freeze(credential);
resolvedCredentialFacts.set(proof, Object.freeze({
identities: Object.freeze(secrets.map((s) => createHash("sha256").update(s).digest("base64url"))),
headers: Object.freeze(headers),
...(conflict !== undefined ? { keyHeaderConflict: Object.freeze(conflict) } : {}),
}));
return proof;
}
function boundElsewhere(identities, requestOrigin) {
const table = credentialOriginTable();
return identities.map((id) => table.get(id)).find((bound) => bound !== undefined && bound !== requestOrigin);
}
function bindCredentialOrigin(identities, requestOrigin) {
const table = credentialOriginTable();
for (const id of identities) {
table.delete(id);
if (table.size >= CREDENTIAL_ORIGIN_BINDINGS_MAX) {
const oldest = table.keys().next();
if (oldest.done !== true)
table.delete(oldest.value);
}
table.set(id, requestOrigin);
}
}
function routeHalves(model, config) {
const entry = routeKeyOf(model.baseUrl);
const declared = routeKeyOf(config.baseUrl);
const deploymentRoot = declared || routeKeyOf(config.laneDefault);
return { entry, declared, deploymentRoot, request: entry || deploymentRoot };
}
function keyHeaderConflict(apiKey, headers, keyHeader) {
if (apiKey === undefined || apiKey === "" || keyHeader === undefined || headers === undefined)
return undefined;
const fold = keyHeader.toLowerCase();
const written = Object.keys(headers).find((name) => name.toLowerCase() === fold);
return written === undefined ? undefined : { header: written, keyHeader };
}
export function resolveRouteCredential(input) {
const { optionsApiKey, optionsHeaders, modelHeaders, configApiKey, configHeaders, keyHeader } = input;
holdOwnCredentialValues(credentialHeaderValues(modelHeaders));
const answerHeadersBorne = hasAuthCarrier(optionsHeaders);
if (optionsApiKey !== undefined || answerHeadersBorne) {
if (optionsApiKey !== undefined && typeof optionsApiKey !== "string")
throw new TypeError(`route pairing: apiKey must be a string (got ${typeof optionsApiKey})`);
return perModelCredential({
source: "per-model",
...(optionsApiKey !== undefined ? { apiKey: optionsApiKey } : {}),
...(answerHeadersBorne ? { headersBorne: true, carrierBag: "options" } : {}),
}, optionsApiKey, answerHeadersBorne ? optionsHeaders : undefined, keyHeaderConflict(optionsApiKey, optionsHeaders, keyHeader));
}
if (hasAuthCarrier(modelHeaders))
return perModelCredential({ source: "per-model", headersBorne: true, carrierBag: "model" }, undefined, modelHeaders);
const configHeadersBorne = hasAuthCarrier(configHeaders);
if (configApiKey !== undefined || configHeadersBorne) {
if (configApiKey !== undefined && typeof configApiKey !== "string") {
throw new TypeError(`route pairing: apiKey must be a string (got ${typeof configApiKey})`);
}
return {
source: "deployment-config",
...(configApiKey !== undefined ? { apiKey: configApiKey } : {}),
...(configHeadersBorne ? { headersBorne: true, carrierBag: "config" } : {}),
};
}
return { source: "absent" };
}
export function adjudicateModelRoute(model, credential, config) {
const { entry, declared, deploymentRoot, request } = routeHalves(model, config);
const entryShown = displayUrlOf(entry);
const declaredShown = displayUrlOf(declared);
const requestShown = displayUrlOf(request);
if (credential.source === "per-model") {
const facts = resolvedCredentialFacts.get(credential);
const conflict = facts?.keyHeaderConflict;
if (conflict !== undefined) {
return {
ok: false,
code: "route.credential_answer_conflict",
detail: {
modelId: model.id,
entryBaseUrl: entryShown,
requestBaseUrl: requestShown,
configBaseUrl: declaredShown,
fixHint: `model "${model.id}": the request carries both an apiKey and a "${conflict.header}" header, and this lane writes the apiKey in ` +
`"${conflict.keyHeader}" — one header cannot hold two credentials; hand the key as apiKey or as the header, not both`,
},
};
}
const identities = facts?.identities;
const requestOrigin = routeOriginOf(request);
if (identities === undefined || requestOrigin === "")
return { ok: true, posture: "per-model" };
const boundOrigin = boundElsewhere(identities, requestOrigin);
if (boundOrigin === undefined)
return { ok: true, posture: "per-model" };
const boundShown = displayUrlOf(boundOrigin);
const requestOriginShown = displayUrlOf(requestOrigin);
return {
ok: false,
code: "route.credential_origin_mismatch",
detail: {
modelId: model.id,
entryBaseUrl: entryShown,
requestBaseUrl: requestShown,
configBaseUrl: declaredShown,
boundOrigin: boundShown,
requestOrigin: requestOriginShown,
fixHint: `model "${model.id}" targets ${requestShown} (origin ${requestOriginShown}) but its own credential is bound to ${boundShown}, ` +
`the origin it was first sent to — a credential does not follow its entry to another host: change the entry's credential ` +
`reference in the same step as its baseUrl (a new credential is a new binding), or point the entry back at ${boundShown}; ` +
`a binding lasts as long as the engine process, so a provider that only moved domains and accepts the same credential there needs a restart`,
},
};
}
if (credential.source === "deployment-config") {
if (request !== "" && request === deploymentRoot)
return { ok: true, posture: "paired" };
const pairedWith = declared !== ""
? `is paired with ${declaredShown}`
: deploymentRoot !== ""
? `is paired with this brain's default endpoint ${displayUrlOf(deploymentRoot)} (config.baseUrl is unset)`
: `names no root of its own (config.baseUrl is unset)`;
return {
ok: false,
code: "route.credential_mismatch",
detail: {
modelId: model.id,
entryBaseUrl: entryShown,
requestBaseUrl: requestShown,
configBaseUrl: declaredShown,
fixHint: `model "${model.id}" targets ${requestShown || "no root"} but the deployment credential ${pairedWith}` +
`${queryDifferenceNote(request, deploymentRoot)}: ` +
`give the model its own credential (a per-model key via getApiKeyAndHeaders, or an auth header on Model.headers), ` +
`or serve it through a brain whose config.baseUrl is the model's own root`,
},
};
}
if (declared === "" || entry === "" || request === declared)
return { ok: true, posture: "keyless" };
return {
ok: false,
code: "route.credential_missing",
detail: {
modelId: model.id,
entryBaseUrl: entryShown,
requestBaseUrl: requestShown,
configBaseUrl: declaredShown,
fixHint: `model "${model.id}" targets ${requestShown} but no credential is configured for that endpoint ` +
`(the deployment credential root is ${declaredShown})${queryDifferenceNote(request, declared)}: supply a per-model credential ` +
`(getApiKeyAndHeaders, or an auth header on Model.headers), or serve it through a brain constructed for that root`,
},
};
}
export function routeRefusalText(verdict) {
return `${verdict.code}: ${verdict.detail.fixHint}`;
}
export function routeRefusalError(verdict) {
const { modelId, boundOrigin, requestOrigin } = verdict.detail;
if (verdict.code === "route.credential_origin_mismatch" && boundOrigin !== undefined && requestOrigin !== undefined) {
reportCredentialOriginRefusal({ modelId, boundOrigin, requestOrigin });
}
return new BrainError("invalid_request", routeRefusalText(verdict));
}
export function admitRouteRequest(model, credential, config) {
const judged = { id: model.id, baseUrl: model.baseUrl };
const { baseUrl, laneDefault } = config;
const pairing = { ...(baseUrl !== undefined ? { baseUrl } : {}), ...(laneDefault !== undefined ? { laneDefault } : {}) };
const judgeOrRefuse = () => {
const verdict = adjudicateModelRoute(judged, credential, pairing);
if (!verdict.ok)
throw routeRefusalError(verdict);
};
judgeOrRefuse();
const identities = credential.source === "per-model" ? resolvedCredentialFacts.get(credential)?.identities : undefined;
const requestOrigin = routeOriginOf(routeHalves(judged, pairing).request);
if (identities === undefined || identities.length === 0 || requestOrigin === "")
return () => { };
return () => {
judgeOrRefuse();
bindCredentialOrigin(identities, requestOrigin);
};
}
export function applyRouteCredentialHeaders(merged, credential) {
if (credential.source !== "per-model")
return;
for (const k of Object.keys(merged)) {
if (isCredentialHeaderName(k))
delete merged[k];
}
Object.assign(merged, resolvedCredentialFacts.get(credential)?.headers);
}
export function brainConfigSnapshot(given, subject) {
const words = { subject, unit: "member", moment: "at construction" };
const received = receiveOwnProperties(given, words);
const headers = received.headers;
if (headers !== undefined) {
if (headers === null || typeof headers !== "object")
throw receiptRefusal(`${subject}.headers must be an object of header names to values`);
Object.defineProperty(received, "headers", { value: Object.freeze(receiveOwnProperties(headers, { ...words, subject: `${subject}.headers`, unit: "header" })), enumerable: true });
}
const snapshot = Object.freeze(received);
holdOwnCredentialValues([snapshot.apiKey, ...credentialHeaderValues(snapshot.headers)]);
return snapshot;
}
export function createBrainRouteJudge(config) {
return (model, perModelAuth) => {
const credential = resolveRouteCredential({
optionsApiKey: perModelAuth?.apiKey,
optionsHeaders: perModelAuth?.headers,
modelHeaders: model.headers,
configApiKey: config.apiKey,
configHeaders: config.headers,
keyHeader: config.keyHeader,
});
return adjudicateModelRoute(model, credential, { baseUrl: config.baseUrl, laneDefault: config.laneDefault });
};
}
const BROKEN_PAIRING_STATUS = {
"route.credential_mismatch": "broken:credential_mismatch",
"route.credential_missing": "broken:credential_missing",
"route.credential_origin_mismatch": "broken:credential_origin_mismatch",
"route.credential_answer_conflict": "broken:credential_answer_conflict",
};
export function routePairingStatus(brain, model, perModelAuth) {
const verdict = brain.adjudicateRoute?.(model, perModelAuth);
if (verdict === undefined)
return "unknown";
if (verdict.ok)
return `ok:${verdict.posture}`;
return BROKEN_PAIRING_STATUS[verdict.code];
}
export const DERIVED_ROUTE_PREFLIGHT_HOOK_BOUND_MS = 5_000;
export async function adjudicateDerivedRoute(args) {
const judge = args.brain.adjudicateRoute;
if (judge === undefined)
return undefined;
let auth;
if (args.getApiKeyAndHeaders !== undefined) {
let timer;
const ABSTAIN = Symbol("preflight-abstain");
try {
const bound = new Promise((resolve) => {
timer = setTimeout(() => resolve(ABSTAIN), DERIVED_ROUTE_PREFLIGHT_HOOK_BOUND_MS);
});
const hookResult = Promise.resolve(args.getApiKeyAndHeaders(args.model));
hookResult.catch(() => { });
const raced = await Promise.race([hookResult, bound]);
if (raced === ABSTAIN)
return undefined;
auth = raced;
}
catch {
return undefined;
}
finally {
if (timer !== undefined)
clearTimeout(timer);
}
}
try {
return judge(args.model, auth);
}
catch {
return undefined;
}
}
export function sameRouteIdentity(a, b) {
return a.id === b.id && routeKeyOf(a.baseUrl) === routeKeyOf(b.baseUrl);
}
export function fallbackToPrimaryNotice(args) {
return {
code: "route.fallback_to_primary",
message: `derived model "${args.from}" (${args.seat}) is not routable (${args.verdict.code}) — ` +
`falling back to the primary model "${args.to}". ${args.verdict.detail.fixHint}`,
detail: {
seat: args.seat,
from: args.from,
to: args.to,
cause: args.verdict.code,
fixHint: args.verdict.detail.fixHint,
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
...(args.runId !== undefined ? { runId: args.runId } : {}),
},
};
}
export function unroutableSeatNotice(args) {
return {
code: "route.classifier_unroutable",
message: `derived model "${args.from}" (${args.seat}) is not routable (${args.verdict.code}) and this deployment declared no fallback — ` +
`the seat is kept and its calls are refused rather than served by "${args.primary}". ${args.verdict.detail.fixHint}`,
detail: {
seat: args.seat,
from: args.from,
cause: args.verdict.code,
fixHint: args.verdict.detail.fixHint,
action: "refused",
...(args.sessionId !== undefined ? { sessionId: args.sessionId } : {}),
...(args.runId !== undefined ? { runId: args.runId } : {}),
},
};
}