Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# clawrouter-codex

**Run [OpenAI Codex](https://github.com/openai/codex) on any [ClawRouter](https://github.com/BlockRunAI/ClawRouter) / BlockRun model — Claude, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok, GPT and more — paid per request from a wallet (x402 USDC), no API keys.**
**Run [OpenAI Codex](https://github.com/openai/codex) on any [ClawRouter](https://github.com/BlockRunAI/ClawRouter) / BlockRun model — Claude, Gemini, DeepSeek, Kimi, GLM, Qwen, Grok, GPT and more — using an account API key or x402 USDC on Solana or Base.**

[![npm](https://img.shields.io/npm/v/@blockrun/clawrouter-codex?color=cb3837&logo=npm)](https://www.npmjs.com/package/@blockrun/clawrouter-codex)

Codex only speaks the OpenAI **Responses API** (`/v1/responses`). BlockRun speaks **Chat Completions**. This is a local bridge that translates between them and pays per request, so Codex (CLI, IDE, and Desktop) can use BlockRun's models:

```
Codex ──/v1/responses──▶ clawrouter-codex ──@blockrun/llm──▶ BlockRun (x402 USDC, default)
Codex ──/v1/responses──▶ clawrouter-codex ──@blockrun/llm──▶ BlockRun (account API or x402)
```

By default the bridge pays BlockRun **directly** via the official [`@blockrun/llm`](https://www.npmjs.com/package/@blockrun/llm) SDK (plain per-request x402 on Base) — one process, no proxy, and the model list comes live from the source. It's wire-format translation plus a few conveniences (model picker, web search, a dashboard).
Expand All @@ -18,9 +18,15 @@ By default the bridge pays BlockRun **directly** via the official [`@blockrun/ll

---

## Account API setup

Register at [user.blockrun.ai](https://user.blockrun.ai), create an [API key](https://user.blockrun.ai/dashboard/keys), and add [credits](https://user.blockrun.ai/dashboard/credits). Export `BLOCKRUN_API_KEY` before `up`; direct mode then skips wallet discovery and sends authenticated chat, catalog and web-search requests to `https://api.blockrun.ai` (`BLOCKRUN_API_BASE_URL` overrides staging). Health and stats report account mode and link to the credits portal instead of showing wallet balance or x402 spend.

This branch requires the TypeScript SDK release containing [PR #36](https://github.com/BlockRunAI/blockrun-llm-ts/pull/36); update `@blockrun/llm` from `^3.5.0` before publishing. Proxy mode requires ClawRouter PR #338 or later. Responses SSE production acceptance also needs Enterprise PR #10 deployed. New wallet users should select Solana before Base; the legacy direct wallet bridge remains Base-only and says so.

## Quick start

You need [Node ≥ 20](https://nodejs.org) and a funded BlockRun wallet (`~/.blockrun/.session`, or set `BLOCKRUN_WALLET_KEY`). Then:
You need [Node ≥ 20](https://nodejs.org) and either `BLOCKRUN_API_KEY` or a funded BlockRun wallet (`~/.blockrun/.session`, or set `BLOCKRUN_WALLET_KEY`). Then:

```bash
npx @blockrun/clawrouter-codex up # start the bridge + write the Codex profile + build the catalog
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@blockrun/clawrouter-codex",
"version": "0.4.0",
"description": "Front-adapter that lets OpenAI Codex (Responses API) talk to a ClawRouter proxy — wallet-signed, x402-paid, zero API keys.",
"description": "Responses bridge from Codex to BlockRun via account API keys or x402 wallets on Solana/Base.",
"type": "module",
"license": "MIT",
"bin": {
Expand Down
5 changes: 3 additions & 2 deletions scripts/start.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -96,9 +96,10 @@ const PROXY_MODE = process.env.BRIDGE_MODE === "proxy" || Boolean(process.env.CL

async function startDirect() {
if (await healthy(PORT)) { log(`bridge already up on :${PORT}`); return; }
const { key, auto } = resolveWallet();
const account = process.env.BLOCKRUN_API_KEY;
const { key, auto } = account ? { key: undefined, auto: false } : resolveWallet();
if (auto) log(`auto-detected BlockRun wallet at ${BLOCKRUN_WALLET}`);
log(`starting bridge on :${PORT} — direct mode (pays BlockRun via @blockrun/llm, no proxy)`);
log(`starting bridge on :${PORT} — direct mode (${account ? "account API" : "x402 wallet"})`);
supervise("bridge", process.execPath, [join(ROOT, "src", "server.js")], {
PORT: String(PORT),
BLOCKRUN_DIRECT: "1",
Expand Down
34 changes: 16 additions & 18 deletions src/direct.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ import { readFileSync } from "node:fs";
import { LLMClient, SearchClient } from "@blockrun/llm";
import { resolvePrivateKey, paths } from "@blockrun/core";

const DEFAULT_API = process.env.BLOCKRUN_API_URL ?? "https://blockrun.ai/api";
const DEFAULT_WALLET_API = process.env.BLOCKRUN_API_URL ?? "https://blockrun.ai/api";
const DEFAULT_ACCOUNT_API = process.env.BLOCKRUN_API_BASE_URL ?? "https://api.blockrun.ai";
const ACCOUNT_PORTAL = "https://user.blockrun.ai/dashboard/credits";
// Fallback model when smart routing is unavailable. Override w/ BLOCKRUN_DEFAULT_MODEL.
const DEFAULT_MODEL = process.env.BLOCKRUN_DEFAULT_MODEL ?? "anthropic/claude-opus-4.5";
const AUTO = new Set(["blockrun/auto", "auto", ""]);
Expand Down Expand Up @@ -140,10 +142,13 @@ function buildStats() {
* `http://direct/v1`.
*/
export function createDirectFetch(opts = {}) {
const privateKey = opts.privateKey ?? resolveWalletKey();
const apiUrl = opts.apiUrl ?? DEFAULT_API;
const llm = new LLMClient({ privateKey, apiUrl });
const search = new SearchClient({ privateKey, apiUrl });
const apiKey = opts.apiKey ?? process.env.BLOCKRUN_API_KEY;
const privateKey = apiKey ? undefined : (opts.privateKey ?? resolveWalletKey());
const apiUrl = opts.apiUrl ?? (apiKey ? DEFAULT_ACCOUNT_API : DEFAULT_WALLET_API);
if (apiKey && !Object.getOwnPropertyDescriptor(LLMClient.prototype, "authMode")) throw new Error("Account mode requires the @blockrun/llm release containing PR #36.");
const auth = apiKey ? { apiKey, apiUrl } : { privateKey, apiUrl };
const llm = new LLMClient(auth);
const search = new SearchClient(auth);

return async function directFetch(url, init = {}) {
const path = new URL(url, "http://direct").pathname;
Expand Down Expand Up @@ -200,25 +205,18 @@ export function createDirectFetch(opts = {}) {
// Health + wallet/balance for the dashboard.
if (path.endsWith("/health")) {
if (!String(url).includes("full=true")) return json({ status: "ok", mode: "direct" });
let balance = 0;
let address = "";
try { balance = await llm.getBalance(); } catch { /* leave 0 */ }
try { address = search.getWalletAddress(); } catch { /* leave "" */ }
return json({
status: "ok",
mode: "direct",
paymentChain: "base",
wallet: address,
address,
balance: `$${balance.toFixed(2)}`,
isEmpty: balance <= 0,
});
if (llm.authMode === "api-key") return json({ status:"ok", mode:"direct", authMode:"api-key", account:ACCOUNT_PORTAL });
let balance = 0; let address = "";
try { balance = await llm.getBalance(); } catch {}
try { address = search.getWalletAddress(); } catch {}
return json({ status:"ok", mode:"direct", paymentChain:"base", wallet:address, address, balance:`$${balance.toFixed(2)}`, isEmpty:balance<=0 });
}

// Spend stats: build a 7-day window from the SDK's local cost log
// (~/.blockrun/cost_log.jsonl) — direct mode has no server-side ledger,
// but the SDK records every settled payment with a timestamp.
if (path.endsWith("/stats")) {
if (llm.authMode === "api-key") return json({ authMode:"api-key", costSource:"account_portal", url:ACCOUNT_PORTAL });
return json(buildStats());
}

Expand Down
3 changes: 3 additions & 0 deletions test/account-api.test.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.