UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

125 lines (124 loc) • 7.29 kB
import { isToolContentOrigin } from "../core/memory-engine/content-origin.js"; import { inlineUntrusted } from "../core/untrusted-text.js"; import { readToolNameList, valueTypeLabel } from "../core/tool-name-list.js"; export const EXTRA_TOOL_NAMES_CODE = "config.extra_tool_names"; export function extraToolNamesError(message, reason) { const e = new Error(message); e.code = EXTRA_TOOL_NAMES_CODE; e.detail = { reason }; return e; } const shape = (what) => extraToolNamesError(`createSubagentTool: extraToolNames ${what} — it declares the tools the extraTools factory may inject, each { name, aliases?, contentOrigin? }. Fix the declaration.`, "shape"); function own(entry, key) { try { return { ok: true, value: entry[key] }; } catch { return { ok: false }; } } export function readExtraToolNames(value, hasFactory) { if (!hasFactory) throw extraToolNamesError("createSubagentTool: extraToolNames declares the tools an extraTools factory may inject, and this delegation tool has no extraTools factory. Remove the declaration or add the factory.", "shape"); const list = readToolNameList(value); if (list.kind === "not_a_list") throw shape(`is a value that is ${list.type}, not a list`); if (list.kind === "unreadable") throw shape("is a list whose entries cannot be read (an accessor or a proxy trap threw)"); const out = []; const owner = new Map(); list.entries.forEach((entry, index) => { const at = `entry ${index + 1}`; if (entry === null || typeof entry !== "object" || valueTypeLabel(entry) !== "an object") throw shape(`${at} is a value that is ${valueTypeLabel(entry)}, not an object`); const name = own(entry, "name"); if (!name.ok) throw shape(`${at}: its name cannot be read`); if (typeof name.value !== "string" || name.value === "") throw shape(`${at}: its name is ${typeof name.value === "string" ? "empty" : `a value that is ${valueTypeLabel(name.value)}`}, not a tool name`); const aliasesRead = own(entry, "aliases"); if (!aliasesRead.ok) throw shape(`${at}: its aliases cannot be read`); let aliases; if (aliasesRead.value !== undefined) { const al = readToolNameList(aliasesRead.value); if (al.kind !== "entries") throw shape(`${at}: its aliases are ${al.kind === "not_a_list" ? `a value that is ${al.type}` : "a list that cannot be read"}, not a list of names`); aliases = []; for (const a of al.entries) { if (typeof a !== "string" || a === "") throw shape(`${at}: an alias is ${typeof a === "string" ? "empty" : `a value that is ${valueTypeLabel(a)}`}, not a tool name`); aliases.push(a); } } const originRead = own(entry, "contentOrigin"); if (!originRead.ok) throw shape(`${at}: its contentOrigin cannot be read`); if (originRead.value !== undefined && !isToolContentOrigin(originRead.value)) throw shape(`${at}: its contentOrigin is not one of "local", "execution", "external"`); for (const spelling of new Set([name.value, ...(aliases ?? [])])) { const first = owner.get(spelling); if (first !== undefined) throw shape(`entries ${first + 1} and ${index + 1} both answer to ${JSON.stringify(inlineUntrusted(spelling, 80))}`); owner.set(spelling, index); } out.push(Object.freeze({ name: name.value, ...(aliases !== undefined ? { aliases: Object.freeze(aliases) } : {}), ...(originRead.value !== undefined ? { contentOrigin: originRead.value } : {}), })); }); return Object.freeze(out); } const REACH = { local: 0, execution: 1, external: 2 }; const quoted = (name) => `"${inlineUntrusted(name, 80)}"`; function rosterPhrase(roster) { const shown = roster.slice(0, 8).map((e) => quoted(e.name)); return roster.length === 0 ? "none" : shown.join(", ") + (roster.length > shown.length ? `, … (${roster.length - shown.length} more)` : ""); } export function undeclaredExtraTool(roster, product, toolName) { const where = `the extraTools factory of delegation tool ${quoted(toolName)}`; const tail = "the declaration is the upper bound of what the factory may inject. Fix the declaration or the factory."; const name = product.name; if (typeof name !== "string") return { reason: "undeclared_name", sentence: `${where} injected a tool whose name is a value that is ${valueTypeLabel(name)}, which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) cannot name — ${tail}` }; const entry = roster.find((e) => e.name === name || (e.aliases?.includes(name) ?? false)); if (entry === undefined) return { reason: "undeclared_name", sentence: `${where} injected tool ${quoted(name)}, which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) do not name — ${tail}` }; const spellings = new Set([entry.name, ...(entry.aliases ?? [])]); const aliases = product.aliases; const aliasList = Array.isArray(aliases) ? aliases : []; for (const alias of aliasList) { if (typeof alias !== "string" || !spellings.has(alias)) { const said = typeof alias === "string" ? quoted(alias) : `a value that is ${valueTypeLabel(alias)}`; return { reason: "undeclared_name", sentence: `${where} injected tool ${quoted(name)} answering to ${said}, which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) do not name — ${tail}` }; } } const answers = new Set([name, ...aliasList]); const missing = [...spellings].find((n) => !answers.has(n)); if (missing !== undefined) return { reason: "undeclared_name", sentence: `${where} injected tool ${quoted(name)}, which does not answer to ${quoted(missing)} as its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) say it does — ${tail}` }; const stated = product.contentOrigin; const productClass = isToolContentOrigin(stated) ? stated : "external"; const declaredClass = entry.contentOrigin ?? "external"; if (REACH[productClass] > REACH[declaredClass]) { return { reason: "undeclared_origin", sentence: `${where} injected tool ${quoted(name)} of content class "${productClass}", which its declared extra tools (extraToolNames: ${rosterPhrase(roster)}) state as "${declaredClass}" — ${tail}` }; } return undefined; } export function delegationPoolFace(staticPool, hasFactory, roster) { if (hasFactory && roster === undefined) return undefined; return [...staticPool, ...(roster ?? [])].map((t) => { const origin = t.contentOrigin; return { name: t.name, ...(t.aliases !== undefined ? { aliases: t.aliases } : {}), ...(origin !== undefined ? { contentOrigin: origin } : {}) }; }); } export function firstUndeclaredExtraTool(roster, products, toolName) { for (const product of products) { const flaw = undeclaredExtraTool(roster, product, toolName); if (flaw !== undefined) return flaw; } return undefined; }