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.
231 lines • 12.1 kB
TypeScript
/**
* # protocol/wire — the PLATFORM HTTP/SSE wire shapes (kestrel-5rb, OSS-ADR-0046).
*
* The single, authoritative TypeScript mirror of the kestrel.markets contract as
* served on the wire: **snake_case** bodies + the canonical SSE `OperationEvent.type`
* vocabulary, transcribed from
* `kestrel.markets/packages/contracts/openapi/kestrel-markets.openapi.json`
* (schemas TrialCapability / ValidateResult / Operation / OfferResponse / Offer /
* Money / SettlementMethod / HumanAction / Proof / OperationEvent; the schema is
* authoritative for shapes, docs/contract/http-sse-contract.md for semantics).
*
* Per OSS-ADR-0046 these shapes live HERE in `src/protocol` — the same home as the
* rest of the wire vocabulary (the `Scope` union, `RECEIPT_KINDS`, the SSE
* constants) — so a shape change is one edit and drift is a red conformance run.
* The CLI transport (`src/cli/backend/wire.ts`) re-exports this module rather than
* hand-maintaining a parallel copy.
*
* This is DELIBERATELY distinct from the OSS camelCase domain types in
* {@link ./index.ts}: the wire is snake_case, the Offer amount is a {@link WireMoney}
* object (not a bare number), the cursor is an OPAQUE STRING (not `{sequence, token}`),
* and the SSE events are `operation.*`. Both {@link ../cli/backend/remote.ts remote.ts}
* (the client) and {@link ../cli/backend/mock.ts mock.ts} (the truthful in-process
* server) consume it through the transport re-export, so neither can silently
* re-fabricate a dialect: the SSE vocabulary in particular has exactly one definition
* here, drift-guarded against the OpenAPI enum by `tests/cli.remote-conformance.test.ts`.
*
* Pure types + constants + pure decoders. ZERO runtime dependencies — the only import
* is the intra-protocol domain types it decodes into (asserted by
* tests/protocol.boundary.test.ts).
*/
import type { Scope, TrialCapability, Operation } from "./index.ts";
/**
* The OVERSIGHT wire mirror (contract §4.4, kestrel-telx.5) — `WireOversightEvent` + the
* snake_case decoders, re-exported HERE so it sits beside {@link SSE_EVENT_TYPES} on the ONE
* import path the CLI transport ({@link ../cli/backend/wire.ts}) re-exports. The definitions live
* in their own leaf only because they are long; nothing there re-declares an SSE vocabulary (the
* closed oversight enum has exactly one home, `./oversight.ts`).
*/
export * from "./oversight-wire.ts";
/** The canonical `OperationEvent.type` enum (openapi OperationEvent.type). The ONE
* definition of the SSE vocabulary; the conformance suite pins it to the fixture. */
export declare const SSE_EVENT_TYPES: readonly ["operation.started", "operation.progress", "operation.artifact", "operation.receipt", "operation.suspended", "operation.resumed", "operation.completed", "operation.failed"];
export type SseEventType = (typeof SSE_EVENT_TYPES)[number];
/** The two terminal event types that end an Operation's stream (§9 Events). */
export declare const SSE_TERMINAL_TYPES: readonly ["operation.completed", "operation.failed"];
/** True iff `t` is a contract SSE event type. Anything else is a protocol violation
* the client must FAIL CLOSED on (AGENTS.md: unknown series/event → de-arm, never
* silently ignore). */
export declare function isSseEventType(t: string): t is SseEventType;
export declare function isTerminalSseEvent(t: string): t is (typeof SSE_TERMINAL_TYPES)[number];
/** openapi TrialCapability. `capability` is the bearer token (NOT `capabilityId`). */
export interface WireTrialCapability {
readonly capability: string;
readonly subject_commitment: string;
readonly expiry: string;
readonly scopes: readonly Scope[];
readonly rate_limit?: {
readonly requests_per_minute?: number;
readonly compute_quota?: string;
};
}
/** openapi Diagnostic (validate/parse diagnostics; also a ValidationProblem member). */
export interface WireDiagnostic {
readonly severity: "error" | "warning" | "hint";
readonly code: string;
readonly message: string;
readonly span?: {
readonly line?: number;
readonly column?: number;
readonly length?: number;
};
}
/** openapi ValidateResult (200 from POST /validate; pure — no Operation). */
export interface WireValidateResult {
readonly valid: true;
readonly artifact_id?: string;
readonly diagnostics?: readonly WireDiagnostic[];
}
export type WireOperationStatus = "running" | "completed" | "suspended" | "failed";
/** openapi Operation. `cursor` is an opaque STRING; `status` (not `state`); the
* domain artifacts ride as opaque refs, the real payload arrives over SSE. */
export interface WireOperation {
readonly operation_id: string;
readonly intent_hash: string;
readonly status: WireOperationStatus;
readonly checkpoint?: string;
readonly cursor: string;
readonly artifacts?: readonly unknown[];
readonly receipts?: readonly unknown[];
readonly offer?: WireOffer | null;
readonly continuation?: unknown | null;
}
/** openapi Money — a decimal string + asset code (NOT a bare number). */
export interface WireMoney {
readonly value: string;
readonly asset: string;
}
/** openapi SettlementMethod — discriminated on `method` (NOT `kind`). */
export interface WireSettlementMethod {
readonly method: "stripe-mpp" | "x402";
readonly network?: string;
readonly accepts?: readonly string[];
readonly params?: Record<string, unknown>;
}
/** openapi HumanAction — the contextual human path; `url` is DATA, never followed. */
export interface WireHumanAction {
readonly kind: "claim-and-fund";
readonly url: string;
readonly required: boolean;
readonly label?: string;
}
/** openapi Proof — the free result already earned at the sealed checkpoint. */
export interface WireProof {
readonly proof_id: string;
readonly operation_id: string;
readonly intent_hash?: string;
readonly artifacts?: readonly unknown[];
readonly receipts?: readonly unknown[];
readonly url?: string;
}
/** openapi Offer — `amount` is {@link WireMoney}; bound to exactly one Operation. */
export interface WireOffer {
readonly offer_id: string;
readonly operation_id: string;
readonly intent_hash: string;
readonly scope: readonly Scope[];
readonly ceiling: number;
readonly budget?: number;
readonly amount: WireMoney;
readonly terms_digest: string;
readonly expiry: string;
readonly nonce: string;
readonly signature?: string;
readonly requires_human?: boolean;
}
/** openapi OfferResponse — the STRUCTURED 402 body (§"The 402 Offer"). Every face
* surfaces it as DATA; none may silently redirect into a browser checkout. */
export interface WireOfferResponse {
readonly operation_id: string;
readonly offer: WireOffer;
readonly proof?: WireProof | null;
readonly settlement_methods: readonly WireSettlementMethod[];
readonly human_action?: WireHumanAction;
}
/** openapi OperationEvent — the JSON `data:` payload of an SSE frame; `cursor`
* echoes the SSE `id:`; the opaque domain payload rides in `data`. */
export interface WireOperationEvent {
readonly cursor: string;
readonly type: SseEventType;
readonly operation_id: string;
readonly data?: Record<string, unknown>;
}
/**
* openapi Operation.resume (ResumeAffordance) — the self-describing next call that drives a
* NON-TERMINAL Operation forward (bead kestrel-markets-770.14, ADR-0009). It names the SAME
* canonical `POST /api/simulate` the caller made, re-driving the SAME durable Operation from
* `cursor` without repeating completed work. `requires_settlement:true` marks the SPEND
* BOUNDARY (Operation `status:"suspended"`): the attached `offer` must be settled before the
* resume POST. `detail` is the actionable protocol note the platform ships inline.
*/
export interface WireResumeAffordance {
readonly method: "POST";
readonly path: string;
readonly operation_id: string;
readonly cursor: string;
readonly requires_settlement: boolean;
readonly detail: string;
}
/**
* openapi SettlementAffordance — the machine-followable SETTLE rail an Offer advertises
* (bead kestrel-markets-1gr8, PR #334). Embedded in the 402 OfferResponse and, from
* wave-3, in the suspended `SimOperationResponse` too: the EXACT wired URL an agent POSTs
* settlement evidence to (`POST /api/operations/{id}/settlement`), the method, the accepted
* rails, and the `SettlementRequest` body shape. This is "where to actually pay" — distinct
* from the {@link WireResumeAffordance} `resume` (which re-drives `/api/simulate` AFTER the
* Offer is settled). Optional on the wire: a response minted before the rail was wired
* carries none, so the CLI degrades to the resume affordance rather than fabricating a URL.
*/
export interface WireSettlementAffordance {
readonly method: "POST";
readonly url: string;
readonly accepts?: readonly string[];
readonly body_schema?: {
readonly offer_id: string;
readonly method: string;
readonly evidence: {
readonly provider_payment_id: string;
};
};
readonly detail?: string;
}
/** Decode a wire {@link WireTrialCapability} into the OSS {@link TrialCapability}.
* The bearer token (`capability`) becomes the OSS `capabilityId` (the capability id
* IS the bearer for a trial cap); `rate_limit.requests_per_minute` → the OSS
* requests-per-window shape. */
export declare function decodeTrialCapability(w: WireTrialCapability): TrialCapability;
/** Decode a wire {@link WireOperation} into the OSS {@link Operation}. The opaque
* wire `cursor` string is carried verbatim in `EventCursor.token` (its `sequence`
* is not on the wire — the cursor is an opaque bookmark, handed back by string). */
export declare function decodeOperation(w: WireOperation): Operation;
/**
* Fail-closed structural check that `v` is a SETTLEABLE {@link WireOffer} — a full,
* platform-signed Offer carrying terms (an `offer_id`, a non-empty `scope[]`, a finite
* `ceiling`, and a {@link WireMoney} `amount`), NOT the degraded `{offer_id, operation_id}`
* boundary fallback the platform emits when no signing key is wired (session.ts@toResponse).
* The bare fallback carries no itemized terms to surface as data, so it returns `null` and the
* caller keeps its existing typed behavior — never a silent default, never a guessed Offer.
*/
export declare function asWireOffer(v: unknown): WireOffer | null;
/**
* Fail-closed check that `v` is a SETTLEMENT-GATED {@link WireResumeAffordance} bound to
* `operationId`: the canonical `POST /api/simulate` re-drive with `requires_settlement:true`.
* Anything else (a non-canonical path, a foreign operation, `requires_settlement` other than
* `true`) returns `null` — the Spend-boundary shape is recognized exactly, or not at all.
*/
export declare function asSettlementResume(v: unknown, operationId: string): WireResumeAffordance | null;
/**
* Fail-closed check that `v` is a well-formed {@link WireSettlementAffordance}: the wired
* settle rail an Offer advertises (`POST /api/operations/{id}/settlement`). Requires the two
* load-bearing fields an agent needs to REACH the rail — `method:"POST"` and a non-empty
* `url` — and shape-checks the optional descriptive fields when present. Returns `null` for a
* response that carries no settle affordance (minted before the rail was wired, bead
* kestrel-markets-1gr8), so the caller degrades to the resume affordance rather than printing
* a fabricated URL — never a silent default.
*/
export declare function asSettlementAffordance(v: unknown): WireSettlementAffordance | null;
/** Fail-closed structural check that a decoded 402 body is a real OfferResponse
* (the required members per openapi OfferResponse). Returns the narrowed value or
* `null` — the caller turns `null` into a loud error, never a silent default. */
export declare function asOfferResponse(body: unknown): WireOfferResponse | null;
//# sourceMappingURL=wire.d.ts.map