@sema-agent/core
Version:
Stateless, task-oriented AI agent core
96 lines (95 loc) • 5.16 kB
TypeScript
import type { AgentTool } from "../internal/harness-types.js";
import type { A2aServerSpec, ToolEffect } from "./types.js";
/** The hard per-skill description bound (ruled 2026-08-03: ~2KiB). */
export declare const A2A_SKILL_DESCRIPTION_MAX_CHARS = 2048;
/**
* The safety axes carried alongside one materialized A2A tool, for `prepare-task` to fold into the SAME
* irreversible/egress collections first-party and MCP tools feed.
*
* Unlike MCP there is no server-hint leg to merge: the protocol has no per-skill annotation vocabulary,
* and a remote agent's self-declaration would not be trustworthy if it had one. Every A2A tool therefore
* starts at the ruled default (`egress:true`, the effect undeclared — read as `write` everywhere) and the CALLER's
* {@link A2aServerSpec.toolAxes} is the only thing that can lower it (design F: caller = trust root).
*/
export interface A2aToolAxis {
/** Namespaced tool name (`a2a__<peer>__<skill>`). */
name: string;
/** `"never"` appears ONLY via an explicit caller override — enforcement ignores it (tighten-only),
* the ask's risk-axes report face consumes it. */
irreversibility?: "always" | "never";
/** `false` appears ONLY via an explicit caller override — same posture: enforcement ignores it,
* the report face consumes it (an explicit negative is a judgment, not silence). */
egress?: boolean;
/** Present ONLY when the caller's override declares it; absent is the repeat-unsafe `write` every reader applies (and
* the roster names `default` provenance — a default is not a declaration). */
effect?: ToolEffect;
}
/** design/99 §E9 equivalent for A2A — a per-peer projection of THIS materialization. A2A holds no
* connection (every call is its own HTTPS request), so `ready` means "the card was fetched and its
* skills were mounted at prepare time", never "the peer is alive right now". */
export interface A2aPeerStatus {
name: string;
status: "ready" | "failed";
/** The card's self-declared name, neutralized + bounded (it rides consumer-rendered lines). */
agentName?: string;
agentDescription?: string;
/** The negotiated JSON-RPC endpoint this task will call. */
endpoint?: string;
toolNames?: string[];
error?: string;
}
/** One per-peer entry of {@link MaterializedA2a.refresh}. */
export interface A2aRefreshResult {
peer: string;
/** The peer's namespaced-name prefix — the consumer's SPLICE DOMAIN for a wholesale swap (the same
* self-healing prefix domain the MCP refresh receipt hands out). Present on every entry. */
prefix: string;
status: "refreshed" | "failed" | "disposed";
toolCount: number;
added: string[];
removed: string[];
tools?: AgentTool[];
axes?: A2aToolAxis[];
error?: string;
}
/**
* Tools materialized from one or more A2A peers.
*
* Deliberately NARROWER than {@link import("./mcp.js").MaterializedMcp}: A2A has no server-instructions
* channel and no intake schema gate, so there is no `serverInstructions`, no `instructionsDelta` and no
* `droppedTools` face — an absent face is honest, an empty one that can never fill is a face a consumer
* writes code against for nothing.
*/
export interface MaterializedA2a {
tools: AgentTool[];
toolAxes: A2aToolAxis[];
/** One entry per peer that could not be materialized and was SKIPPED (fail-open, design/29): the task
* runs with the healthy peers' tools. The Runner forwards these to `onError(phase:"a2a")`. */
warnings: Error[];
statuses: A2aPeerStatus[];
/** Re-read one peer's card (or every declared peer) and re-derive its tools. Unlike the MCP refresh
* this dials nothing new — every A2A call is a fresh request anyway — so a peer that failed at
* prepare time can genuinely recover here. */
refresh: (peer?: string) => Promise<A2aRefreshResult[]>;
/**
* Task teardown. There is no socket to close (every call is its own request), so what this owns is the
* LIFETIME statement: after it resolves, a tool that somehow still holds a reference fails loudly
* instead of talking to a remote agent on behalf of a task that has ended.
*/
dispose: () => Promise<void>;
}
/** A JSON-RPC error answered BY the peer (the exchange happened; the peer refused or failed). Carries the
* numeric code for `instanceof`-plus-code discrimination — never message-text matching. */
export declare class A2aRpcError extends Error {
readonly code: number;
constructor(peer: string, method: string, code: number, message: string);
}
/**
* Materialize every declared A2A peer into tools for one task.
*
* Peers are set up IN PARALLEL and merged in SPEC ORDER, so the tool roster (and therefore the system
* prompt's tool list and the prefix cache) is byte-identical regardless of which card answered first.
* `allSettled` keeps it fail-open: one unreachable peer is a skipped peer with a warning, never a task
* that cannot start.
*/
export declare function materializeA2aTools(specs: readonly A2aServerSpec[], principal?: string, signal?: AbortSignal): Promise<MaterializedA2a>;