@workweave/router 0.2.10 → 0.2.12
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 +40 -14
- package/cc-statusline.sh +274 -23
- package/codex-skills/fm/SKILL.md +15 -0
- package/codex-skills/fm/scripts/emit.sh +8 -0
- package/codex-skills/force-model/SKILL.md +15 -0
- package/codex-skills/force-model/scripts/emit.sh +9 -0
- package/codex-skills/rf/SKILL.md +15 -0
- package/codex-skills/rf/scripts/emit.sh +7 -0
- package/codex-skills/router-feedback/SKILL.md +15 -0
- package/codex-skills/router-feedback/scripts/emit.sh +9 -0
- package/codex-skills/router-models/SKILL.md +51 -0
- package/codex-skills/router-off/SKILL.md +22 -0
- package/codex-skills/router-on/SKILL.md +22 -0
- package/codex-skills/router-status/SKILL.md +19 -0
- package/codex-skills/ufm/SKILL.md +14 -0
- package/codex-skills/ufm/scripts/emit.sh +3 -0
- package/codex-skills/unforce-model/SKILL.md +14 -0
- package/codex-skills/unforce-model/scripts/emit.sh +4 -0
- package/codex-status.sh +312 -0
- package/commands/beta.md +5 -0
- package/commands/models.md +46 -0
- package/commands/router-models.md +46 -0
- package/directives.tsv +13 -0
- package/install.sh +1849 -263
- package/package.json +7 -1
- package/pi-router/README.md +39 -7
- package/pi-router/skills/install-lsps/SKILL.md +75 -0
- package/pi-router/skills/lsp-guide/SKILL.md +63 -0
- package/pi-router/src/beta.ts +21 -0
- package/pi-router/src/compaction.ts +46 -8
- package/pi-router/src/config.ts +34 -5
- package/pi-router/src/context-window.ts +12 -0
- package/pi-router/src/dispatch.ts +35 -2
- package/pi-router/src/index.ts +12 -1
- package/pi-router/src/lsp-broker.ts +255 -0
- package/pi-router/src/lsp-client.ts +435 -0
- package/pi-router/src/lsp-format.ts +230 -0
- package/pi-router/src/lsp-install.ts +215 -0
- package/pi-router/src/lsp-protocol.ts +128 -0
- package/pi-router/src/lsp-servers.ts +361 -0
- package/pi-router/src/lsp.ts +529 -0
- package/pi-router/src/pricing.generated.ts +77 -70
- package/pi-router/src/provider.ts +15 -2
- package/pi-router/src/routed-model.ts +17 -0
- package/pi-router/src/savings.ts +1 -1
- package/registry.sh +102 -0
- package/uninstall.sh +210 -31
package/README.md
CHANGED
|
@@ -15,11 +15,13 @@ npx @workweave/router --non-interactive # reads $WEAVE_ROUTER_KEY, no prompt
|
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Re-running the installer to pick up changes reuses the key already on disk, so
|
|
18
|
-
you paste it once and never again
|
|
19
|
-
(safe for cron; errors instead of
|
|
18
|
+
you paste it once and never again — for every client, not just Claude Code.
|
|
19
|
+
`update` is the never-prompting form of that (safe for cron; errors instead of
|
|
20
|
+
asking when no key can be found):
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
23
|
npx @workweave/router --claude # reuses the installed key
|
|
24
|
+
npx @workweave/router --codex # same for Codex, opencode, and pi
|
|
23
25
|
npx @workweave/router --claude --rotate-key # ignore it and prompt for a new one
|
|
24
26
|
npx @workweave/router update --claude # non-interactive refresh in place
|
|
25
27
|
```
|
|
@@ -27,8 +29,11 @@ npx @workweave/router update --claude # non-interactive refresh in place
|
|
|
27
29
|
For Claude Code the installed statusline and `/force-model`, `/router-*` slash
|
|
28
30
|
commands also refresh themselves in the background about once a week (never
|
|
29
31
|
overwriting a wrapper you edited). Opt out with `WEAVE_STATUSLINE_UPDATE=0`, or
|
|
30
|
-
just the commands with `WEAVE_COMMANDS_UPDATE=0`. Codex
|
|
31
|
-
|
|
32
|
+
just the commands with `WEAVE_COMMANDS_UPDATE=0`. Codex installs native `$` skills
|
|
33
|
+
plus managed `SessionStart`/`Stop` hooks: the latest routed model is reflected in
|
|
34
|
+
the terminal title and a compact `Weave Router · …` status message is shown when
|
|
35
|
+
the router reports a new route. Existing Codex hooks are preserved. OpenCode and pi
|
|
36
|
+
have their own target-specific integrations.
|
|
32
37
|
|
|
33
38
|
Version-pin for reproducible setups:
|
|
34
39
|
|
|
@@ -48,12 +53,28 @@ npx @workweave/router status --codex # is Codex on the router or direct?
|
|
|
48
53
|
Claude Code reads its router setting at launch, so quit and reopen it after an
|
|
49
54
|
on/off. Codex and opencode pick it up on their next run. Inside Claude Code the
|
|
50
55
|
slash commands `/router-off`, `/router-on`, and `/router-status` do the same.
|
|
51
|
-
Codex installs
|
|
56
|
+
Codex installs `$router-status`, `$router-off`, `$router-on`, and
|
|
57
|
+
`$router-models` skills that call the same CLI verbs, plus a
|
|
58
|
+
`$disable-routing` skill that switches its next session back
|
|
52
59
|
to the normal provider; Codex does not support third-party `/disable-routing`
|
|
53
60
|
slash commands. The shell equivalent is `npx @workweave/router disable-routing`.
|
|
54
61
|
Cursor has no config file we own — toggle its base URL override in **Settings →
|
|
55
62
|
Models** instead.
|
|
56
63
|
|
|
64
|
+
Pick which models the router is allowed to route to:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx @workweave/router models --claude # list every model, with its on/off state
|
|
68
|
+
npx @workweave/router models --codex # same, for a Codex install
|
|
69
|
+
npx @workweave/router models disable gpt-5.6 --claude # take one out of rotation
|
|
70
|
+
npx @workweave/router models enable gpt-5.6 --claude # put it back
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Inside Claude Code that's `/router-models` (alias `/models`). Editing needs a
|
|
74
|
+
router that serves the model-selection API; against the Weave-hosted router the
|
|
75
|
+
list still prints and points you at the dashboard, where model selection is an
|
|
76
|
+
organization-wide setting.
|
|
77
|
+
|
|
57
78
|
Uninstall:
|
|
58
79
|
|
|
59
80
|
```bash
|
|
@@ -82,18 +103,24 @@ Four install targets:
|
|
|
82
103
|
- **Codex** (`--codex`) — patches `~/.codex/config.toml` (or
|
|
83
104
|
`<repo>/.codex/config.toml`) with a managed `[model_providers.weave]`
|
|
84
105
|
block plus `model_provider = "weave"`. The provider preserves the existing
|
|
85
|
-
ChatGPT OAuth login.
|
|
86
|
-
|
|
87
|
-
their router's configured default because its HMM sidecar is optional. HMM or forced
|
|
106
|
+
ChatGPT OAuth login. No install pins `X-Weave-Router-Strategy`; every
|
|
107
|
+
endpoint keeps its router's configured default. HMM or forced
|
|
88
108
|
`gpt-5.6-sol`, `gpt-5.6-terra`, and `gpt-5.6-luna` turns use that plan;
|
|
89
109
|
every other selected model uses its WorkWeave deployment or BYOK credential.
|
|
90
110
|
The block lives between begin/end markers
|
|
91
111
|
so re-running the installer rewrites it cleanly and `--uninstall --codex`
|
|
92
112
|
removes it without touching the rest of your config. Codex does not load
|
|
93
|
-
third-party slash-command files;
|
|
94
|
-
|
|
95
|
-
`$
|
|
96
|
-
|
|
113
|
+
third-party slash-command files; the installer provides native skills
|
|
114
|
+
`$force-model` (`$fm`), `$unforce-model` (`$ufm`), and `$router-feedback`
|
|
115
|
+
(`$rf`), each of which execs a local `scripts/emit.sh` that prints the same
|
|
116
|
+
leading-space directive Claude Code uses (for example,
|
|
117
|
+
` /force-model gpt-5.6-terra`) — you can also type that form directly. It
|
|
118
|
+
also installs `$router-status`, `$router-off`, `$router-on`, and
|
|
119
|
+
`$router-models`, which call this installer's own verbs, plus a
|
|
120
|
+
`$disable-routing` skill that returns the next Codex session to the default
|
|
121
|
+
provider without logging out or deleting the router configuration. The
|
|
122
|
+
managed lifecycle hooks also keep the latest routed model in the terminal
|
|
123
|
+
title and emit a compact status message when the router reports a new route.
|
|
97
124
|
- **opencode** (`--opencode`) — merges a `provider.weave` entry (backed by
|
|
98
125
|
opencode's built-in `@ai-sdk/anthropic` provider) into
|
|
99
126
|
`~/.config/opencode/opencode.json` (or `<repo>/opencode.json` with
|
|
@@ -115,8 +142,7 @@ for the full reference.
|
|
|
115
142
|
|
|
116
143
|
- Node ≥ 18 (ships with `npx`)
|
|
117
144
|
- `bash` on PATH (macOS / Linux native; Windows needs Git Bash or WSL)
|
|
118
|
-
- `jq` on PATH — used by the Claude Code status line and the opencode/pi JSON
|
|
119
|
-
merges. Not required for the Codex path.
|
|
145
|
+
- `jq` on PATH — used by the Claude Code status line, the Codex lifecycle helper, and the opencode/pi JSON merges.
|
|
120
146
|
|
|
121
147
|
## Why npx
|
|
122
148
|
|
package/cc-statusline.sh
CHANGED
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
# Renders:
|
|
17
17
|
# WEAVE ROUTER — claude-sonnet-4-5 ← claude-opus-4-7 · saved $1.23 · 12.4k in / 3.1k out / 45.2k cached
|
|
18
18
|
#
|
|
19
|
-
# Pricing source of truth: router/
|
|
20
|
-
#
|
|
21
|
-
#
|
|
19
|
+
# Pricing source of truth: internal/router/catalog. Input/output prices and
|
|
20
|
+
# cache-read multipliers are generated by cmd/genprices. Cache creation remains
|
|
21
|
+
# at 1.25× input pending TTL-aware pricing.
|
|
22
22
|
|
|
23
23
|
set -euo pipefail
|
|
24
24
|
|
|
@@ -159,6 +159,23 @@ weave_command_tracked_by_git() {
|
|
|
159
159
|
git -C "$(dirname "$1")" ls-files --error-unmatch -- "$1" >/dev/null 2>&1
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
# weave_installed_command_names lists the wrappers this install may refresh.
|
|
163
|
+
# The statusline ships standalone (no registry.sh beside it), so the installed
|
|
164
|
+
# set — itself written from the registry — is the only source of truth here.
|
|
165
|
+
#
|
|
166
|
+
# Files written before ownership markers existed carry none, so matching on the
|
|
167
|
+
# marker alone would freeze every pre-marker install out of refreshes forever.
|
|
168
|
+
# List every wrapper instead and let the baseline comparison below decide: a
|
|
169
|
+
# file is replaced only when its bytes still match the last canonical copy, so
|
|
170
|
+
# a user-authored command is never touched whether or not it carries a marker.
|
|
171
|
+
weave_installed_command_names() {
|
|
172
|
+
local dir="$1" file
|
|
173
|
+
for file in "$dir"/*.md; do
|
|
174
|
+
[ -f "$file" ] || continue
|
|
175
|
+
printf '%s\n' "$(basename "$file" .md)"
|
|
176
|
+
done
|
|
177
|
+
}
|
|
178
|
+
|
|
162
179
|
# weave_render_command prints $1 with the installer's {{SCOPE}} placeholder
|
|
163
180
|
# replaced by $2, matching how install_slash_commands writes the same file.
|
|
164
181
|
# Trailing newlines are stripped on both sides of every comparison below.
|
|
@@ -228,8 +245,7 @@ weave_sync_commands() {
|
|
|
228
245
|
# Detach stdin (CC pipes JSON to us) so curl can't consume it, and silence
|
|
229
246
|
# everything so no output leaks into the statusline.
|
|
230
247
|
exec </dev/null
|
|
231
|
-
|
|
232
|
-
router-off router-on router-status router-session; do
|
|
248
|
+
while IFS= read -r name; do
|
|
233
249
|
installed="$cmd_dir/$name.md"
|
|
234
250
|
# Only ever refresh a wrapper that is already installed: a missing one
|
|
235
251
|
# was uninstalled or deliberately deleted, and resurrecting it would be
|
|
@@ -258,10 +274,10 @@ weave_sync_commands() {
|
|
|
258
274
|
if [ -f "$prev" ]; then
|
|
259
275
|
new_body="$(weave_render_command "$raw" "$scope_args")"
|
|
260
276
|
prev_body="$(weave_render_command "$prev" "$scope_args")"
|
|
261
|
-
installed_body="$(cat "$installed" 2>/dev/null)" || installed_body=""
|
|
277
|
+
installed_body="$(cat "$installed" 2>/dev/null | sed '/^<!-- weave-router managed command: .* -->$/d')" || installed_body=""
|
|
262
278
|
if [ "$prev_body" = "$installed_body" ] && [ "$new_body" != "$installed_body" ]; then
|
|
263
279
|
tmp="$installed.tmp.$$"
|
|
264
|
-
if printf '%s\n' "$new_body" >"$tmp" 2>/dev/null; then
|
|
280
|
+
if printf '%s\n<!-- weave-router managed command: %s -->' "$new_body" "$name" >"$tmp" 2>/dev/null; then
|
|
265
281
|
mv "$tmp" "$installed" 2>/dev/null || rm -f "$tmp"
|
|
266
282
|
else
|
|
267
283
|
rm -f "$tmp"
|
|
@@ -269,13 +285,152 @@ weave_sync_commands() {
|
|
|
269
285
|
fi
|
|
270
286
|
fi
|
|
271
287
|
mv "$raw" "$prev" 2>/dev/null || rm -f "$raw"
|
|
272
|
-
done
|
|
288
|
+
done <<EOF
|
|
289
|
+
$(weave_installed_command_names "$cmd_dir")
|
|
290
|
+
EOF
|
|
273
291
|
) >/dev/null 2>&1 &
|
|
274
292
|
disown 2>/dev/null || true
|
|
275
293
|
return 0
|
|
276
294
|
}
|
|
277
295
|
weave_sync_commands 2>/dev/null || true
|
|
278
296
|
|
|
297
|
+
# ---------- org "hide terminal surfaces" gate ----------
|
|
298
|
+
#
|
|
299
|
+
# When the org has hidden the router's terminal surfaces, the statusline
|
|
300
|
+
# renders nothing: this prints no output and exits 0, leaving the slot blank.
|
|
301
|
+
# The setting comes from GET /v1/display-settings with the install's own
|
|
302
|
+
# router key, but the foreground path NEVER touches the network: it decides
|
|
303
|
+
# solely from a per-install cache file (TTL WEAVE_DISPLAY_SETTINGS_TTL_SECONDS,
|
|
304
|
+
# default 1h), and a missing or stale cache fails open — the statusline
|
|
305
|
+
# renders normally. When the cache is missing or stale a detached background
|
|
306
|
+
# refresh re-fetches the setting and rewrites the cache atomically, so the
|
|
307
|
+
# next turn picks up the fresh value; a refresh failure simply leaves the
|
|
308
|
+
# cache stale, which keeps failing open rather than pinning the gate closed.
|
|
309
|
+
#
|
|
310
|
+
# Claude Code runs the statusline every turn, so several invocations can see a
|
|
311
|
+
# stale cache at once. Letting each fetch independently is not safe: the
|
|
312
|
+
# responses can land out of order, and the loser's mv would replace a newer
|
|
313
|
+
# setting with an older one AND stamp it fresh, pinning the gate on a stale
|
|
314
|
+
# value for a full TTL. Refreshes are therefore serialized per cache key on a
|
|
315
|
+
# mkdir mutex (atomic everywhere; flock is absent on macOS), held across both
|
|
316
|
+
# the fetch and the write. A refresh that finds the mutex held exits rather
|
|
317
|
+
# than queueing — the in-flight one is at least as fresh as anything it would
|
|
318
|
+
# fetch, so waiting only to overwrite it is the bug. The one path that is not
|
|
319
|
+
# strictly exclusive is reclaiming a lock whose holder died; see below.
|
|
320
|
+
weave_hidden_gate() {
|
|
321
|
+
command -v curl >/dev/null 2>&1 || return 1
|
|
322
|
+
command -v jq >/dev/null 2>&1 || return 1
|
|
323
|
+
|
|
324
|
+
local cache_dir="${XDG_CACHE_HOME:-$HOME/.cache}/weave-router"
|
|
325
|
+
mkdir -p "$cache_dir" 2>/dev/null || return 1
|
|
326
|
+
local self="${BASH_SOURCE[0]:-$0}"
|
|
327
|
+
local script_slug
|
|
328
|
+
script_slug="$(printf '%s' "$self" | tr -c 'A-Za-z0-9._-' '_')"
|
|
329
|
+
local cache="$cache_dir/display-settings${script_slug}"
|
|
330
|
+
local ttl="${WEAVE_DISPLAY_SETTINGS_TTL_SECONDS:-3600}"
|
|
331
|
+
|
|
332
|
+
local now mtime fresh="false"
|
|
333
|
+
now="$(date +%s 2>/dev/null)" || now=0
|
|
334
|
+
if [ -f "$cache" ]; then
|
|
335
|
+
mtime="$(stat -c %Y "$cache" 2>/dev/null || stat -f %m "$cache" 2>/dev/null)" || mtime=0
|
|
336
|
+
if [ -n "${mtime:-}" ] && [ "$mtime" -gt 0 ] && [ $(( now - mtime )) -lt "$ttl" ]; then
|
|
337
|
+
fresh="true"
|
|
338
|
+
fi
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
# Foreground decision: only a fresh cache hides the surfaces. Anything else
|
|
342
|
+
# (no cache, stale cache, unreadable cache) renders normally so a slow or
|
|
343
|
+
# unreachable router can never stall a turn or wedge the statusline blank.
|
|
344
|
+
if [ "$fresh" = "true" ]; then
|
|
345
|
+
[ "$(cat "$cache" 2>/dev/null)" = "1" ]
|
|
346
|
+
return
|
|
347
|
+
fi
|
|
348
|
+
|
|
349
|
+
# Background refresh for the next invocation. Resolve the router base URL
|
|
350
|
+
# and key inside the subshell from the Claude Code settings the installer
|
|
351
|
+
# wrote: project/--dir installs put both under <base>/.claude alongside
|
|
352
|
+
# this script (key in settings.local.json), while a user-scope install
|
|
353
|
+
# lives under ~/.weave and reads ~/.claude. Resolve relative to the
|
|
354
|
+
# script's own location, falling back to user scope, so a project install
|
|
355
|
+
# never reads (or leaks) the user-scope key. ANTHROPIC_BASE_URL and
|
|
356
|
+
# WEAVE_ROUTER_BASE_URL may also be set in the environment. A file:// base
|
|
357
|
+
# URL is the offline/test seam: curl reads it as the response body
|
|
358
|
+
# directly, so the endpoint path is meaningless for it; real router URLs
|
|
359
|
+
# (https) get /v1/display-settings appended.
|
|
360
|
+
(
|
|
361
|
+
exec </dev/null
|
|
362
|
+
|
|
363
|
+
# Take the per-cache-key mutex, or bail. mkdir is the portable atomic
|
|
364
|
+
# test-and-set. A crashed holder would otherwise block refreshes forever,
|
|
365
|
+
# so a lock older than the fetch timeout is treated as abandoned and
|
|
366
|
+
# reclaimed. Releasing from a trap covers every exit path below.
|
|
367
|
+
lock="$cache.lock"
|
|
368
|
+
if ! mkdir "$lock" 2>/dev/null; then
|
|
369
|
+
lock_mtime="$(stat -c %Y "$lock" 2>/dev/null || stat -f %m "$lock" 2>/dev/null)" || lock_mtime=0
|
|
370
|
+
lock_now="$(date +%s 2>/dev/null)" || lock_now=0
|
|
371
|
+
if [ "${lock_mtime:-0}" -le 0 ] || [ $(( lock_now - lock_mtime )) -le 30 ]; then
|
|
372
|
+
exit 0
|
|
373
|
+
fi
|
|
374
|
+
# Reclaiming an abandoned lock must not be delete-then-recreate. With
|
|
375
|
+
# `rm -rf` + `mkdir`, refreshers that all see the same stale lock each
|
|
376
|
+
# delete the next one's freshly created directory, so many end up holding
|
|
377
|
+
# it at once (measured at 50 claimants: 4-11 concurrent holders) and their
|
|
378
|
+
# writes can land out of order. Renaming is atomic, so only one racer can
|
|
379
|
+
# move a given directory aside and the losers exit instead of clobbering
|
|
380
|
+
# the winner (same measurement: 1-2). It is not a perfect mutex — a
|
|
381
|
+
# straggler can still reclaim the new lock a winner just created, since
|
|
382
|
+
# nothing distinguishes it from the stale one — but real refreshes arrive
|
|
383
|
+
# one per turn rather than 50 at once, and the staleness threshold below
|
|
384
|
+
# is what bounds the rest.
|
|
385
|
+
dead="$lock.dead.$$"
|
|
386
|
+
mv "$lock" "$dead" 2>/dev/null || exit 0
|
|
387
|
+
rm -rf "$dead" 2>/dev/null
|
|
388
|
+
mkdir "$lock" 2>/dev/null || exit 0
|
|
389
|
+
fi
|
|
390
|
+
trap 'rmdir "$lock" 2>/dev/null' EXIT
|
|
391
|
+
|
|
392
|
+
self_dir="$(cd "$(dirname "$self")" 2>/dev/null && pwd)"
|
|
393
|
+
settings_base="$HOME"
|
|
394
|
+
case "$self_dir" in
|
|
395
|
+
*/.claude) settings_base="${self_dir%/.claude}" ;;
|
|
396
|
+
esac
|
|
397
|
+
settings="$settings_base/.claude/settings.json"
|
|
398
|
+
local_settings="$settings_base/.claude/settings.local.json"
|
|
399
|
+
base_url="${WEAVE_ROUTER_BASE_URL:-${ANTHROPIC_BASE_URL:-}}"
|
|
400
|
+
key="${WEAVE_ROUTER_KEY:-}"
|
|
401
|
+
if [ -z "$key" ] && [ -f "$settings" ]; then
|
|
402
|
+
key="$(jq -r '.env.ANTHROPIC_CUSTOM_HEADERS // "" | split("\n")[] | select(startswith("X-Weave-Router-Key:")) | sub("^X-Weave-Router-Key:[[:space:]]*";"")' "$settings" 2>/dev/null | head -n1)"
|
|
403
|
+
fi
|
|
404
|
+
if [ -z "$key" ] && [ -f "$local_settings" ]; then
|
|
405
|
+
key="$(jq -r '.env.ANTHROPIC_CUSTOM_HEADERS // "" | split("\n")[] | select(startswith("X-Weave-Router-Key:")) | sub("^X-Weave-Router-Key:[[:space:]]*";"")' "$local_settings" 2>/dev/null | head -n1)"
|
|
406
|
+
fi
|
|
407
|
+
if [ -z "$base_url" ] && [ -f "$settings" ]; then
|
|
408
|
+
base_url="$(jq -r '.env.ANTHROPIC_BASE_URL // empty' "$settings" 2>/dev/null)"
|
|
409
|
+
fi
|
|
410
|
+
[ -n "$base_url" ] && [ -n "$key" ] || exit 0
|
|
411
|
+
url="${base_url%/}"
|
|
412
|
+
case "$url" in
|
|
413
|
+
file://*) ;;
|
|
414
|
+
*) url="$url/v1/display-settings" ;;
|
|
415
|
+
esac
|
|
416
|
+
body="$(curl -fsS --max-time 5 -H "X-Weave-Router-Key: $key" "$url" 2>/dev/null)" || exit 0
|
|
417
|
+
hidden="$(printf '%s' "$body" | jq -r '.hide_terminal_surfaces // false' 2>/dev/null)"
|
|
418
|
+
tmp="$cache.tmp.$$"
|
|
419
|
+
if [ "$hidden" = "true" ]; then
|
|
420
|
+
printf '1' >"$tmp" 2>/dev/null && mv "$tmp" "$cache" 2>/dev/null
|
|
421
|
+
else
|
|
422
|
+
printf '0' >"$tmp" 2>/dev/null && mv "$tmp" "$cache" 2>/dev/null
|
|
423
|
+
fi
|
|
424
|
+
rm -f "$tmp" 2>/dev/null
|
|
425
|
+
) >/dev/null 2>&1 &
|
|
426
|
+
disown 2>/dev/null || true
|
|
427
|
+
return 1
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
if weave_hidden_gate </dev/null; then
|
|
431
|
+
exit 0
|
|
432
|
+
fi
|
|
433
|
+
|
|
279
434
|
input="$(cat)"
|
|
280
435
|
transcript_path="$(printf '%s' "$input" | jq -r '.transcript_path // empty')"
|
|
281
436
|
# Prefer model.id over display_name: pricing keys + the routed model id in
|
|
@@ -316,6 +471,7 @@ prices='{
|
|
|
316
471
|
"claude-sonnet-5": 0.003,
|
|
317
472
|
"deepseek/deepseek-v4-flash": 0.0001134,
|
|
318
473
|
"deepseek/deepseek-v4-pro": 0.00174,
|
|
474
|
+
"deepseek/deepseek-v4-pro-0813": 0.00174,
|
|
319
475
|
"gemini-2.0-flash": 0.0001,
|
|
320
476
|
"gemini-2.0-flash-lite": 0.000075,
|
|
321
477
|
"gemini-2.5-flash": 0.0003,
|
|
@@ -328,6 +484,7 @@ prices='{
|
|
|
328
484
|
"gemini-3.5-flash": 0.0015,
|
|
329
485
|
"gemini-3.5-flash-lite": 0.0003,
|
|
330
486
|
"gemini-3.6-flash": 0.0015,
|
|
487
|
+
"gemini-3.7-flash": 0.0015,
|
|
331
488
|
"google/gemma-4-26b-a4b-it": 0.00015,
|
|
332
489
|
"gpt-4.1": 0.002,
|
|
333
490
|
"gpt-4.1-mini": 0.0004,
|
|
@@ -347,7 +504,9 @@ prices='{
|
|
|
347
504
|
"gpt-5.5-nano": 0.00015,
|
|
348
505
|
"gpt-5.5-pro": 0.03,
|
|
349
506
|
"gpt-5.6-luna": 0.001,
|
|
507
|
+
"gpt-5.6-luna-pro": 0.001,
|
|
350
508
|
"gpt-5.6-sol": 0.005,
|
|
509
|
+
"gpt-5.6-sol-pro": 0.005,
|
|
351
510
|
"gpt-5.6-terra": 0.0025,
|
|
352
511
|
"grok-4.5": 0.002,
|
|
353
512
|
"grok-4.6": 0.002,
|
|
@@ -370,7 +529,9 @@ prices='{
|
|
|
370
529
|
"xiaomi/mimo-v2.5-pro": 0.001,
|
|
371
530
|
"z-ai/glm-5": 0.001,
|
|
372
531
|
"z-ai/glm-5.1": 0.0014,
|
|
373
|
-
"z-ai/glm-5.2": 0.0014
|
|
532
|
+
"z-ai/glm-5.2": 0.0014,
|
|
533
|
+
"z-ai/glm-5.3": 0.0014,
|
|
534
|
+
"z-ai/glm-5.3-flash": 0.00015
|
|
374
535
|
},
|
|
375
536
|
"output": {
|
|
376
537
|
"claude-fable-5": 0.05,
|
|
@@ -387,6 +548,7 @@ prices='{
|
|
|
387
548
|
"claude-sonnet-5": 0.015,
|
|
388
549
|
"deepseek/deepseek-v4-flash": 0.0002791,
|
|
389
550
|
"deepseek/deepseek-v4-pro": 0.00348,
|
|
551
|
+
"deepseek/deepseek-v4-pro-0813": 0.00348,
|
|
390
552
|
"gemini-2.0-flash": 0.0004,
|
|
391
553
|
"gemini-2.0-flash-lite": 0.0003,
|
|
392
554
|
"gemini-2.5-flash": 0.0012,
|
|
@@ -399,6 +561,7 @@ prices='{
|
|
|
399
561
|
"gemini-3.5-flash": 0.009,
|
|
400
562
|
"gemini-3.5-flash-lite": 0.0025,
|
|
401
563
|
"gemini-3.6-flash": 0.0075,
|
|
564
|
+
"gemini-3.7-flash": 0.0075,
|
|
402
565
|
"google/gemma-4-26b-a4b-it": 0.0006,
|
|
403
566
|
"gpt-4.1": 0.008,
|
|
404
567
|
"gpt-4.1-mini": 0.0016,
|
|
@@ -418,7 +581,9 @@ prices='{
|
|
|
418
581
|
"gpt-5.5-nano": 0.0006,
|
|
419
582
|
"gpt-5.5-pro": 0.18,
|
|
420
583
|
"gpt-5.6-luna": 0.006,
|
|
584
|
+
"gpt-5.6-luna-pro": 0.006,
|
|
421
585
|
"gpt-5.6-sol": 0.03,
|
|
586
|
+
"gpt-5.6-sol-pro": 0.03,
|
|
422
587
|
"gpt-5.6-terra": 0.015,
|
|
423
588
|
"grok-4.5": 0.006,
|
|
424
589
|
"grok-4.6": 0.006,
|
|
@@ -441,7 +606,86 @@ prices='{
|
|
|
441
606
|
"xiaomi/mimo-v2.5-pro": 0.003,
|
|
442
607
|
"z-ai/glm-5": 0.0032,
|
|
443
608
|
"z-ai/glm-5.1": 0.0044,
|
|
444
|
-
"z-ai/glm-5.2": 0.0044
|
|
609
|
+
"z-ai/glm-5.2": 0.0044,
|
|
610
|
+
"z-ai/glm-5.3": 0.0044,
|
|
611
|
+
"z-ai/glm-5.3-flash": 0.0005
|
|
612
|
+
},
|
|
613
|
+
"cache_read": {
|
|
614
|
+
"claude-fable-5": 0.1,
|
|
615
|
+
"claude-haiku-4-5": 0.1,
|
|
616
|
+
"claude-opus-4-0": 0.1,
|
|
617
|
+
"claude-opus-4-1": 0.1,
|
|
618
|
+
"claude-opus-4-5": 0.1,
|
|
619
|
+
"claude-opus-4-6": 0.1,
|
|
620
|
+
"claude-opus-4-7": 0.1,
|
|
621
|
+
"claude-opus-4-8": 0.1,
|
|
622
|
+
"claude-opus-5": 0.1,
|
|
623
|
+
"claude-sonnet-4-5": 0.1,
|
|
624
|
+
"claude-sonnet-4-6": 0.1,
|
|
625
|
+
"claude-sonnet-5": 0.1,
|
|
626
|
+
"deepseek/deepseek-v4-flash": 0.2,
|
|
627
|
+
"deepseek/deepseek-v4-pro": 0.11494252873563218,
|
|
628
|
+
"deepseek/deepseek-v4-pro-0813": 0.11494252873563218,
|
|
629
|
+
"gemini-2.0-flash": 0.25,
|
|
630
|
+
"gemini-2.0-flash-lite": 0.25,
|
|
631
|
+
"gemini-2.5-flash": 0.1,
|
|
632
|
+
"gemini-2.5-flash-lite": 0.1,
|
|
633
|
+
"gemini-2.5-pro": 0.1,
|
|
634
|
+
"gemini-3-flash-preview": 0.1,
|
|
635
|
+
"gemini-3-pro-preview": 0.1,
|
|
636
|
+
"gemini-3.1-flash-lite-preview": 0.1,
|
|
637
|
+
"gemini-3.1-pro-preview": 0.1,
|
|
638
|
+
"gemini-3.5-flash": 0.1,
|
|
639
|
+
"gemini-3.5-flash-lite": 0.1,
|
|
640
|
+
"gemini-3.6-flash": 0.1,
|
|
641
|
+
"gemini-3.7-flash": 0.1,
|
|
642
|
+
"google/gemma-4-26b-a4b-it": 0.1,
|
|
643
|
+
"gpt-4.1": 0.25,
|
|
644
|
+
"gpt-4.1-mini": 0.25,
|
|
645
|
+
"gpt-4.1-nano": 0.25,
|
|
646
|
+
"gpt-4o": 0.5,
|
|
647
|
+
"gpt-4o-mini": 0.5,
|
|
648
|
+
"gpt-5": 0.1,
|
|
649
|
+
"gpt-5-chat": 0.1,
|
|
650
|
+
"gpt-5-mini": 0.1,
|
|
651
|
+
"gpt-5-nano": 0.1,
|
|
652
|
+
"gpt-5.4": 0.1,
|
|
653
|
+
"gpt-5.4-mini": 0.1,
|
|
654
|
+
"gpt-5.4-nano": 0.1,
|
|
655
|
+
"gpt-5.4-pro": 1,
|
|
656
|
+
"gpt-5.5": 0.1,
|
|
657
|
+
"gpt-5.5-mini": 0.1,
|
|
658
|
+
"gpt-5.5-nano": 0.1,
|
|
659
|
+
"gpt-5.5-pro": 1,
|
|
660
|
+
"gpt-5.6-luna": 0.1,
|
|
661
|
+
"gpt-5.6-luna-pro": 0.1,
|
|
662
|
+
"gpt-5.6-sol": 0.1,
|
|
663
|
+
"gpt-5.6-sol-pro": 0.1,
|
|
664
|
+
"gpt-5.6-terra": 0.1,
|
|
665
|
+
"grok-4.5": 0.25,
|
|
666
|
+
"grok-4.6": 0.25,
|
|
667
|
+
"minimax/minimax-m2.7": 0.2,
|
|
668
|
+
"minimax/minimax-m3": 0.2,
|
|
669
|
+
"mistralai/mistral-small-2603": 0.1,
|
|
670
|
+
"moonshotai/kimi-k2.5": 0.5,
|
|
671
|
+
"moonshotai/kimi-k2.6": 0.1684,
|
|
672
|
+
"moonshotai/kimi-k2.7": 0.2,
|
|
673
|
+
"moonshotai/kimi-k3": 0.1,
|
|
674
|
+
"qwen/qwen3-235b-a22b-2507": 0.5,
|
|
675
|
+
"qwen/qwen3-30b-a3b-instruct-2507": 0.1684,
|
|
676
|
+
"qwen/qwen3-coder": 0.1684,
|
|
677
|
+
"qwen/qwen3-coder-next": 0.5,
|
|
678
|
+
"qwen/qwen3-next-80b-a3b-instruct": 0.5,
|
|
679
|
+
"qwen/qwen3.5-flash-02-23": 0.1,
|
|
680
|
+
"qwen/qwen3.6-35b-a3b": 0.1,
|
|
681
|
+
"qwen/qwen3.7-plus": 0.2,
|
|
682
|
+
"qwen/qwen3.8-max": 0.125,
|
|
683
|
+
"xiaomi/mimo-v2.5-pro": 0.1,
|
|
684
|
+
"z-ai/glm-5": 0.2,
|
|
685
|
+
"z-ai/glm-5.1": 0.18571428571428572,
|
|
686
|
+
"z-ai/glm-5.2": 0.18571428571428572,
|
|
687
|
+
"z-ai/glm-5.3": 0.18571428571428572,
|
|
688
|
+
"z-ai/glm-5.3-flash": 0.2
|
|
445
689
|
}
|
|
446
690
|
}'
|
|
447
691
|
# END_GENERATED_PRICES
|
|
@@ -496,6 +740,12 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
496
740
|
# * unrecognized model → "… isn't a recognized model · keeping
|
|
497
741
|
# automatic routing" — a NO-OP: the prior
|
|
498
742
|
# pin, if any, is left untouched
|
|
743
|
+
# * listing (historical) → "… pick a model by id …" / "no models are
|
|
744
|
+
# available to pin …" — also NO-OPs. The
|
|
745
|
+
# router no longer emits these, but a
|
|
746
|
+
# transcript written while it did still
|
|
747
|
+
# carries them, and they must not read as
|
|
748
|
+
# a clear or a live pin loses its [forced]
|
|
499
749
|
# These persist on disk (the ingress stripper only scrubs them from upstream
|
|
500
750
|
# requests). Classify each weave-router turn newest-first, skip the no-op
|
|
501
751
|
# "rejected" acks, and let the latest real state change decide: an "applied"
|
|
@@ -509,6 +759,7 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
509
759
|
| ([.message.content[]? | select(.type? == "text") | .text] | join(" ") | gsub("[\n\r]"; " ")) as $t
|
|
510
760
|
| if ($t | test("force-model applied:")) then "APPLIED " + ($t | capture("force-model applied: (?<m>[^ ]+)").m)
|
|
511
761
|
elif ($t | test("isn.t a recognized model")) then "REJECTED"
|
|
762
|
+
elif ($t | test("pick a model by id|no models are available to pin")) then "REJECTED"
|
|
512
763
|
else "CLEARED" end' 2>/dev/null \
|
|
513
764
|
| grep -m1 -v '^REJECTED$' || true)"
|
|
514
765
|
if [[ "$force_state" == APPLIED\ * ]]; then
|
|
@@ -519,12 +770,9 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
519
770
|
# Compute a session running total: savings across every assistant turn
|
|
520
771
|
# whose marker reports a requested ≠ routed swap, plus cumulative token
|
|
521
772
|
# counts across every assistant turn (rerouted or not — total work the
|
|
522
|
-
# session has done). cache_creation is priced at 1.25× input
|
|
523
|
-
#
|
|
524
|
-
#
|
|
525
|
-
# in the savings comparison: both costs apply the same 0.1× weight to
|
|
526
|
-
# cache_read_input_tokens, so the delta reflects the model-price
|
|
527
|
-
# difference on the cached portion as well.
|
|
773
|
+
# session has done). cache_creation is priced at 1.25× input; cache_read
|
|
774
|
+
# uses each model's generated catalog multiplier. Both are no-ops when the
|
|
775
|
+
# provider does not return those fields.
|
|
528
776
|
#
|
|
529
777
|
# The marker regex tolerates the optional "(<provider>)" segment and a
|
|
530
778
|
# `[1m]` / `-YYYYMMDD` suffix on either model name so transcripts written
|
|
@@ -557,14 +805,17 @@ if [[ -n "$transcript_path" && -f "$transcript_path" ]]; then
|
|
|
557
805
|
} as $t |
|
|
558
806
|
(if $requested == "" or $requested == $rm then 0
|
|
559
807
|
else
|
|
560
|
-
($p.input[$rm] // null)
|
|
561
|
-
($p.
|
|
562
|
-
|
|
808
|
+
($p.input[$rm] // null) as $rin | ($p.output[$rm] // null) as $rout |
|
|
809
|
+
($p.cache_read[$rm] // null) as $rcr |
|
|
810
|
+
($p.input[$requested] // null) as $sin | ($p.output[$requested] // null) as $sout |
|
|
811
|
+
($p.cache_read[$requested] // null) as $scr |
|
|
812
|
+
if ($rin == null or $rout == null or $rcr == null or $sin == null or $sout == null or $scr == null) then 0
|
|
563
813
|
else
|
|
564
|
-
(($t.in + 1.25 * $t.cwrt +
|
|
565
|
-
($t.
|
|
566
|
-
($
|
|
567
|
-
($
|
|
814
|
+
(($t.in + 1.25 * $t.cwrt + $rcr * $t.crd) / 1000) as $routed_input_units |
|
|
815
|
+
(($t.in + 1.25 * $t.cwrt + $scr * $t.crd) / 1000) as $requested_input_units |
|
|
816
|
+
($t.out / 1000) as $output_units |
|
|
817
|
+
($routed_input_units * $rin + $output_units * $rout) as $routed_cost |
|
|
818
|
+
($requested_input_units * $sin + $output_units * $sout) as $requested_cost |
|
|
568
819
|
($requested_cost - $routed_cost)
|
|
569
820
|
end
|
|
570
821
|
end) as $savings |
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fm
|
|
3
|
+
description: "Alias for force-model — pin this Codex session to a specific model through the Weave Router."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed fm skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$fm <model-id>`, run this skill's `scripts/emit.sh` using the
|
|
9
|
+
absolute path shown in this skill's `<path>` (its directory + `/scripts/emit.sh`).
|
|
10
|
+
Pass the model id as arguments, quoted as a single argument list.
|
|
11
|
+
Do not send a user message and do not type a Codex slash command.
|
|
12
|
+
|
|
13
|
+
The script prints a leading-space `/force-model` line. The Weave Router reads that
|
|
14
|
+
exec output, pins the session, and continues this turn — so run the script once and
|
|
15
|
+
do not repeat its output as your answer. Then report the router's response.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: force-model
|
|
3
|
+
description: "Pin this Codex session to a specific model through the Weave Router."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed force-model skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$force-model <model-id>` (or `$fm`), run this skill's `scripts/emit.sh` using the
|
|
9
|
+
absolute path shown in this skill's `<path>` (its directory + `/scripts/emit.sh`).
|
|
10
|
+
Pass the model id as arguments, quoted as a single argument list.
|
|
11
|
+
Do not send a user message and do not type a Codex slash command.
|
|
12
|
+
|
|
13
|
+
The script prints a leading-space `/force-model` line. The Weave Router reads that
|
|
14
|
+
exec output, pins the session, and continues this turn — so run the script once and
|
|
15
|
+
do not repeat its output as your answer. Then report the router's response.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Print a leading-space /force-model directive. Codex runs this via exec;
|
|
3
|
+
# the Weave Router intercepts the tool output and pins the session.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
if [ "$#" -eq 0 ] || [ -z "${1:-}" ]; then
|
|
6
|
+
echo "usage: emit.sh <model-id>" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
printf ' /force-model %s\n' "$*"
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rf
|
|
3
|
+
description: "Alias for router-feedback — submit feedback about a Weave Router decision or model performance."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed rf skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$rf <feedback>`, run this skill's `scripts/emit.sh` using the
|
|
9
|
+
absolute path shown in this skill's `<path>` (its directory + `/scripts/emit.sh`).
|
|
10
|
+
Pass the feedback text as arguments, quoted as a single argument list.
|
|
11
|
+
Do not send a user message and do not type a Codex slash command.
|
|
12
|
+
|
|
13
|
+
The script prints a leading-space `/router-feedback` line. The Weave Router reads that
|
|
14
|
+
exec output, records the feedback, and continues this turn — so run the script once and
|
|
15
|
+
do not repeat its output as your answer. Then report the router's response.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-feedback
|
|
3
|
+
description: "Submit feedback about a Weave Router decision or model performance."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed router-feedback skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$router-feedback <feedback>` (or `$rf`), run this skill's `scripts/emit.sh` using the
|
|
9
|
+
absolute path shown in this skill's `<path>` (its directory + `/scripts/emit.sh`).
|
|
10
|
+
Pass the feedback text as arguments, quoted as a single argument list.
|
|
11
|
+
Do not send a user message and do not type a Codex slash command.
|
|
12
|
+
|
|
13
|
+
The script prints a leading-space `/router-feedback` line. The Weave Router reads that
|
|
14
|
+
exec output, records the feedback, and continues this turn — so run the script once and
|
|
15
|
+
do not repeat its output as your answer. Then report the router's response.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Print a leading-space /router-feedback directive. Codex runs this via exec;
|
|
3
|
+
# the Weave Router intercepts the tool output and records the feedback.
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
if [ "$#" -eq 0 ] || [ -z "${1:-}" ]; then
|
|
6
|
+
echo "usage: emit.sh <feedback>" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
printf ' /router-feedback %s\n' "$*"
|