UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

162 lines 12 kB
/** * # sdk/op-schema — the ONE self-description of the agent JSONL protocol (kestrel-yuw4) * * The `kestrel agent` JSONL face lists its ops (`kestrel agent --help`, and the `unknown-op` refusal, #115) * but historically documented NONE of the per-op ARGUMENT OBJECT shapes — the `{ kind: … }` unions a * `subject` / `advance.response` must carry, and the exact accepted field NAMES (`document`, not * `text`/`bytes`/`source`/`content`). A cold integrator had to brute-force ~14 trial-and-error guesses to * discover them. This module is the SINGLE, machine-readable catalogue of those shapes: * * - {@link AGENT_OP_SCHEMAS} — one {@link AgentOpSchema} per op (its args + response shape), the source the * `describe` op emits VERBATIM on the wire, the source `kestrel agent --help` renders, and the source the * MCP face's argument descriptions read. ONE definition; the cross-face drift guards * (tests/cli.agent-describe.test.ts, tests/mcp.projection.test.ts) pin that nothing forks from it. * - The shape VOCABULARY constants ({@link SUBJECT_SHAPE} et al.) — referenced by BOTH the catalogue entries * AND the argument VALIDATORS below, so a refusal that names the expected shape can never drift from the * shape the catalogue advertises (they read the same string). * - The argument VALIDATORS ({@link asSessionRef} / {@link asAuthoredResponse} / {@link asBoundResponse} / * {@link asEventCursor}) — a malformed argument object is a fail-closed {@link ArgShapeError} whose message * NAMES the expected `{ kind: … }` vocabulary. STRUCTURE only: a well-formed-but-unresolvable VALUE (an * unknown catalog id, an unsupported subject kind) is deliberately passed through to the SDK, whose typed * DOMAIN refusal answers it — the transport/argument-error class must never absorb a Kestrel refusal. * * ZERO-WEIGHT: pure data + pure functions over type-only imports (erased at build). No runtime-semantics * import, so the light agent/SDK projection stays light (pinned by tests/package.boundary.test.ts). */ import type { AuthoredResponse, BoundResponse, SessionRef } from "./types.ts"; import type { EventCursor } from "../protocol/index.ts"; /** The `openSession.subject` shape — a discriminated {@link SessionRef} union (a STRING is rejected). */ export declare const SUBJECT_SHAPE = "{ \"kind\": \"catalog\", \"id\": string } | { \"kind\": \"dataset\", \"source\": string, \"dataset\": string }"; /** * The `advance.response` / `revise.response` shape — an {@link AuthoredResponse} union. `document` is the * ONLY accepted authored-body field name (NOT `text` / `bytes` / `source` / `content`), the exact fact a cold * integrator could not discover from any published surface (kestrel-yuw4). */ export declare const AUTHORED_RESPONSE_SHAPE = "{ \"kind\": \"authored\", \"document\": string } | { \"kind\": \"pass\" } | { \"kind\": \"stand-down\", \"reason\": string }"; /** * The CLOSED `AuthoredResponse.kind` union (kestrel-lzz7). UNLIKE `subject.kind` — an open discovery union * whose unknown values the SDK answers with an `unsupported-subject` DOMAIN refusal — the authored-response * kind is a fixed protocol enum with NO domain answer for an unknown value: a `frobnicate` kind reaches the * controller's exhaustive `responseToBody` switch, matches no case, and dereferences `body.kind` on the * resulting `undefined` (a raw `TypeError` surfaced as `code:"error"`). An unknown value is therefore a * malformed ARGUMENT, caught at the boundary as `bad-arg`, not passed through. */ export declare const AUTHORED_RESPONSE_KINDS: readonly ["authored", "pass", "stand-down"]; /** The `submit.response` shape — a fully-bound {@link BoundResponse} (the full pentad header + a TurnBody). */ export declare const BOUND_RESPONSE_SHAPE = "{ \"sessionId\": string, \"ordinal\": number, \"parentHash\": string, \"frameRoot\": string, \"body\": TurnBody }"; /** * The CLOSED {@link import("../protocol/session.ts").TurnBody}`.kind` union (kestrel-lzz7). A `submit` * response whose `body` is absent or carries an unknown `kind` reaches the controller's exhaustive * `reconcileTurn` switch, matches no case, and dereferences `body.kind` on `undefined` — the same raw * `code:"error"` leak as an unknown authored kind. A missing/unknown body kind is a malformed argument. */ export declare const TURN_BODY_KINDS: readonly ["authored", "stand-down", "failure"]; /** The optional `resume.after` / `resumeOperation.after` resume-cursor shape — an {@link EventCursor}. */ export declare const EVENT_CURSOR_SHAPE = "{ \"sequence\": number, \"token\": string }"; /** The `validate.document` shape — the Kestrel plan document source text (REQUIRED). */ export declare const VALIDATE_DOCUMENT_SHAPE = "the Kestrel plan document (source text)"; /** The `grade.blotters` shape — a REQUIRED non-empty array of Blotter artifact ids. */ export declare const BLOTTERS_SHAPE = "string[] \u2014 Blotter artifact ids (a Blotter's sessionId is its id)"; /** The `artifact.ref` shape — a REQUIRED content-addressed artifact reference. */ export declare const ARTIFACT_REF_SHAPE = "a content-addressed artifact ref"; /** The `resumeOperation.operationId` shape — the REQUIRED durable Operation id. */ export declare const OPERATION_ID_SHAPE = "the durable Operation id"; /** * The `catalog` op's response ROW shape — a {@link import("../protocol/catalog.ts").CatalogListing} (kestrel-dnxq). * The catalog op UNDER-specified its response before dnxq (`"CatalogEntry[] — each carries the id"`, no field * schema), so two divergent shapes both satisfied it; this pins the exact canonical fields BOTH transports emit * byte-identically. The reproducibility pins (content roots / engine versions / View identities) are NOT part of * discovery — they resolve at `openSession`/grade. */ export declare const CATALOG_LISTING_SHAPE = "{ \"id\": string, \"title\": string, \"description\": string, \"instrument\": string, \"period\": { \"start\": string, \"end\": string }, \"frameCount\": number, \"free\": boolean }"; /** One argument of an agent op: its wire field name, whether it is required, its JSON type, and — the point * of this whole module — the exact `{ kind: … }` object SHAPE it must carry. */ export interface AgentOpArg { readonly name: string; readonly required: boolean; readonly type: "string" | "object" | "array"; /** The exact accepted shape, in the `{ kind: … }` vocabulary — what a cold integrator needs to stop guessing. */ readonly shape: string; } /** The self-description of one agent op: its args and its response shape, plus whether it needs a live Session. */ export interface AgentOpSchema { /** The `op` field a request line carries (`{ "op": <name>, …args }`). */ readonly op: string; /** `true` ⇒ the op requires a prior `openSession` in the same stream (else it refuses `no-session`). */ readonly session: boolean; /** A one-line human summary of what the op does. */ readonly summary: string; /** The op's argument fields (empty for the session-free discovery ops). */ readonly args: readonly AgentOpArg[]; /** A one-line description of the protocol payload the op emits on the `value` channel. */ readonly response: string; } /** * THE single ordered catalogue of the agent protocol's ops — the source `describe` emits, `--help` renders, * and the MCP argument descriptions read. `describe` leads (the self-description entry point); the order is * the handler/dispatch order and is the order {@link agentOpNames} (and thus `AGENT_OPS`) preserves. */ export declare const AGENT_OP_SCHEMAS: readonly AgentOpSchema[]; /** The ordered op names — derived from {@link AGENT_OP_SCHEMAS} so the advertised set can never fork from the * described set. `AGENT_OPS` (src/cli/commands/agent.ts) is this list; the switch dispatches exactly it. */ export declare const agentOpNames: readonly string[]; /** The `describe` op's on-the-wire payload — the whole catalogue, tagged with the protocol version. */ export interface AgentDescribe { readonly protocol: string; readonly ops: readonly AgentOpSchema[]; } /** Build the `describe` payload for a given protocol version tag (the agent face passes its own version). */ export declare function agentDescribe(protocolVersion: string): AgentDescribe; /** * A fail-closed ARGUMENT-SHAPE refusal: a malformed argument OBJECT whose message NAMES the expected shape. * Carries a stable `code` (`bad-arg`) an agent matches on, and the offending `field`. DISTINCT from a Kestrel * DOMAIN refusal (an unresolvable but well-formed value) — validators throw this only for STRUCTURE errors. */ export declare class ArgShapeError extends Error { readonly name = "ArgShapeError"; readonly code = "bad-arg"; readonly field: string; constructor(field: string, expected: string, got: unknown); } /** * VALIDATE an `openSession.subject` — an OBJECT with a string `kind` and the fields its known kind requires * (`catalog` → `id`; `dataset` → `source` + `dataset`). An UNKNOWN kind value is passed through UNCHANGED to * the SDK (its `unsupported-subject` domain refusal answers it) — only STRUCTURE is enforced here. */ export declare function asSessionRef(v: unknown, field?: string): SessionRef; /** * VALIDATE an `advance.response` / `revise.response` — an {@link AuthoredResponse} union. Enforces the exact * accepted authored-body field name (`document`, a string) — the fact a cold integrator could not discover. * `pass` needs no field; `stand-down` needs a string `reason`. An UNKNOWN kind is passed through to the SDK. */ export declare function asAuthoredResponse(v: unknown, field?: string): AuthoredResponse; /** * VALIDATE a `submit.response` — a fully-bound {@link BoundResponse} OBJECT carrying a well-formed * {@link import("../protocol/session.ts").TurnBody} `body`. Field-level RECONCILIATION (a sessionId / ordinal * / parentHash mismatch against the recorded slot) stays the Session's DOMAIN job; only the STRUCTURE is * enforced here — including that `body` is present with a known `kind` (kestrel-lzz7), because a missing or * unknown body kind reaches the exhaustive `reconcileTurn` switch and dereferences `body.kind` on `undefined`. */ export declare function asBoundResponse(v: unknown, field?: string): BoundResponse; /** * VALIDATE a REQUIRED string arg — `artifact.ref`, `resumeOperation.operationId`, `validate.document` * (kestrel-8h9f). An ABSENT value (`undefined`, or an empty `""` string) is a malformed ARGUMENT: the * `describe` catalogue marks the field `required: true`, and forwarding the absent value as `""` made the * server answer a MISDIRECTING `not-found` (404) / `422` instead of naming the omitted field. Refuse it * client-side as `bad-arg` naming the described shape, matching `openSession.subject` / `resume.after`. */ export declare function asRequiredString(v: unknown, field: string, shape: string): string; /** * VALIDATE `grade.blotters` — a REQUIRED non-empty array of Blotter id strings (kestrel-8h9f). An absent / * empty / non-string-element array is a malformed argument refused `bad-arg` naming the shape, rather than the * server-side `empty-grade` domain refusal (inconsistent validation locus with the client-side arg checks). */ export declare function asBlotters(v: unknown, field?: string): readonly string[]; /** * VALIDATE an optional resume `after` cursor. ABSENT (`undefined`) is fine (resume from the start); a PRESENT * one must be a well-formed {@link EventCursor} `{ sequence:number, token:string }`. A malformed present * cursor is a bad ARGUMENT SHAPE (never silently ignored). STRICT: `null` is a present, wrong-typed value and * fails closed (deliberately NOT coalesced to undefined). */ export declare function asEventCursor(v: unknown, field?: string): EventCursor | undefined; //# sourceMappingURL=op-schema.d.ts.map