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.

95 lines (91 loc) 4.48 kB
/** * # cli/render/offer — the 402/Offer as STRUCTURED output (ADR-0004). * * A gated remote verb returns the platform's {@link WireOfferResponse} as DATA. This * renders it to stdout in the active mode — the conversion moment — and the caller * exits `5` (PAYMENT_REQUIRED). It NEVER launches a browser, follows * `human_action.url`, or auto-settles: the Offer, its proof, and the settlement * methods are printed for the agent/human to act on. Wire shape is snake_case and * `offer.amount` is a {@link ../backend/wire.ts Money} object (value + asset). */ import type { WireOfferResponse } from "../backend/wire.ts"; import type { SettlementRequired } from "../backend/hosted.ts"; import type { OutputCtx } from "../context.ts"; /** Render a 402/Offer to stdout as DATA — the conversion moment, never a browser redirect. */ export function renderOffer(p: WireOfferResponse, ctx: OutputCtx): void { if (ctx.mode === "json") { process.stdout.write(JSON.stringify({ schema: "kestrel.payment-required/v1", ...p }) + "\n"); return; } const o = p.offer; const amount = `${o.amount.value} ${o.amount.asset}`; const methods = p.settlement_methods.map((m) => m.method).join(","); const human = p.human_action?.url ?? "(none)"; const proof = p.proof?.url ?? p.proof?.proof_id ?? "(none)"; if (ctx.mode === "text") { process.stdout.write( `payment-required\toperation=${p.operation_id}\toffer=${o.offer_id}\tscope=${o.scope.join("+")}` + `\tamount=${amount}\tceiling=${o.ceiling}\tmethods=${methods}` + `\thuman=${human}\tproof=${proof}\n`, ); return; } // human process.stdout.write( `Payment required to continue operation ${p.operation_id}\n` + ` earned proof: ${proof}\n` + ` offer ${o.offer_id}: ${amount} for scope ${o.scope.join("+")} (ceiling ${o.ceiling})\n` + ` machine settlement: ${p.settlement_methods.map((m) => m.method).join(", ")}\n` + ` human claim-and-fund: ${human}\n`, ); } /** * Render a SPEND-BOUNDARY suspension as DATA (kestrel-e8e7). The hosted sim paused at the Spend * boundary (`status:"suspended"`) with a settleable Offer + a `resume` affordance inline; the * caller exits `5` (PAYMENT_REQUIRED). Distinct from {@link renderOffer} (the pre-drive 402 * data-access gate): this boundary carries no `settlement_methods`, but it DOES carry the exact * resume call, so the output states what is owed, for what, and how to continue — the frame is * the docs (an agent decides its next move from this alone), never a misleading 5xx. */ export function renderSettlementRequired(s: SettlementRequired, ctx: OutputCtx): void { const o = s.offer; const amount = `${o.amount.value} ${o.amount.asset}`; const r = s.resume; // The wired settle URL (bead kestrel-markets-1gr8) when the platform advertised one; otherwise // the resume affordance is the only continuation — never fabricate a settle endpoint. const settleUrl = s.settlement?.url ?? "(settle the offer, then resume)"; if (ctx.mode === "json") { process.stdout.write( JSON.stringify({ schema: "kestrel.payment-required/v1", boundary: "settlement", operation_id: s.operationId, offer: o, ...(s.settlement !== undefined ? { settlement: s.settlement } : {}), resume: r, }) + "\n", ); return; } if (ctx.mode === "text") { process.stdout.write( `payment-required\tboundary=settlement\toperation=${s.operationId}\toffer=${o.offer_id}` + `\tscope=${o.scope.join("+")}\tamount=${amount}\tceiling=${o.ceiling}\texpiry=${o.expiry}` + `\tsettle=${settleUrl}\tresume=${r.method} ${r.path}\trequires_settlement=${r.requires_settlement}` + `\tdetail=${r.detail}\n`, ); return; } // human — the Offer's essentials legible: price, what-you-get (scope + ceiling), settle URL, expiry. const settleLine = s.settlement !== undefined ? ` settle at: ${s.settlement.method} ${s.settlement.url} (offer ${o.offer_id})\n` : ` settle the offer ${o.offer_id}, then resume\n`; process.stdout.write( `Payment required to continue operation ${s.operationId} (Spend boundary)\n` + ` offer ${o.offer_id}: ${amount} for scope ${o.scope.join("+")} (ceiling ${o.ceiling}, expires ${o.expiry})\n` + settleLine + ` then resume: ${r.method} ${r.path} (operation ${s.operationId}, cursor ${r.cursor})\n` + ` ${r.detail}\n`, ); }