UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

137 lines (136 loc) • 5.16 kB
import { createHash } from "node:crypto"; const PRESENTATION_KEYWORDS = new Set(["description", "title", "examples", "$id"]); const SCHEMA_NAME_MAPS = new Set([ "properties", "patternProperties", "definitions", "$defs", "dependentSchemas", ]); function stripPresentation(node) { if (Array.isArray(node)) return node.map(stripPresentation); if (node === null || typeof node !== "object") return node; const out = {}; for (const [k, v] of Object.entries(node)) { if (PRESENTATION_KEYWORDS.has(k)) continue; if (SCHEMA_NAME_MAPS.has(k) && v !== null && typeof v === "object" && !Array.isArray(v)) { const named = {}; for (const [name, sub] of Object.entries(v)) named[name] = stripPresentation(sub); out[k] = named; continue; } out[k] = k === "required" && Array.isArray(v) ? [...v].sort() : stripPresentation(v); } return out; } function canonical(node) { if (Array.isArray(node)) return `[${node.map(canonical).join(",")}]`; if (node === null || typeof node !== "object") return JSON.stringify(node); const keys = Object.keys(node).sort(); return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(node[k])}`).join(",")}}`; } function sha(text) { return createHash("sha256").update(text).digest("hex").slice(0, 16); } export function computeShapeDigest(schema) { return sha(canonical(stripPresentation(schema ?? null))); } export function computeWireSchemaDigest(schema, description) { return sha(`${canonical(schema ?? null)}\u0000${description}`); } const TOOL_CONTRACT = Symbol("sema.toolContract"); export function attachToolContract(tool, input) { const descriptor = { contractId: input.contractId, implementationRevision: input.implementationRevision, wireName: tool.name, ...(input.aliases && input.aliases.length > 0 ? { aliases: input.aliases } : {}), origin: input.origin, shapeDigest: computeShapeDigest(tool.parameters), wireSchemaDigest: computeWireSchemaDigest(tool.parameters, tool.description ?? ""), cardId: `builtin:${tool.name}`, }; tool[TOOL_CONTRACT] = descriptor; } export function getToolContract(tool, fallbackOrigin = "caller") { const attached = tool[TOOL_CONTRACT]; if (attached) { const mountedShape = computeShapeDigest(tool.parameters); if (mountedShape !== attached.shapeDigest) { return { contractId: `legacy:${mountedShape}`, implementationRevision: "unversioned", wireName: tool.name, ...(tool.aliases && tool.aliases.length > 0 ? { aliases: tool.aliases } : {}), origin: attached.origin, shapeDigest: mountedShape, wireSchemaDigest: computeWireSchemaDigest(tool.parameters, tool.description ?? ""), cardId: `legacy:${tool.name}`, }; } return { ...attached, wireName: tool.name, shapeDigest: mountedShape, wireSchemaDigest: computeWireSchemaDigest(tool.parameters, tool.description ?? ""), }; } const shapeDigest = computeShapeDigest(tool.parameters); return { contractId: `legacy:${shapeDigest}`, implementationRevision: "unversioned", wireName: tool.name, ...(tool.aliases && tool.aliases.length > 0 ? { aliases: tool.aliases } : {}), origin: fallbackOrigin, shapeDigest, wireSchemaDigest: computeWireSchemaDigest(tool.parameters, tool.description ?? ""), cardId: `legacy:${tool.name}`, }; } export function hasToolContract(tool) { return tool[TOOL_CONTRACT] !== undefined; } export function projectToolManifest(tools, originOf) { return tools.map((t) => { const d = getToolContract(t, originOf?.(t) ?? "caller"); return { wireName: d.wireName, aliases: d.aliases ?? [], contractId: d.contractId, implementationRevision: d.implementationRevision, cardId: d.cardId, shapeDigest: d.shapeDigest, wireSchemaDigest: d.wireSchemaDigest, }; }); } export function auditToolCollisions(tools) { const canonical = new Map(); const aliasOwner = new Map(); const yields = []; const aliasCollisions = []; for (const t of tools) { if (canonical.has(t.name)) yields.push({ name: t.name }); canonical.set(t.name, t); } for (const t of tools) { for (const a of t.aliases ?? []) { if (canonical.has(a) && canonical.get(a) !== t) { aliasCollisions.push({ alias: a, owners: [canonical.get(a).name, t.name] }); } const prior = aliasOwner.get(a); if (prior !== undefined && prior !== t.name) { aliasCollisions.push({ alias: a, owners: [prior, t.name] }); } aliasOwner.set(a, t.name); } } return { yields, aliasCollisions }; }