@sema-agent/core
Version:
Stateless, task-oriented AI agent core
89 lines (88 loc) • 6.21 kB
TypeScript
/**
* Protocol namespace table (roadmap 2026-08-02, the non-additive precondition for a second protocol).
*
* design/108 ① established ONE namespaced family — `mcp__<server>__<tool>` — and six call sites grew
* their own hard-coded `"mcp__"` reads (origin projection, grouped display, permission-rule lanes,
* the `__` reservation message). Each new protocol would need a twin branch at every site. This table
* makes "which protocol owns this tool name" a lookup: a second protocol (e.g. A2A) lands by
* APPENDING one entry, and every table-driven site picks it up unchanged.
*
* Dependency-free by design (pure string logic) — `mcp.ts` re-exports {@link MCP_NAMESPACE}'s prefix
* as its public `MCP_PREFIX` face, never the other way around. The MINTING pipeline (charset
* normalization + the assembled-name budget, which needs a digest) lives in `protocol-naming.ts`, which
* imports this module's type; the dependency never runs the other way, so this file stays dependency-free.
*/
/** The ids of every protocol the engine speaks. Doubles as the protocol slice of the tool-manifest
* `ToolOrigin` union — adding a protocol extends BOTH (tsc pairs the change). */
export type ProtocolId = "mcp" | "a2a";
/** The (peer, tool) pair a namespaced wire name decomposes into. */
export interface ProtocolPeerTool {
peer: string;
tool: string;
}
/**
* One tool-name namespace owned by an external protocol (design/108 ① generalized).
*
* `P` carries the namespace's prefix as a LITERAL type so a public face re-exporting it (mcp.ts's
* `MCP_PREFIX`) keeps `"mcp__"` rather than widening to `string` — a widened prefix compiles fine
* here while breaking every downstream literal-typed position, and the export-surface snapshot
* (name → kind) cannot see that class of change at all.
*/
export interface ProtocolNamespace<P extends string = string> {
/** Stable protocol id — doubles as the tool-manifest origin tag (e.g. `"mcp"`). */
readonly id: ProtocolId;
/** Global marker prefix (`mcp__`). A name in this namespace starts with it; no caller tool name may
* contain the `__` separator at all (prepare-task's reservation enforces the umbrella). */
readonly prefix: P;
/**
* Compose the wire name for a (peer, tool) pair.
*
* NAME SHAPE ONLY — this is the namespace's spelling contract (prefix + separator placement), NOT the
* full minting pipeline: sanitizing and length-clamping the segments is `protocol-naming.ts`'s
* `mintNamespacedToolName`, which every protocol registering real tools goes through. Composing a raw,
* unsanitized pair here yields a syntactically valid name that no mint would have produced — use this
* leg for a name whose segments are already known-good (a test, a display form), the mint otherwise.
*
* NAMING CONSTRAINTS (enforced by a THROW, not a silent mangle): `peer` and `tool` must be non-empty and
* `peer` must not contain the `__` separator. A separator inside `peer` makes the name un-round-trippable
* — {@link parse} splits at the FIRST separator, so `("prod__db", "query")` reads back as peer `"prod"`,
* tool `"db__query"` — i.e. a name that silently denotes a different peer than the caller asked for.
* `tool` may contain the separator (it is the trailing remainder and round-trips verbatim).
*/
makeName(peer: string, tool: string): string;
/** Parse a wire name; `undefined` when the name is not in this namespace (or malformed inside it —
* empty peer segment or empty tool segment both count as malformed). */
parse(name: string): ProtocolPeerTool | undefined;
/** Collapsed display key for grouped listings (`mcp__<server>__*`); identity for a malformed name. */
displayGroupKey(name: string): string;
}
declare const MCP_PREFIX_NAME: "mcp__";
/** A2A's marker prefix. `a2a__<peer>__<skill>`: `peer` is the remote agent (whatever id the deployment
* knows it by), `skill` the capability that agent advertises — the same two-segment shape MCP uses, so
* every table-driven site (origin projection, grouped display, the caller-name reservation) reads it
* unchanged. Deliberately NOT a prefix of, nor prefixed by, `mcp__`. */
declare const A2A_PREFIX_NAME: "a2a__";
export declare const MCP_NAMESPACE: ProtocolNamespace<typeof MCP_PREFIX_NAME>;
/**
* The A2A tool namespace (roadmap 2026-08-03, appended for the A2A client/server work).
*
* NAME LAYER ONLY: this entry decides how an A2A tool name is SPELLED and how it decomposes. The
* client leg (`a2a.ts`, `materializeA2aTools`) mints live names in this namespace since v5.3.0; the
* table entry predates it so that every table-driven site (`protocolOf`, the grouped display key, the
* `ToolOrigin` union) needed no change when the leg landed — and needs none for the server-side leg.
*/
export declare const A2A_NAMESPACE: ProtocolNamespace<typeof A2A_PREFIX_NAME>;
/** Every protocol namespace the engine knows. APPEND-ONLY: adding a protocol must not change how any
* existing name resolves — prefixes must stay pairwise non-overlapping (pinned in the table test). */
export declare const PROTOCOL_TABLE: readonly ProtocolNamespace[];
/** The namespaced-name shapes the protocol table currently owns, rendered for the two messages that have
* to name them (the caller-name reservation and the policy audit's unprefixed-name arm). Read from the
* table rather than written out: a message that hard-codes ONE protocol becomes wrong — while staying
* green — the moment a second one is appended, and both messages tell a deployment what to write. */
export declare const NAMESPACED_NAME_SHAPES: string;
/** Whether `name` carries the namespace separator — reserved for protocol tool names: a deployment tool's name or alias
* carrying it is refused, and a rule name carrying it with no protocol prefix names no tool. The one spelling of the test. */
export declare function carriesNamespaceSeparator(name: string): boolean;
/** The namespace owning `name`, or `undefined` for a caller/first-party tool. */
export declare function protocolOf(name: string): ProtocolNamespace | undefined;
export {};