llm-relay 0.14.5 → 0.15.0

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.
package/README.md CHANGED
@@ -64,7 +64,7 @@ llm-relay
64
64
  - **Multi-Candidate Failover**: `routing.default` and every `routing.tiers` entry accept an **array** of target specs (e.g. `["nim/z-ai/glm-5.2", "groq/llama-3.3-70b"]`) for continuous fallback. ⚠ Ranking and failover both require **more than one** candidate — a single pinned model silently disables both, and on providers where a listed model may not actually be servable that turns one dead backend into a dead relay. Prefer arrays.
65
65
  - **Named pools** (`routing.pools`, addressed as `model: "pool/<name>"`): the same ranked-candidate behaviour for callers that can only send **one model string** — notably Claude Code subagent frontmatter. Lets an agent ask for *the best available coding model* instead of naming one. An unknown pool is a loud 400, never a silent fall-through.
66
66
  - **Passthrough targets**: a provider with `kind:"anthropic"` and **no `authEnv`** forwards the caller's own credentials untouched, so real Claude traffic stays on real Anthropic while `pool/*` requests route elsewhere — from the same proxy.
67
- - **Subagent offload** (`routing.offload` + `routing.subagents`): route Claude Code *subagents* to other providers while the human's own conversation stays on passthrough with no agent files and no model ids in the prompt. **Off by default**; `llm-relay offload on` flips it without a restart, `llm-relay candidates` shows what to point it at. See below.
67
+ - **Granular offload** (`routing.offload` + `routing.subagents`): independently route Claude, Codex, and future client requests to other providers. Each client can be limited to subagents or set to `scope: "all"` to reroute its main conversation too. **Off by default**; `llm-relay candidates` shows what to point it at. See below.
68
68
 
69
69
  ### 3. Prompt Token & Context Length Guardrails
70
70
  - Estimates the request's prompt token count (`estimateRequestTokens`) against the target model's context limit, read from the warm catalog cache (`cachedLimits()` — it never fetches, so a cold cache costs no round-trip on the request path).
@@ -89,7 +89,7 @@ llm-relay
89
89
 
90
90
  ### 6. Programmatic Telemetry & Quota Access for Claude
91
91
  - **Read-only HTTP endpoints**: `GET /telemetry` (live JSON metrics), `GET /registry` (full provider/routing/model catalog with quality scores), `GET /ping` (trigger health probe pass & mode summary), `GET /health` (diagnostic status), `GET /candidates` (the un-blended offload decision table).
92
- - **Mutating HTTP endpoints**: `GET|POST /offload` (read/flip the subagent-offload switch), `GET|POST /dispatch` (the dispatch ladder). ⚠ **Loopback is not authorization** — any page you visit can POST cross-origin to a loopback listener without a preflight, and these two write your `config.json` and steer lane order. They therefore reject a present-but-non-loopback `Origin` with 403, require `content-type: application/json` on a mutating request, and require a loopback `Host` (closing DNS rebinding). An **absent** `Origin` is allowed on purpose — that is what a CLI sends, and it is what keeps `llm-relay offload on` working against a running proxy with no restart.
92
+ - **Mutating HTTP endpoints**: `GET|POST /offload` (read/set per-client offload rules), `GET|POST /dispatch` (the dispatch ladder). ⚠ **Loopback is not authorization** — any page you visit can POST cross-origin to a loopback listener without a preflight, and these two write your `config.json` and steer lane order. They therefore reject a present-but-non-loopback `Origin` with 403, require `content-type: application/json` on a mutating request, and require a loopback `Host` (closing DNS rebinding). An **absent** `Origin` is allowed on purpose — that is what a CLI sends, and it is what keeps targeted offload changes working against a running proxy with no restart.
93
93
  - **CLI Commands**: `llm-relay telemetry` outputs live telemetry metrics; `llm-relay models` lists live model catalogs with SWE-bench & quality scores; `llm-relay ping` performs live health & latency probes.
94
94
  - **Response Headers**: Proxy responses include `x-llm-relay-quota-percent`, `x-llm-relay-stability-score`, and `x-llm-relay-target`.
95
95
 
@@ -108,9 +108,9 @@ llm-relay
108
108
  | `llm-relay telemetry` | Output live JSON telemetry, stability scores, and quota metrics |
109
109
  | `llm-relay models [-p <name>] [-r]` | Query live `/models` catalog per provider (`-p` filter, `-r` force refresh) |
110
110
  | `llm-relay ping [-p <name>]` | Perform live health, latency & quota probe across providers |
111
- | `llm-relay offload [on\|off\|status]` | Read or flip the subagent-offload switch applies to the next request, no restart |
111
+ | `llm-relay offload [client] [on\|off\|status] [--scope subagents\|all]` | Read or change one client's offload rule; changes apply to the next request, no restart |
112
112
  | `llm-relay candidates [-p <name>]` | The un-blended offload decision table (capability, cost, live health, quota, breaker state) |
113
- | `llm-relay dispatch [lane] [-t <task>]` | Which lane to hand a whole delegated task to next; it returns the command, **you** run it (`-x <lane>` reports one spent) |
113
+ | `llm-relay dispatch [lane] [-t <task>] [--client <name>]` | Which lane to hand a whole delegated task to next; it returns the command, **you** run it (`-x <lane>` reports one spent) |
114
114
 
115
115
  ---
116
116
 
@@ -180,8 +180,14 @@ directories: `~/.claude/skills/llm-relay/SKILL.md` for Claude Code and
180
180
  switch, `@relay:` directives, reading the candidates table, failure modes) cannot drift between
181
181
  hosts. Both refresh automatically on every upgrade; local/dev installs touch neither directory.
182
182
 
183
+ The same global install also provisions local Codex: it adds the `llm-relay` Responses provider to
184
+ `~/.codex/config.toml` and creates relay-backed `default` and `relay_coding` child agents under
185
+ `~/.codex/agents/` when those files are absent. Existing Codex config and agent files are preserved.
186
+ This keeps the parent on its normal provider while making generic or named child dispatches use the
187
+ relay automatically.
188
+
183
189
  If your npm blocks unknown install scripts (`npm warn install-scripts … blocked`), allow this one —
184
- `npm config set allow-scripts=llm-relay --location=user` — or install the skill by hand:
190
+ `npm config set allow-scripts=llm-relay --location=user` — or install the host integrations by hand:
185
191
  `node "$(npm root -g)/llm-relay/scripts/install-skill.mjs" --force`.
186
192
 
187
193
  ### Staying current
@@ -400,43 +406,51 @@ refusing safe calls. So:
400
406
  There is no built-in list inside the proxy: an empty `repair.destructiveTools` refuses nothing, so
401
407
  coverage is always traceable to your config.
402
408
 
403
- ### Subagent offload (`routing.offload` + `routing.subagents`)
404
-
405
- Send Claude Code **subagents** to other providers while the human's own conversation stays on the
406
- Anthropic passthrough — without writing agent files and without naming a model.
407
-
408
- **Off by default.** Until you turn it on, subagents route exactly like everything else:
409
-
410
- ```bash
411
- llm-relay offload on
412
- ```
409
+ ### Granular offload (`routing.offload` + `routing.subagents`)
413
410
 
414
- That reaches the running proxy over loopback, so it applies to the next request without a restart,
415
- and is persisted to `config.json` so it survives one. `llm-relay offload status` shows the state and
416
- where each tier goes; `off` reverts.
411
+ Offload rules are keyed by the originating harness. Claude requests use the `/v1/messages` front
412
+ door; Codex requests use `/v1/responses`. Each rule is independent and chooses whether it applies
413
+ to marked subagents only (the current behavior) or to the whole conversation:
417
414
 
418
415
  ```jsonc
419
416
  "routing": {
420
- "offload": false, // master switch (default). `subagents` is inert until this is true.
421
417
  "tiers": { "opus": "anthropic", "sonnet": "anthropic", "haiku": "anthropic", "fable": "anthropic" },
422
- "subagents": { "opus": "pool/reasoning", "haiku": "pool/fast", "default": "pool/coding" }
418
+ "subagents": { "opus": "pool/reasoning", "haiku": "pool/fast", "default": "pool/coding" },
419
+ "offload": {
420
+ "claude": { "enabled": true, "scope": "subagents" },
421
+ "codex": { "enabled": false, "scope": "all" }
422
+ }
423
423
  }
424
424
  ```
425
425
 
426
- Why opt-in: offloading silently changes *which model answers* for every built-in agent (Explore,
427
- general-purpose, every one-off dispatch). That is worth deciding on purpose rather than inheriting
428
- from the presence of a config key.
426
+ `scope: "subagents"` preserves the existing topology. `scope: "all"` also applies the same
427
+ `routing.subagents` tier/default map to the client's main conversation, which is useful when a
428
+ Claude or Codex quota is exhausted. Rules may use any future client name; an explicit `default`
429
+ rule is the opt-in catch-all for otherwise unnamed front doors. All rules are off by default.
430
+
431
+ The CLI changes one client without restarting the proxy:
432
+
433
+ ```bash
434
+ llm-relay offload status
435
+ llm-relay offload claude on --scope subagents
436
+ llm-relay offload codex on --scope all
437
+ llm-relay offload claude off
438
+ ```
439
+
440
+ The old `llm-relay offload on|off` command remains a global compatibility switch for configs that
441
+ still use the boolean form (`"offload": false`). `GET /offload?client=claude` reads one rule;
442
+ `POST /offload` accepts `{"client":"claude","enabled":true,"scope":"all"}`. Changes are
443
+ persisted and take effect on the next request.
429
444
 
430
445
  Claude Code stamps `cc_is_subagent=true` into the `system` block of subagent requests (built-in
431
- agents like Explore included — verified on the wire, Claude Code 2.1.220). llm-relay reads that flag
432
- and only then consults `routing.subagents`. **This is the entire reason the feature is safe.**
433
- Without the flag, a subagent asking for `haiku` and a human picking Haiku are byte-identical
434
- requests, so any tier→provider mapping silently drops the human's own conversation onto a weak
435
- model. `routing.tiers` therefore stays free to point at a passthrough.
446
+ agents like Explore included — verified on the wire, Claude Code 2.1.220). Local Codex stamps
447
+ `x-codex-turn-metadata: {"request_kind":"subagent",...}` on child-agent turns. A subagents-only
448
+ rule requires that marker; an all-scope rule also accepts ordinary main-conversation requests.
449
+ An explicit `@relay:` directive remains a subagent-only per-call opt-in, even when a client has an
450
+ all-scope rule, so text in a human conversation cannot self-reroute it.
436
451
 
437
- A dispatcher then chooses a destination with the one per-call knob it already has — the Agent tool's
438
- `model` parameter (`sonnet|opus|haiku|fable`) or by not choosing at all, in which case
439
- `subagents.default` applies and the pool's ranking picks the model.
452
+ A dispatcher chooses a destination with the Agent tool's `model` parameter (`sonnet|opus|haiku|fable`)
453
+ or, when it does not choose, `subagents.default` applies and the pool's ranking picks the model.
440
454
 
441
455
  **To pin an exact model for one call**, put a directive on its own line in the subagent's prompt:
442
456
 
@@ -454,9 +468,75 @@ authored prompt. Block 0 is Claude Code's injected `<system-reminder>` (your CLA
454
468
  …), and later messages carry tool results, i.e. file contents. Reading either would let any file a
455
469
  subagent happens to read redirect its own routing. Both cases are covered by tests.
456
470
 
457
- Precedence for a subagent request: `@relay:` directive → `subagents[<tier>]` →
458
- `subagents.default` → normal routing. The middle two apply only while `routing.offload` is on; omit
459
- `routing.subagents` entirely and nothing changes either way.
471
+ Precedence for a marked subagent request: `@relay:` directive → `subagents[<tier>]` →
472
+ `subagents.default` → normal routing. The map applies only when that request's client rule is
473
+ enabled and its scope admits the request; omit `routing.subagents` entirely and nothing changes.
474
+
475
+ #### Local Codex setup
476
+
477
+ For the intended split, keep the parent Codex session on its normal provider and define a named
478
+ child agent whose own Responses requests use llm-relay. A global `llm-relay` install creates the
479
+ provider and agents below automatically. If npm lifecycle scripts were blocked, run the bundled
480
+ installer manually with `--force`, or create the files yourself as follows.
481
+
482
+ ```toml
483
+ [model_providers.llm-relay]
484
+ name = "llm-relay"
485
+ base_url = "http://127.0.0.1:8791/v1"
486
+ wire_api = "responses"
487
+ requires_openai_auth = true
488
+ ```
489
+
490
+ Then create `~/.codex/agents/relay_coding.toml`:
491
+
492
+ ```toml
493
+ name = "relay_coding"
494
+ description = "Read-only coding child routed through llm-relay."
495
+ developer_instructions = "Work read-only. Return a concise result to the parent and do not modify files."
496
+
497
+ model_provider = "llm-relay"
498
+ model = "pool/coding"
499
+ model_reasoning_effort = "medium"
500
+ ```
501
+
502
+ To make an unqualified child dispatch use the relay automatically, override Codex's built-in
503
+ `default` agent with `~/.codex/agents/default.toml`:
504
+
505
+ ```toml
506
+ name = "default"
507
+ description = "General-purpose read-only child routed through llm-relay."
508
+ developer_instructions = "Work read-only. Return a concise result to the parent and do not modify files."
509
+
510
+ model_provider = "llm-relay"
511
+ model = "pool/coding"
512
+ model_reasoning_effort = "medium"
513
+ ```
514
+
515
+ With that override, a normal “use a subagent” request keeps the parent native while the generic child
516
+ goes through `pool/coding`; named agents can still select a different pool explicitly.
517
+
518
+ Run Codex normally, without the `llm-relay` profile. Ask the parent to use exactly one subagent of
519
+ type `relay_coding`; Codex keeps the parent on its normal provider and starts the child through the
520
+ relay. The relay pool then chooses the configured provider and can fail over normally.
521
+
522
+ Enable only Codex child offload in `~/.llm-relay/config.json`:
523
+
524
+ ```bash
525
+ llm-relay offload codex on --scope subagents
526
+ ```
527
+
528
+ To redirect the parent Codex conversation through the same relay as well, use
529
+ `llm-relay offload codex on --scope all`. Claude's rule is unaffected.
530
+
531
+ The `llm-relay` profile remains available as an explicit all-relay mode, but it routes the parent
532
+ through the relay too and is not the split setup described above. The automatic
533
+ `x-codex-turn-metadata` marker is still recognized when a Codex client sends it; using a relay pool
534
+ as the named child model keeps the split setup reliable even when a custom-agent request omits that
535
+ private marker.
536
+
537
+ This applies to local Codex clients that can reach `127.0.0.1`. Hosted ChatGPT/Cloud tasks cannot
538
+ reach a loopback relay, and the relay cannot spend a ChatGPT subscription on behalf of an upstream
539
+ request; those remain separate CLI/client-bound dispatch lanes.
460
540
 
461
541
  Whole-task CLI dispatch can likewise vary by tier with `routing.ladders.{reasoning,coding,fast}`.
462
542
  Use `llm-relay dispatch --tier reasoning -t "..."`; without `--tier`, the ladder matching
@@ -88,7 +88,10 @@
88
88
  "fable": "pool/fast",
89
89
  "default": "pool/coding"
90
90
  },
91
- "offload": false
91
+ "offload": {
92
+ "claude": { "enabled": false, "scope": "subagents" },
93
+ "codex": { "enabled": false, "scope": "subagents" }
94
+ }
92
95
  },
93
96
  "mode": "repair",
94
97
  "repair": {
@@ -1,4 +1,4 @@
1
- import type { Config } from "./config.js";
1
+ import { type Config, type OffloadRule } from "./config.js";
2
2
  import type { ModelCatalog } from "./catalog.js";
3
3
  import type { PingLoop } from "./ping/cadence.js";
4
4
  import { type StrengthBasis } from "./benchmarks.js";
@@ -128,7 +128,10 @@ export interface Candidate {
128
128
  }
129
129
  export interface CandidatesView {
130
130
  generated_at: string;
131
+ /** True when at least one client-specific rule is enabled. */
131
132
  offload_enabled: boolean;
133
+ /** The per-originating-client rules; empty for the legacy boolean form. */
134
+ offload_clients: Record<string, OffloadRule>;
132
135
  note: string;
133
136
  candidates: Candidate[];
134
137
  }
@@ -1,3 +1,4 @@
1
+ import { anyOffloadEnabled } from "./config.js";
1
2
  import { POOL_PREFIX } from "./config.js";
2
3
  import { getStrength } from "./benchmarks.js";
3
4
  import { findTierModel } from "./tier-data.js";
@@ -176,7 +177,10 @@ export async function buildCandidates(cfg, opts = {}) {
176
177
  }
177
178
  return {
178
179
  generated_at: opts.now ?? new Date(nowMs).toISOString(),
179
- offload_enabled: cfg.routing.offload === true,
180
+ offload_enabled: anyOffloadEnabled(cfg),
181
+ offload_clients: typeof cfg.routing.offload === "object" && cfg.routing.offload !== null
182
+ ? cfg.routing.offload
183
+ : {},
180
184
  note: NOTE,
181
185
  candidates,
182
186
  };
@@ -1 +1 @@
1
- {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../src/candidates.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"candidates.js","sourceRoot":"","sources":["../src/candidates.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts CHANGED
@@ -94,8 +94,8 @@ export declare function renderCommand(invoke: {
94
94
  * order and configuration but knows nothing about what is currently spent.
95
95
  */
96
96
  export declare function runDispatch(arg: string | undefined): Promise<void>;
97
- /** `llm-relay offload [on|off|status]` — the subagent-offload master switch. */
98
- export declare function runOffload(arg: string | undefined): Promise<void>;
97
+ /** `llm-relay offload [client] [on|off|status]` — configure one client or the legacy global rule. */
98
+ export declare function runOffload(arg: string | undefined, nextArg?: string): Promise<void>;
99
99
  /** `llm-relay candidates` — every dimension of every offload target, side by side, unranked. */
100
100
  export declare function runCandidates(): Promise<void>;
101
101
  /**
package/dist/cli.js CHANGED
@@ -66,6 +66,8 @@ const VALUE_FLAGS = new Set([
66
66
  '--after', '-after',
67
67
  '--lane', '-lane',
68
68
  '--tier', '-tier',
69
+ '--client', '-client',
70
+ '--scope', '-scope',
69
71
  '--shell', '-shell',
70
72
  ]);
71
73
  /** Extract non-flag positional arguments from an argv array, skipping flags and their values. */
@@ -111,13 +113,17 @@ A provider with kind:"anthropic" and NO authEnv is a passthrough: the caller's o
111
113
  credentials are forwarded untouched. Point the tiers at one to keep real Claude
112
114
  traffic on real Anthropic while pool/* requests go to other providers.
113
115
 
114
- Claude Code SUBAGENTS (flagged cc_is_subagent=true on the wire) may be OFFLOADED to
115
- other providers while the human's own conversation stays on passthrough. This is OFF
116
- by default and is a deliberate choice, not a background behaviour:
116
+ Claude Code requests (the /v1/messages front door) and Codex requests (the /v1/responses
117
+ front door) have independent offload rules. Each can be disabled, limited to marked
118
+ subagents, or applied to the whole conversation:
117
119
 
118
- llm-relay offload on routing.subagents{} (a tier -> spec map) starts applying
119
- llm-relay offload off subagents route like any other request (the default)
120
- llm-relay offload status current switch state and where each tier goes
120
+ llm-relay offload status
121
+ llm-relay offload claude on --scope subagents
122
+ llm-relay offload codex on --scope all
123
+ llm-relay offload claude off
124
+
125
+ The old "llm-relay offload on|off" form remains a global compatibility switch. Changes
126
+ take effect on the next request without a restart and are persisted to the config file.
121
127
  llm-relay candidates every dimension of every target, side by side, unranked
122
128
 
123
129
  The toggle reaches a running proxy over loopback, so it takes effect on the next
@@ -148,8 +154,8 @@ Usage:
148
154
  llm-relay models [-p <name>] [-r] List live models per provider
149
155
  llm-relay pools [--probe] List pool members; --probe tests each for real
150
156
  llm-relay ping [-p <name>] Probe model latency, stability & quota across providers
151
- llm-relay offload [on|off|status] Turn subagent offload on/off (default: off)
152
- llm-relay dispatch [lane] [-t <task>] [--tier] Which tier-specific lane to use next
157
+ llm-relay offload [client] [on|off|status] Configure client offload; --scope subagents|all
158
+ llm-relay dispatch [lane] [-t <task>] [--tier] [--client] Which client-specific lane to use next
153
159
  llm-relay candidates [-p <name>] Un-blended decision table for offload targets
154
160
  llm-relay help | --help | -h Show this help documentation
155
161
  llm-relay version | --version | -v Show version number
@@ -163,8 +169,9 @@ Commands:
163
169
  telemetry Output JSON telemetry & quota report
164
170
  models Query live /models catalog across providers
165
171
  ping Probe model latency, stability & quota metrics
166
- offload on | off | status Master switch for subagent offload (off by default)
167
- dispatch [lane] Next lane; --tier reasoning|coding|fast selects ladder
172
+ offload [client] on|off|status Per-client offload; client is claude, codex, or future name
173
+ dispatch [lane] Next lane; --client claude|codex selects offload hints;
174
+ --tier reasoning|coding|fast selects ladder
168
175
  the command, --after <lane> to walk past a spent
169
176
  rung, -x/--exhausted <lane> to report one spent,
170
177
  --shell sh|pwsh to quote for another shell,
@@ -200,8 +207,8 @@ Proxy Server Endpoints:
200
207
  POST /v1/responses OpenAI-compatible Responses front (Codex/IDE)
201
208
  GET /registry Full JSON view of providers, routing & capabilities
202
209
  GET /candidates [?provider=] Per-target raw benchmarks, health, quota, breaker state
203
- GET|POST /offload Read or set the subagent-offload switch {"enabled":bool}
204
- GET|POST /dispatch [?tier=&lane=&after=&task=] Next dispatch lane; POST {"exhausted":"<lane>"} to walk on
210
+ GET|POST /offload [?client=] Read/set {"enabled":bool,"scope":"subagents"|"all"}
211
+ GET|POST /dispatch [?client=&tier=&lane=&after=&task=] Next dispatch lane; POST {"exhausted":"<lane>"} to walk on
205
212
  GET /telemetry Live JSON telemetry, quota & stability scores for Claude
206
213
  GET /ping Trigger health probe pass & query ping mode summary
207
214
  GET /health Diagnostic JSON summary of provider availability & health
@@ -251,11 +258,14 @@ const DEFAULT_CONFIG_TEMPLATE = JSON.stringify({
251
258
  coding: ["nim/z-ai/glm-5.2", "nim/deepseek-ai/deepseek-v4-pro", "nim/moonshotai/kimi-k2.6"],
252
259
  fast: ["nim/meta/llama-3.1-8b-instruct", "nim/openai/gpt-oss-20b"],
253
260
  },
254
- // Where subagents go WHEN offload is on. Inert while `offload` is false.
261
+ // Where marked subagents (and, with scope "all", conversations) go when offload is on.
255
262
  subagents: { opus: "pool/coding", sonnet: "pool/coding", haiku: "pool/fast", default: "pool/coding" },
256
- // Master switch, off by default: subagents route like everything else until you run
257
- // `llm-relay offload on`. Silently answering as a different vendor's model has to be chosen.
258
- offload: false,
263
+ // Per-originating-harness switches, all off by default. `scope: "all"` also reroutes the
264
+ // main conversation; omit it or use "subagents" to preserve the current topology.
265
+ offload: {
266
+ claude: { enabled: false, scope: "subagents" },
267
+ codex: { enabled: false, scope: "subagents" },
268
+ },
259
269
  },
260
270
  mode: "repair",
261
271
  repair: {
@@ -607,11 +617,12 @@ export async function runDispatch(arg) {
607
617
  const after = argValue("--after");
608
618
  const lane = arg && !arg.startsWith("-") ? arg : argValue("--lane");
609
619
  const tier = argValue("--tier");
620
+ const client = argValue("--client");
610
621
  if (spent) {
611
622
  const live = await tryServer(cfg, "/dispatch", {
612
623
  method: "POST",
613
624
  headers: { "content-type": "application/json" },
614
- body: JSON.stringify({ exhausted: spent, ...(tier ? { tier } : {}) }),
625
+ body: JSON.stringify({ exhausted: spent, ...(tier ? { tier } : {}), ...(client ? { client } : {}) }),
615
626
  });
616
627
  // Cooldowns are runtime state held by the proxy; with nothing listening there is no
617
628
  // process to remember it, and pretending otherwise would silently lose the report.
@@ -629,6 +640,8 @@ export async function runDispatch(arg) {
629
640
  qs.set("after", after);
630
641
  if (tier)
631
642
  qs.set("tier", tier);
643
+ if (client)
644
+ qs.set("client", client);
632
645
  const path = `/dispatch${qs.toString() ? `?${qs}` : ""}`;
633
646
  const live = (await tryServer(cfg, path));
634
647
  const view = live ??
@@ -637,12 +650,13 @@ export async function runDispatch(arg) {
637
650
  ...(lane ? { lane } : {}),
638
651
  ...(after ? { after } : {}),
639
652
  ...(tier ? { tier } : {}),
653
+ ...(client ? { client } : {}),
640
654
  });
641
655
  if (hasFlag("--json")) {
642
656
  process.stdout.write(JSON.stringify(view, null, 2) + "\n");
643
657
  return;
644
658
  }
645
- process.stdout.write(`subagent offload: ${view.offload ? "ON" : "OFF"}\n`);
659
+ process.stdout.write(`${view.client === "default" ? "subagent" : view.client} offload: ${view.offload ? "ON" : "OFF"}\n`);
646
660
  if (view.tier)
647
661
  process.stdout.write(`dispatch tier: ${view.tier}\n`);
648
662
  if (!live)
@@ -678,22 +692,51 @@ export async function runDispatch(arg) {
678
692
  }
679
693
  process.stdout.write(`\n${view.next ? `use: ${view.next.id}` : "no lane available"} — ${view.reason}\n`);
680
694
  }
681
- /** `llm-relay offload [on|off|status]` — the subagent-offload master switch. */
682
- export async function runOffload(arg) {
695
+ /** `llm-relay offload [client] [on|off|status]` — configure one client or the legacy global rule. */
696
+ export async function runOffload(arg, nextArg) {
683
697
  const cfg = loadOrExit();
684
- const want = arg === "on" || arg === "enable" ? true : arg === "off" || arg === "disable" ? false : null;
685
- if (want === null && arg !== undefined && arg !== "status") {
686
- process.stderr.write(`llm-relay offload: expected "on", "off" or "status" (got "${arg}")\n`);
698
+ const actions = new Set(["on", "enable", "off", "disable", "status"]);
699
+ const legacy = arg === undefined || actions.has(arg);
700
+ const client = legacy ? undefined : arg;
701
+ const action = legacy ? (arg ?? "status") : (nextArg ?? "status");
702
+ const want = action === "on" || action === "enable" ? true : action === "off" || action === "disable" ? false : null;
703
+ const scopeArg = argValue("--scope", "-scope");
704
+ const scope = scopeArg === "all" || scopeArg === "subagents" ? scopeArg : undefined;
705
+ if (!legacy && (!client || !/^[A-Za-z0-9_.-]+$/.test(client))) {
706
+ process.stderr.write(`llm-relay offload: client must be a simple name such as "claude" or "codex"\n`);
707
+ process.exit(1);
708
+ }
709
+ if (!["on", "enable", "off", "disable", "status"].includes(action)) {
710
+ process.stderr.write(`llm-relay offload: expected [client] on|off|status (got "${action}")\n`);
711
+ process.exit(1);
712
+ }
713
+ if (scopeArg !== undefined && scope === undefined) {
714
+ process.stderr.write(`llm-relay offload: --scope expects "subagents" or "all"\n`);
715
+ process.exit(1);
716
+ }
717
+ if (scope !== undefined && client === undefined) {
718
+ process.stderr.write(`llm-relay offload: --scope requires a client name (claude, codex, or another configured client)\n`);
687
719
  process.exit(1);
688
720
  }
689
- const live = (await tryServer(cfg, "/offload", want === null
721
+ if (scope !== undefined && want === null) {
722
+ process.stderr.write(`llm-relay offload: --scope may be used when enabling/disabling a client\n`);
723
+ process.exit(1);
724
+ }
725
+ const query = client ? `?client=${encodeURIComponent(client)}` : "";
726
+ const live = (await tryServer(cfg, `/offload${query}`, want === null
690
727
  ? undefined
691
- : { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ enabled: want }) }));
728
+ : {
729
+ method: "POST",
730
+ headers: { "content-type": "application/json" },
731
+ body: JSON.stringify({ enabled: want, ...(client ? { client } : {}), ...(scope ? { scope } : {}) }),
732
+ }));
692
733
  // No proxy listening: still honour the change by writing the file, but say plainly that
693
- // nothing is running to apply it to.
694
- const state = live ?? (want === null ? offloadState(cfg) : setOffload(cfg, want));
695
- process.stdout.write(`subagent offload: ${state.enabled ? "ON" : "OFF"}\n`);
696
- // Only a real change reports where it went; a status read must not imply it wrote anything.
734
+ // nothing is running to apply it.
735
+ const state = live ?? (want === null ? offloadState(cfg, client) : setOffload(cfg, want, client, scope));
736
+ const label = client ?? "global";
737
+ const configuredClients = state.clients ?? {};
738
+ const effectiveScope = state.scope ?? "subagents";
739
+ process.stdout.write(`${label} offload: ${state.enabled ? "ON" : "OFF"}${client ? ` (${effectiveScope})` : ""}\n`);
697
740
  if (want !== null) {
698
741
  process.stdout.write(live ? " applied to the running proxy (effective now)\n" : " no proxy listening — config file only\n");
699
742
  if (!state.persisted) {
@@ -706,20 +749,23 @@ export async function runOffload(arg) {
706
749
  else {
707
750
  process.stdout.write(` source: ${live ? "running proxy" : `${state.configPath ?? "config"} (no proxy listening)`}\n`);
708
751
  }
709
- if (state.enabled) {
710
- const entries = Object.entries(state.subagents);
711
- if (entries.length === 0) {
752
+ if (client) {
753
+ if (state.enabled && Object.keys(state.subagents).length === 0) {
712
754
  process.stdout.write(" ⚠ routing.subagents is empty — offload is on but routes nowhere\n");
713
755
  }
714
- else {
715
- process.stdout.write("\n tier -> target\n");
716
- for (const [tier, spec] of entries)
717
- process.stdout.write(` ${tier.padEnd(9)} -> ${spec}\n`);
756
+ process.stdout.write(` scope: ${effectiveScope} (subagents only, or the full ${client} conversation)\n`);
757
+ }
758
+ else if (Object.keys(configuredClients).length > 0) {
759
+ process.stdout.write("\n client enabled scope\n");
760
+ for (const [name, rule] of Object.entries(configuredClients)) {
761
+ process.stdout.write(` ${name.padEnd(9)} ${(rule.enabled ? "ON" : "OFF").padEnd(8)} ${rule.scope}\n`);
718
762
  }
719
763
  }
764
+ else if (state.enabled) {
765
+ process.stdout.write(" legacy global rule applies to marked subagents for every front door\n");
766
+ }
720
767
  else {
721
- process.stdout.write("\n Subagents route like any other request (Anthropic passthrough).\n");
722
- process.stdout.write(" An `@relay: <spec>` line in a subagent prompt still offloads that one call.\n");
768
+ process.stdout.write(" offload is disabled; an `@relay: <spec>` line still offloads one marked subagent call\n");
723
769
  }
724
770
  }
725
771
  function fmt(v, suffix = "") {
@@ -765,7 +811,10 @@ export async function runCandidates() {
765
811
  process.stdout.write("No offload targets configured (routing.pools / routing.subagents are empty).\n");
766
812
  return;
767
813
  }
768
- process.stdout.write(`Offload targets offload is ${view.offload_enabled ? "ON" : "OFF"}\n`);
814
+ const clientState = Object.entries(view.offload_clients ?? {})
815
+ .map(([name, rule]) => `${name}:${rule.enabled ? "on" : "off"}/${rule.scope}`)
816
+ .join(", ");
817
+ process.stdout.write(`Offload targets — ${clientState || `legacy/global: ${view.offload_enabled ? "on" : "off"}`}\n`);
769
818
  process.stdout.write(`${view.note}\n\n`);
770
819
  const head = "target".padEnd(32) +
771
820
  "pools / tiers".padEnd(24) +
@@ -924,6 +973,7 @@ export function main() {
924
973
  const positionals = getPositionalArgs(process.argv);
925
974
  const arg2 = positionals[0];
926
975
  const arg3 = positionals[1];
976
+ const arg4 = positionals[2];
927
977
  if (hasFlag("--help", "-h") || arg2 === "help") {
928
978
  process.stdout.write(HELP);
929
979
  process.exit(0);
@@ -971,7 +1021,7 @@ export function main() {
971
1021
  return;
972
1022
  }
973
1023
  if (arg2 === "offload") {
974
- runOffload(arg3).catch((e) => {
1024
+ runOffload(arg3, arg4).catch((e) => {
975
1025
  process.stderr.write(`llm-relay offload: ${e.message}\n`);
976
1026
  process.exit(1);
977
1027
  });
@@ -1039,6 +1089,7 @@ export function classifyCommand(argv) {
1039
1089
  if (sub === undefined)
1040
1090
  return "mutating";
1041
1091
  const arg3 = positionals[1];
1092
+ const arg4 = positionals[2];
1042
1093
  switch (sub) {
1043
1094
  // Writes ~/.llm-relay/.env.
1044
1095
  case "onboard":
@@ -1046,9 +1097,12 @@ export function classifyCommand(argv) {
1046
1097
  // `setup claude-desktop` writes claude_desktop_config.json; bare `setup` only prints.
1047
1098
  case "setup":
1048
1099
  return arg3 === "claude-desktop" || arg3 === "desktop" ? "mutating" : "read-only";
1049
- // `offload on|off` rewrites config.json; `offload` / `offload status` only report.
1100
+ // `offload [client] on|off` rewrites config.json; status forms only report.
1050
1101
  case "offload":
1051
- return arg3 === "on" || arg3 === "enable" || arg3 === "off" || arg3 === "disable" ? "mutating" : "read-only";
1102
+ return arg3 === "on" || arg3 === "enable" || arg3 === "off" || arg3 === "disable" ||
1103
+ arg4 === "on" || arg4 === "enable" || arg4 === "off" || arg4 === "disable"
1104
+ ? "mutating"
1105
+ : "read-only";
1052
1106
  // keys, check-keys, models, telemetry, dispatch, candidates, pools, ping, help, version —
1053
1107
  // and anything not yet listed. `dispatch -x` is included on purpose: it reports spend to a
1054
1108
  // running proxy's in-memory cooldowns and changes nothing on this machine.