@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.
package/cc-statusline.sh CHANGED
@@ -130,14 +130,18 @@ normalize_model() {
130
130
  prices='{
131
131
  "input": {
132
132
  "claude-fable-5": 0.01,
133
- "claude-haiku-4-5": 0.0008,
133
+ "claude-haiku-4-5": 0.001,
134
+ "claude-opus-4-0": 0.015,
135
+ "claude-opus-4-1": 0.015,
136
+ "claude-opus-4-5": 0.005,
134
137
  "claude-opus-4-6": 0.005,
135
138
  "claude-opus-4-7": 0.005,
136
139
  "claude-opus-4-8": 0.005,
137
140
  "claude-sonnet-4-5": 0.003,
138
141
  "claude-sonnet-4-6": 0.003,
139
- "deepseek/deepseek-v4-flash": 0.00014,
140
- "deepseek/deepseek-v4-pro": 0.00174,
142
+ "claude-sonnet-5": 0.003,
143
+ "deepseek/deepseek-v4-flash": 0.0001134,
144
+ "deepseek/deepseek-v4-pro": 0.001318,
141
145
  "gemini-2.0-flash": 0.0001,
142
146
  "gemini-2.0-flash-lite": 0.000075,
143
147
  "gemini-2.5-flash": 0.0003,
@@ -148,6 +152,7 @@ prices='{
148
152
  "gemini-3.1-flash-lite-preview": 0.0001,
149
153
  "gemini-3.1-pro-preview": 0.002,
150
154
  "gemini-3.5-flash": 0.0015,
155
+ "google/gemma-4-26b-a4b-it": 0.00015,
151
156
  "gpt-4.1": 0.002,
152
157
  "gpt-4.1-mini": 0.0004,
153
158
  "gpt-4.1-nano": 0.0001,
@@ -157,10 +162,10 @@ prices='{
157
162
  "gpt-5-chat": 0.0025,
158
163
  "gpt-5-mini": 0.0005,
159
164
  "gpt-5-nano": 0.0001,
160
- "gpt-5.4": 0.003,
161
- "gpt-5.4-mini": 0.0004,
162
- "gpt-5.4-nano": 0.0001,
163
- "gpt-5.4-pro": 0.02,
165
+ "gpt-5.4": 0.0025,
166
+ "gpt-5.4-mini": 0.00075,
167
+ "gpt-5.4-nano": 0.0002,
168
+ "gpt-5.4-pro": 0.03,
164
169
  "gpt-5.5": 0.005,
165
170
  "gpt-5.5-mini": 0.0005,
166
171
  "gpt-5.5-nano": 0.00015,
@@ -177,22 +182,27 @@ prices='{
177
182
  "qwen/qwen3-coder-next": 0.0005,
178
183
  "qwen/qwen3-next-80b-a3b-instruct": 0.00015,
179
184
  "qwen/qwen3.5-flash-02-23": 0.00005,
180
- "qwen/qwen3.6-35b-a3b": 0.00015,
185
+ "qwen/qwen3.6-35b-a3b": 0.000172,
181
186
  "qwen/qwen3.7-plus": 0.0004,
182
187
  "xiaomi/mimo-v2.5-pro": 0.001,
183
- "z-ai/glm-5": 0.0006,
184
- "z-ai/glm-5.1": 0.00105
188
+ "z-ai/glm-5": 0.001,
189
+ "z-ai/glm-5.1": 0.0014,
190
+ "z-ai/glm-5.2": 0.0014
185
191
  },
186
192
  "output": {
187
193
  "claude-fable-5": 0.05,
188
- "claude-haiku-4-5": 0.004,
194
+ "claude-haiku-4-5": 0.005,
195
+ "claude-opus-4-0": 0.075,
196
+ "claude-opus-4-1": 0.075,
197
+ "claude-opus-4-5": 0.025,
189
198
  "claude-opus-4-6": 0.025,
190
199
  "claude-opus-4-7": 0.025,
191
200
  "claude-opus-4-8": 0.025,
192
201
  "claude-sonnet-4-5": 0.015,
193
202
  "claude-sonnet-4-6": 0.015,
194
- "deepseek/deepseek-v4-flash": 0.00028,
195
- "deepseek/deepseek-v4-pro": 0.00348,
203
+ "claude-sonnet-5": 0.015,
204
+ "deepseek/deepseek-v4-flash": 0.0002791,
205
+ "deepseek/deepseek-v4-pro": 0.0026361,
196
206
  "gemini-2.0-flash": 0.0004,
197
207
  "gemini-2.0-flash-lite": 0.0003,
198
208
  "gemini-2.5-flash": 0.0012,
@@ -203,6 +213,7 @@ prices='{
203
213
  "gemini-3.1-flash-lite-preview": 0.0004,
204
214
  "gemini-3.1-pro-preview": 0.008,
205
215
  "gemini-3.5-flash": 0.009,
216
+ "google/gemma-4-26b-a4b-it": 0.0006,
206
217
  "gpt-4.1": 0.008,
207
218
  "gpt-4.1-mini": 0.0016,
208
219
  "gpt-4.1-nano": 0.0004,
@@ -212,14 +223,14 @@ prices='{
212
223
  "gpt-5-chat": 0.01,
213
224
  "gpt-5-mini": 0.002,
214
225
  "gpt-5-nano": 0.0004,
215
- "gpt-5.4": 0.012,
216
- "gpt-5.4-mini": 0.0016,
217
- "gpt-5.4-nano": 0.0004,
218
- "gpt-5.4-pro": 0.08,
219
- "gpt-5.5": 0.04,
226
+ "gpt-5.4": 0.015,
227
+ "gpt-5.4-mini": 0.0045,
228
+ "gpt-5.4-nano": 0.00125,
229
+ "gpt-5.4-pro": 0.18,
230
+ "gpt-5.5": 0.03,
220
231
  "gpt-5.5-mini": 0.0025,
221
232
  "gpt-5.5-nano": 0.0006,
222
- "gpt-5.5-pro": 0.12,
233
+ "gpt-5.5-pro": 0.18,
223
234
  "minimax/minimax-m2.7": 0.0012,
224
235
  "minimax/minimax-m3": 0.0012,
225
236
  "mistralai/mistral-small-2603": 0.0006,
@@ -232,11 +243,12 @@ prices='{
232
243
  "qwen/qwen3-coder-next": 0.0012,
233
244
  "qwen/qwen3-next-80b-a3b-instruct": 0.0012,
234
245
  "qwen/qwen3.5-flash-02-23": 0.00015,
235
- "qwen/qwen3.6-35b-a3b": 0.00095,
246
+ "qwen/qwen3.6-35b-a3b": 0.0012002,
236
247
  "qwen/qwen3.7-plus": 0.0016,
237
248
  "xiaomi/mimo-v2.5-pro": 0.003,
238
- "z-ai/glm-5": 0.00208,
239
- "z-ai/glm-5.1": 0.0035
249
+ "z-ai/glm-5": 0.0032,
250
+ "z-ai/glm-5.1": 0.0044,
251
+ "z-ai/glm-5.2": 0.0044
240
252
  }
241
253
  }'
242
254
  # END_GENERATED_PRICES
@@ -425,20 +437,23 @@ elif [[ "$routed" == "failure" ]]; then
425
437
  # swap or compute savings against a non-model.
426
438
  printf '%s — %s%s' "$brand" "$routed" "$tokens_clause"
427
439
  elif [[ -n "$routed" ]]; then
428
- # Show the savings clause only when the session is genuinely net-saving.
440
+ # Always show the savings clause, flooring a non-positive total at $0.00.
429
441
  # session_savings is "0.0000" on fresh sessions or sessions where every
430
- # turn routed back to the selected model; it can also go negative when
431
- # sticky routing forces a haiku-tagged side-call up to a cached
432
- # sonnet/opus decision. In both cases the word "saved" would mislead,
433
- # so drop the savings clause but keep the token totals.
434
- has_savings="false"
435
- if [[ -n "$session_savings" ]] \
436
- && awk -v v="$session_savings" 'BEGIN{exit !(v+0 > 0.005)}'; then
437
- has_savings="true"
442
+ # turn routed back to the selected model, and can go negative when routing
443
+ # upgrades a turn to a pricier model (e.g. a sticky/hard-pinned side-call,
444
+ # or a hard turn escalated to opus). "saved -$X" would mislead, so clamp
445
+ # the display to $0.00 rather than dropping the clause — a $0.00 readout
446
+ # tells the user the router ran and simply didn't beat their selection.
447
+ # When the CC selection is unknown ("?" or empty) there's nothing to
448
+ # compare against, so drop the "← selection" arrow and just show routed.
449
+ display_savings="$session_savings"
450
+ if [[ -z "$display_savings" ]] \
451
+ || awk -v v="$display_savings" 'BEGIN{exit !(v+0 < 0)}'; then
452
+ display_savings="0"
438
453
  fi
439
- if [[ "$has_savings" == "true" ]]; then
454
+ if [[ -n "$selected_display" && "$selected_display" != "?" ]]; then
440
455
  printf '%s — %s ← %s · saved %s%s' \
441
- "$brand" "$routed" "$selected_display" "$(fmt_money "$session_savings")" "$tokens_clause"
456
+ "$brand" "$routed" "$selected_display" "$(fmt_money "$display_savings")" "$tokens_clause"
442
457
  else
443
458
  printf '%s — %s%s' "$brand" "$routed" "$tokens_clause"
444
459
  fi
package/install.sh CHANGED
@@ -533,20 +533,26 @@ TOML
533
533
  chmod 600 "$config_file"
534
534
  }
535
535
 
536
- # write_opencode_config merges TWO managed providers into opencode's
537
- # opencode.json plus the bundled Codex-subscription plugin:
538
- # - provider.weave : Anthropic-shaped (@ai-sdk/anthropic) the router
539
- # speaks the Anthropic Messages API natively, so the
540
- # bundled provider works unmodified. The default.
541
- # - provider.weave-codex : OpenAI-shaped (@ai-sdk/openai, Responses wire)
542
- # lets a caller's own ChatGPT (Codex) subscription
543
- # pay for their opencode turns. Dormant until the
544
- # user runs `opencode auth login` ChatGPT and the
545
- # weave-codex plugin's loader injects the bearer.
536
+ # write_opencode_config merges the managed Weave provider(s) into opencode's
537
+ # opencode.json plus the bundled subscription plugin:
538
+ # - provider.weave : OpenAI/Responses-shaped (@ai-sdk/openai /v1/responses).
539
+ # The single request provider. The router routes every
540
+ # turn across all models the caller's subscriptions +
541
+ # Weave key can pay for, and bills the plan matching the
542
+ # model it served. The default.
543
+ # - provider.weave-claude : login-only storage for a Claude (Pro/Max) subscription
544
+ # (no models, never serves requests). Written only when
545
+ # the bundled plugin is present, since the Claude login
546
+ # method lives in the plugin. The `weave` loader reads
547
+ # this slot and attaches the Claude sub.
546
548
  # The plugin (src bundled at $script_dir/opencode-weave/src/index.ts) is dropped
547
549
  # into $opencode_dir/.weave/ and registered via opencode.json's `plugin` array
548
- # by absolute path — scope-independent (no reliance on an auto-load dir).
549
- # Re-running rewrites both blocks in-place via jq; uninstall strips them.
550
+ # by absolute path — scope-independent (no reliance on an auto-load dir). It owns
551
+ # both the ChatGPT (on `weave`) and Claude (on `weave-claude`) logins and attaches
552
+ # whichever subscriptions are connected to every request via the router's
553
+ # dedicated X-Weave-*-Subscription headers. Re-running rewrites the blocks
554
+ # in-place via jq (and strips the legacy `weave-codex` provider); uninstall
555
+ # strips them.
550
556
  #
551
557
  # Usage: write_opencode_config <config_file_path> <base_url> <api_key> [user_email] [user_name]
552
558
  write_opencode_config() {
@@ -573,61 +579,53 @@ write_opencode_config() {
573
579
 
574
580
  # Headline models we surface in opencode's picker. The router re-routes
575
581
  # each request anyway, so this list is mostly UX — what shows up when the
576
- # user runs /models inside opencode. Keep it short and Anthropic-shaped
577
- # so the bundled @ai-sdk/anthropic provider can request them.
582
+ # user runs /models inside opencode. We list a mix of GPT + Claude so the
583
+ # picker reflects that the router spans families; whichever model serves the
584
+ # turn, its matching subscription (if connected) pays.
578
585
  #
579
- # apiKey is set to the router key as well as planted in headers. opencode's
580
- # @ai-sdk/anthropic provider treats apiKey as required at config-parse time
581
- # and otherwise falls back to ANTHROPIC_API_KEY from the environment;
582
- # without this, a user who's never had an Anthropic key in their shell hits
583
- # a startup error before the router ever sees a request. The router itself
584
- # ignores the value (auth runs off X-Weave-Router-Key); apiKey here is just
585
- # a placeholder that satisfies the SDK's "is auth configured" check.
586
- # baseURL KEEPS its /v1 here do NOT align it with the pi block, which is
587
- # root. opencode's @ai-sdk/anthropic (Vercel) appends only /messages to
588
- # baseURL (its default is api.anthropic.com/v1), so /v1 yields the correct
589
- # /v1/messages. Dropping it would hit /messages and 404. (pi uses the official
590
- # @anthropic-ai/sdk, which appends /v1/messages to a root baseURL — opposite
591
- # convention.) Verified by install/pi-router/test/opencode_smoke.sh.
586
+ # npm is @ai-sdk/openai and baseURL KEEPS its /v1 here: opencode's
587
+ # @ai-sdk/openai provider appends /responses, yielding the router's
588
+ # /v1/responses surface (the canonical inbound the router translates to
589
+ # Anthropic/OSS as it routes, and ships verbatim to the Codex backend for GPT
590
+ # turns). apiKey is the router key as a parse-time placeholder; the router
591
+ # authenticates off X-Weave-Router-Key (planted in headers) and the plugin's
592
+ # loader attaches subscriptions via the dedicated X-Weave-*-Subscription
593
+ # headers, so the apiKey value is never used upstream.
592
594
  local block
593
595
  block="$(jq -n \
594
596
  --arg url "$block_url/v1" \
595
597
  --arg key "$block_key" \
596
598
  --argjson headers "$headers_json" '
597
599
  {
598
- npm: "@ai-sdk/anthropic",
600
+ npm: "@ai-sdk/openai",
599
601
  name: "Weave Router",
600
602
  options: { apiKey: $key, baseURL: $url, headers: $headers },
601
603
  models: {
602
- "claude-fable-5": { name: "Claude Fable 5 (via Weave Router)" },
603
- "claude-opus-4-8": { name: "Claude Opus 4.8 (via Weave Router)" },
604
- "claude-opus-4-7": { name: "Claude Opus 4.7 (via Weave Router)" },
604
+ "claude-sonnet-5": { name: "Claude Sonnet 5 (via Weave Router)" },
605
605
  "claude-sonnet-4-6": { name: "Claude Sonnet 4.6 (via Weave Router)" },
606
- "claude-haiku-4-5": { name: "Claude Haiku 4.5 (via Weave Router)" }
606
+ "claude-opus-4-8": { name: "Claude Opus 4.8 (via Weave Router)" },
607
+ "claude-haiku-4-5": { name: "Claude Haiku 4.5 (via Weave Router)" },
608
+ "gpt-5.5": { name: "GPT-5.5 (via Weave Router)" },
609
+ "gpt-5.4": { name: "GPT-5.4 (via Weave Router)" }
607
610
  }
608
611
  }
609
612
  ')"
610
613
 
611
- # The OpenAI-shaped Codex-subscription provider. Reuses the same X-Weave-*
612
- # header triplet (router key + identity) the plugin's loader adds only the
613
- # dynamic subscription Authorization + ChatGPT-Account-Id on top. baseURL
614
- # KEEPS /v1: opencode's @ai-sdk/openai provider appends /responses, yielding
615
- # the router's /v1/responses Codex passthrough. apiKey is the router key as a
616
- # parse-time placeholder (the loader overrides it once a subscription is
617
- # linked), mirroring the weave block above.
618
- local codex_block
619
- codex_block="$(jq -n \
614
+ # Login-only storage provider for a Claude (Pro/Max) subscription. It serves
615
+ # no requests (no models), so its npm/baseURL are inert opencode just needs
616
+ # it registered so the plugin's Claude login method has a home and `opencode
617
+ # auth login` lists it. The `weave` loader reads this slot off disk and
618
+ # attaches the Claude sub. Reuses the same router key + identity headers.
619
+ local claude_block
620
+ claude_block="$(jq -n \
620
621
  --arg url "$block_url/v1" \
621
622
  --arg key "$block_key" \
622
623
  --argjson headers "$headers_json" '
623
624
  {
624
625
  npm: "@ai-sdk/openai",
625
- name: "Weave Router (Codex subscription)",
626
+ name: "Weave Router Claude plan",
626
627
  options: { apiKey: $key, baseURL: $url, headers: $headers },
627
- models: {
628
- "gpt-5.5": { name: "GPT-5.5 — Codex subscription (via Weave Router)" },
629
- "gpt-5.4": { name: "GPT-5.4 — Codex subscription (via Weave Router)" }
630
- }
628
+ models: {}
631
629
  }
632
630
  ')"
633
631
 
@@ -653,7 +651,7 @@ write_opencode_config() {
653
651
  chmod 644 "$plugin_spec"
654
652
  plugin_arg="$plugin_spec"
655
653
  else
656
- warn "opencode Codex plugin source not found at $plugin_src — skipping the weave-codex plugin registration. (Use a packaged 'npx @workweave/router' install.)"
654
+ warn "opencode subscription plugin source not found at $plugin_src — skipping the Claude login + subscription routing. (Use a packaged 'npx @workweave/router' install.)"
657
655
  fi
658
656
 
659
657
  # Merge into any existing opencode.json. We always overwrite provider.weave
@@ -661,21 +659,23 @@ write_opencode_config() {
661
659
  # file (other providers, mcp, agent settings) untouched. Top-level `model` is
662
660
  # only set when the user hasn't already picked one.
663
661
  #
664
- # The weave-codex provider AND its plugin entry are written together only when
665
- # the bundled plugin was present and copied ($plugin non-empty). Codex
666
- # subscription auth depends on that plugin, so registering the provider
667
- # without it (e.g. the `curl | sh` path, which carries no plugin source)
668
- # would leave a non-working provider — instead we omit it (and strip any
669
- # stale one from a prior install).
662
+ # The weave-claude login provider AND the plugin entry are written together
663
+ # only when the bundled plugin was present and copied ($plugin non-empty): the
664
+ # Claude login method lives in the plugin, so registering the provider without
665
+ # it (e.g. the `curl | sh` path, which carries no plugin source) would leave a
666
+ # non-working login — instead we omit it (and strip any stale one). The legacy
667
+ # `weave-codex` provider is always stripped: the single Responses `weave`
668
+ # provider supersedes it.
670
669
  local merged
671
670
  if [ -f "$config_file" ]; then
672
671
  merged="$(jq \
673
672
  --argjson block "$block" \
674
- --argjson codex "$codex_block" \
673
+ --argjson claude "$claude_block" \
675
674
  --arg plugin "$plugin_arg" \
676
675
  --arg pluginspec "$plugin_spec" '
677
676
  .provider = ((.provider // {}) | .weave = $block)
678
- | (if $plugin != "" then .provider["weave-codex"] = $codex else .provider |= del(."weave-codex") end)
677
+ | (.provider |= del(."weave-codex"))
678
+ | (if $plugin != "" then .provider["weave-claude"] = $claude else .provider |= del(."weave-claude") end)
679
679
  # Register the managed plugin path when we installed it; otherwise strip a
680
680
  # stale entry left by a prior install (the provider was just removed, so a
681
681
  # lingering plugin reference would be dead weight).
@@ -686,23 +686,24 @@ write_opencode_config() {
686
686
  else . end)
687
687
  end)
688
688
  | (if (.model // "") == "" then .model = "weave/claude-sonnet-4-6" else . end)
689
- # If we just stripped weave-codex (plugin-less re-install) but the default
690
- # model still points at it, reset to the Anthropic weave model so opencode
691
- # does not boot with a model whose provider no longer exists.
692
- | (if $plugin == "" and (.model // "" | tostring | startswith("weave-codex/")) then .model = "weave/claude-sonnet-4-6" else . end)
689
+ # Reset the default model if it points at a provider we just removed: the
690
+ # retired weave-codex, or weave-claude on a plugin-less re-install (it has
691
+ # no models anyway). Otherwise opencode boots with a dangling model.
692
+ | (if (.model // "" | tostring | startswith("weave-codex/")) then .model = "weave/claude-sonnet-4-6" else . end)
693
+ | (if $plugin == "" and (.model // "" | tostring | startswith("weave-claude/")) then .model = "weave/claude-sonnet-4-6" else . end)
693
694
  | (.["$schema"] //= "https://opencode.ai/config.json")
694
695
  ' "$config_file")"
695
696
  else
696
697
  merged="$(jq -n \
697
698
  --argjson block "$block" \
698
- --argjson codex "$codex_block" \
699
+ --argjson claude "$claude_block" \
699
700
  --arg plugin "$plugin_arg" '
700
701
  {
701
702
  "$schema": "https://opencode.ai/config.json",
702
703
  model: "weave/claude-sonnet-4-6",
703
704
  provider: { weave: $block }
704
705
  }
705
- | (if $plugin != "" then .provider["weave-codex"] = $codex | .plugin = [$plugin] else . end)
706
+ | (if $plugin != "" then .provider["weave-claude"] = $claude | .plugin = [$plugin] else . end)
706
707
  ')"
707
708
  fi
708
709
  printf '%s\n' "$merged" >"$config_file"
@@ -1402,9 +1403,9 @@ toggle_claude() {
1402
1403
  else
1403
1404
  # Park just the router-owned env keys, then strip them so Claude Code
1404
1405
  # falls back to its own Anthropic login.
1405
- jq '{env: ((.env // {}) | {ANTHROPIC_BASE_URL, ANTHROPIC_CUSTOM_HEADERS} | with_entries(select(.value != null)))}' "$settings_file" >"$parked"
1406
+ jq '{env: ((.env // {}) | {ANTHROPIC_BASE_URL, ANTHROPIC_CUSTOM_HEADERS, ENABLE_TOOL_SEARCH} | with_entries(select(.value != null)))}' "$settings_file" >"$parked"
1406
1407
  chmod 600 "$parked"
1407
- merged="$(jq '(.env // {}) |= del(.ANTHROPIC_BASE_URL, .ANTHROPIC_CUSTOM_HEADERS)
1408
+ merged="$(jq '(.env // {}) |= del(.ANTHROPIC_BASE_URL, .ANTHROPIC_CUSTOM_HEADERS, .ENABLE_TOOL_SEARCH)
1408
1409
  | (if (.env // {} | length) == 0 then del(.env) else . end)' "$settings_file")"
1409
1410
  printf '%s\n' "$merged" >"$settings_file"
1410
1411
  fi
@@ -1543,8 +1544,8 @@ toggle_opencode() {
1543
1544
  model="$(jq -r '.model // empty' "$f" 2>/dev/null || true)"
1544
1545
  if [ "$(jq -r '((.provider // {}) | has("weave"))' "$f" 2>/dev/null || true)" = "true" ]; then has_weave="true"; fi
1545
1546
  fi
1546
- # Either managed provider counts as router-on: the Anthropic-shaped `weave/…`
1547
- # default or a caller-selected `weave-codex/…` Codex-subscription model.
1547
+ # A managed model counts as router-on: the Responses-shaped `weave/…` default,
1548
+ # or a legacy `weave-codex/…` model parked by a pre-upgrade install.
1548
1549
  case "$model" in weave/* | weave-codex/*) on="true" ;; esac
1549
1550
 
1550
1551
  case "$mode" in
@@ -1588,10 +1589,10 @@ toggle_opencode() {
1588
1589
  | "weave/" + .
1589
1590
  ' "$f" 2>/dev/null || echo "weave/claude-sonnet-4-6")"
1590
1591
  fi
1591
- # Never restore a weave-codex model whose provider is no longer
1592
- # registered (e.g. a plugin-less re-install stripped it after `off`
1593
- # parked a weave-codex/… model). Fall back to the Anthropic weave default
1594
- # so `on` can't leave opencode pointing at a missing provider.
1592
+ # Never restore a legacy weave-codex model whose provider is no longer
1593
+ # registered (every current install strips weave-codex). Fall back to the
1594
+ # `weave` default so `on` can't leave opencode pointing at a missing
1595
+ # provider.
1595
1596
  case "$restore_model" in
1596
1597
  weave-codex/*)
1597
1598
  if [ "$(jq -r '((.provider // {}) | has("weave-codex"))' "$f" 2>/dev/null || true)" != "true" ]; then
@@ -1849,13 +1850,14 @@ if [ "$target" = "opencode" ]; then
1849
1850
  printf "\n"
1850
1851
  printf "%s✓%s %s%sWeave Router installed for opencode.%s\n" \
1851
1852
  "$C_GREEN" "$C_RESET" "$C_BOLD" "$C_BRAND" "$C_RESET"
1852
- # Surface the optional Codex-subscription path only when this run actually
1853
- # registered the weave-codex provider. Gate on the provider's presence in the
1854
- # written config (authoritative) rather than a plugin file on disk a
1855
- # leftover plugin from a prior install can outlive a plugin-less re-install
1856
- # that stripped the provider, which would make these instructions misleading.
1857
- if jq -e '(.provider // {}) | has("weave-codex")' "$opencode_config_file" >/dev/null 2>&1; then
1858
- info "To pay for opencode turns with your own ChatGPT (Codex) subscription: run ${C_BOLD}opencode auth login${C_RESET} ${C_BOLD}ChatGPT Pro/Plus${C_RESET}, then pick a ${C_BOLD}weave-codex${C_RESET} model."
1853
+ # Surface the optional subscription-routing path only when this run actually
1854
+ # registered the weave-claude login provider (which is written together with
1855
+ # the plugin). Gate on its presence in the written config (authoritative)
1856
+ # rather than a plugin file on disk a leftover plugin from a prior install
1857
+ # can outlive a plugin-less re-install that stripped the provider, which would
1858
+ # make these instructions misleading.
1859
+ if jq -e '(.provider // {}) | has("weave-claude")' "$opencode_config_file" >/dev/null 2>&1; then
1860
+ info "Optional: connect your AI plans so they pay for the matching turns. Run ${C_BOLD}opencode auth login${C_RESET} → ${C_BOLD}Weave Router${C_RESET} for ${C_BOLD}ChatGPT Pro/Plus${C_RESET} (GPT/Codex turns) and/or ${C_BOLD}Weave Router — Claude plan${C_RESET} for ${C_BOLD}Claude Pro/Max${C_RESET} (Claude turns). The router still routes every turn; your Weave key pays for the rest."
1859
1861
  fi
1860
1862
  if [ -n "$install_dir" ]; then
1861
1863
  # --dir installs land outside opencode's discovery roots, so the caller
@@ -2069,14 +2071,18 @@ normalize_model() {
2069
2071
  prices='{
2070
2072
  "input": {
2071
2073
  "claude-fable-5": 0.01,
2072
- "claude-haiku-4-5": 0.0008,
2074
+ "claude-haiku-4-5": 0.001,
2075
+ "claude-opus-4-0": 0.015,
2076
+ "claude-opus-4-1": 0.015,
2077
+ "claude-opus-4-5": 0.005,
2073
2078
  "claude-opus-4-6": 0.005,
2074
2079
  "claude-opus-4-7": 0.005,
2075
2080
  "claude-opus-4-8": 0.005,
2076
2081
  "claude-sonnet-4-5": 0.003,
2077
2082
  "claude-sonnet-4-6": 0.003,
2078
- "deepseek/deepseek-v4-flash": 0.00014,
2079
- "deepseek/deepseek-v4-pro": 0.00174,
2083
+ "claude-sonnet-5": 0.003,
2084
+ "deepseek/deepseek-v4-flash": 0.0001134,
2085
+ "deepseek/deepseek-v4-pro": 0.001318,
2080
2086
  "gemini-2.0-flash": 0.0001,
2081
2087
  "gemini-2.0-flash-lite": 0.000075,
2082
2088
  "gemini-2.5-flash": 0.0003,
@@ -2087,6 +2093,7 @@ prices='{
2087
2093
  "gemini-3.1-flash-lite-preview": 0.0001,
2088
2094
  "gemini-3.1-pro-preview": 0.002,
2089
2095
  "gemini-3.5-flash": 0.0015,
2096
+ "google/gemma-4-26b-a4b-it": 0.00015,
2090
2097
  "gpt-4.1": 0.002,
2091
2098
  "gpt-4.1-mini": 0.0004,
2092
2099
  "gpt-4.1-nano": 0.0001,
@@ -2096,10 +2103,10 @@ prices='{
2096
2103
  "gpt-5-chat": 0.0025,
2097
2104
  "gpt-5-mini": 0.0005,
2098
2105
  "gpt-5-nano": 0.0001,
2099
- "gpt-5.4": 0.003,
2100
- "gpt-5.4-mini": 0.0004,
2101
- "gpt-5.4-nano": 0.0001,
2102
- "gpt-5.4-pro": 0.02,
2106
+ "gpt-5.4": 0.0025,
2107
+ "gpt-5.4-mini": 0.00075,
2108
+ "gpt-5.4-nano": 0.0002,
2109
+ "gpt-5.4-pro": 0.03,
2103
2110
  "gpt-5.5": 0.005,
2104
2111
  "gpt-5.5-mini": 0.0005,
2105
2112
  "gpt-5.5-nano": 0.00015,
@@ -2116,22 +2123,27 @@ prices='{
2116
2123
  "qwen/qwen3-coder-next": 0.0005,
2117
2124
  "qwen/qwen3-next-80b-a3b-instruct": 0.00015,
2118
2125
  "qwen/qwen3.5-flash-02-23": 0.00005,
2119
- "qwen/qwen3.6-35b-a3b": 0.00015,
2126
+ "qwen/qwen3.6-35b-a3b": 0.000172,
2120
2127
  "qwen/qwen3.7-plus": 0.0004,
2121
2128
  "xiaomi/mimo-v2.5-pro": 0.001,
2122
- "z-ai/glm-5": 0.0006,
2123
- "z-ai/glm-5.1": 0.00105
2129
+ "z-ai/glm-5": 0.001,
2130
+ "z-ai/glm-5.1": 0.0014,
2131
+ "z-ai/glm-5.2": 0.0014
2124
2132
  },
2125
2133
  "output": {
2126
2134
  "claude-fable-5": 0.05,
2127
- "claude-haiku-4-5": 0.004,
2135
+ "claude-haiku-4-5": 0.005,
2136
+ "claude-opus-4-0": 0.075,
2137
+ "claude-opus-4-1": 0.075,
2138
+ "claude-opus-4-5": 0.025,
2128
2139
  "claude-opus-4-6": 0.025,
2129
2140
  "claude-opus-4-7": 0.025,
2130
2141
  "claude-opus-4-8": 0.025,
2131
2142
  "claude-sonnet-4-5": 0.015,
2132
2143
  "claude-sonnet-4-6": 0.015,
2133
- "deepseek/deepseek-v4-flash": 0.00028,
2134
- "deepseek/deepseek-v4-pro": 0.00348,
2144
+ "claude-sonnet-5": 0.015,
2145
+ "deepseek/deepseek-v4-flash": 0.0002791,
2146
+ "deepseek/deepseek-v4-pro": 0.0026361,
2135
2147
  "gemini-2.0-flash": 0.0004,
2136
2148
  "gemini-2.0-flash-lite": 0.0003,
2137
2149
  "gemini-2.5-flash": 0.0012,
@@ -2142,6 +2154,7 @@ prices='{
2142
2154
  "gemini-3.1-flash-lite-preview": 0.0004,
2143
2155
  "gemini-3.1-pro-preview": 0.008,
2144
2156
  "gemini-3.5-flash": 0.009,
2157
+ "google/gemma-4-26b-a4b-it": 0.0006,
2145
2158
  "gpt-4.1": 0.008,
2146
2159
  "gpt-4.1-mini": 0.0016,
2147
2160
  "gpt-4.1-nano": 0.0004,
@@ -2151,14 +2164,14 @@ prices='{
2151
2164
  "gpt-5-chat": 0.01,
2152
2165
  "gpt-5-mini": 0.002,
2153
2166
  "gpt-5-nano": 0.0004,
2154
- "gpt-5.4": 0.012,
2155
- "gpt-5.4-mini": 0.0016,
2156
- "gpt-5.4-nano": 0.0004,
2157
- "gpt-5.4-pro": 0.08,
2158
- "gpt-5.5": 0.04,
2167
+ "gpt-5.4": 0.015,
2168
+ "gpt-5.4-mini": 0.0045,
2169
+ "gpt-5.4-nano": 0.00125,
2170
+ "gpt-5.4-pro": 0.18,
2171
+ "gpt-5.5": 0.03,
2159
2172
  "gpt-5.5-mini": 0.0025,
2160
2173
  "gpt-5.5-nano": 0.0006,
2161
- "gpt-5.5-pro": 0.12,
2174
+ "gpt-5.5-pro": 0.18,
2162
2175
  "minimax/minimax-m2.7": 0.0012,
2163
2176
  "minimax/minimax-m3": 0.0012,
2164
2177
  "mistralai/mistral-small-2603": 0.0006,
@@ -2171,11 +2184,12 @@ prices='{
2171
2184
  "qwen/qwen3-coder-next": 0.0012,
2172
2185
  "qwen/qwen3-next-80b-a3b-instruct": 0.0012,
2173
2186
  "qwen/qwen3.5-flash-02-23": 0.00015,
2174
- "qwen/qwen3.6-35b-a3b": 0.00095,
2187
+ "qwen/qwen3.6-35b-a3b": 0.0012002,
2175
2188
  "qwen/qwen3.7-plus": 0.0016,
2176
2189
  "xiaomi/mimo-v2.5-pro": 0.003,
2177
- "z-ai/glm-5": 0.00208,
2178
- "z-ai/glm-5.1": 0.0035
2190
+ "z-ai/glm-5": 0.0032,
2191
+ "z-ai/glm-5.1": 0.0044,
2192
+ "z-ai/glm-5.2": 0.0044
2179
2193
  }
2180
2194
  }'
2181
2195
  # END_GENERATED_PRICES
@@ -2364,20 +2378,23 @@ elif [[ "$routed" == "failure" ]]; then
2364
2378
  # swap or compute savings against a non-model.
2365
2379
  printf '%s — %s%s' "$brand" "$routed" "$tokens_clause"
2366
2380
  elif [[ -n "$routed" ]]; then
2367
- # Show the savings clause only when the session is genuinely net-saving.
2381
+ # Always show the savings clause, flooring a non-positive total at $0.00.
2368
2382
  # session_savings is "0.0000" on fresh sessions or sessions where every
2369
- # turn routed back to the selected model; it can also go negative when
2370
- # sticky routing forces a haiku-tagged side-call up to a cached
2371
- # sonnet/opus decision. In both cases the word "saved" would mislead,
2372
- # so drop the savings clause but keep the token totals.
2373
- has_savings="false"
2374
- if [[ -n "$session_savings" ]] \
2375
- && awk -v v="$session_savings" 'BEGIN{exit !(v+0 > 0.005)}'; then
2376
- has_savings="true"
2377
- fi
2378
- if [[ "$has_savings" == "true" ]]; then
2383
+ # turn routed back to the selected model, and can go negative when routing
2384
+ # upgrades a turn to a pricier model (e.g. a sticky/hard-pinned side-call,
2385
+ # or a hard turn escalated to opus). "saved -$X" would mislead, so clamp
2386
+ # the display to $0.00 rather than dropping the clause — a $0.00 readout
2387
+ # tells the user the router ran and simply didn't beat their selection.
2388
+ # When the CC selection is unknown ("?" or empty) there's nothing to
2389
+ # compare against, so drop the "← selection" arrow and just show routed.
2390
+ display_savings="$session_savings"
2391
+ if [[ -z "$display_savings" ]] \
2392
+ || awk -v v="$display_savings" 'BEGIN{exit !(v+0 < 0)}'; then
2393
+ display_savings="0"
2394
+ fi
2395
+ if [[ -n "$selected_display" && "$selected_display" != "?" ]]; then
2379
2396
  printf '%s — %s ← %s · saved %s%s' \
2380
- "$brand" "$routed" "$selected_display" "$(fmt_money "$session_savings")" "$tokens_clause"
2397
+ "$brand" "$routed" "$selected_display" "$(fmt_money "$display_savings")" "$tokens_clause"
2381
2398
  else
2382
2399
  printf '%s — %s%s' "$brand" "$routed" "$tokens_clause"
2383
2400
  fi
@@ -2414,14 +2431,19 @@ if [ -n "$user_name" ]; then
2414
2431
  fi
2415
2432
  custom_headers="$custom_headers"$'\n'"X-App: claude-code"
2416
2433
 
2434
+ # Setting ANTHROPIC_BASE_URL makes Claude Code treat us as non-first-party and
2435
+ # disable MCP tool-search deferral, inlining every tool schema into every request
2436
+ # — a large uncompactable prefix that can push a session into an autocompact
2437
+ # thrash loop. ENABLE_TOOL_SEARCH=auto restores on-demand loading (Claude Code's
2438
+ # own first-party default).
2417
2439
  if [ "$scope" = "project" ] && [ -z "$install_dir" ]; then
2418
2440
  jq -n --arg url "$base_url" --arg sl "$statusline_path_for_settings" '{
2419
- env: { ANTHROPIC_BASE_URL: $url },
2441
+ env: { ANTHROPIC_BASE_URL: $url, ENABLE_TOOL_SEARCH: "auto" },
2420
2442
  statusLine: { type: "command", command: $sl }
2421
2443
  }' >"$tmp_patch"
2422
2444
  else
2423
2445
  jq -n --arg url "$base_url" --arg header "$custom_headers" --arg sl "$statusline_path_for_settings" '{
2424
- env: { ANTHROPIC_BASE_URL: $url, ANTHROPIC_CUSTOM_HEADERS: $header },
2446
+ env: { ANTHROPIC_BASE_URL: $url, ANTHROPIC_CUSTOM_HEADERS: $header, ENABLE_TOOL_SEARCH: "auto" },
2425
2447
  statusLine: { type: "command", command: $sl }
2426
2448
  }' >"$tmp_patch"
2427
2449
  fi