llm-switcher 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +24 -0
- package/README.md +25 -17
- package/README.vi.md +24 -13
- package/config.example.json +0 -11
- package/docs/TOKEN-OPTIMIZER-INTEROP.md +110 -110
- package/docs/response-matrix.json +1130 -1130
- package/docs/tool-vocabulary.md +236 -0
- package/formats.mjs +277 -55
- package/package.json +1 -1
- package/proxy.mjs +55 -19
- package/service.mjs +6 -3
- package/skills/llm-switcher/SKILL.md +93 -93
- package/state.mjs +23 -91
- package/switch +0 -0
- package/switch.cmd +2 -2
- package/switch.mjs +2 -2
- package/tests/dashboard.e2e.test.mjs +10 -13
- package/tests/formats.test.mjs +98 -1
- package/tests/gateway.e2e.test.mjs +23 -9
- package/tests/helpers.mjs +24 -24
- package/tests/live-optimizer-interop.mjs +205 -205
- package/tests/service.test.mjs +6 -4
- package/tests/state.test.mjs +6 -7
- package/tests/tool-repair.test.mjs +280 -0
- package/tests/ui.test.mjs +22 -15
- package/tests/update.test.mjs +16 -0
- package/tests/windows.test.mjs +124 -0
- package/toolvocab.mjs +272 -0
- package/ui.html +32 -76
- package/update.mjs +13 -5
- package/windows.mjs +141 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog — LLM Switcher
|
|
2
2
|
|
|
3
|
+
## Release 1.4.0 — 2026-10-07
|
|
4
|
+
|
|
5
|
+
- **A model answers with the tool names it was trained on, and the caller only runs its own.** A Codex model says `shell` where Claude Code declared `Bash`, and the turn ends with `Error: No such tool available: shell`. The gateway now writes the call back in the vocabulary the caller declared, and says the names in the prompt so most calls never need rewriting.
|
|
6
|
+
- Two layers, because neither covers the whole case. The prompt layer is one sentence in `auto` mode, and only for the tool names the caller's own prompt does not already mention: Claude Code names its tools at length, so it gets nothing and there is no second list to contradict the first. It cannot help a provider that refuses the request before the model reads it (the Zen free tier answers 403 `FreeTierError`), nor a model that ignores the list. The repair layer is behind it, and pays only on the turns that need it.
|
|
7
|
+
- Only a tool the caller declared can be a target, and two declared tools of the same kind are left alone: a caller with both `Bash` and `PowerShell` cannot run the one it did not get. A namespaced name (`mcp__github__create_issue`) never leaves its namespace, because turning it into a local `Write` produces a call the caller cannot detect as wrong. A renamed tool keeps the caller's schema, so no argument key is translated and none can miss: opencode spells it `filePath`, Claude Code spells it `file_path`, and the caller's own spelling is what both the model and the caller see.
|
|
8
|
+
- Arguments are matched against the caller's `input_schema` and only when exactly one key fits, the same rule openclaw arrived at separately (openclaw#16717 took 70+ failures in eight days from that shape). A key the schema does not name is left alone, and `offset: 0` is a value.
|
|
9
|
+
- `Edit` is deliberately not renamed into `apply_patch`: that would translate a payload rather than a name, and a usable `old_string` needs a file the gateway does not read. `docs/tool-vocabulary.md` records the decision and the three ways to want first-class editing instead, the first of which needs no translation at all — passing the provider's own edit tool through (OpenAI's server-defined `apply_patch`, Anthropic's `text_editor`, Gemini's `replace`).
|
|
10
|
+
- `docs/tool-vocabulary.md` records every name with its source, the two industry answers to this problem, and what each layer still cannot do.
|
|
11
|
+
- **Anthropic's server tools were being sent to other providers as functions.** A tool with a dated type and no `input_schema` (`web_search_20260209`, `code_execution_20250825`) runs on Anthropic's infrastructure and its result reaches the caller without the caller executing anything. `anthropicToIR` recorded it as an ordinary tool, so a provider without an equivalent was asked for a function the caller would then have to run. The IR now marks which side runs a tool, and an emitter sends only the caller's own. LiteLLM's "web search interception" moves a capability in this direction deliberately; this was the same move made by accident.
|
|
12
|
+
|
|
13
|
+
- **The window is the model's own, so there is nothing to set.** `/v1/models` no longer reports a hardcoded 1M. The gateway reads each model's window from the profile's own list and serves it as `max_context_window` (the whole window) and `context_window` (the point to compress at). A 872K Codex model now gets 872K and still compacts at its own 272K threshold, instead of planning for 1M and failing at the upstream. The list is read once an hour per profile and cached in `model-windows.json`; a list that cannot be read keeps the last one, and the Codex CLI's own cache is the fallback before the catalog template.
|
|
14
|
+
- **`compact_window`:** intact serves the window to compress at beside `context_length`, and the gateway reads it from any provider list that names it. `POST /api/fetch-models` returns it as `compact`, and the dashboard shows each model's window under its slot.
|
|
15
|
+
- **`model1M` retired:** the flag, the 1M checkbox and the hardcoded 1M check are gone. A flag left in an old `config.json` is read by nothing and is dropped when the profile is next saved. The `[1m]` scrub of a leftover `ANTHROPIC_DEFAULT_*_MODEL` value stays, as does the `[1m]` stripping in model mapping: a client may still send the marker.
|
|
16
|
+
- **Thinking effort survives the hop, in both directions:** one ladder (`none` … `ultra`) orders the levels, and each level has one token budget, so a level and its budget are the same request. Before, `xhigh` and `max` became `medium` on a strict OpenAI upstream, `medium`, `high` and `xhigh` all became the same 8000-token budget, and a client that asked with Anthropic `output_config.effort` and no `thinking` object had the request dropped.
|
|
17
|
+
- Codex `reasoning.effort` reaches OpenAI Chat `reasoning_effort` unchanged on a forwarding gateway, and as the deepest level the target names on a strict one (`xhigh` → `high`, never `medium`).
|
|
18
|
+
- It reaches Anthropic as `output_config.effort` when no budget was sent, and as `budget_tokens` otherwise. A client that sent `budget_tokens` keeps it: the same request is not restated twice.
|
|
19
|
+
- Vertex asks Gemini for the budget the level stands for, and never above the 32768 Gemini takes. Gemini refuses a larger budget instead of trimming it, so the gateway trims it instead of losing the request.
|
|
20
|
+
- The four levels OpenAI names keep the budgets this gateway has always read (`1024` low, `4000` medium, `8000` high), so an existing request changes nothing.
|
|
21
|
+
- **Claude Code Remote Control:** the README now says which hop the switcher replaces. The session bridge at `api.anthropic.com/v1/code/sessions` carries the conversation and checks the subscription, so a Remote Control session needs quota on the Claude account even when the profile routes inference elsewhere.
|
|
22
|
+
## Release 1.3.8
|
|
23
|
+
|
|
24
|
+
- **Update now with the new code already on disk:** The update compared the version on disk, not the version of the running gateway. When the checkout or the npm install already had the new release, the update said "Already on the latest version" and did not restart. The gateway then ran the old code and showed the update notice again and again. The gateway now restarts into the newer code on disk.
|
|
25
|
+
- **Windows service without a window:** The logon task started `node.exe` directly. `node.exe` is a console program, so Windows opened a terminal window for the gateway, and when you closed that window the gateway stopped. The task now starts the gateway through `conhost.exe --headless`, and no window opens. Run `switch service install` again to update an installed task.
|
|
26
|
+
|
|
3
27
|
## Release 1.3.7
|
|
4
28
|
|
|
5
29
|
- **Update notice in time:** The gateway kept the npm answer for 12 hours. A release published after the last check did not show an update button until the next day. It now asks npm again after 5 minutes.
|
package/README.md
CHANGED
|
@@ -141,7 +141,7 @@ LLM Switcher acts as a transparent man-in-the-middle without ever touching clien
|
|
|
141
141
|
- Fixes orphaned `tool_result` blocks caused by aggressive prompt pruners (RTK, Headroom, Ponytail) before sending to Anthropic/OpenAI upstream.
|
|
142
142
|
- Automatically restores thinking parameters if an intermediary tool stripped them.
|
|
143
143
|
- Merges consecutive same-role turns to enforce strict alternating turn requirements.
|
|
144
|
-
- **Context windows come from the model:** the switcher no longer forces a
|
|
144
|
+
- **Context windows come from the model:** the switcher no longer forces a window and it writes no model name into your environment. A profile's own model list is the source: the whole window becomes `max_context_window` and the window to compress at becomes `context_window` in `/v1/models`, so Codex plans and compacts against what the model really has. Claude Code sizes its own session from the model it calls, and a backend with a smaller window than that model can overflow in a long session.
|
|
145
145
|
- **Zero Config Mutation:** Never reads or writes `~/.claude/settings.json` or `~/.codex/config.toml`, and writes no environment variable and no `--config` argument that a coding tool reads as configuration. The tool reaches the gateway only through the interceptor, so no provider warning banner appears.
|
|
146
146
|
- **Live Request / Response Inspector:** Built-in dashboard tab displaying real-time requests, latency, token consumption, prompt previews, and thinking blocks.
|
|
147
147
|
- **Native Background Service:** Install and run as an OS background daemon on Windows (Task Scheduler), macOS (launchd), or Linux (systemd).
|
|
@@ -390,14 +390,15 @@ Read [📖 `docs/codex-blindfold.md`](docs/codex-blindfold.md) before you turn i
|
|
|
390
390
|
|
|
391
391
|
### What you give up
|
|
392
392
|
|
|
393
|
-
- **
|
|
394
|
-
or an auto-compact limit, and it writes no model name into your environment.
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
`
|
|
399
|
-
|
|
400
|
-
|
|
393
|
+
- **The window follows the model, so there is nothing to set.** The switcher no longer forces a
|
|
394
|
+
window or an auto-compact limit, and it writes no model name into your environment. What a
|
|
395
|
+
coding tool gets is what the model has: the gateway reads the window of each model from the
|
|
396
|
+
profile's own list (`context_length`, `max_context_window`, or the `compact_window` intact adds
|
|
397
|
+
for a Codex model) and serves it at `/v1/models` as `max_context_window` for the whole window and
|
|
398
|
+
`context_window` for the point to compress at. A 1M model gets 1M; a 872K model gets 872K, and it
|
|
399
|
+
still compacts at its own threshold instead of at 95% of a window it does not have. When the list
|
|
400
|
+
names no window, the catalog template is used. Claude Code sizes its own session from the model it
|
|
401
|
+
calls, and a backend whose window is smaller than that model can overflow in a long session.
|
|
401
402
|
- **Codex needs certificates once.** Blindfold mode is what keeps Codex on its official endpoint,
|
|
402
403
|
and it needs a private CA plus a leaf naming the three hosts above. Skip it and Codex shows the
|
|
403
404
|
`base URL is overridden` line on its `/model` screen instead.
|
|
@@ -690,12 +691,6 @@ of the routing.
|
|
|
690
691
|
"sonnet": "ag/gemini-3.7-flash",
|
|
691
692
|
"haiku": "ag/gemini-3.6-flash-medium",
|
|
692
693
|
"fable": "ag/gemini-3.8-flash"
|
|
693
|
-
},
|
|
694
|
-
"model1M": {
|
|
695
|
-
"opus": true,
|
|
696
|
-
"sonnet": true,
|
|
697
|
-
"haiku": false,
|
|
698
|
-
"fable": true
|
|
699
694
|
}
|
|
700
695
|
},
|
|
701
696
|
"codex-default": {
|
|
@@ -708,8 +703,7 @@ of the routing.
|
|
|
708
703
|
// A profile that serves Codex MUST have publicModels (see Codex-first setup).
|
|
709
704
|
"publicModels": ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"], // official names for main, review, subagent
|
|
710
705
|
"codexRoles": { "review": "gpt-5.6-sol" }, // optional: pair one role with another public name
|
|
711
|
-
"defaultModels": { "main": "gemini-3.8-flash", "review": "gemini-3.7-flash-medium", "subagent": "gemini-3.6-flash-low" },
|
|
712
|
-
"model1M": { "main": false, "review": false, "subagent": false }
|
|
706
|
+
"defaultModels": { "main": "gemini-3.8-flash", "review": "gemini-3.7-flash-medium", "subagent": "gemini-3.6-flash-low" } "defaultModels": { "main": "gemini-3.8-flash", "review": "gemini-3.7-flash-medium", "subagent": "gemini-3.6-flash-low" },
|
|
713
707
|
},
|
|
714
708
|
"agy-default": {
|
|
715
709
|
"name": "agy via intact",
|
|
@@ -796,6 +790,20 @@ Because of this split, a provider fingerprint is never a rule in this gateway. I
|
|
|
796
790
|
- **Codex tools:** `custom`/freeform tools (e.g. `apply_patch` with its Lark grammar), `namespace` tools and `local_shell` are exposed to upstreams as function tools and converted back to `custom_tool_call` / namespaced `function_call` / `local_shell_call` items. Hosted tools (`web_search`, `file_search`, `tool_search`, image generation) execute on OpenAI's servers, so other upstreams cannot provide them and they are omitted.
|
|
797
791
|
- **Gemini 3 thought signatures:** signatures returned with function calls are cached in memory by tool call id (last 5,000 calls) and replayed on the matching `functionCall` part, including through Gemini's OpenAI-compatible `extra_content`. After a gateway restart, unknown calls in the current turn get Google's documented `skip_thought_signature_validator` value, which Google notes may reduce quality.
|
|
798
792
|
- **`/v1/messages/count_tokens`:** exact when the active Claude Code profile uses a native Anthropic upstream; otherwise an estimate (other providers have no equivalent endpoint).
|
|
793
|
+
- **Thinking effort:** one ladder orders the levels, `none` → `ultra`, and each level stands for one token budget, so a level and a budget are the same request in either vocabulary. A level crosses every hop: OpenAI Chat `reasoning_effort`, Anthropic `output_config.effort`, Codex `reasoning.effort`, and a budget for Anthropic `thinking.budget_tokens` and Gemini `thinkingBudget`. A target that names fewer levels is asked for the deepest one it names (`xhigh` becomes `high` on a strict OpenAI upstream, never `medium`), and the Gemini budget never goes above the 32768 Gemini accepts.
|
|
794
|
+
|
|
795
|
+
### Claude Code Remote Control (`claude rc`)
|
|
796
|
+
|
|
797
|
+
A Remote Control session runs on two hops, and only one of them is yours.
|
|
798
|
+
|
|
799
|
+
| Hop | Where it goes | Served by |
|
|
800
|
+
|---|---|---|
|
|
801
|
+
| Session bridge | `https://api.anthropic.com/v1/code/sessions/...` | Anthropic |
|
|
802
|
+
| Inference | the gateway on `127.0.0.1`, then the upstream of the profile | LLM Switcher |
|
|
803
|
+
|
|
804
|
+
The bridge carries the conversation itself: the prompt typed in the app or web UI, the session sync, and the account check against your Max/Pro subscription. Only the model calls of the agent it drives are yours to replace.
|
|
805
|
+
|
|
806
|
+
**An empty Claude quota stops the session at the bridge, before any model call is made.** The subscription is checked before the prompt is dispatched, so the UI answers out-of-quota and nothing ever reaches the gateway. Pointing the profile at an upstream with a full balance does not help: the bridge refuses first. Keep quota on the Claude account for the bridge, and a working profile for the inference.
|
|
799
807
|
|
|
800
808
|
## Research & Protocol Matrices
|
|
801
809
|
|
package/README.vi.md
CHANGED
|
@@ -141,7 +141,7 @@ LLM Switcher hoạt động như một lớp trung gian mạng trong suốt (tra
|
|
|
141
141
|
- Khắc phục lỗi mồ côi `tool_result` do các công cụ nén token (RTK, Headroom, Ponytail) vô tình cắt mất turn `assistant` phía trước $\implies$ chống lỗi `HTTP 400 Bad Request`.
|
|
142
142
|
- Tự động bù lại tham số `thinking` nếu tool ngoài cắt mất trên các reasoning model.
|
|
143
143
|
- Gộp các turn cùng role liên tiếp để đáp ứng nghiêm ngặt luật xen kẽ lượt nói của Anthropic.
|
|
144
|
-
- **Context window
|
|
144
|
+
- **Context window đến từ chính model:** switcher không còn ép cửa sổ nào và không ghi tên model nào vào môi trường của bạn. Danh sách model của profile là nguồn: cửa sổ thật thành `max_context_window`, cửa sổ nén thành `context_window` trong `/v1/models`, nên Codex lên kế hoạch và nén đúng theo những gì model thật sự có. Claude Code tự ước lượng phiên theo model nó gọi; backend có cửa sổ nhỏ hơn model đó có thể tràn trong phiên dài.
|
|
145
145
|
- **Không làm bẩn `settings.json` (Zero Config Mutation):** Không bao giờ đọc hay ghi `~/.claude/settings.json` hay `~/.codex/config.toml`, và không ghi biến môi trường nào cũng không thêm tham số `--config` nào mà công cụ đọc như cấu hình. Công cụ chỉ đến gateway qua interceptor, nên không hiện banner cảnh báo của nhà cung cấp.
|
|
146
146
|
- **Live Request / Response Inspector:** Bảng theo dõi thời gian thực ngay trên Web UI: xem độ trễ, token prompt/output, preview prompt câu hỏi và khối suy luận thinking.
|
|
147
147
|
- **Cài đặt Daemon Service nền:** Cung cấp lệnh cài đặt gateway chạy ngầm tự khởi động cùng hệ điều hành trên Windows (Task Scheduler), macOS (launchd) và Linux (systemd).
|
|
@@ -388,10 +388,14 @@ Hãy đọc [📖 `docs/codex-blindfold.md`](docs/codex-blindfold.md) trước k
|
|
|
388
388
|
|
|
389
389
|
### Những gì bạn đánh đổi
|
|
390
390
|
|
|
391
|
-
- **
|
|
392
|
-
auto-compact, cũng không ghi tên model nào vào môi trường
|
|
393
|
-
|
|
394
|
-
`
|
|
391
|
+
- **Cửa sổ đi theo model, nên không còn gì để phải bật.** Switcher không ép cửa sổ hay ngưỡng
|
|
392
|
+
auto-compact, cũng không ghi tên model nào vào môi trường. Coding tool nhận đúng cửa sổ của model:
|
|
393
|
+
gateway đọc cửa sổ từng model trong danh sách của profile (`context_length`, `max_context_window`,
|
|
394
|
+
hoặc `compact_window` mà intact thêm cho model Codex) rồi phục vụ tại `/v1/models` — cửa sổ thật là
|
|
395
|
+
`max_context_window`, mốc nén là `context_window`. Model 1M nhận 1M; model 872K nhận 872K và vẫn nén
|
|
396
|
+
đúng mốc của nó, thay vì nén ở 95% một cửa sổ nó không có. Danh sách không nói cửa sổ thì dùng
|
|
397
|
+
template catalog. Claude Code tự ước lượng phiên theo model nó gọi; backend có cửa sổ nhỏ hơn model
|
|
398
|
+
đó có thể tràn trong phiên dài.
|
|
395
399
|
- **Codex cần chứng chỉ làm một lần.** Blindfold là thứ giữ Codex trên endpoint chính thức, và nó
|
|
396
400
|
cần một CA riêng cộng leaf nêu đúng ba host ở trên. Không bật thì Codex hiện dòng
|
|
397
401
|
`base URL is overridden` trên màn `/model`.
|
|
@@ -683,12 +687,6 @@ route.
|
|
|
683
687
|
"sonnet": "ag/gemini-3.7-flash",
|
|
684
688
|
"haiku": "ag/gemini-3.6-flash-medium",
|
|
685
689
|
"fable": "ag/gemini-3.8-flash"
|
|
686
|
-
},
|
|
687
|
-
"model1M": {
|
|
688
|
-
"opus": true,
|
|
689
|
-
"sonnet": true,
|
|
690
|
-
"haiku": false,
|
|
691
|
-
"fable": true
|
|
692
690
|
}
|
|
693
691
|
},
|
|
694
692
|
"codex-default": {
|
|
@@ -701,8 +699,7 @@ route.
|
|
|
701
699
|
// Profile phục vụ Codex BẮT BUỘC có publicModels (xem Cấu hình ưu tiên Codex).
|
|
702
700
|
"publicModels": ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"], // tên chính thức cho main, review, subagent
|
|
703
701
|
"codexRoles": { "review": "gpt-5.6-sol" }, // không bắt buộc: ghép một vai trò với tên public khác
|
|
704
|
-
"defaultModels": { "main": "gemini-3.8-flash", "review": "gemini-3.7-flash-medium", "subagent": "gemini-3.6-flash-low" },
|
|
705
|
-
"model1M": { "main": false, "review": false, "subagent": false }
|
|
702
|
+
"defaultModels": { "main": "gemini-3.8-flash", "review": "gemini-3.7-flash-medium", "subagent": "gemini-3.6-flash-low" } "defaultModels": { "main": "gemini-3.8-flash", "review": "gemini-3.7-flash-medium", "subagent": "gemini-3.6-flash-low" },
|
|
706
703
|
},
|
|
707
704
|
"agy-default": {
|
|
708
705
|
"name": "agy qua intact",
|
|
@@ -790,6 +787,20 @@ Nhờ cách chia này, fingerprint của provider không bao giờ là rule tron
|
|
|
790
787
|
- **Tool của Codex:** tool `custom`/freeform (VD `apply_patch` với Lark grammar), tool `namespace` và `local_shell` được đưa lên upstream dưới dạng function tool rồi chuyển ngược thành item `custom_tool_call` / `function_call` có namespace / `local_shell_call`. Hosted tool (`web_search`, `file_search`, `tool_search`, sinh ảnh) chạy trên server OpenAI nên upstream khác không cung cấp được và bị lược bỏ.
|
|
791
788
|
- **Thought signature của Gemini 3:** chữ ký đi kèm function call được cache trong RAM theo tool call id (5.000 call gần nhất) và gắn lại vào đúng part `functionCall`, kể cả qua `extra_content` của endpoint OpenAI-compatible của Gemini. Sau khi restart gateway, call không rõ chữ ký trong lượt hiện tại dùng giá trị `skip_thought_signature_validator` mà Google cho phép (Google lưu ý có thể giảm chất lượng).
|
|
792
789
|
- **`/v1/messages/count_tokens`:** chính xác khi profile của Claude Code dùng upstream Anthropic native; các trường hợp khác là ước lượng (provider khác không có endpoint tương đương).
|
|
790
|
+
- **Effort của thinking:** một thang duy nhất xếp các mức, `none` → `ultra`, và mỗi mức đứng cho một ngân sách token, nên mức và ngân sách là cùng một yêu cầu dưới hai cách diễn đạt. Mức đó đi qua mọi chặng: `reasoning_effort` của OpenAI Chat, `output_config.effort` của Anthropic, `reasoning.effort` của Codex, và dạng ngân sách cho `thinking.budget_tokens` của Anthropic cùng `thinkingBudget` của Gemini. Chặng nào chỉ biết ít mức hơn sẽ được hỏi mức sâu nhất mà nó biết (`xhigh` thành `high` ở upstream OpenAI nghiêm ngặt, không bao giờ thành `medium`), và ngân sách Gemini không bao giờ vượt 32768 mức mà Gemini nhận.
|
|
791
|
+
|
|
792
|
+
### Claude Code Remote Control (`claude rc`)
|
|
793
|
+
|
|
794
|
+
Một phiên Remote Control đi qua hai chặng, và chỉ một chặng là của bạn.
|
|
795
|
+
|
|
796
|
+
| Chặng | Đi đến đâu | Ai phục vụ |
|
|
797
|
+
|---|---|---|
|
|
798
|
+
| Session bridge | `https://api.anthropic.com/v1/code/sessions/...` | Anthropic |
|
|
799
|
+
| Inference | gateway ở `127.0.0.1`, rồi tới upstream của profile | LLM Switcher |
|
|
800
|
+
|
|
801
|
+
Bridge mang theo chính cuộc hội thoại: prompt gõ trong app hoặc web UI, việc đồng bộ phiên, và kiểm tra tài khoản theo gói Max/Pro của bạn. Chỉ có các lệnh gọi model của agent do nó điều khiển là thứ bạn thay thế được.
|
|
802
|
+
|
|
803
|
+
**Hết quota Claude thì phiên chết ở bridge, trước cả lệnh gọi model đầu tiên.** Gói thuê bao được kiểm tra trước khi prompt được dispatch, nên UI trả về hết quota và không có gì tới được gateway. Đổi profile sang một upstream còn dư token cũng không giúp: bridge chặn trước. Cần quota trên tài khoản Claude cho bridge, và một profile chạy được cho phần inference.
|
|
793
804
|
|
|
794
805
|
## Nghiên cứu & Ma trận Giao thức Response
|
|
795
806
|
|
package/config.example.json
CHANGED
|
@@ -21,12 +21,6 @@
|
|
|
21
21
|
"sonnet": "ag/gemini-3.7-flash",
|
|
22
22
|
"haiku": "ag/gemini-3.6-flash-medium",
|
|
23
23
|
"fable": "ag/gemini-3.8-flash"
|
|
24
|
-
},
|
|
25
|
-
"model1M": {
|
|
26
|
-
"opus": true,
|
|
27
|
-
"sonnet": true,
|
|
28
|
-
"haiku": false,
|
|
29
|
-
"fable": true
|
|
30
24
|
}
|
|
31
25
|
},
|
|
32
26
|
"codex-default": {
|
|
@@ -46,11 +40,6 @@
|
|
|
46
40
|
"main": "gemini-3.8-flash",
|
|
47
41
|
"review": "gemini-3.7-flash-medium",
|
|
48
42
|
"subagent": "gemini-3.6-flash-low"
|
|
49
|
-
},
|
|
50
|
-
"model1M": {
|
|
51
|
-
"main": false,
|
|
52
|
-
"review": false,
|
|
53
|
-
"subagent": false
|
|
54
43
|
}
|
|
55
44
|
},
|
|
56
45
|
"agy-default": {
|
|
@@ -1,110 +1,110 @@
|
|
|
1
|
-
# Token Optimizer Interoperability & Failure Mode Report
|
|
2
|
-
|
|
3
|
-
**How LLM Switcher acts as the protective outermost edge gateway for aggressive prompt/token optimizers (Headroom, RTK, Ponytail).**
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
## Executive Summary
|
|
8
|
-
|
|
9
|
-
Third-party prompt optimizers and token compressors — such as **Headroom**, **RTK (Rust Token Killer)**, and **Ponytail** — attempt to reduce LLM input tokens by aggressively pruning message history, truncating command stdout, or forcing extreme prompt brevity.
|
|
10
|
-
|
|
11
|
-
While these tools can reduce raw token counts in simple scenarios, **they frequently break complex agentic coding workflows** by corrupting message graphs, orphaning tool calls, and stripping reasoning parameters. When these pruned payloads hit upstream APIs directly (such as Anthropic, OpenAI, or 9Router), the provider immediately throws fatal `HTTP 400 Bad Request` errors or severely degrades reasoning depth.
|
|
12
|
-
|
|
13
|
-
**LLM Switcher solves this by acting as the outermost edge gatekeeper (`127.0.0.1:3456`).** It intercepts the pruned payload before it leaves your machine, runs its built-in **Healer Engine** to repair message graphs and restore reasoning parameters, unlocks 1M context windows, and safely converts the protocol to your upstream provider.
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## Tool Breakdown: What They Do & How They Break Payloads
|
|
18
|
-
|
|
19
|
-
### 1. Headroom (`headroomlabs-ai/headroom`)
|
|
20
|
-
- **Mechanism:** Runs as a local proxy on `:8787` (or wraps CLI agents). Compresses conversation history, RAG chunks, and tool outputs using SmartCrusher (JSON), CodeCompressor (AST), and Kompress-v2-base. Also attempts "effort routing" to dial down thinking budgets.
|
|
21
|
-
- **Critical Failure Points:**
|
|
22
|
-
- **Orphaned `tool_result` blocks:** When pruning historical turns, Headroom often discards the `assistant` turn containing a `tool_use`, while retaining the subsequent `user` turn containing the `tool_result`. Anthropic's API strictly validates tool use IDs and crashes with:
|
|
23
|
-
```
|
|
24
|
-
HTTP 400 invalid_request_error: "tool_use_id 'xxx' does not correspond to any tool_use"
|
|
25
|
-
```
|
|
26
|
-
- **Consecutive `user` turns:** Dropping intermediary assistant turns causes multiple user messages to sit adjacent to each other. Anthropic strictly throws:
|
|
27
|
-
```
|
|
28
|
-
HTTP 400 invalid_request_error: "roles must alternate between 'user' and 'assistant'"
|
|
29
|
-
```
|
|
30
|
-
- **Reasoning Suppression:** Its "effort routing" dials down `thinking.budget_tokens` on routine tool turns. On complex models (Claude Opus, Gemini Flash), this prevents the model from formulating multi-step reasoning before acting.
|
|
31
|
-
|
|
32
|
-
### 2. RTK (`rtk-ai/rtk` - Rust Token Killer)
|
|
33
|
-
- **Mechanism:** A single Rust binary that hooks into shell tool execution (e.g. `PreToolUse` in Claude Code / Cursor) and rewrites CLI commands (`git`, `ls`, `cat`, `grep`, `pytest`) to filter out noise, truncate lines, and inject recall tokens (`[full output: rtk recall xxx]`).
|
|
34
|
-
- **Critical Failure Points:**
|
|
35
|
-
- **Corrupted Structural Data:** When an agent invokes a tool expecting machine-readable JSON or exact AST formatting, RTK's heuristic summaries can alter structural delimiters, causing downstream tool call parsing errors.
|
|
36
|
-
- **Custom Tracking Headers:** RTK and associated tracing proxies inject headers (`x-rtk-*`, `traceparent`, `x-optimizer-id`) that some strict upstream endpoints reject if not cleanly forwarded.
|
|
37
|
-
|
|
38
|
-
### 3. Ponytail (`DietrichGebert/ponytail`)
|
|
39
|
-
- **Mechanism:** A behavioral prompt engineering plugin/ruleset that injects extreme conciseness instructions ("write one line, it works, YAGNI") into agent system prompts across 20+ coding tools.
|
|
40
|
-
- **Critical Failure Points:**
|
|
41
|
-
- **Premature Execution without Reasoning:** By commanding the model to be maximally brief and avoid planning, reasoning models are discouraged from spending thinking tokens. The model outputs untested single-liners that often fail type checks and test suites.
|
|
42
|
-
- **System Prompt Prefix Invalidation:** Injected rules alter the leading system prompt bytes, busting provider prompt caches unless carefully aligned.
|
|
43
|
-
|
|
44
|
-
---
|
|
45
|
-
|
|
46
|
-
## The Healer Engine: How LLM Switcher Protects the Workflow
|
|
47
|
-
|
|
48
|
-
LLM Switcher sits between the optimizer tool and the upstream LLM:
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
[CLI Agent] ──> [Optimizer: Headroom / RTK] ──> [LLM Switcher :3456] ──> [Upstream / 9Router]
|
|
52
|
-
│
|
|
53
|
-
├── 1. Heal Orphaned tool_results
|
|
54
|
-
├── 2. Merge Consecutive Turns
|
|
55
|
-
├── 3. Restore Stripped Thinking
|
|
56
|
-
└── 4. Enforce 1M Context
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
### Protection Matrix: Before vs. After
|
|
60
|
-
|
|
61
|
-
| Scenario | Direct to Upstream (Without Switcher) | Through LLM Switcher (Healer Engine) |
|
|
62
|
-
|---|---|---|
|
|
63
|
-
| **Orphaned `tool_result` turn** | ❌ **HTTP 400 Crash**: `tool_use_id does not correspond to any tool_use` | ✅ **HTTP 200 OK**: Heals orphaned result into contextual text block `[Tool Result (id)]: ...` |
|
|
64
|
-
| **Consecutive `user` turns** | ❌ **HTTP 400 Crash**: `roles must alternate` | ✅ **HTTP 200 OK**: Merges consecutive turns into a single valid turn seamlessly |
|
|
65
|
-
| **Stripped `thinking` parameters** | ⚠️ **Degraded AI**: Reasoning disabled, model outputs shallow single-liners | ✅ **HTTP 200 OK**: Detects reasoning models and automatically restores safe thinking budget |
|
|
66
|
-
| **Orphaned `tool` role in Chat API** | ❌ **HTTP 400 Crash**: `tool role must respond to tool_calls` | ✅ **HTTP 200 OK**: Converts orphaned tool message into user context |
|
|
67
|
-
| **Custom tracking headers** | ⚠️ Connection dropped / unrecognized header warnings | ✅ **HTTP 200 OK**: Cleanly passes through `traceparent`, `x-request-id`, `x-rtk-*` |
|
|
68
|
-
|
|
69
|
-
---
|
|
70
|
-
|
|
71
|
-
## Test Methodology & Verification Suite
|
|
72
|
-
|
|
73
|
-
We created an automated verification suite in [`tests/live-optimizer-interop.mjs`](../tests/live-optimizer-interop.mjs) that systematically replicates the failure modes of each tool:
|
|
74
|
-
|
|
75
|
-
### Running the Test Suite
|
|
76
|
-
```bash
|
|
77
|
-
node tests/live-optimizer-interop.mjs
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
### Live Test Results
|
|
81
|
-
```text
|
|
82
|
-
================================================================
|
|
83
|
-
LLM SWITCHER — TOKEN OPTIMIZER INTEROPERABILITY TEST SUITE
|
|
84
|
-
Simulating failure modes from Headroom, RTK, and Ponytail
|
|
85
|
-
================================================================
|
|
86
|
-
|
|
87
|
-
[TEST] Headroom Simulation: Orphaned tool_result turn... PASS (8840ms)
|
|
88
|
-
↳ Healed orphaned tool_result. Response HTTP 200: "It looks like you've shared a fragment of context ..."
|
|
89
|
-
[TEST] Headroom Simulation: Consecutive User turns (Role alternation violation)... PASS (2683ms)
|
|
90
|
-
↳ Merged consecutive turns seamlessly. Stop reason: end_turn
|
|
91
|
-
[TEST] Thinking Guard: Restoring stripped thinking parameter on reasoning models... PASS (5372ms)
|
|
92
|
-
↳ Automatically restored thinking: thinking_delta=true, signature_delta=true, text_delta=true
|
|
93
|
-
[TEST] RTK Intermediary: Custom headers and traceparent passthrough... PASS (2453ms)
|
|
94
|
-
↳ Headers accepted cleanly with HTTP 200 OK
|
|
95
|
-
[TEST] OpenAI Chat Healer: Orphaned role tool without preceding assistant tool_calls... PASS (1561ms)
|
|
96
|
-
↳ Chat Healer rescued orphaned tool role. Stop reason: length
|
|
97
|
-
|
|
98
|
-
================================================================
|
|
99
|
-
TEST RESULTS: 5 PASSED / 0 FAILED
|
|
100
|
-
================================================================
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
---
|
|
104
|
-
|
|
105
|
-
## Recommended User Setup
|
|
106
|
-
|
|
107
|
-
For developers using prompt optimization tools:
|
|
108
|
-
1. Keep the optimizer installed in your CLI tool as usual.
|
|
109
|
-
2. In the optimizer's configuration (e.g. `headroom.yaml` or RTK upstream settings), set the upstream target URL to **LLM Switcher** (`http://127.0.0.1:3456`).
|
|
110
|
-
3. Enjoy prompt compression savings without worrying about broken conversation graphs, HTTP 400 crashes, or lost reasoning depth.
|
|
1
|
+
# Token Optimizer Interoperability & Failure Mode Report
|
|
2
|
+
|
|
3
|
+
**How LLM Switcher acts as the protective outermost edge gateway for aggressive prompt/token optimizers (Headroom, RTK, Ponytail).**
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Executive Summary
|
|
8
|
+
|
|
9
|
+
Third-party prompt optimizers and token compressors — such as **Headroom**, **RTK (Rust Token Killer)**, and **Ponytail** — attempt to reduce LLM input tokens by aggressively pruning message history, truncating command stdout, or forcing extreme prompt brevity.
|
|
10
|
+
|
|
11
|
+
While these tools can reduce raw token counts in simple scenarios, **they frequently break complex agentic coding workflows** by corrupting message graphs, orphaning tool calls, and stripping reasoning parameters. When these pruned payloads hit upstream APIs directly (such as Anthropic, OpenAI, or 9Router), the provider immediately throws fatal `HTTP 400 Bad Request` errors or severely degrades reasoning depth.
|
|
12
|
+
|
|
13
|
+
**LLM Switcher solves this by acting as the outermost edge gatekeeper (`127.0.0.1:3456`).** It intercepts the pruned payload before it leaves your machine, runs its built-in **Healer Engine** to repair message graphs and restore reasoning parameters, unlocks 1M context windows, and safely converts the protocol to your upstream provider.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Tool Breakdown: What They Do & How They Break Payloads
|
|
18
|
+
|
|
19
|
+
### 1. Headroom (`headroomlabs-ai/headroom`)
|
|
20
|
+
- **Mechanism:** Runs as a local proxy on `:8787` (or wraps CLI agents). Compresses conversation history, RAG chunks, and tool outputs using SmartCrusher (JSON), CodeCompressor (AST), and Kompress-v2-base. Also attempts "effort routing" to dial down thinking budgets.
|
|
21
|
+
- **Critical Failure Points:**
|
|
22
|
+
- **Orphaned `tool_result` blocks:** When pruning historical turns, Headroom often discards the `assistant` turn containing a `tool_use`, while retaining the subsequent `user` turn containing the `tool_result`. Anthropic's API strictly validates tool use IDs and crashes with:
|
|
23
|
+
```
|
|
24
|
+
HTTP 400 invalid_request_error: "tool_use_id 'xxx' does not correspond to any tool_use"
|
|
25
|
+
```
|
|
26
|
+
- **Consecutive `user` turns:** Dropping intermediary assistant turns causes multiple user messages to sit adjacent to each other. Anthropic strictly throws:
|
|
27
|
+
```
|
|
28
|
+
HTTP 400 invalid_request_error: "roles must alternate between 'user' and 'assistant'"
|
|
29
|
+
```
|
|
30
|
+
- **Reasoning Suppression:** Its "effort routing" dials down `thinking.budget_tokens` on routine tool turns. On complex models (Claude Opus, Gemini Flash), this prevents the model from formulating multi-step reasoning before acting.
|
|
31
|
+
|
|
32
|
+
### 2. RTK (`rtk-ai/rtk` - Rust Token Killer)
|
|
33
|
+
- **Mechanism:** A single Rust binary that hooks into shell tool execution (e.g. `PreToolUse` in Claude Code / Cursor) and rewrites CLI commands (`git`, `ls`, `cat`, `grep`, `pytest`) to filter out noise, truncate lines, and inject recall tokens (`[full output: rtk recall xxx]`).
|
|
34
|
+
- **Critical Failure Points:**
|
|
35
|
+
- **Corrupted Structural Data:** When an agent invokes a tool expecting machine-readable JSON or exact AST formatting, RTK's heuristic summaries can alter structural delimiters, causing downstream tool call parsing errors.
|
|
36
|
+
- **Custom Tracking Headers:** RTK and associated tracing proxies inject headers (`x-rtk-*`, `traceparent`, `x-optimizer-id`) that some strict upstream endpoints reject if not cleanly forwarded.
|
|
37
|
+
|
|
38
|
+
### 3. Ponytail (`DietrichGebert/ponytail`)
|
|
39
|
+
- **Mechanism:** A behavioral prompt engineering plugin/ruleset that injects extreme conciseness instructions ("write one line, it works, YAGNI") into agent system prompts across 20+ coding tools.
|
|
40
|
+
- **Critical Failure Points:**
|
|
41
|
+
- **Premature Execution without Reasoning:** By commanding the model to be maximally brief and avoid planning, reasoning models are discouraged from spending thinking tokens. The model outputs untested single-liners that often fail type checks and test suites.
|
|
42
|
+
- **System Prompt Prefix Invalidation:** Injected rules alter the leading system prompt bytes, busting provider prompt caches unless carefully aligned.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The Healer Engine: How LLM Switcher Protects the Workflow
|
|
47
|
+
|
|
48
|
+
LLM Switcher sits between the optimizer tool and the upstream LLM:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
[CLI Agent] ──> [Optimizer: Headroom / RTK] ──> [LLM Switcher :3456] ──> [Upstream / 9Router]
|
|
52
|
+
│
|
|
53
|
+
├── 1. Heal Orphaned tool_results
|
|
54
|
+
├── 2. Merge Consecutive Turns
|
|
55
|
+
├── 3. Restore Stripped Thinking
|
|
56
|
+
└── 4. Enforce 1M Context
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Protection Matrix: Before vs. After
|
|
60
|
+
|
|
61
|
+
| Scenario | Direct to Upstream (Without Switcher) | Through LLM Switcher (Healer Engine) |
|
|
62
|
+
|---|---|---|
|
|
63
|
+
| **Orphaned `tool_result` turn** | ❌ **HTTP 400 Crash**: `tool_use_id does not correspond to any tool_use` | ✅ **HTTP 200 OK**: Heals orphaned result into contextual text block `[Tool Result (id)]: ...` |
|
|
64
|
+
| **Consecutive `user` turns** | ❌ **HTTP 400 Crash**: `roles must alternate` | ✅ **HTTP 200 OK**: Merges consecutive turns into a single valid turn seamlessly |
|
|
65
|
+
| **Stripped `thinking` parameters** | ⚠️ **Degraded AI**: Reasoning disabled, model outputs shallow single-liners | ✅ **HTTP 200 OK**: Detects reasoning models and automatically restores safe thinking budget |
|
|
66
|
+
| **Orphaned `tool` role in Chat API** | ❌ **HTTP 400 Crash**: `tool role must respond to tool_calls` | ✅ **HTTP 200 OK**: Converts orphaned tool message into user context |
|
|
67
|
+
| **Custom tracking headers** | ⚠️ Connection dropped / unrecognized header warnings | ✅ **HTTP 200 OK**: Cleanly passes through `traceparent`, `x-request-id`, `x-rtk-*` |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Test Methodology & Verification Suite
|
|
72
|
+
|
|
73
|
+
We created an automated verification suite in [`tests/live-optimizer-interop.mjs`](../tests/live-optimizer-interop.mjs) that systematically replicates the failure modes of each tool:
|
|
74
|
+
|
|
75
|
+
### Running the Test Suite
|
|
76
|
+
```bash
|
|
77
|
+
node tests/live-optimizer-interop.mjs
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Live Test Results
|
|
81
|
+
```text
|
|
82
|
+
================================================================
|
|
83
|
+
LLM SWITCHER — TOKEN OPTIMIZER INTEROPERABILITY TEST SUITE
|
|
84
|
+
Simulating failure modes from Headroom, RTK, and Ponytail
|
|
85
|
+
================================================================
|
|
86
|
+
|
|
87
|
+
[TEST] Headroom Simulation: Orphaned tool_result turn... PASS (8840ms)
|
|
88
|
+
↳ Healed orphaned tool_result. Response HTTP 200: "It looks like you've shared a fragment of context ..."
|
|
89
|
+
[TEST] Headroom Simulation: Consecutive User turns (Role alternation violation)... PASS (2683ms)
|
|
90
|
+
↳ Merged consecutive turns seamlessly. Stop reason: end_turn
|
|
91
|
+
[TEST] Thinking Guard: Restoring stripped thinking parameter on reasoning models... PASS (5372ms)
|
|
92
|
+
↳ Automatically restored thinking: thinking_delta=true, signature_delta=true, text_delta=true
|
|
93
|
+
[TEST] RTK Intermediary: Custom headers and traceparent passthrough... PASS (2453ms)
|
|
94
|
+
↳ Headers accepted cleanly with HTTP 200 OK
|
|
95
|
+
[TEST] OpenAI Chat Healer: Orphaned role tool without preceding assistant tool_calls... PASS (1561ms)
|
|
96
|
+
↳ Chat Healer rescued orphaned tool role. Stop reason: length
|
|
97
|
+
|
|
98
|
+
================================================================
|
|
99
|
+
TEST RESULTS: 5 PASSED / 0 FAILED
|
|
100
|
+
================================================================
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Recommended User Setup
|
|
106
|
+
|
|
107
|
+
For developers using prompt optimization tools:
|
|
108
|
+
1. Keep the optimizer installed in your CLI tool as usual.
|
|
109
|
+
2. In the optimizer's configuration (e.g. `headroom.yaml` or RTK upstream settings), set the upstream target URL to **LLM Switcher** (`http://127.0.0.1:3456`).
|
|
110
|
+
3. Enjoy prompt compression savings without worrying about broken conversation graphs, HTTP 400 crashes, or lost reasoning depth.
|