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.

234 lines (230 loc) 6.88 kB
import { bearerHeader, consumeOperationStream, jsonHeaders, mintTrialCapability, parseValidateDiagnostics, postEffectful } from "./bin-qewrvqtv.js"; import { CliError, EXIT } from "./bin-t9ggwnv5.js"; import { canonicalPlansText } from "./bin-29be75ss.js"; import { decodeOperation } from "./bin-2ywrx58g.js"; // src/cli/backend/sse.ts function httpErr(status, m) { const exit = status === 404 ? EXIT.NOT_FOUND : status >= 500 ? EXIT.RUNTIME_UNAVAILABLE : EXIT.GENERIC; return new CliError({ code: `HTTP_${status}`, exit, message: `${m} (HTTP ${status})` }); } function unknownEvent(type) { return new CliError({ code: "SSE_PROTOCOL_VIOLATION", exit: EXIT.RUNTIME_UNAVAILABLE, message: `unknown SSE event type ${JSON.stringify(type)} — not in the OperationEvent contract vocabulary` }); } // src/cli/backend/remote.ts class RemoteBackend { kind = "remote"; base; fetch; cap; bearer; jwtSigner; constructor(opts) { this.base = opts.baseUrl.replace(/\/+$/, ""); const f = opts.fetch ?? globalThis.fetch; if (f === undefined) throw new CliError({ code: "RUNTIME_UNAVAILABLE", exit: EXIT.RUNTIME_UNAVAILABLE, message: "global fetch unavailable — need node ≥18 or bun for --api" }); this.fetch = f; if (opts.jwtSigner !== undefined) { this.jwtSigner = opts.jwtSigner; } if (opts.capability !== undefined) { this.cap = opts.capability; this.bearer = opts.capability.capabilityId; } else if (opts.bearer !== undefined) { this.bearer = opts.bearer; } } async ensureBearer() { if (this.bearer !== undefined) return; await this.bootstrapCapability(); } async bootstrapCapability() { if (this.cap) return this.cap; const { capability, bearer } = await mintTrialCapability({ fetch: this.fetch, base: this.base, onHttpError: remoteHttpErr }); this.bearer = bearer; this.cap = capability; return this.cap; } async getPercept(_args) { throw new CliError({ code: "REMOTE_UNSUPPORTED", exit: EXIT.USAGE, message: "the frame/percept View has no remote contract route (M1: catalog, validate, sim, grade, operations, offers, proof, events)", hint: "run `frame` locally — omit --api; a remote View projection is not in the platform contract" }); } async submitPlan(documents) { const auth = await this.authHeaders(); const canonicalText = canonicalPlansText(documents); const res = await this.fetch(`${this.base}/validate`, { method: "POST", headers: { ...jsonHeaders(), ...auth }, body: JSON.stringify({ source: canonicalText }) }); if (res.status === 422) { return { gated: false, value: { ok: false, canonicalText, documents, diagnostics: await parseValidateDiagnostics(res) } }; } if (!res.ok) throw await remoteHttpErr(res, "POST /validate failed"); const wire = await res.json(); return { gated: false, value: { ok: wire.valid === true, canonicalText, documents, diagnostics: (wire.diagnostics ?? []).map((d) => `${d.severity} ${d.code}: ${d.message}`) } }; } async openSession(_scope, args) { return this.runSim(args, false); } async openDaySession(_scope, args) { return this.runSim(args, true); } async runSim(args, _day) { const source = canonicalPlansText(args.documents); const artifactId = args.busPath; if (artifactId === undefined) throw new CliError({ code: "USAGE", exit: EXIT.USAGE, message: "remote sim requires a dataset — pass a catalog artifact id (the bus/data reference)" }); const req = { source, dataset: { artifact_id: artifactId }, params: { fill_model: args.fillModel, r_usd: args.rUsd } }; const opened = await this.effectfulPost("/sim", req); if (opened.gated) return opened; const { payload } = await this.consumeStream(opened.value); const report = payload["report"]; if (report === undefined) throw httpErr(502, "sim stream carried no report artifact"); const blotter = payload["blotter"]; if (blotter === undefined) throw httpErr(502, "sim stream carried no blotter artifact"); const wakes = payload["wakes"] ?? []; return { gated: false, value: { report, plansText: source, wakes, operation: decodeOperation(opened.value), blotter } }; } async grade(subject, opts) { const blotters = [blotterId(subject), ...(opts.corpus ?? []).map((b) => b.sessionId)]; const req = { blotters }; const opened = await this.effectfulPost("/grade", req); if (opened.gated) return opened; const { payload } = await this.consumeStream(opened.value); const result = payload["grade"] ?? payload["result"]; if (result === undefined) throw httpErr(502, "grade stream carried no grade artifact"); return { gated: false, value: result }; } async effectfulPost(path, body) { return postEffectful({ fetch: this.fetch, base: this.base, path, body, authHeaders: await this.authHeaders(), httpError: httpErr, onHttpError: remoteHttpErr }); } async consumeStream(op) { return consumeOperationStream({ fetch: this.fetch, base: this.base, operationId: op.operation_id, cursor: op.cursor, headers: await this.authHeaders(), httpError: httpErr, unknownEventError: unknownEvent }); } async authHeaders() { if (this.jwtSigner !== undefined) { return { authorization: `Bearer ${await this.jwtSigner()}` }; } await this.ensureBearer(); return bearerHeader(this.bearer); } } async function remoteHttpErr(res, message) { const base = httpErr(res.status, message); const domainCode = await readProblemCode(res); if (domainCode === undefined) return base; return new CliError({ code: domainCode, exit: base.exit, message: base.message, ...base.hint !== undefined ? { hint: base.hint } : {} }); } var MAX_DOMAIN_CODE = 128; async function readProblemCode(res) { try { const body = await res.json(); if (body === null || typeof body !== "object") return; const code = body["code"]; if (typeof code !== "string" || code.length === 0 || code.length > MAX_DOMAIN_CODE) return; return code; } catch { return; } } function blotterId(subject) { if (typeof subject.sessionId === "string") return subject.sessionId; throw new CliError({ code: "USAGE", exit: EXIT.USAGE, message: "remote grade requires a Blotter artifact id — run the sim remotely first, then grade its Blotter" }); } export { RemoteBackend };