@workweave/router 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,59 +1,78 @@
1
- # opencode Weave Codex plugin
1
+ # opencode Weave subscription plugin
2
2
 
3
- Lets a caller's own **ChatGPT (Codex) subscription** pay for their **opencode**
4
- turns, routed through the Weave Router. Bundled into `@workweave/router`; the
5
- installer (`--codex` / `--opencode`) drops `src/index.ts` into the user's
6
- opencode plugins dir and writes a matching `weave-codex` provider block into
7
- `opencode.json`.
3
+ Lets a caller's own **AI subscriptions** pay for their **opencode** turns, routed
4
+ through the Weave Router. A subscription is a **credential scoped to the model
5
+ family it can pay for**, not a provider you pick: you connect your ChatGPT
6
+ (Codex) and/or Claude (Pro/Max) plan once, the router routes every turn to the
7
+ best model, and bills the plan that matches the model it served — ChatGPT pays
8
+ for GPT/Codex turns, Claude pays for Claude turns, your Weave key pays for
9
+ everything else.
10
+
11
+ Bundled into `@workweave/router`; the installer (`--codex` / `--opencode`) drops
12
+ `src/index.ts` into the user's opencode plugins dir and writes a single
13
+ Responses-format `weave` provider (plus a login-only `weave-claude` provider)
14
+ into `opencode.json`.
8
15
 
9
16
  ## Why a plugin (config alone can't do it)
10
17
 
11
18
  opencode removed built-in subscription auth in 1.3.0 and binds OAuth to its own
12
- first-party providers its bundled `openai/codex.ts` plugin hardcodes the
13
- upstream to `chatgpt.com` and binds provider id `openai`, so a custom router
14
- provider can't reuse it. And a subscription needs the caller's OAuth token in
15
- `Authorization`, which expires hourly — a static `options.headers` string can't
16
- refresh it.
17
-
18
- This plugin re-implements the same ChatGPT OAuth + refresh against a **custom**
19
- provider id (`weave-codex`) and, crucially, **leaves the request URL on the
20
- Weave Router** instead of rewriting it to `chatgpt.com`.
19
+ first-party providers, so a custom router provider can't reuse it. And
20
+ subscription tokens expire hourly a static `options.headers` string can't
21
+ refresh them, nor carry two subscriptions whose tokens rotate independently.
21
22
 
22
23
  ## Wire shape it produces
23
24
 
24
- Matches the router's `/v1/responses` Codex passthrough:
25
+ opencode talks to one Responses-format `weave` provider; the plugin's loader
26
+ attaches whichever subscriptions are connected to **every** request via the
27
+ router's dedicated headers:
25
28
 
26
29
  | | |
27
30
  |---|---|
28
31
  | `POST {router}/v1/responses` | Responses wire format (opencode's default for an `@ai-sdk/openai` provider) |
29
- | `Authorization: Bearer <ChatGPT JWT>` | the caller's subscription, refreshed on expiry |
30
- | `ChatGPT-Account-Id: <id>` | paired account id (required by the Codex backend) |
31
- | `X-Weave-Router-Key: rk_…` | from `opencode.json` `options.headers` — the router authenticates off this, leaving `Authorization` free for the JWT |
32
- | `originator`, `session-id` | from the plugin's `chat.headers` hook (Codex backend session continuity) |
32
+ | `X-Weave-OpenAI-Subscription: <ChatGPT JWT>` | pays GPT/Codex turns, refreshed on expiry |
33
+ | `X-Weave-OpenAI-Account-ID: <id>` | paired account id (required by the Codex backend) |
34
+ | `X-Weave-Anthropic-Subscription: <sk-ant-oat token>` | pays Claude turns, refreshed on expiry |
35
+ | `X-Weave-Router-Key: rk_…` | from `opencode.json` `options.headers` the router authenticates off this |
36
+
37
+ The router routes the turn across every model the caller's subs + key can pay
38
+ for and resolves the subscription matching the chosen provider, so a sub is
39
+ never billed for a turn outside its family.
40
+
41
+ ## Two storage slots, one request provider
42
+
43
+ opencode stores one credential per provider id and the loader's `getAuth()` is
44
+ scoped to its own provider, so the two logins live in two slots:
45
+
46
+ - **`weave`** — the request provider. Owns the **ChatGPT** login and the loader
47
+ that attaches both subscriptions. Connecting ChatGPT activates sub-routing.
48
+ - **`weave-claude`** — login-only (no models, serves no requests). Owns the
49
+ **Claude** login. Its token is read from opencode's on-disk auth store by the
50
+ `weave` loader (the SDK exposes no get-by-id).
33
51
 
34
- The router detects the inbound Codex bearer and serves the turn on the caller's
35
- own plan at the subscription fee.
52
+ With neither connected, `weave` is a plain router provider (your Weave key pays)
53
+ the loader simply doesn't run. Connecting ChatGPT is what turns on
54
+ subscription routing; the Claude sub then rides along when present.
36
55
 
37
- ## Division of responsibility
56
+ ## Login
38
57
 
39
- - **Installer** writes the router key + identity headers (`X-Weave-Router-Key`,
40
- `X-App`, `X-Weave-User-Email`) into `opencode.json` `options.headers`.
41
- - **This plugin** manages only the dynamic, secret, refreshable subscription
42
- credential (the `Authorization` bearer + `ChatGPT-Account-Id`) via the auth
43
- `loader`, and the login flow via auth `methods` (browser PKCE + headless
44
- device code). Tokens are stored in opencode's own auth store under the
45
- `weave-codex` provider id.
58
+ `opencode auth login` **Weave Router** *ChatGPT Pro/Plus* (browser or
59
+ headless device code) and/or **Weave Router — Claude plan** → *Claude Pro/Max*
60
+ (browser; paste the `code#state` shown after authorizing).
46
61
 
47
- ## Env overrides
62
+ ## Env overrides (self-host + tests)
48
63
 
49
- - `WEAVE_CODEX_OAUTH_ISSUER` — override the OpenAI auth issuer (self-hosted
50
- OpenAI auth proxies; also used by the capture tests).
64
+ - `WEAVE_CODEX_OAUTH_ISSUER` — OpenAI auth issuer.
65
+ - `WEAVE_ANTHROPIC_OAUTH_AUTHORIZE` / `WEAVE_ANTHROPIC_OAUTH_TOKEN` Anthropic
66
+ OAuth authorize host / token endpoint.
67
+ - `WEAVE_OPENCODE_AUTH_FILE` — path to opencode's `auth.json` (the `weave`
68
+ loader reads the `weave-claude` slot from here; defaults to
69
+ `$XDG_DATA_HOME/opencode/auth.json`).
51
70
 
52
71
  ## Verification
53
72
 
54
- Capture-tested end-to-end against a local server standing in for the router
55
- (opencode 1.17.9, real ChatGPT login): the inject path forwards the real JWT +
56
- account-id in Responses format to `/v1/responses`, and the refresh path detects
57
- an expired token, refreshes against the issuer, rotates + persists the tokens,
58
- and injects the refreshed bearer. Typechecks under `strict` against
59
- `@opencode-ai/plugin`.
73
+ `bun test test/` (run under bun, opencode's own runtime) covers: dual-sub
74
+ injection via the dedicated headers with the router key preserved and
75
+ `Authorization` left clean; ChatGPT-only graceful degradation; expired Claude
76
+ token refresh + persist + rotated injection; the loader staying inert without
77
+ oauth; and the Claude login hook's canonical OAuth flow + `code#state` exchange.
78
+ Typechecks under `strict` against `@opencode-ai/plugin`.