UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

96 lines (95 loc) • 5.16 kB
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>;