@sema-agent/core
Version:
Stateless, task-oriented AI agent core
83 lines (82 loc) • 4.57 kB
TypeScript
import type { TSchema } from "typebox";
import type { AgentTool } from "../internal/harness-types.js";
import type { ProtocolId } from "../core/protocol-table.js";
/** The protocol slice comes from the protocol table — adding a protocol extends ProtocolId and this
* union follows automatically (the origin projection is table-driven, prepare-task). */
export type ToolOrigin = "core" | "caller" | "synthetic" | ProtocolId;
export interface ToolContractDescriptor {
/** Stable contract identity, e.g. `core.read@2`. Versioned independently of the wire name. */
contractId: string;
/** Declared behavior revision (protocol §7.1: declarative — hashing a JS closure proves nothing). */
implementationRevision: string;
wireName: string;
/** Live deployment aliases (ToolSpec.aliases) — dispatchable alternate wire names. Retired-name
* replay aliases are gone (RB-476-A, 5.0.0): a persisted old name misses loudly instead. */
aliases?: string[];
origin: ToolOrigin;
/** Structure-only schema digest (descriptions/titles/examples stripped) — presentation-invariant. */
shapeDigest: string;
/** Full materialized schema digest (incl. descriptions) — the model-visible wire face. */
wireSchemaDigest: string;
/** The presentation card in effect. v1: `builtin:<wireName>` (the factory's inline text). */
cardId: string;
}
/**
* Manifest v2 row (protocol §5.4) — {@link projectToolManifest}'s per-tool projection of
* {@link ToolContractDescriptor} onto the wire-visible manifest plane. REF-D25 (2026-08):
* this is an intentional projection, not a subset alias — it drops `origin` and normalizes
* `aliases` from optional to required (empty array when there are none), so it cannot be
* expressed as `Pick<ToolContractDescriptor, ...>`. `core/trace.ts` (TraceEvent's
* `prompt.assembled` manifest) and `core/runner/prepare-task.ts` (`Prepared.promptManifest.tools`)
* both reference this same type — the value flows prepare-task.ts → runtask.ts → trace.ts
* untouched, so a shape drift between those two sites would make a real call-shape change
* invisible to replay/audit (the same shapeDigest-collision class documented above on
* `stripPresentation`).
*/
export interface ToolManifestRow {
wireName: string;
aliases: string[];
contractId: string;
implementationRevision: string;
cardId: string;
shapeDigest: string;
wireSchemaDigest: string;
}
export declare function computeShapeDigest(schema: TSchema | undefined): string;
export declare function computeWireSchemaDigest(schema: TSchema | undefined, description: string): string;
export interface AttachContractInput {
contractId: string;
implementationRevision: string;
origin: ToolOrigin;
aliases?: string[];
}
/** Attach a contract descriptor to a materialized tool (factory exit point). Digests are computed
* from the tool's REAL mounted schema/description, so they always reflect the wire face. */
export declare function attachToolContract(tool: AgentTool, input: AttachContractInput): void;
/**
* Resolve a tool's descriptor. Undeclared tools get a deterministic legacy descriptor derived from
* their shape (`legacy:<shapeDigest>`), origin per the mount channel — an external tool NEVER
* inherits a core contract by name (§7.2: don't guess contracts from names).
*/
export declare function getToolContract(tool: AgentTool, fallbackOrigin?: ToolOrigin): ToolContractDescriptor;
export declare function hasToolContract(tool: AgentTool): boolean;
/** Manifest projection for the final mounted tool set (manifest v2 `tools`, protocol §5.4). */
export declare function projectToolManifest(tools: readonly AgentTool[], originOf?: (tool: AgentTool) => ToolOrigin): ToolManifestRow[];
/**
* RB-15: wire-identity collision audit over the FINAL mounted tool set.
* Rules (plan §6.2 tool row):
* - an ALIAS colliding with another tool's canonical name or alias = FATAL (silent misdispatch
* of durable replays would follow — never ship such a mount);
* - two tools sharing a CANONICAL name = the historic deliberate yield (a caller tool may shadow
* a built-in; the harness Map keeps the later mount) — NOT fatal, but returned as an explicit
* exemption record so the mount site can surface it (observable, never silent).
*/
export declare function auditToolCollisions(tools: readonly AgentTool[]): {
yields: Array<{
name: string;
}>;
aliasCollisions: Array<{
alias: string;
owners: string[];
}>;
};