@sema-agent/core
Version:
Stateless, task-oriented AI agent core
85 lines (84 loc) • 5.59 kB
TypeScript
import type { ToolContentOrigin } from "../types.js";
export interface ClassifyToolContentOriginInput {
/** The tool's own `contentOrigin` declaration (ToolSpec/AgentTool carry) — wins when present AND
* a member of the closed vocabulary ({@link isToolContentOrigin}); a present non-member is the
* unreadable-declaration case and classifies EXTERNAL. The annotation is intent, not a guarantee:
* the property rides tool objects untyped. */
declared?: ToolContentOrigin;
/** True ⇔ the name belongs to a protocol namespace (mcp__/a2a__ …) — an external channel. */
isProtocolTool: boolean;
/** True ⇔ the tool came from the caller/host surface (`TaskSpec.tools`), not a core mount. */
isCallerTool: boolean;
/** True ⇔ `TaskSpec.memory.trustedTools` names this tool (explicit host exemption). */
trusted: boolean;
}
/** The vocabulary in wire/message order — the one spelling list a refusal quotes back. */
export declare const TOOL_CONTENT_ORIGINS: readonly ToolContentOrigin[];
/** Runtime membership in the closed vocabulary. */
export declare function isToolContentOrigin(value: unknown): value is ToolContentOrigin;
export declare function classifyToolContentOrigin(input: ClassifyToolContentOriginInput): ToolContentOrigin;
/**
* Declare `origin` on a tool object built OUTSIDE `defineTool` — a raw AgentTool literal, which is
* how core's protocol-facing tools are constructed. The axis rides the tool object untyped, exactly
* as `defineTool` spreads it, and this function is the ONE place that cast lives; the classifier
* reads the same property either way, so the "classification lives with the tool definition" rule
* holds for both construction styles.
*/
export declare function withContentOrigin<T extends object>(tool: T, origin: ToolContentOrigin): T;
/** One offered agent type's tool narrowing, as the delegation tool declares it. */
export interface AgentToolFace {
name: string;
allowTools?: readonly string[];
denyTools?: readonly string[];
/** True ⇔ a child of this type is itself handed a delegation tool (the depth budget permits another
* level and this face does not exclude the delegation tool). Such a child can reach any SIBLING
* face, so its own narrowing is not the boundary — see {@link delegationCallIsExternal}. */
canRedelegate?: boolean;
}
/** One tool in the pool a delegated child draws from — enough to classify it, nothing more. */
export interface AgentPoolTool {
name: string;
aliases?: readonly string[];
/** The pool row's own declaration — a STATIC surface the deployment writes when it composes the
* delegation tool, and the only class information this judgment has about the child's roster.
*
* A per-entry MCP declaration (design/378, `McpServerSpec.contentOrigin`) does NOT reach here: the
* pool is a declaration, not a live roster, and this judgment never sees the server entries. A
* deployment that hands a declared server's `mcp__` tools to children MIRRORS the value onto the
* matching pool rows; not mirroring it leaves them on the protocol arm, so the delegation reads
* external and the parent over-marks — the safe direction, and the reason this is a documented
* duty rather than a refusal. */
contentOrigin?: ToolContentOrigin;
}
/**
* design/178 §3 (ruled 2026-08-08) — classify ONE delegation call by the child's tool face.
*
* A child that can reach a network or protocol tool returns content from outside the trust boundary
* just as surely as calling that tool in this session would, and the parent's transcript is where it
* lands. The judgment is STATIC — the child's face is known from its definition before the call — so
* nothing here tracks what the child actually did (runtime provenance is design/180 half A). That
* makes it conservative in the safe direction: a child that COULD reach external content counts as
* having done so. Whether this verdict MARKS the judging session's memory when no attestation
* arrives is the deployment's evidence standard (design/324,
* `RunnerDeps.memoryDelegationEvidence`) — the classification itself is value-independent, and the
* wrap computes it on every standard (the waiver announcement keys on it).
*
* `pool` is the CHILD'S pool, not the parent's roster: a delegation tool carries its own tool set, and
* the two are explicitly allowed to differ — a parent mounting nothing but the delegation tool can
* hand its children a network tool. Comparing against the parent's roster would call that local.
*
* FAIL-CLOSED on ignorance: an unnamed type, a type the delegation tool does not offer, a tool that
* declares no faces, or a pool that cannot be enumerated statically (a spawn-time tool factory) is
* EXTERNAL. "We could not tell" and "it is safe" are different answers.
*/
export declare function delegationCallIsExternal(input: {
requestedType: string | undefined;
faces: ReadonlyArray<AgentToolFace> | undefined;
/** Undefined ⇔ the child's pool is not statically knowable ⇒ fail closed. */
pool: ReadonlyArray<AgentPoolTool> | undefined;
/** Whether one pool tool carries a polluting class — the caller owns the deployment's config. */
isPolluting: (tool: AgentPoolTool) => boolean;
}): boolean;
/** Whether a tool of `origin` pollutes the session's memory when invoked (strict = the
* `execIsExternalContent` upgrade). */
export declare function contentOriginPollutes(origin: ToolContentOrigin, strict: boolean): boolean;