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.

59 lines (53 loc) 3.12 kB
/** * # cli/backend/select — pick the ExecutionBackend from the global flags + env. * * Local is the DEFAULT (node-light construction). `--api <url>` > `KESTREL_API` env * selects the RemoteBackend. Construction pulls no heavy runtime, so threading a * backend from `main` never breaks the light command path. */ import type { GlobalFlags } from "../context.ts"; import type { ExecutionBackend } from "./index.ts"; import { LocalBackend } from "./local.ts"; import { RemoteBackend, DEFAULT_API } from "./remote.ts"; import { loadCredential, loadKeypair } from "../credentials.ts"; import { mintRequestJwt } from "../keypair.ts"; interface Env { readonly [k: string]: string | undefined; } /** Local is the DEFAULT. `--api <url>` > `KESTREL_API` env selects remote. * A bare `--api` (or `--api default`) uses the canonical base. * * When a REGISTERED-agent credential (`kestrel register`, kestrel-markets-0t0) is * stored for the SAME api base, it is presented as the bearer so remote calls run as * the registered agent instead of self-minting an anonymous trial each time. A * credential minted for a DIFFERENT host is ignored (host-scoped, fail-safe). Loading * is best-effort (fail-quiet) — a missing/corrupt file just falls back to the trial. */ export function resolveBackend(globals: GlobalFlags, env: Env): ExecutionBackend { const raw = globals.api ?? env.KESTREL_API; if (raw === undefined) return new LocalBackend(); const baseUrl = (raw === "" || raw === "default" ? DEFAULT_API : raw).replace(/\/+$/, ""); // KEYPAIR-JWT auth (kestrel-markets-0t0 slice 2) takes PRECEDENCE over the bearer: when an // enrolled keyfile (`kestrel register --keypair`) is host-scoped to THIS base, mint a fresh // short-lived JWT per request (a possession proof signed by the enrolled private key) instead // of replaying the durable capability. Minted on demand, so it needs no freshness check — an // enrolled key does not expire like the 1h kcap. Absent ⇒ the kcap path below runs unchanged. const keypair = loadKeypair(); if (keypair !== undefined && keypair.api.replace(/\/+$/, "") === baseUrl) { const jwtSigner = () => mintRequestJwt({ privateJwk: keypair.private_jwk, subject: keypair.subject, audience: keypair.audience, ttlSec: keypair.max_ttl_seconds, }); return new RemoteBackend({ baseUrl, jwtSigner }); } const cred = loadCredential(); // Present the stored credential only when it is host-scoped to THIS base AND still // valid: an EXPIRED credential is ignored so a stale token can never wedge remote // calls (the platform would 401 it) — the backend falls back to a fresh anonymous // trial instead. A malformed/absent expiry is treated as expired (fail-safe). const fresh = cred !== undefined && Number.isFinite(Date.parse(cred.expiry)) && Date.parse(cred.expiry) > Date.now(); const bearer = cred !== undefined && fresh && cred.api.replace(/\/+$/, "") === baseUrl ? cred.capability : undefined; return new RemoteBackend({ baseUrl, ...(bearer !== undefined ? { bearer } : {}) }); }