Status: build spec for a fresh repository. This document is the single source of truth for building v1. It is written to be handed to an agent that loops until every item in §19 Acceptance Criteria is green. Read §0 Build-Loop Protocol first.
You are building bitrouter-agent v1 from an empty repository. Work milestone
by milestone in the order given in §18. After each
milestone:
- Run the checks:
pnpm typecheck && pnpm lint && pnpm test. - Run the end-to-end smoke (§20) once that milestone makes it runnable.
- Commit with a conventional-commit message (
feat:,chore:,test:…), title ≤ 60 chars. Branch offmain; do not push unless asked. - Re-read §19. The loop is done when, and only when, every acceptance box can be checked and the checks in step 1–2 pass on a clean tree. Until then, pick the next unmet item and continue.
Rules that hold for the whole build:
- Zero-write to user code. The agent this tool runs must never modify or create files inside the user's existing source. It may only create the new artifact files in §3. Enforce this by tool absence (ship no edit/write-to-source tool), not by a scanner. See §9.
- pi-only. Do not add
@anthropic-ai/claude-agent-sdk. The agent loop is the pi SDK. See §5 and §7. - Verify external APIs against installed types. Code sketches below match
pi
~0.79(the version@posthog/wizardships against). Pin the version; if a signature differs, trust the installed.d.ts, not this doc. - The sibling checkout at
../bitrouteris the source of truth for the config schema, example config, registry prices, and the/bitrouterskill. Reference it at build time (see §12, §13).
bitrouter-agent is a CLI wizard — modeled on npx @posthog/wizard — that a
developer runs inside their production agentic codebase. It statically
analyzes how the codebase spends LLM tokens and produces three artifacts that
help the user adopt BitRouter (the cost-optimizing LLM gateway/router, the
sibling project) to cut that spend. It is built on the pi coding-agent SDK
(@earendil-works/pi-coding-agent) and routes its own LLM calls through
BitRouter via the @bitrouter/pi provider package.
The UX north star is PostHog's wizard: a focused, mostly-scripted terminal flow where an embedded coding agent does the codebase-specific reasoning, gated by clear consent, ending in concrete artifacts.
In (v1 = "T0 · Instant", static cold-start):
- Standalone CLI, distributed as an npm package (
npx @bitrouter/agent). - Static analysis of the user's repo by a pi agent, read-only.
- Three output artifacts: an audit, a savings estimate, and an
MVP
bitrouter.yaml(§3). - The agent routed through BitRouter via
@bitrouter/pi(§8). - A single consent disclosure before analysis (§14).
- A functional (not necessarily fancy) terminal UI (§16).
Out (design the seams, do not build):
- Any write to the user's existing files (env swaps, code instrumentation). v1 instructs via comments in the generated yaml only.
- T2 · Cloud upload /
bitrouter-mcpconnection / advanced optimization. @anthropic-ai/claude-agent-sdk, the "anthropic" harness, orchestrator mode,magicast/recast.- Multi-framework special-casing beyond LLM-SDK detection.
Next milestone, spec'd but after v1 core (T1 · Watch): a watch mode that
reads the local running daemon's telemetry (bitrouter.db metering + /metrics)
to upgrade the estimate from a rate-card to observed dollars. Spec'd in
§4 and §18 M6; not part of the
v1 acceptance gate.
All artifacts are new files written into the target repo's working dir (or
--out-dir). Writing these is the only filesystem mutation allowed.
A valid BitRouter config (§13) that:
- declares the providers the codebase already uses (keys as
${VAR}placeholders — never real secrets), - defines virtual models with
strategy: priorityfallback chains that route routine call-classes to a cheap open model and keep the current frontier model as the escalation/fallback, - carries the
OPENAI_BASE_URL=http://localhost:4356adoption step as comments, not applied edits, - passes validation (§13.3).
A Markdown report containing:
- Detected setup: SDK/harness, provider(s), and every model id found, each
with its call-sites (
path:line) and an inferred call-class (routine|complex). - Loop shape: sub-agents, tool calls, MCP servers, retry/fallback wiring observed.
- Savings estimate (rate card): for each frontier model in use, the
current
$/Mtokvs the cheapest open-weight equivalent, the routine-share estimate, and a blended % reduction. Framed as a rate comparison / scenario — never a fabricated absolute dollar bill (§12). - Recommended routing: the rationale behind the
bitrouter.yamlchoices.
Structured JSON that both bitrouter-audit.md renders from and the future
watch mode diffs observed telemetry against. Minimum shape:
The tier model resolves the cold-start problem (no traffic ⇒ no real numbers). v1 builds T0 only; the architecture must leave clean seams for T1/T2.
| Tier | v1? | Trigger | Data source | Output upgrade |
|---|---|---|---|---|
| T0 · Instant | ✅ build | at install, 0 traffic | static analysis + registry prices | rate-card estimate |
| T1 · Watch | ⬜ next (M6) | first N metered runs / few days | local bitrouter.db metering + /metrics |
observed absolute $ + per-hop attribution + tightened policy diff |
| T2 · Cloud | ❌ v2 | explicit consent | upload via bitrouter-mcp |
cross-run advisor self-tuning |
Seam requirements (must exist in v1):
- The analysis engine emits
.bitrouter/analysis.jsonso T1 has something to diff against. - The tool registry is capability-partitioned (
readonlyset vs awriteset that is empty in v1) so T1/T2 add a tool behind a flag, not a refactor. The first future write tool is a telemetry-wiring one (setOTEL_EXPORTER_OTLP_ENDPOINT/ add anobserve:block), never a code edit.
Layered, pi-only, per-concern (mirrors the reusable half of @posthog/wizard,
minus everything the zero-write decision removes).
bin.ts # node ≥22 preflight + Wizard.use(...).init()
src/wizard.ts # yargs wrapper + global options (lift from wizard)
src/commands/ # command.ts (Command iface) + one file per command
command.ts # the Command → yargs adapter (lift from wizard)
init.ts # $0 default: run all three deliverables
audit.ts estimate.ts # focused single-artifact entrypoints
src/programs/ # one "program" per deliverable: prompt + toolset + runner
run-analysis.ts # the scripted spine (audit → estimate → yaml)
src/agent/ # pi embedding
session.ts # createAgentSession wiring (§7)
provider.ts # @bitrouter/pi bootstrap + model pick (§8)
tools/ # read-only + emit_* custom tools (§9)
prompts.ts # system + phase prompts (§10)
src/detection/ # host-side pre-agent scan (§11)
src/pricing/ # registry snapshot + rate-card math (§12)
src/yaml/ # bitrouter.yaml builder + validator (§13)
src/ui/ # WizardUI interface + minimal impl (§16)
src/config.ts # resolved run config (cwd, target, model, flags)
scripts/sync-registry.ts # build-time: snapshot ../bitrouter/registry → bundled JSON
fixtures/sample-agent-app/ # a tiny fake agentic repo for e2e (§20)
Control flow: bin.ts → Wizard(yargs) → a Command.handler →
runAnalysis(config) → mount UI → host-side detection → bootstrap pi session
(provider + tools + prompts) → scripted spine: prompt(AUDIT) →
prompt(ESTIMATE) → prompt(YAML), each emitting its artifact → summary →
exit.
- Runtime: Node ≥ 22.22 (match pi). Language: TypeScript, ESM
(
"type": "module"). Package manager: pnpm. Build:tsdown(ortsup). Test:vitest. Lint: eslint + prettier. - Package: name
@bitrouter/agent;bin: { "bitrouter-agent": "dist/bin.js" }.
Dependencies (use):
| Package | Why |
|---|---|
@earendil-works/pi-coding-agent |
the agent loop + defineTool + resource loader (pin ~0.79) |
@bitrouter/pi |
registers bitrouter as the agent's model provider; also bundles the /bitrouter skill |
typebox |
tool parameter schemas (Type.Object) — pi's schema lib |
yargs |
CLI parsing |
fast-glob |
host-side file scanning |
yaml (or js-yaml) |
build + serialize bitrouter.yaml |
ink + react |
terminal UI (may defer rich screens — see §16) |
semver |
node-version preflight |
Do NOT add: @anthropic-ai/claude-agent-sdk, magicast, recast,
langchain (detect its usage, don't depend on it). If you find yourself
reaching for the Claude Agent SDK, re-read §17.
Battle-tested shape from @posthog/wizard's pi harness
(src/lib/agent/runner/harness/pi/index.ts), adapted for read-only + our
provider. The agent loop is session.prompt() — not runPrintMode/runRpcMode.
import {
createAgentSession, DefaultResourceLoader, SessionManager,
} from '@earendil-works/pi-coding-agent';
export async function buildSession(cfg: RunConfig, ui: WizardUI) {
const model = await pickAgentModel(cfg); // §8 — a "bitrouter/<id>" id
const resourceLoader = new DefaultResourceLoader({
cwd: cfg.installDir,
// Load exactly ONE extension: the bitrouter provider (+ its bundled skill).
additionalExtensionPaths: [bitrouterProviderExtensionPath()], // §8
systemPromptOverride: () => SYSTEM_PROMPT, // §10
noContextFiles: true, noThemes: true, noPromptTemplates: true,
});
const { session } = await createAgentSession({
model,
cwd: cfg.installDir,
sessionManager: SessionManager.inMemory(cfg.installDir),
resourceLoader,
noTools: 'builtin', // disable pi built-ins…
customTools: buildTools(cfg, ui), // …re-register OUR read-only + emit set (§9)
});
await session.bindExtensions({}); // fires session_start → provider registers
// Progress/telemetry: forward pi events to the UI.
session.subscribe((e) => {
if (e.type === 'tool_execution_start') ui.onToolStart(e);
if (e.type === 'tool_execution_end') ui.onToolEnd(e);
if (e.type === 'message_end') ui.onAssistantText(extractText(e));
});
return session;
}Scripted spine (src/programs/run-analysis.ts). Because pi ends a run on any
tool-less turn, use one discrete prompt() per deliverable — deterministic,
and it sidesteps the "continue nudge" loop the wizard needs for open-ended runs:
await session.prompt(AUDIT_PROMPT); // agent explores repo, calls emit_audit
await session.prompt(ESTIMATE_PROMPT); // agent calls registry_price_lookup + emit_estimate
await session.prompt(YAML_PROMPT); // agent calls emit_bitrouter_yaml
const stats = await session.getSessionStats(); // token/cost of the wizard's own runEach phase prompt instructs the agent to finish by calling exactly one emit_*
tool; the tool's success is the phase's completion signal.
The agent's own model is BitRouter-routed. @bitrouter/pi is a pi extension
that discovers models (with cost) from GET ${baseUrl}/models and calls
pi.registerProvider("bitrouter", …).
- Configured by env:
BITROUTER_TARGET(localdefault →http://127.0.0.1:4356/v1;cloud→https://api.bitrouter.ai/v1),BITROUTER_BASE_URL(override),BITROUTER_API_KEY(brvk_, local only; cloud reuses the daemon credential file frombitrouter auth login). bitrouterProviderExtensionPath()resolves the extension entry from the installed package (verify the exact subpath against the installed package, e.g.require.resolve('@bitrouter/pi/extensions/bitrouter')).
Bootstrapping order: the provider registers during bindExtensions, but
createAgentSession needs a model up front. So pickAgentModel(cfg)
resolves the id host-side first: call GET ${baseUrl}/models, pick a capable
model (do not reflexively pick the cheapest — policy generation is real
reasoning; prefer a strong model, configurable via --model), pass it in, then
the extension registers the provider so the id resolves at bind.
Ensure BitRouter is reachable. If /models is unreachable in local mode,
the wizard should attempt bitrouter start (zero-config BYOK) or surface a clear
"run bitrouter start / bitrouter auth login" error and exit — do not fall
back to a direct provider (keeps one path for T0 and T1, and standing up
BitRouter is step 1 of adoption anyway).
Tools are pi defineTool with typebox schemas. Partition into two sets;
v1 registers only the readonly set + the emit set. The write set is empty.
Read-only (exploration): re-register pi built-ins read, grep, find,
ls. No bash, no edit, no write. (If a bash-like capability is ever
needed, it must be non-mutating and env-scrubbed — out of v1 scope.)
Emit (the only mutations — each writes a new artifact file):
| Tool | Params (typebox) | Behavior |
|---|---|---|
registry_price_lookup |
{ modelId: string } |
returns { current, cheapestOpenAlt } rates from the pricing engine (§12). Read-only helper. |
emit_audit_report |
{ markdown: string, analysis: object } |
writes bitrouter-audit.md and .bitrouter/analysis.json (host stamps detectedAt). |
emit_estimate |
{ perModel: [...], routineShares: {...} } |
merges estimate into .bitrouter/analysis.json; host computes blendedReductionPct (§12) so the math is trusted, not model-guessed. |
emit_bitrouter_yaml |
{ yaml: string } |
validates (§13.3) then writes bitrouter.yaml; returns validation errors to the agent to retry on failure. |
emit_* tools resolve/normalize paths against cfg.outDir, refuse to overwrite
outside it, and must reject any attempt to target an existing user file.
Keep the money math (blendedReductionPct) and yaml validation in host code, not
in the model's free text — the agent supplies inputs; the host computes and
validates.
src/agent/prompts.ts exports a SYSTEM_PROMPT and three phase prompts.
SYSTEM_PROMPT— role ("you analyze an agentic codebase to help the user adopt BitRouter"), the read-only contract (you have no ability to modify the user's code; produce artifacts only), the call-class rubric (routine= file reads, summarization, formatting, scaffolding, retries;complex= planning, multi-step reasoning, code generation), and the finish discipline (end each phase by calling its singleemit_*tool). The/bitrouterskill (bundled via@bitrouter/pi) supplies BitRouter config facts — do not restate them here.AUDIT_PROMPT— "explore the repo with read/grep/find/ls; identify the SDK/harness, every model id and its call-sites, the loop shape; classify each call-site; callemit_audit_report."ESTIMATE_PROMPT— "for each frontier model found, callregistry_price_lookup; estimate the routine share per model; callemit_estimate."YAML_PROMPT— "produce an MVPbitrouter.yaml(providers with${VAR}, virtual models withstrategy: prioritychains routing routine→open, frontier as fallback; theOPENAI_BASE_URLswap as comments); callemit_bitrouter_yaml; if it returns validation errors, fix and re-emit."
Prompts must be unit-testable as plain strings (no ink/React imports).
Fast, deterministic pre-agent scan (mirrors @posthog/wizard
src/lib/detection/features.ts) that seeds the agent so it doesn't start blind.
- Parse
package.json/requirements.txt/pyproject.toml/go.modfor known LLM SDKs (openai,@anthropic-ai/sdk,@google/genai,langchain,llamaindex,litellm, …) →harnessguess. fast-globfor agent-runtime config (e.g..claude/,codexconfig, abitrouter.yamlalready present) with sane ignores (node_modules,dist,.git,build).- Output a
DetectionResulthanded intoAUDIT_PROMPTas grounding. Detection never writes anything.
src/pricing/. Honest cold-start framing: output a rate card, never an
invented dollar total.
- Price source, in order: (1) live
GET ${baseUrl}/modelsfrom the running daemon (authoritative, current, includes cost); (2) a bundled snapshotpricing/registry-snapshot.jsongenerated at build time byscripts/sync-registry.tsfrom../bitrouter/registry/. - Model metadata (
open_weights,family) comes from../bitrouter/registry/models/*.yaml(snapshot it too). The provider price shape lives in../bitrouter/registry/providers/*.yaml— read the real files for the exact nesting; it is roughly:- id: openai/gpt-5.4 # … input_tokens: { no_cache: 2.5, cache_read: 0.25 } # $/Mtok output_tokens: { text: 15 } # $/Mtok
- "Cheapest open equivalent": among models with
open_weights: truein the same capability tier as the frontier model in use, pick the one with the lowest cheapest-provider blended rate. blendedReductionPct(computed in host code):1 − (routineShare · openBlended + (1−routineShare) · currentBlended) / currentBlendedwhereblended = 0.75·in + 0.25·out(or a documented input/output weight). Also surface the raw before/after$/Mtokper model and the assumedroutineShareso the number is transparent, not magic.
Match ../bitrouter/examples/bitrouter.yaml. Minimum:
# yaml-language-server: $schema=https://bitrouter.dev/schema/v<VERSION>/config.schema.json
#
# Generated by bitrouter-agent. To adopt: point your harness at the router —
# OPENAI_BASE_URL=http://localhost:4356 # ← the one change you make
# then: bitrouter start
server:
listen: 127.0.0.1:4356
providers:
anthropic:
api_base: https://api.anthropic.com
api_key: ${ANTHROPIC_API_KEY} # placeholder — resolved from env, never written
models: [ { id: claude-opus-4.6 } ]
alibaba_us:
# …cheapest open provider for the chosen routine model…
models:
# A virtual model: routine work → open; escalates to the frontier model.
smart:
strategy: priority
endpoints:
- { provider: alibaba_us, service_id: qwen3-coder }
- { provider: anthropic, service_id: claude-opus-4.6 }- Every secret is a
${VAR}placeholder. Never emit a real key, even if one is visible in the repo/env. - The adoption instructions (
OPENAI_BASE_URLswap,bitrouter start) are comments — no applied changes (zero-write). - Provider ids, model ids, and price data come from the registry — do not invent provider names.
emit_bitrouter_yaml validates before writing:
- If the
bitrouterbinary is onPATH: shellbitrouter config validate -c <tmpfile>(the real parser — preferred). - Else: validate against the bundled JSON schema
(
../bitrouter/dist/schema/bitrouter.config.schema.json, snapshot it into the package). On failure, return the errors to the agent for a retry; never write an invalid file.
Even local cold-start sends code snippets to an LLM to analyze them. Before the
first prompt(), show one disclosure and get an explicit choice (the analog of
the wizard's AI opt-in gate):
- What leaves the machine: code excerpts the agent reads, sent to the model
via BitRouter to
<target>(localdaemon → your configured upstreams / BYOK, orcloud). No artifacts are uploaded in v1. - Choice: proceed with
local(BYOK/your keys) orcloud; or abort. - Persist nothing sensitive; honor a
--yes/non-interactive flag for CI that documents the same disclosure in output.
Lift the command framework from @posthog/wizard (src/wizard.ts +
src/commands/command.ts): Wizard.use(cmd…).init() over yargs, env-prefixed
BITROUTER_AGENT_* options, .strictCommands(), a clean .fail().
| Command | Effect |
|---|---|
bitrouter-agent ($0 = init) |
run all three deliverables |
bitrouter-agent audit |
audit + estimate only (no yaml) |
bitrouter-agent estimate |
estimate only |
(reserved) bitrouter-agent watch |
T1, milestone M6 — not v1 |
Global flags: --out-dir, --model, --target {local,cloud}, --yes
(non-interactive), --debug, --version, --help.
Abstract behind a WizardUI interface (onToolStart/onToolEnd/onAssistantText/ askConsent/summary/spinner) with two implementations, so the runner is
UI-agnostic (this is how the wizard supports Ink + headless behind one iface):
- v1 required: a functional line-based UI — a spinner, per-phase progress,
the consent prompt, and a final summary listing the three artifacts + the
headline blended-% and the wizard's own run cost (from
getSessionStats). - v1 required: a
--yesnon-interactive/logging UI for CI. - Later (polish, not in the v1 gate): a rich Ink/React TUI (screens, a live
progress list, a token/cost HUD) modeled on the wizard's
src/ui/tui/.
Ship ink+react as deps so the polish milestone doesn't need a dependency
change, but the v1 acceptance gate only requires the functional UI.
Study github.com/PostHog/wizard (MIT). It is built on the same pi stack, so
it is a direct reference.
Lift (adapt near-verbatim):
src/wizard.ts+src/commands/command.ts— command framework.src/lib/agent/runner/harness/pi/index.ts— thecreateAgentSession+subscribe+promptwiring.src/lib/agent/runner/harness/pi/tools.ts—defineTool+ typebox tool shape.src/lib/agent/token-pricing.ts— the exact-match$/Mtoktable pattern (ours sources from the registry, not a hardcoded table).src/lib/detection/features.ts— LLM-SDK detection.
Skip entirely (the zero-write dividend): src/lib/agent/runner/harness/pi/security.ts
(YARA/permission fence — unneeded with no write tools), wizardCanUseTool,
the secret vault, .claude/settings backup, env-scrubbed bash, magicast/
recast (they are in the wizard's package.json but unused), the entire
harness/anthropic/ tree, createSdkMcpServer (Claude Agent SDK), the
switchboard harness axis, and the orchestrator sequence.
Each milestone ends with checks + commit (§0).
- M0 — Scaffold.
package.json(deps §6,bin, scripts),tsconfig,tsdown/vitest/eslintconfig,bin.tsnode preflight,src/wizard.ts+src/commands/command.ts, aninitcommand that prints help. DoD:pnpm build && node dist/bin.js --helplists the commands. - M1 — Config + detection.
src/config.ts,src/detection/. DoD: detection returns aDetectionResultforfixtures/sample-agent-app(unit test). - M2 — Provider bootstrap.
src/agent/provider.ts—pickAgentModel+ extension path resolution; the "ensure BitRouter reachable" flow. DoD: against a running local daemon,pickAgentModelreturns a validbitrouter/<id>(integration test may be skipped when no daemon, but code path is unit-tested with a mocked/models). - M3 — pi session + read-only tools + one emit.
src/agent/session.ts,src/agent/tools/(read/grep/find/ls + stubemit_bitrouter_yaml),src/programs/run-analysis.tsrunning just the YAML phase. DoD: runninginitagainst the fixture produces a validatedbitrouter.yaml. (This is the vertical slice that de-risks the whole pi embedding — reach it early.) - M4 — Estimate engine.
scripts/sync-registry.ts,src/pricing/,registry_price_lookup+emit_estimate, host-sideblendedReductionPct. DoD:.bitrouter/analysis.jsonhas a transparent rate card; math unit-tested. - M5 — Audit + full spine + UI + consent.
emit_audit_report, all three phase prompts wired,WizardUI(functional +--yes), the consent gate. DoD:initon the fixture produces all three artifacts and the summary. - M6 — (next, not in v1 gate)
watch/T1. Readbitrouter.db+/metrics, upgrade the estimate to observed dollars, propose a policy diff. Capability- gated; still zero-write.
The build loop is complete when all of these hold on a clean tree:
-
pnpm build && pnpm typecheck && pnpm lint && pnpm testall pass. -
node dist/bin.js --helplistsinit,audit,estimate;--versionworks. - Running
initagainstfixtures/sample-agent-appproduces exactly three new artifacts —bitrouter.yaml,bitrouter-audit.md,.bitrouter/analysis.json— and modifies/creates no other file in the fixture (assert the fixture git status shows only these, ignored via--out-dirto a temp dir in the test). - The generated
bitrouter.yamlpasses validation (§13.3). -
bitrouter-audit.mdlists every model id in the fixture with call-sites and call-classes, and a rate-card estimate with before/after$/Mtokand a blended %; no invented absolute dollar total. - No secret value appears in any artifact; all keys are
${VAR}placeholders. -
package.jsonhas no@anthropic-ai/claude-agent-sdk,magicast, orrecast; the agent loop is pi (session.prompt). - The agent is given no write/edit/bash-to-source tool (grep the tool
registry: only
read/grep/find/ls+registry_price_lookup+emit_*). - A single consent disclosure is shown before analysis;
--yesbypasses it non-interactively. -
README.mddocuments install, the three commands, the artifacts, and thelocal/cloudtargets.
fixtures/sample-agent-app/— a tiny fake agentic repo: apackage.jsondepending onopenai+@anthropic-ai/sdk, and 2–3 source files with hardcoded model ids across obviousroutineandcomplexcall-sites (e.g. a cheap "summarize" call and an expensive "plan" call). This is the e2e target.- Unit tests (
vitest): detection, pricing math (blendedReductionPct), yaml validation, prompt strings, tool path-safety (emit refuses paths outside--out-dir). - E2e smoke: run
init --yes --out-dir <tmp>against the fixture with a mocked or live/models; assert the three artifacts + validation + the "no other files touched" invariant. - The pi/model calls in e2e may run against a live local BitRouter when present, otherwise a recorded/mock model response — do not hard-require network in unit tests.
- Conventional commits; PR titles in the same format. Branch off
main. - Keep
README.mdin step with the CLI surface. - No
// @ts-ignore/eslint-disableto bypass checks; no dead code. - Secrets never logged or written.
- pi docs:
pi.dev/docs/latest(+/sdk,/rpc,/json,/tui). - PostHog wizard (blueprint, MIT):
github.com/PostHog/wizard. @bitrouter/piprovider:github.com/bitrouter/bitrouter-pi-provider.- Sibling BitRouter checkout
../bitrouter:examples/bitrouter.yaml(yaml shape),registry/(prices + model metadata),dist/schema/bitrouter.config.schema.json(validation),skills/bitrouter/(the skill, also bundled by@bitrouter/pi),CLI.md(bitrouter config validate,start,route,observe status).
pi SDK (~0.79) — createAgentSession({ model, cwd, sessionManager, resourceLoader, noTools, customTools }) → { session };
SessionManager.inMemory(cwd); DefaultResourceLoader({ cwd, additionalExtensionPaths, extensionFactories, systemPromptOverride, noContextFiles, noThemes, noPromptTemplates, noSkills, noExtensions });
session.bindExtensions({}); session.prompt(text) (resolves on a tool-less
turn); session.subscribe(cb) (events: message_update{text_delta},
message_end, tool_execution_start|update|end, turn_start|end,
agent_start|end); session.getSessionStats(); defineTool({ name, description, parameters: Type.Object({…}), execute: async (toolCallId, params) => ({ content: [{type:'text', text}], details }) }).
@bitrouter/pi — extension default-exports async (pi) => pi.registerProvider( "bitrouter", { baseUrl, api:"openai-completions", apiKey?, authHeader?, models }),
discovering models from GET ${baseUrl}/models. Env: BITROUTER_TARGET
(local|cloud), BITROUTER_BASE_URL, BITROUTER_API_KEY (brvk_, local).
BitRouter registry provider price shape (../bitrouter/registry/providers/*.yaml)
— per model: input_tokens:{no_cache,cache_read}, output_tokens:{text} in
$/Mtok. Model metadata (../bitrouter/registry/models/*.yaml): id, family,
open_weights.
{ "schemaVersion": 1, "detectedAt": "<ISO8601>", // injected by host, not the agent "harness": "openai-sdk | anthropic-sdk | claude-code | langchain | custom", "models": [ { "id": "anthropic/claude-opus-4.6", "callSites": ["src/agent.ts:42"], "callClass": "complex", "estCallShare": 0.2 } ], "estimate": { "perModel": [ { "current": "anthropic/claude-opus-4.6", "currentRate": {"in":5,"out":25}, "openAlt": "alibaba/qwen3-coder", "altRate": {"in":0.4,"out":1.6}, "routineShare": 0.6 } ], "blendedReductionPct": 0.0 // computed, see §12 } }