UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

69 lines (68 loc) • 5.07 kB
import type { ProtocolNamespace } from "./protocol-table.js"; /** The provider's tool-name pattern is `^[a-zA-Z0-9_-]{1,64}$` — the limit applies to the ASSEMBLED name, * not to its segments (design/116 W5-5, CC 2.1.187 parity `buildMcpToolName`). */ export declare const TOOL_NAME_MAX_CHARS = 64; /** Reserved for the tool segment when budgeting the peer segment: a peer kept at the full name budget * overflows the assembled name on its own (prefix + 64 + separator = 71 > 64), which is how the tool * segment used to be handed a NEGATIVE budget. A tool name shorter than this is not recognizable. */ export declare const MINTED_TOOL_SEGMENT_MIN_CHARS = 16; /** * design/116 W5-5 (CC 2.1.187 parity) — normalize a peer/tool segment to the API tool-name charset by * replacing every other character with `_`. CC does exactly this on BOTH segments when composing * `mcp__<server>__<tool>` (services/mcp/normalization.ts `normalizeNameForMCP` + mcpStringUtils.ts * `buildMcpToolName`), so a peer advertising a dotted/spaced/unicode name can't mint a name the provider * rejects. NOTE: only the MODEL-FACING namespaced name is normalized — the raw remote name still goes on * the wire and still keys the caller-facing maps (`allowTools`/`toolAxes`). The collision property is * inherent (two remote names that normalize to the same string mint the same string; CC accepts this at * the same layer) — what this engine does NOT accept is two PEERS colliding, because a peer prefix is a * live domain key here (the refresh splice). That case is decided by * {@link findNamespacePrefixCollision} and refused at the mount, loudly; the mint stays total. * * The pattern this enforces is `{1,64}`, and only the charset half was * enforced. Both other halves matter for the same reason the charset does — an over-long or empty segment * produces a name the provider rejects, which 400s the WHOLE model request, the exact failure the intake * gate exists to contain ("a single misconfigured server never bricks the whole task"). */ export declare function normalizeNameSegment(name: string): string; /** Force a normalized segment into `{1,max}`, keeping long names distinct via a stable digest. */ export declare function clampNameSegment(seg: string, max?: number): string; /** * The registered-name prefix for `peer` in `ns` — i.e. the set `name.startsWith(prefix)` that a consumer * uses as its splice domain when swapping one peer's tools wholesale. * * The peer segment is kept as long as the assembled name allows (it is how an operator recognizes which * peer a tool came from) and absorbs the clamp, digest-suffixed, when it does not fit. * * NOT `ns.makeName`: that leg REFUSES a peer carrying the separator, while this pipeline SANITIZES its * inputs (the peer name is deployment/remote data, not a caller-authored identifier) and must not turn a * long or exotic peer name into a materialization failure. SANITIZE, however, means the sanitized result * must be sound: {@link settlePeerSegment} owes the same round-trip guarantee `makeName` gets by refusing * (ticket #10), and the property test holds the two legs to the identical law. */ export declare function mintNamespacePrefix(ns: ProtocolNamespace, peer: string): string; /** * The first pair of peers in `peers` that mint the SAME {@link mintNamespacePrefix} — i.e. that would * register into one indistinguishable namespace domain — or `undefined` when every peer owns its own. * * Two spellings collide whenever charset normalization, the length clamp or the separator fold maps * them together: `"prod.db"` and `"prod_db"` both mint `mcp__prod_db__`, and the same name listed twice * collides trivially. The mint itself cannot refuse (its input is deployment/remote data and a dotted * name must not become a materialization failure — see {@link normalizeNameSegment}), so the refusal * belongs to the MOUNT, which is the layer that knows the whole peer list. This function is that * layer's decision procedure: pure name arithmetic, decidable before any I/O. * * Why a collision cannot be tolerated downstream: the prefix IS the domain key. A refresh splices * `name.startsWith(prefix)` out and re-inserts only the refreshed peer's listing, so refreshing one of * two colliding peers silently unmounts the other's tools; equal tool names additionally shadow each * other last-write-wins in the harness map. Both failures are invisible at the moment they happen. */ export declare function findNamespacePrefixCollision(ns: ProtocolNamespace, peers: readonly string[]): { prefix: string; peers: [string, string]; } | undefined; /** * The full registered name for `(peer, tool)` in `ns`. Always starts with {@link mintNamespacePrefix}'s * answer for the same peer (the invariant ticket #9's pins hold), and never exceeds * {@link TOOL_NAME_MAX_CHARS}: whatever the peer segment leaves over is the tool segment's budget. */ export declare function mintNamespacedToolName(ns: ProtocolNamespace, peer: string, tool: string): string;