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
text/typescript
/**
* # 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 } : {}) });
}