@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
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-models
|
|
3
|
+
description: "List the models the Weave Router may route to, and turn them on or off."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed router-models skill -->
|
|
7
|
+
|
|
8
|
+
# Weave Router model selection
|
|
9
|
+
|
|
10
|
+
When the user invokes `$router-models`, show which models this
|
|
11
|
+
installation lets the router pick from, and change that selection when asked.
|
|
12
|
+
This is the same list — and the same stored setting — as the checkboxes on the
|
|
13
|
+
router dashboard's settings page.
|
|
14
|
+
|
|
15
|
+
Start by running:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx --package @workweave/router -y -- weave-router models --codex{{SCOPE}}
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That prints every deployed model grouped by provider, with `[x]` for models the
|
|
22
|
+
router may pick and `[ ]` for models it may not. Present it back as a compact
|
|
23
|
+
checklist in that same `[x]` / `[ ]` form, keeping the provider grouping and the
|
|
24
|
+
exact model ids — the user selects models by id.
|
|
25
|
+
|
|
26
|
+
Then:
|
|
27
|
+
|
|
28
|
+
- If the user named models or providers, work out whether they want them on or
|
|
29
|
+
off from how they phrased it, and apply it with
|
|
30
|
+
`weave-router models enable <id>... --codex{{SCOPE}}` or
|
|
31
|
+
`weave-router models disable <id>... --codex{{SCOPE}}` (add `providers` before
|
|
32
|
+
`enable`/`disable` to switch a whole provider). Several ids can go in one
|
|
33
|
+
call. Then re-run the list and show the result.
|
|
34
|
+
- If they named nothing, stop after the list and ask which ones to change.
|
|
35
|
+
Never change anything they did not ask for.
|
|
36
|
+
|
|
37
|
+
Other things they might ask for:
|
|
38
|
+
|
|
39
|
+
- Rank models by preference:
|
|
40
|
+
`weave-router models prefer <id> <id>... --codex{{SCOPE}}` (order matters), or
|
|
41
|
+
`weave-router models prefer clear --codex{{SCOPE}}` to drop the ranking.
|
|
42
|
+
- Providers only: `weave-router models providers --codex{{SCOPE}}`.
|
|
43
|
+
|
|
44
|
+
If the command reports that this router doesn't expose model selection, that's a
|
|
45
|
+
Weave-hosted router: model selection belongs to the whole organization there, so
|
|
46
|
+
tell the user to change it at https://router.workweave.ai/dashboard/settings —
|
|
47
|
+
don't try to work around it. Its listing carries no on/off state, so present it
|
|
48
|
+
as a plain list; don't infer which models are enabled.
|
|
49
|
+
|
|
50
|
+
Disabling a model takes effect on the router's next routing decision; no restart
|
|
51
|
+
is needed.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-off
|
|
3
|
+
description: "Route Codex to its default provider again (turn the Weave Router off)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed router-off skill -->
|
|
7
|
+
|
|
8
|
+
# Turn Weave routing off
|
|
9
|
+
|
|
10
|
+
When the user invokes `$router-off`, switch this Codex installation off the
|
|
11
|
+
Weave Router without logging out or deleting its router configuration.
|
|
12
|
+
|
|
13
|
+
Run exactly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --package @workweave/router -y -- weave-router off --codex{{SCOPE}}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then report the result and tell the user the change takes effect on their next
|
|
20
|
+
`codex` launch — Codex reads its provider config at startup, so the current
|
|
21
|
+
session keeps routing as it already was. It can be reversed with
|
|
22
|
+
`$router-on`. Do not uninstall the router or alter any other Codex settings.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-on
|
|
3
|
+
description: "Route Codex through the Weave Router again (turn it back on)."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed router-on skill -->
|
|
7
|
+
|
|
8
|
+
# Turn Weave routing on
|
|
9
|
+
|
|
10
|
+
When the user invokes `$router-on`, switch this Codex installation back onto
|
|
11
|
+
the Weave Router using the managed configuration already on disk.
|
|
12
|
+
|
|
13
|
+
Run exactly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --package @workweave/router -y -- weave-router on --codex{{SCOPE}}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then report the result and tell the user the change takes effect on their next
|
|
20
|
+
`codex` launch — Codex reads its provider config at startup, so the current
|
|
21
|
+
session keeps routing as it already was. Do not re-run the installer or alter
|
|
22
|
+
any other Codex settings.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: router-status
|
|
3
|
+
description: "Show whether Codex is routing through the Weave Router or using its default provider."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed router-status skill -->
|
|
7
|
+
|
|
8
|
+
# Weave Router status
|
|
9
|
+
|
|
10
|
+
When the user invokes `$router-status`, report whether this Codex installation
|
|
11
|
+
routes through the Weave Router.
|
|
12
|
+
|
|
13
|
+
Run exactly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx --package @workweave/router -y -- weave-router status --codex{{SCOPE}}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Then summarize the result in one line. Do not change any configuration.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ufm
|
|
3
|
+
description: "Alias for unforce-model — clear the active Weave Router model pin."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed ufm skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$ufm`, run this skill's `scripts/emit.sh` using the
|
|
9
|
+
absolute path shown in this skill's `<path>` (its directory + `/scripts/emit.sh`).
|
|
10
|
+
Do not send a user message and do not type a Codex slash command.
|
|
11
|
+
|
|
12
|
+
The script prints a leading-space `/unforce-model` line. The Weave Router reads that
|
|
13
|
+
exec output, clears the pin, and continues this turn — so run the script once and
|
|
14
|
+
do not repeat its output as your answer. Then report the router's response.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: unforce-model
|
|
3
|
+
description: "Clear the active Weave Router model pin for this Codex session."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- weave-router managed unforce-model skill -->
|
|
7
|
+
|
|
8
|
+
When the user invokes `$unforce-model` (or `$ufm`), run this skill's `scripts/emit.sh` using the
|
|
9
|
+
absolute path shown in this skill's `<path>` (its directory + `/scripts/emit.sh`).
|
|
10
|
+
Do not send a user message and do not type a Codex slash command.
|
|
11
|
+
|
|
12
|
+
The script prints a leading-space `/unforce-model` line. The Weave Router reads that
|
|
13
|
+
exec output, clears the pin, and continues this turn — so run the script once and
|
|
14
|
+
do not repeat its output as your answer. Then report the router's response.
|
package/codex-status.sh
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# <!-- weave-router managed codex status -->
|
|
3
|
+
#
|
|
4
|
+
# Codex lifecycle hook for the Weave Router. Codex passes a JSON object on
|
|
5
|
+
# stdin; the Stop hook includes the last assistant message, which carries the
|
|
6
|
+
# router's routed-model marker when the selected model changes. The helper
|
|
7
|
+
# keeps the last known routed model per session and reflects it in the terminal
|
|
8
|
+
# title, so the active router remains visible between turns without injecting
|
|
9
|
+
# another message into the conversation.
|
|
10
|
+
#
|
|
11
|
+
# Savings come from the router, not from local arithmetic. Codex records its
|
|
12
|
+
# own requested model on every turn and never the served one, so the per-turn
|
|
13
|
+
# pricing the Claude Code statusline does cannot be reproduced here — it would
|
|
14
|
+
# price both sides of the comparison at the same model and report zero. The
|
|
15
|
+
# router already sums the real thing per session, so the hook fetches
|
|
16
|
+
# GET <base>/v1/sessions/<id>/cost and renders what it returns. The fetch runs
|
|
17
|
+
# in a detached subshell writing a cache the NEXT turn reads, so no turn ever
|
|
18
|
+
# blocks on the network, and every failure path leaves the title model-only.
|
|
19
|
+
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
state_root="${XDG_CACHE_HOME:-$HOME/.cache}/weave-router/codex"
|
|
23
|
+
helper_dir="$(cd "$(dirname "$0")" 2>/dev/null && pwd -P)"
|
|
24
|
+
disabled_marker="$helper_dir/.weave-router-disabled"
|
|
25
|
+
router_badge_sentinel=$''
|
|
26
|
+
# Must stay verbatim in sync with install.sh / uninstall.sh: the endpoint read
|
|
27
|
+
# below is scoped to this block so a key-shaped string elsewhere in the user's
|
|
28
|
+
# config.toml is never adopted.
|
|
29
|
+
codex_begin_marker="# >>> weave-router managed (do not edit between markers) >>>"
|
|
30
|
+
codex_end_marker="# <<< weave-router managed <<<"
|
|
31
|
+
|
|
32
|
+
emit_title() {
|
|
33
|
+
local title="$1"
|
|
34
|
+
if [ -n "${WEAVE_CODEX_STATUS_TITLE_FILE:-}" ]; then
|
|
35
|
+
printf '%s\n' "$title" >"$WEAVE_CODEX_STATUS_TITLE_FILE"
|
|
36
|
+
elif [ -w /dev/tty ]; then
|
|
37
|
+
printf '\033]0;%s\007' "$title" >/dev/tty
|
|
38
|
+
fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
safe_session_id() {
|
|
42
|
+
local id="$1"
|
|
43
|
+
case "$id" in
|
|
44
|
+
''|*[!A-Za-z0-9._-]*) return 1 ;;
|
|
45
|
+
esac
|
|
46
|
+
[ "${#id}" -le 128 ] || return 1
|
|
47
|
+
printf '%s' "$id"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
safe_display_value() {
|
|
51
|
+
printf '%s' "$1" | sed 's/[^A-Za-z0-9._:\/-]//g' | cut -c1-128
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
state_file_for() {
|
|
55
|
+
local id
|
|
56
|
+
id="$(safe_session_id "$1")" || return 1
|
|
57
|
+
printf '%s/%s.state' "$state_root" "$id"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
read_state() {
|
|
61
|
+
local file="$1" key value
|
|
62
|
+
[ -f "$file" ] || return 0
|
|
63
|
+
while IFS='=' read -r key value; do
|
|
64
|
+
case "$key" in
|
|
65
|
+
routed_model) routed_model="$value" ;;
|
|
66
|
+
esac
|
|
67
|
+
done <"$file"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
write_state() {
|
|
71
|
+
local file="$1" tmp
|
|
72
|
+
mkdir -p "$state_root"
|
|
73
|
+
chmod 700 "$state_root"
|
|
74
|
+
tmp="$(mktemp "$state_root/.state.XXXXXX")"
|
|
75
|
+
printf 'requested_model=%s\nrouted_model=%s\n' "$requested_model" "$routed_model" >"$tmp"
|
|
76
|
+
chmod 600 "$tmp"
|
|
77
|
+
mv "$tmp" "$file"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
cost_file_for() {
|
|
81
|
+
local id
|
|
82
|
+
id="$(safe_session_id "$1")" || return 1
|
|
83
|
+
printf '%s/%s.cost' "$state_root" "$id"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Reads the router base URL and key out of the Codex config this install owns.
|
|
87
|
+
# Resolved from the helper's own location first so a project-scope install never
|
|
88
|
+
# reads (or leaks) the user-scope key: the project helper lives in the same
|
|
89
|
+
# .codex directory as its config, while the user-scope helper sits in ~/.weave
|
|
90
|
+
# and reads ~/.codex. Values are scoped to the managed block so a key-shaped
|
|
91
|
+
# string the user wrote elsewhere in the file is never adopted. awk, not a TOML
|
|
92
|
+
# parser, because the Codex target deliberately does not require jq for config
|
|
93
|
+
# reads.
|
|
94
|
+
read_codex_endpoint() {
|
|
95
|
+
local config=""
|
|
96
|
+
# Project/custom installs name the helper weave-status.sh and keep it next
|
|
97
|
+
# to their config.toml. Falling through to ~/.codex would fetch another
|
|
98
|
+
# installation's session cost with that key. The user-scope helper
|
|
99
|
+
# (codex-status.sh in ~/.weave) has no adjacent config and owns HOME.
|
|
100
|
+
if [ -f "$helper_dir/config.toml" ]; then
|
|
101
|
+
config="$helper_dir/config.toml"
|
|
102
|
+
elif [ "$(basename "$0")" != "weave-status.sh" ] && [ -f "$HOME/.codex/config.toml" ]; then
|
|
103
|
+
config="$HOME/.codex/config.toml"
|
|
104
|
+
fi
|
|
105
|
+
[ -n "$config" ] || return 0
|
|
106
|
+
awk -v begin="$codex_begin_marker" -v end="$codex_end_marker" '
|
|
107
|
+
$0 == begin { inblk = 1; next }
|
|
108
|
+
$0 == end { inblk = 0; next }
|
|
109
|
+
!inblk { next }
|
|
110
|
+
match($0, /base_url[[:space:]]*=[[:space:]]*"[^"]*"/) {
|
|
111
|
+
v = substr($0, RSTART, RLENGTH)
|
|
112
|
+
sub(/^.*=[[:space:]]*"/, "", v); sub(/"$/, "", v)
|
|
113
|
+
url = v
|
|
114
|
+
}
|
|
115
|
+
match($0, /"X-Weave-Router-Key"[[:space:]]*=[[:space:]]*"[^"]*"/) {
|
|
116
|
+
v = substr($0, RSTART, RLENGTH)
|
|
117
|
+
sub(/^.*=[[:space:]]*"/, "", v); sub(/"$/, "", v)
|
|
118
|
+
key = v
|
|
119
|
+
}
|
|
120
|
+
END { if (url != "" && key != "") printf "%s\n%s\n", url, key }
|
|
121
|
+
' "$config" 2>/dev/null || true
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
# Kicks off a detached fetch of this session's committed cost. The result lands
|
|
125
|
+
# in a cache the next turn reads; this turn renders whatever is already there.
|
|
126
|
+
# Fire-and-forget on purpose — a slow or unreachable router must never stall a
|
|
127
|
+
# Codex turn, and every failure simply leaves the previous cache in place.
|
|
128
|
+
refresh_session_cost() {
|
|
129
|
+
local id="$1" file="$2"
|
|
130
|
+
[ "${WEAVE_CODEX_STATUS_SAVINGS:-1}" = "0" ] && return 0
|
|
131
|
+
command -v curl >/dev/null 2>&1 || return 0
|
|
132
|
+
|
|
133
|
+
local endpoint base_url key
|
|
134
|
+
endpoint="$(read_codex_endpoint)" || return 0
|
|
135
|
+
base_url="$(printf '%s' "$endpoint" | sed -n 1p)"
|
|
136
|
+
key="$(printf '%s' "$endpoint" | sed -n 2p)"
|
|
137
|
+
[ -n "$base_url" ] && [ -n "$key" ] || return 0
|
|
138
|
+
|
|
139
|
+
mkdir -p "$state_root" 2>/dev/null || return 0
|
|
140
|
+
chmod 700 "$state_root" 2>/dev/null || true
|
|
141
|
+
|
|
142
|
+
(
|
|
143
|
+
exec </dev/null
|
|
144
|
+
# mkdir is the portable atomic test-and-set. A crashed holder would block
|
|
145
|
+
# refreshes forever, so a lock older than the fetch timeout is reclaimed.
|
|
146
|
+
lock="$file.lock"
|
|
147
|
+
if ! mkdir "$lock" 2>/dev/null; then
|
|
148
|
+
lock_mtime="$(stat -c %Y "$lock" 2>/dev/null || stat -f %m "$lock" 2>/dev/null)" || lock_mtime=0
|
|
149
|
+
lock_now="$(date +%s 2>/dev/null)" || lock_now=0
|
|
150
|
+
if [ "${lock_mtime:-0}" -le 0 ] || [ $(( lock_now - lock_mtime )) -le 30 ]; then
|
|
151
|
+
exit 0
|
|
152
|
+
fi
|
|
153
|
+
rm -rf "$lock" 2>/dev/null
|
|
154
|
+
mkdir "$lock" 2>/dev/null || exit 0
|
|
155
|
+
fi
|
|
156
|
+
trap 'rmdir "$lock" 2>/dev/null' EXIT
|
|
157
|
+
|
|
158
|
+
# A file:// base is the offline/test seam: curl reads it as the response
|
|
159
|
+
# body directly, so the endpoint path is meaningless for it.
|
|
160
|
+
url="${base_url%/}"
|
|
161
|
+
case "$url" in
|
|
162
|
+
file://*) ;;
|
|
163
|
+
*) url="${url%/v1}/v1/sessions/$id/cost" ;;
|
|
164
|
+
esac
|
|
165
|
+
body="$(curl -fsS --max-time 5 -H "X-Weave-Router-Key: $key" "$url" 2>/dev/null)" || exit 0
|
|
166
|
+
# savings_usd is the router's own (requested - actual). A body without it
|
|
167
|
+
# (404, error envelope, older router) writes nothing and leaves the cache.
|
|
168
|
+
savings="$(printf '%s' "$body" | jq -r '.savings_usd // empty' 2>/dev/null)" || exit 0
|
|
169
|
+
case "$savings" in
|
|
170
|
+
''|*[!0-9.eE+-]*) exit 0 ;;
|
|
171
|
+
esac
|
|
172
|
+
tmp="$file.tmp.$$"
|
|
173
|
+
mkdir -p "$(dirname "$file")" 2>/dev/null
|
|
174
|
+
if printf '%s' "$savings" >"$tmp" 2>/dev/null; then
|
|
175
|
+
chmod 600 "$tmp" 2>/dev/null
|
|
176
|
+
mv "$tmp" "$file" 2>/dev/null
|
|
177
|
+
fi
|
|
178
|
+
rm -f "$tmp" 2>/dev/null
|
|
179
|
+
) >/dev/null 2>&1 &
|
|
180
|
+
disown 2>/dev/null || true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Renders the cached savings as a display clause, or nothing. Values below a
|
|
184
|
+
# cent read as "<$0.01" rather than "$0.00", which would be indistinguishable
|
|
185
|
+
# from "the router ran and did not beat your selection". Negative totals are
|
|
186
|
+
# omitted entirely: the router picked a pricier model for quality on this
|
|
187
|
+
# session and "saved -$0.02" is a worse answer than staying quiet.
|
|
188
|
+
savings_clause() {
|
|
189
|
+
local file="$1" raw
|
|
190
|
+
[ "${WEAVE_CODEX_STATUS_SAVINGS:-1}" = "0" ] && return 0
|
|
191
|
+
[ -f "$file" ] || return 0
|
|
192
|
+
raw="$(cat "$file" 2>/dev/null)" || return 0
|
|
193
|
+
case "$raw" in
|
|
194
|
+
''|*[!0-9.eE+-]*) return 0 ;;
|
|
195
|
+
esac
|
|
196
|
+
awk -v v="$raw" 'BEGIN{
|
|
197
|
+
v = v + 0
|
|
198
|
+
if (v < 0.005) { exit }
|
|
199
|
+
if (v < 0.01) { printf " · saved <$0.01"; exit }
|
|
200
|
+
printf " · saved $%.2f", v
|
|
201
|
+
}' 2>/dev/null || true
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
set -e
|
|
205
|
+
|
|
206
|
+
case "${1:-hook}" in
|
|
207
|
+
--direct)
|
|
208
|
+
emit_title "Codex · direct"
|
|
209
|
+
exit 0
|
|
210
|
+
;;
|
|
211
|
+
--on)
|
|
212
|
+
rm -f "$disabled_marker"
|
|
213
|
+
emit_title "Weave Router · active"
|
|
214
|
+
exit 0
|
|
215
|
+
;;
|
|
216
|
+
--off)
|
|
217
|
+
[ ! -L "$disabled_marker" ] || exit 0
|
|
218
|
+
: >"$disabled_marker"
|
|
219
|
+
chmod 600 "$disabled_marker"
|
|
220
|
+
emit_title "Codex · direct"
|
|
221
|
+
exit 0
|
|
222
|
+
;;
|
|
223
|
+
esac
|
|
224
|
+
|
|
225
|
+
payload="$(cat)"
|
|
226
|
+
[ -n "$payload" ] || exit 0
|
|
227
|
+
command -v jq >/dev/null 2>&1 || exit 0
|
|
228
|
+
jq -e . >/dev/null 2>&1 <<<"$payload" || exit 0
|
|
229
|
+
|
|
230
|
+
hook_event_name="$(jq -r '.hook_event_name // ""' <<<"$payload")"
|
|
231
|
+
if [ "$hook_event_name" = "SessionStart" ]; then
|
|
232
|
+
if [ -f "$disabled_marker" ]; then
|
|
233
|
+
emit_title "Codex · direct"
|
|
234
|
+
jq -cn '{systemMessage:"Codex direct · Weave Router is off"}'
|
|
235
|
+
else
|
|
236
|
+
emit_title "Weave Router · active"
|
|
237
|
+
jq -cn '{systemMessage:"Weave Router active · routed model appears in the terminal title"}'
|
|
238
|
+
fi
|
|
239
|
+
exit 0
|
|
240
|
+
fi
|
|
241
|
+
|
|
242
|
+
if [ -f "$disabled_marker" ]; then
|
|
243
|
+
exit 0
|
|
244
|
+
fi
|
|
245
|
+
|
|
246
|
+
requested_model="$(safe_display_value "$(jq -r '.model // ""' <<<"$payload")")"
|
|
247
|
+
routed_model=""
|
|
248
|
+
session_id="$(jq -r '.session_id // ""' <<<"$payload")"
|
|
249
|
+
last_assistant_message="$(jq -r '.last_assistant_message // ""' <<<"$payload")"
|
|
250
|
+
|
|
251
|
+
file=""
|
|
252
|
+
if file="$(state_file_for "$session_id" 2>/dev/null)"; then
|
|
253
|
+
read_state "$file"
|
|
254
|
+
fi
|
|
255
|
+
|
|
256
|
+
# The marker is intentionally matched only at the beginning of the assistant
|
|
257
|
+
# message. Do not treat ordinary prose that mentions the heading as metadata.
|
|
258
|
+
first_line="${last_assistant_message%%$'\n'*}"
|
|
259
|
+
marker_model=""
|
|
260
|
+
force_model=""
|
|
261
|
+
case "$first_line" in
|
|
262
|
+
"${router_badge_sentinel}✦ **Weave Router** → "*)
|
|
263
|
+
marker_model="${first_line#"${router_badge_sentinel}✦ **Weave Router** → "}"
|
|
264
|
+
marker_model="${marker_model%% ·*}"
|
|
265
|
+
;;
|
|
266
|
+
"✦ **Weave Router** → "*)
|
|
267
|
+
marker_model="${first_line#"✦ **Weave Router** → "}"
|
|
268
|
+
marker_model="${marker_model%% ·*}"
|
|
269
|
+
;;
|
|
270
|
+
esac
|
|
271
|
+
case "$first_line" in
|
|
272
|
+
"Weave Router: force-model applied: "*" ("*)
|
|
273
|
+
force_model="${first_line#"Weave Router: force-model applied: "}"
|
|
274
|
+
force_model="${force_model%% (*}"
|
|
275
|
+
;;
|
|
276
|
+
esac
|
|
277
|
+
if [ -n "$marker_model" ]; then
|
|
278
|
+
routed_model="$(safe_display_value "$marker_model")"
|
|
279
|
+
elif [ -n "$force_model" ]; then
|
|
280
|
+
routed_model="$(safe_display_value "$force_model")"
|
|
281
|
+
else
|
|
282
|
+
routed_model="$(safe_display_value "$routed_model")"
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
if [ -n "$file" ]; then
|
|
286
|
+
write_state "$file"
|
|
287
|
+
fi
|
|
288
|
+
|
|
289
|
+
# The cache holds the previous turn's fetch; the refresh below serves the next
|
|
290
|
+
# one. Router telemetry is written asynchronously anyway, so a just-finished
|
|
291
|
+
# turn would not be included even in a blocking read — reading first and
|
|
292
|
+
# refreshing after costs a turn of freshness and buys never blocking Codex.
|
|
293
|
+
savings=""
|
|
294
|
+
cost_file=""
|
|
295
|
+
if cost_file="$(cost_file_for "$session_id" 2>/dev/null)"; then
|
|
296
|
+
savings="$(savings_clause "$cost_file")"
|
|
297
|
+
refresh_session_cost "$(safe_session_id "$session_id")" "$cost_file"
|
|
298
|
+
fi
|
|
299
|
+
|
|
300
|
+
if [ -n "$routed_model" ] && [ -n "$requested_model" ] && [ "$routed_model" != "$requested_model" ]; then
|
|
301
|
+
title="Weave Router · $routed_model ← $requested_model$savings"
|
|
302
|
+
elif [ -n "$routed_model" ]; then
|
|
303
|
+
title="Weave Router · $routed_model$savings"
|
|
304
|
+
elif [ -n "$requested_model" ]; then
|
|
305
|
+
title="Weave Router · active ← $requested_model$savings"
|
|
306
|
+
else
|
|
307
|
+
title="Weave Router · active$savings"
|
|
308
|
+
fi
|
|
309
|
+
emit_title "$title"
|
|
310
|
+
if [ -n "$marker_model" ] || [ -n "$force_model" ]; then
|
|
311
|
+
printf '%s' "$title" | jq -Rc '{systemMessage: .}'
|
|
312
|
+
fi
|
package/commands/beta.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Alias for /router-models — list the models the Weave Router may route to, and turn them on or off.
|
|
3
|
+
argument-hint: [model or provider to enable/disable]
|
|
4
|
+
allowed-tools: Bash(npx:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Show me which models this installation lets the Weave Router pick from, and
|
|
8
|
+
change that selection when I ask. This is the same list — and the same stored
|
|
9
|
+
setting — as the checkboxes on the router dashboard's settings page.
|
|
10
|
+
|
|
11
|
+
Start by running:
|
|
12
|
+
|
|
13
|
+
`npx @workweave/router models --claude{{SCOPE}}`
|
|
14
|
+
|
|
15
|
+
That prints every deployed model grouped by provider, with `[x]` for models the
|
|
16
|
+
router may pick and `[ ]` for models it may not. Present it back to me as a
|
|
17
|
+
compact checklist in that same `[x]` / `[ ]` form, keeping the provider
|
|
18
|
+
grouping and the exact model ids — I select models by id.
|
|
19
|
+
|
|
20
|
+
Then:
|
|
21
|
+
|
|
22
|
+
- If I named models or providers in `$ARGUMENTS`, work out whether I want them
|
|
23
|
+
on or off from how I phrased it, and apply it with
|
|
24
|
+
`npx @workweave/router models enable <id>... --claude{{SCOPE}}` or
|
|
25
|
+
`npx @workweave/router models disable <id>... --claude{{SCOPE}}` (add
|
|
26
|
+
`providers` before `enable`/`disable` to switch a whole provider). Several
|
|
27
|
+
ids can go in one call. Then re-run the list and show me the result.
|
|
28
|
+
- If I named nothing, stop after the list and ask which ones I want to change.
|
|
29
|
+
Don't change anything I didn't ask for.
|
|
30
|
+
|
|
31
|
+
Other things I might ask for:
|
|
32
|
+
|
|
33
|
+
- Rank models by preference:
|
|
34
|
+
`npx @workweave/router models prefer <id> <id>... --claude{{SCOPE}}` (order
|
|
35
|
+
matters), or `npx @workweave/router models prefer clear --claude{{SCOPE}}` to
|
|
36
|
+
drop the ranking.
|
|
37
|
+
- Providers only: `npx @workweave/router models providers --claude{{SCOPE}}`.
|
|
38
|
+
|
|
39
|
+
If the command reports that this router doesn't expose model selection, that's
|
|
40
|
+
a Weave-hosted router: model selection belongs to the whole organization there,
|
|
41
|
+
so tell me to change it at https://router.workweave.ai/dashboard/settings —
|
|
42
|
+
don't try to work around it. Its listing carries no on/off state, so present it
|
|
43
|
+
as a plain list; don't infer which models are enabled.
|
|
44
|
+
|
|
45
|
+
Disabling a model takes effect on the router's next routing decision; no
|
|
46
|
+
restart is needed.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: List the models the Weave Router may route to, and turn them on or off.
|
|
3
|
+
argument-hint: [model or provider to enable/disable]
|
|
4
|
+
allowed-tools: Bash(npx:*)
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Show me which models this installation lets the Weave Router pick from, and
|
|
8
|
+
change that selection when I ask. This is the same list — and the same stored
|
|
9
|
+
setting — as the checkboxes on the router dashboard's settings page.
|
|
10
|
+
|
|
11
|
+
Start by running:
|
|
12
|
+
|
|
13
|
+
`npx @workweave/router models --claude{{SCOPE}}`
|
|
14
|
+
|
|
15
|
+
That prints every deployed model grouped by provider, with `[x]` for models the
|
|
16
|
+
router may pick and `[ ]` for models it may not. Present it back to me as a
|
|
17
|
+
compact checklist in that same `[x]` / `[ ]` form, keeping the provider
|
|
18
|
+
grouping and the exact model ids — I select models by id.
|
|
19
|
+
|
|
20
|
+
Then:
|
|
21
|
+
|
|
22
|
+
- If I named models or providers in `$ARGUMENTS`, work out whether I want them
|
|
23
|
+
on or off from how I phrased it, and apply it with
|
|
24
|
+
`npx @workweave/router models enable <id>... --claude{{SCOPE}}` or
|
|
25
|
+
`npx @workweave/router models disable <id>... --claude{{SCOPE}}` (add
|
|
26
|
+
`providers` before `enable`/`disable` to switch a whole provider). Several
|
|
27
|
+
ids can go in one call. Then re-run the list and show me the result.
|
|
28
|
+
- If I named nothing, stop after the list and ask which ones I want to change.
|
|
29
|
+
Don't change anything I didn't ask for.
|
|
30
|
+
|
|
31
|
+
Other things I might ask for:
|
|
32
|
+
|
|
33
|
+
- Rank models by preference:
|
|
34
|
+
`npx @workweave/router models prefer <id> <id>... --claude{{SCOPE}}` (order
|
|
35
|
+
matters), or `npx @workweave/router models prefer clear --claude{{SCOPE}}` to
|
|
36
|
+
drop the ranking.
|
|
37
|
+
- Providers only: `npx @workweave/router models providers --claude{{SCOPE}}`.
|
|
38
|
+
|
|
39
|
+
If the command reports that this router doesn't expose model selection, that's
|
|
40
|
+
a Weave-hosted router: model selection belongs to the whole organization there,
|
|
41
|
+
so tell me to change it at https://router.workweave.ai/dashboard/settings —
|
|
42
|
+
don't try to work around it. Its listing carries no on/off state, so present it
|
|
43
|
+
as a plain list; don't infer which models are enabled.
|
|
44
|
+
|
|
45
|
+
Disabling a model takes effect on the router's next routing decision; no
|
|
46
|
+
restart is needed.
|
package/directives.tsv
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Weave Router directive registry
|
|
2
|
+
# canonical|aliases|capability|claude|codex|opencode|pi|cursor|adapter
|
|
3
|
+
# aliases are comma-separated; client columns are yes/no. adapter is the native asset kind.
|
|
4
|
+
force-model|fm|prompt|yes|yes|yes|yes|manual|command,skill
|
|
5
|
+
unforce-model|ufm|prompt|yes|yes|yes|yes|manual|command,skill
|
|
6
|
+
router-feedback|rf|prompt|yes|yes|yes|no|manual|command,skill
|
|
7
|
+
router-off||local-toggle|yes|yes|no|no|manual|command,skill
|
|
8
|
+
router-on||local-toggle|yes|yes|no|no|manual|command,skill
|
|
9
|
+
router-status||local-toggle|yes|yes|no|no|manual|command,skill
|
|
10
|
+
router-session||prompt|yes|no|no|no|manual|command
|
|
11
|
+
router-models|models|local-toggle|yes|yes|no|no|manual|command,skill
|
|
12
|
+
disable-routing||local-toggle|no|yes|no|no|manual|skill
|
|
13
|
+
beta||prompt|yes|no|no|yes|manual|command
|