llm-switcher 1.1.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.
Files changed (51) hide show
  1. package/.gitattributes +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +587 -0
  4. package/README.vi.md +585 -0
  5. package/blindfold/blindfold.mjs +633 -0
  6. package/blindfold/make-certs.sh +88 -0
  7. package/blindfold/wsframe.mjs +176 -0
  8. package/codex-catalog-template.json +1 -0
  9. package/config.example.json +84 -0
  10. package/contract-exclusions.json +41 -0
  11. package/contract.mjs +561 -0
  12. package/docs/LLM-RESPONSE-MATRIX.md +165 -0
  13. package/docs/TOKEN-OPTIMIZER-INTEROP.md +110 -0
  14. package/docs/codex-blindfold.md +214 -0
  15. package/docs/cross-platform.md +136 -0
  16. package/docs/diagrams/blindfold-request-routing.html +14972 -0
  17. package/docs/diagrams/blindfold-request-routing.sequence.json +175 -0
  18. package/docs/diagrams/blindfold-switch-lifecycle.html +14958 -0
  19. package/docs/diagrams/blindfold-switch-lifecycle.lifecycle.json +159 -0
  20. package/docs/diagrams/codex-model-name-resolution.html +15005 -0
  21. package/docs/diagrams/codex-model-name-resolution.workflow.json +71 -0
  22. package/docs/response-matrix.json +1131 -0
  23. package/formats.mjs +2308 -0
  24. package/mcp.mjs +340 -0
  25. package/package.json +36 -0
  26. package/proxy.mjs +1743 -0
  27. package/service.mjs +132 -0
  28. package/shim.mjs +292 -0
  29. package/skills/llm-switcher/SKILL.md +88 -0
  30. package/state.mjs +978 -0
  31. package/switch +5 -0
  32. package/switch.cmd +2 -0
  33. package/switch.mjs +930 -0
  34. package/tests/blindfold.test.mjs +307 -0
  35. package/tests/blindfold.wire.test.mjs +170 -0
  36. package/tests/contract/run.test.mjs +214 -0
  37. package/tests/contract-check.test.mjs +458 -0
  38. package/tests/contract-lab.test.mjs +755 -0
  39. package/tests/datadir.test.mjs +37 -0
  40. package/tests/formats.test.mjs +794 -0
  41. package/tests/gateway.e2e.test.mjs +999 -0
  42. package/tests/helpers.mjs +24 -0
  43. package/tests/lifecycle.test.mjs +416 -0
  44. package/tests/live-optimizer-interop.mjs +205 -0
  45. package/tests/mcp.test.mjs +91 -0
  46. package/tests/service.test.mjs +69 -0
  47. package/tests/shim.test.mjs +228 -0
  48. package/tests/state.test.mjs +675 -0
  49. package/tests/switch.test.mjs +156 -0
  50. package/tests/wsframe.test.mjs +154 -0
  51. package/ui.html +2234 -0
package/README.vi.md ADDED
@@ -0,0 +1,585 @@
1
+ # LLM Switcher (Bản Tiếng Việt)
2
+
3
+ <p align="center">
4
+ <b>Cổng ngõ biên (Edge Gateway) chuyển đổi đa giao thức LLM siêu nhẹ, Zero-Dependency</b><br>
5
+ Cầu nối hai chiều giữa <b>Claude Code</b>, <b>Codex</b>, OpenAI SDKs, Gemini/Vertex SDKs với mọi nhà cung cấp LLM.<br>
6
+ Chuyển đổi giao thức qua IR, mở khoá 1M context, trích xuất thinking blocks và tự chữa lành đồ thị tin nhắn trước khi ra Internet.
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="README.md">English</a> • <b>Tiếng Việt</b>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <img src="https://img.shields.io/badge/Node.js-18%2B-22c55e?logo=node.js&logoColor=white" alt="Node.js 18+">
15
+ <img src="https://img.shields.io/badge/Phụ_thuộc-Zero_Dependencies-38bdf8" alt="Zero Dependencies">
16
+ <img src="https://img.shields.io/badge/Context-1%2C000%2C000_tokens-6366f1" alt="1M Context">
17
+ <img src="https://img.shields.io/badge/Multi--Active-Đa_CLI_Độc_Lập-f59e0b" alt="Multi-Active">
18
+ <img src="https://img.shields.io/badge/Giấy_phép-MIT-gray" alt="License MIT">
19
+ </p>
20
+
21
+ ---
22
+
23
+ > ### 🎯 Vấn đề Cốt lõi: Vì sao Proxy Chung Chung Làm "Tê Liệt" Công cụ Coding AI?
24
+ >
25
+ > Mỗi nhà cung cấp LLM hiện nay sử dụng một **chuẩn API response hoàn toàn khác nhau**:
26
+ > - **Anthropic** bắt buộc phải có các block `thinking` riêng biệt (`thinking_delta` + `signature_delta`), luật xen kẽ lượt nghiêm ngặt (`roles must alternate`), và schema `tool_use` có định kiểu.
27
+ > - **OpenAI** stream reasoning qua các chunk delta `reasoning_content` hoặc `reasoning_details[]`, và định dạng tool thành `tool_calls` chứa chuỗi JSON arguments.
28
+ > - **Google Vertex AI** đặt khối suy luận vào `candidates[0].content.parts[{thought: true, text, thoughtSignature}]` và truyền arguments dạng object thuần.
29
+ > - **Các model mã nguồn mở (DeepSeek, Qwen, GLM)** thường đổ thẳng chain-of-thought vào nội dung `content`, hoặc trả trùng lặp nhiều trường gây rối loạn parser.
30
+ >
31
+ > **Khi các công cụ lập trình cao cấp như Claude Code hoặc Codex nhận về response không chuẩn định dạng gốc, chúng không chỉ hiển thị lỗi — mà hiệu năng và trí thông minh của AI bị suy giảm nghiêm trọng:**
32
+ > 1. **Mất Khối Suy Luận (Lost Chain-of-Thought):** Nếu Claude Code không nhận được block `thinking_delta` chuẩn của Anthropic, nó **hoàn toàn không nhận biết được tiến trình suy luận** của model. Agent sẽ hành động vội vàng, bỏ qua bước lập kế hoạch kiến trúc, và sinh ra code lỗi.
33
+ > 2. **Lỗi Thực Thi Công Cụ (Broken Tool Calling):** Sự sai lệch về stop reason (`tool_calls` vs `tool_use`) hoặc cách cắt chunk arguments làm agent không parse được tham số lệnh, dẫn đến vòng lặp lỗi vô tận.
34
+ > 3. **Lệch Token & Hỏng Prompt Cache:** Tính toán sai cấu trúc token usage làm vỡ cơ chế KV-cache của provider và kích hoạt nén ngữ cảnh (compaction) quá sớm.
35
+ >
36
+ > Nhiều lập trình viên lầm tưởng model AI "ngày càng ngáo đi", nhưng thực chất là **do proxy trung gian đã làm biến dạng cấu trúc response!**
37
+ >
38
+ > ### 🛡️ Giải pháp: Giả Lập Chuẩn Gốc Không Hao Hụt (Zero-Loss Native Emulation)
39
+ >
40
+ > **LLM Switcher giải quyết triệt để bài toán này bằng cơ chế giả lập giao thức chuẩn xác 100%.**
41
+ >
42
+ > Dù upstream phía sau của bạn là 9Router, OpenRouter, Vertex hay DeepSeek, Switcher sẽ chuẩn hoá và tái tạo lại **chính xác từng byte event stream theo đúng chuẩn mà client đó được thiết kế để tiếp nhận**:
43
+ > - **Claude Code** nhận về 100% luồng Anthropic SSE xịn (`message_start` ➔ `thinking_delta` ➔ `signature_delta` ➔ `tool_use` ➔ `message_delta`), hoạt động **mượt mà y hệt như đang dùng gói thuê bao chính chủ đắt đỏ**.
44
+ > - **Codex** nhận về 100% luồng Responses API xịn (`response.created` ➔ `output_text.delta` ➔ `function_call` ➔ `response.completed`).
45
+ >
46
+ > **Bạn vừa được hưởng lợi ích chi phí và độ phủ 1M context của các API bên thứ ba, vừa giữ trọn 100% trí thông minh và sức mạnh của công cụ như dùng gói subscription gốc.**
47
+
48
+ ---
49
+
50
+ > ### 💡 Triết lý Thiết kế: Phần Mở Rộng Ở Biên Tối Ưu Cho 9Router
51
+ >
52
+ > **LLM Switcher CỐ TÌNH KHÔNG làm các tính năng xoay vòng API key (key rotation), quản lý account pool, theo dõi quota, hay chia tải (load balancing) giữa nhiều key của cùng một nhà cung cấp.**
53
+ >
54
+ > Những việc nặng nhọc đó thuộc về các gateway định tuyến chuyên dụng ở phía máy chủ như **[9Router](https://github.com/decolua/9router)**. Máy chủ trung tâm quản lý việc xoay vòng tài khoản, tự động retry khi gặp rate-limit, và tính toán hạn mức tập trung hiệu quả và an toàn hơn rất nhiều so với một công cụ chạy trên từng máy cá nhân.
55
+ >
56
+ > **LLM Switcher được thiết kế chuẩn xác là phần mở rộng ở biên (Client-Side Edge Extension) tối ưu nhất khi kết hợp với 9Router (hoặc các gateway tương tự):**
57
+ > - **Phía máy cá nhân (LLM Switcher đảm nhiệm):** Chuyển đổi giao thức cho các coding tool trên máy bạn (Claude Code `/v1/messages`, Codex `/v1/responses`, Vertex `/v1beta/...`, OpenAI Chat), mở khoá 1M context cục bộ, quản lý đa profile song song cho từng CLI, và làm chốt chặn Healer Engine để tự chữa lành tin nhắn bị các tool nén token ngoài (RTK, Headroom, Ponytail) cắt xén trước khi gửi đi.
58
+ > - **Phía máy chủ trung tâm (9Router đảm nhiệm):** Quản lý account pool, xoay vòng API key, chia tải weighted routing, theo dõi quota và tự động failover giữa các nhà cung cấp.
59
+ >
60
+ > Sự phân định ranh giới rõ ràng này giúp LLM Switcher giữ vững tiêu chí **siêu nhẹ, Zero-Dependency, không phình to tính năng (no bloatware)** nhưng vẫn mang lại trải nghiệm lập trình AI mạnh mẽ nhất.
61
+
62
+ ---
63
+
64
+ ## Kiến trúc & Luồng hoạt động
65
+
66
+ LLM Switcher lắng nghe cục bộ trên máy bạn (`127.0.0.1:3456`), đóng vai trò là **chốt chặn cuối cùng ở cửa ngõ ra Internet** trước khi request được gửi đến các nhà cung cấp LLM (9Router, OpenRouter, Anthropic, Vertex, v.v.).
67
+
68
+ ### 1. Sơ đồ Tổng quan Hệ thống (System Topology)
69
+
70
+ ```mermaid
71
+ flowchart TD
72
+ subgraph Clients["Công cụ Dev & Coding CLI"]
73
+ CC["Claude Code CLI\n(/v1/messages)"]
74
+ CDX["OpenAI Codex CLI\n(/v1/responses)"]
75
+ OAI["OpenAI SDKs / Cursor\n(/v1/chat/completions)"]
76
+ VTX["Gemini / Vertex SDKs\n(/v1beta/models/*)"]
77
+ end
78
+
79
+ subgraph Optimizers["Lớp nén trung gian (Tùy chọn — cài sẵn trong CLI)"]
80
+ OPT["Tool cắt tỉa & nén token\n(Headroom / RTK / Ponytail)\n[Cấu hình upstream: :3456]"]
81
+ end
82
+
83
+ subgraph Switcher["LLM Switcher (:3456) — Chốt chặn cửa ngõ biên ra Internet"]
84
+ direction TB
85
+ ROUTER["Tự nhận diện giao thức & Định tuyến Multi-Active"]
86
+ HEALER["Healer Engine (Tự chữa lành)\n• Cứu tool_result mồ côi\n• Khôi phục thinking params bị cắt\n• Gộp các turn cùng role liên tiếp"]
87
+ IR["Bộ chuyển đổi IR 2 chiều đối xứng\n(4 Chuẩn Client ⟷ 3 Chuẩn Upstream)"]
88
+ M1M["Mở khoá 1M Context\n& Tự tính ngưỡng Auto-Compact"]
89
+ LOGS["Live Inspector\n(Ring Buffer lưu RAM thời gian thực)"]
90
+ ROUTER --> HEALER --> IR --> M1M --> LOGS
91
+ end
92
+
93
+ subgraph Upstream["Internet / Nhà cung cấp LLM Upstream"]
94
+ R9["9Router / Selfhost Gateway"]
95
+ OR["OpenRouter / Together / Groq"]
96
+ ANT["Anthropic Native API"]
97
+ GCP["Google Vertex AI / Gemini"]
98
+ end
99
+
100
+ CC -->|Trực tiếp| ROUTER
101
+ CC -.->|Tùy chọn| OPT
102
+ CDX -->|Trực tiếp| ROUTER
103
+ CDX -.->|Tùy chọn| OPT
104
+ OAI --> ROUTER
105
+ VTX --> ROUTER
106
+ OPT -->|Chuyển tiếp về Switcher| ROUTER
107
+
108
+ LOGS -->|Request đã chuẩn hoá| R9
109
+ LOGS -->|Request đã chuẩn hoá| OR
110
+ LOGS -->|Request đã chuẩn hoá| ANT
111
+ LOGS -->|Request đã chuẩn hoá| GCP
112
+ ```
113
+
114
+ ---
115
+
116
+ ### 2. Pipeline Chuyển đổi Giao thức qua IR & Healer Engine
117
+
118
+ ```mermaid
119
+ sequenceDiagram
120
+ autonumber
121
+ actor CLI as Client (Claude Code / Codex / SDK)
122
+ participant GW as LLM Switcher (:3456)
123
+ participant IR as IR & Healer Engine
124
+ participant UP as Upstream (9Router / Anthropic / Vertex)
125
+
126
+ CLI->>GW: Gửi request (Anthropic, Responses, Chat hoặc Vertex)
127
+ Note over GW,IR: Chuẩn hoá về IR (Intermediate Representation)
128
+ GW->>IR: parseToIR(clientFormat, payload)
129
+ Note over IR: Healer Engine kiểm tra & nắn chỉnh:<br/>1. Biến tool_result mồ côi thành text block ngữ cảnh<br/>2. Tự khôi phục tham số thinking nếu tool ngoài cắt mất<br/>3. Gộp các turn user liên tiếp (chống lỗi 400)<br/>4. Kích hoạt ngưỡng 1M context
130
+ IR->>GW: emitUpstreamBody(outFormat, healedIR)
131
+ GW->>UP: Gọi API Upstream (fetch kèm AbortSignal)
132
+ UP-->>GW: Trả về SSE Stream / JSON Chunks
133
+ Note over GW: normalizeUpstream(chunk)<br/>Bóc tách reasoning_content, tag <think>, tính usage
134
+ GW->>CLI: Render stream chuẩn theo giao thức của Client (ví dụ: thinking_delta + text_delta)
135
+ Note over CLI,GW: Khi Client ngắt kết nối (Ctrl+C) -> Switcher lập tức abort Upstream (tiết kiệm token!)
136
+ ```
137
+
138
+ ---
139
+
140
+ ### 3. Cơ chế Định tuyến Đa CLI Độc lập (Multi-Active Concurrent Routing)
141
+
142
+ Bạn có thể kích hoạt **đồng thời nhiều profile hoạt động song song** — mỗi công cụ CLI kết nối tới 1 profile riêng biệt mà không hề xung đột:
143
+
144
+ ```mermaid
145
+ flowchart LR
146
+ subgraph Inbound["Lượt gọi từ các Client"]
147
+ C1["Claude Code\n(/v1/messages)"]
148
+ C2["Codex CLI\n(/v1/responses)"]
149
+ C3["OpenAI SDK\n(/v1/chat/completions)"]
150
+ C4["Vertex SDK\n(/v1beta/models/*)"]
151
+ end
152
+
153
+ subgraph Core["Lõi LLM Switcher (:3456)"]
154
+ SLOT1["Slot: Anthropic\nActive: [9Router]"]
155
+ SLOT2["Slot: Responses\nActive: [OpenRouter]"]
156
+ SLOT3["Slot: OpenAI\nActive: [Local LLM]"]
157
+ SLOT4["Slot: Vertex\nActive: [Tắt / Official]"]
158
+ end
159
+
160
+ subgraph Egress["Đích Upstream tương ứng"]
161
+ U1["9Router (Mở 1M Context Opus)"]
162
+ U2["OpenRouter (Sonnet Thinking)"]
163
+ U3["Local OpenAI Server (:8000)"]
164
+ U4["Google Cloud Endpoint"]
165
+ end
166
+
167
+ C1 --> SLOT1 --> U1
168
+ C2 --> SLOT2 --> U2
169
+ C3 --> SLOT3 --> U3
170
+ C4 --> SLOT4 --> U4
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Tính năng nổi bật
176
+
177
+ - **Kiến trúc Zero-Dependency:** Xây dựng 100% bằng thư viện chuẩn của Node.js (`http`, `fs`, `os`, `path`, `fetch`). Không cần chạy `npm install`, không kéo theo runtime Bun hay binary nặng, khởi động dưới 50ms.
178
+ - **Chuyển đổi Giao thức 2 Chiều Đối xứng:**
179
+ - **4 Định dạng đầu vào (Client):** Anthropic Messages, OpenAI Chat Completions, Codex Responses API, Vertex `generateContent`.
180
+ - **3 Định dạng đầu ra (Upstream):** OpenAI Chat, Anthropic Native, Vertex Native.
181
+ - **Multi-Active CLI Routing:** Kích hoạt cùng lúc Claude Code dùng Profile A, Codex dùng Profile B, Cursor dùng Profile C trên cùng 1 gateway mà không tranh chấp cấu hình.
182
+ - **Trích xuất Thinking & Reasoning Chuyên sâu:** Kiểm chứng thực tế qua 48 tổ hợp mẫu response live. Tự động bóc tách `reasoning_content`, thẻ `<think>`, các block `thought` của Vertex và thought signature thành các `thinking_delta` chuẩn của Anthropic.
183
+ - **Edge Healer Engine (Tự chữa lành tin nhắn):**
184
+ - 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`.
185
+ - Tự động bù lại tham số `thinking` nếu tool ngoài cắt mất trên các reasoning model.
186
+ - 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.
187
+ - **Mở khoá Context 1,000,000 Tokens (1M):** Đi theo `model1M` của profile cho từng tier: tier nào bật 1M thì được `ANTHROPIC_DEFAULT_<TIER>_MODEL=<tier>[1m]` (nên `/model sonnet`, đổi tier hay subagent vẫn giữ 1M; tier không bật thì ở 200K), kèm cửa sổ nén `CLAUDE_CODE_AUTO_COMPACT_WINDOW=900000`, tích hợp badge cảnh báo trực quan cho model không hỗ trợ.
188
+ - **Không làm bẩn `settings.json` (Zero Config Mutation):** Tuyệt đối không lưu endpoint hay key vào `~/.claude/settings.json` (chỉ gỡ đúng các giá trị do chính switcher ghi: `ANTHROPIC_BASE_URL` trỏ vào cổng của nó và `ANTHROPIC_DEFAULT_<TIER>_MODEL=<tier>[1m]`). Dùng launcher flags và biến môi trường động để không bao giờ bị hiện banner cảnh báo đỏ.
189
+ - **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.
190
+ - **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).
191
+
192
+ ---
193
+
194
+ ## Thay đổi trong lần cập nhật này
195
+
196
+ - Dashboard cho máy tính nay có bố cục gọn như một công cụ dành cho lập trình viên. Các điều khiển route rõ hơn, tab dùng được bằng bàn phím, trường model có nhãn đầy đủ và không còn emoji trang trí.
197
+ - Profile Codex dùng ba vai trò theo tài liệu chính thức: `main`, `review` và `subagent`.
198
+ - Shim Codex truyền các khóa cấu hình chính thức: `model`, `review_model`, `agents.default_subagent_model`, `model_context_window` và `model_auto_compact_token_limit`.
199
+ - Profile cũ vẫn đọc được. Giá trị rỗng ở khóa mới sẽ xóa fallback từ khóa cũ.
200
+ - Model Claude Opus được nhận diện khả năng reasoning mà không phụ thuộc số phiên bản.
201
+
202
+ Shim Codex không còn dựa vào `CODEX_MODEL`, `CODEX_MAX_CONTEXT_TOKENS` hoặc `CODEX_AUTO_COMPACT_WINDOW`. Codex không tài liệu hóa các biến môi trường này. Xem [bảng tham chiếu cấu hình](https://developers.openai.com/codex/config-reference/) và [hướng dẫn cấu hình nâng cao](https://developers.openai.com/codex/config-advanced/) chính thức.
203
+
204
+ ---
205
+
206
+ ## Hướng dẫn Bắt đầu Nhanh
207
+
208
+ ### 1. Yêu cầu hệ thống
209
+ - Node.js 18.17 trở lên.
210
+ - Gateway không cần gói npm phụ thuộc nào.
211
+
212
+ ### 2. Cài đặt và cấu hình
213
+
214
+ **Cách A: npm (khuyên dùng)**
215
+ ```bash
216
+ npm install -g llm-switcher
217
+
218
+ # Chép file cấu hình mẫu vào thư mục dữ liệu.
219
+ mkdir -p ~/.llm-switcher
220
+ cp "$(npm root -g)/llm-switcher/config.example.json" ~/.llm-switcher/config.json
221
+ ```
222
+
223
+ Bản cài bằng npm lưu `config.json`, `admin.token` và các file khởi chạy trong `~/.llm-switcher`. Khi nâng cấp, npm chỉ thay thư mục package, nên cấu hình của bạn vẫn còn.
224
+
225
+ **Cách B: git clone**
226
+ ```bash
227
+ git clone https://github.com/louisphamdev/llm-switcher.git
228
+ cd llm-switcher
229
+
230
+ # Copy file cấu hình mẫu (config.json đã được gitignore chặn an toàn)
231
+ cp config.example.json config.json
232
+ ```
233
+
234
+ Bản checkout lưu dữ liệu cạnh mã nguồn như trước. Muốn dùng thư mục khác ở cả hai cách, đặt biến `LLM_SWITCHER_HOME`.
235
+
236
+ Điền URL và API key của các nhà cung cấp vào `config.json`.
237
+
238
+ Bản cài bằng npm chạy lệnh `switch <lệnh>`. Bản checkout chạy `node switch.mjs <lệnh>` hoặc thêm thư mục checkout vào `PATH`.
239
+
240
+ ### 3. Khởi động Gateway
241
+ ```bash
242
+ # Bật gateway chạy ngầm:
243
+ node switch.mjs on
244
+
245
+ # Hoặc chạy trực tiếp trên terminal:
246
+ node proxy.mjs
247
+ ```
248
+
249
+ Repo có sẵn hai launcher cho cùng một script: `switch` cho Linux và macOS, `switch.cmd`
250
+ cho Windows. Thêm thư mục repo vào PATH là `switch <lệnh>` chạy giống nhau trên cả ba.
251
+ Khác biệt giữa các nền tảng, và hai tính năng không chạy ở mọi nơi, nằm trong
252
+ [📖 `docs/cross-platform.md`](docs/cross-platform.md).
253
+
254
+ Mở Bảng điều khiển Web Dashboard tại: **[http://127.0.0.1:3456/ui](http://127.0.0.1:3456/ui)**
255
+
256
+ ---
257
+
258
+ ## Tích hợp vào các Công cụ CLI
259
+
260
+ ### Bộ nạp Biến Môi trường Toàn năng (`env.cmd` / `env.sh`)
261
+
262
+ Mỗi khi bạn chuyển đổi profile, LLM Switcher sẽ tự động sinh file nạp môi trường tương ứng:
263
+
264
+ - **Trên Windows (CMD / PowerShell wrapper):**
265
+ ```cmd
266
+ call "path\to\llm-switcher\env.cmd"
267
+ ```
268
+ - **Trên macOS / Linux (Bash / Zsh):**
269
+ ```bash
270
+ source "path/to/llm-switcher/env.sh"
271
+ ```
272
+
273
+ ---
274
+
275
+ ### Cấu hình cho Claude Code (Windows)
276
+
277
+ 1. Tạo file wrapper trong thư mục PATH (ví dụ `cc-switch.cmd`):
278
+ ```cmd
279
+ @echo off
280
+ node "path\to\llm-switcher\switch.mjs" %*
281
+ ```
282
+
283
+ 2. Thêm đoạn mã sau vào wrapper chính của Claude Code (`claude.cmd` trong thư mục global npm):
284
+ ```cmd
285
+ SETLOCAL EnableDelayedExpansion
286
+ IF EXIST "path\to\llm-switcher\active.flag" (
287
+ SET "ANTHROPIC_BASE_URL=http://127.0.0.1:3456"
288
+ SET "CLAUDE_CODE_DISABLE_UNKNOWN_MODEL_WINDOW_ENFORCEMENT=1"
289
+ )
290
+ IF EXIST "path\to\llm-switcher\1m.flag" (
291
+ SET /P M1M=<"path\to\llm-switcher\1m.flag"
292
+ IF "!M1M!"=="" SET "M1M=opus[1m]"
293
+ SET "ANTHROPIC_MODEL=!M1M!"
294
+ SET "CLAUDE_CODE_AUTO_COMPACT_WINDOW=900000"
295
+ )
296
+ ```
297
+ > Cần `SETLOCAL EnableDelayedExpansion` để `!M1M!` hoạt động. npm ghi đè `claude.cmd` mỗi lần update, nên tốt hơn là tạo wrapper riêng chạy `call "path\to\llm-switcher\env.cmd"` rồi `claude %*`. Chỉ `env.cmd` / `env.sh` mới có các biến `ANTHROPIC_DEFAULT_<TIER>_MODEL=<tier>[1m]` theo từng tier.
298
+
299
+ ---
300
+
301
+ ### Cấu hình ưu tiên Codex
302
+
303
+ Cài shim, đặt thư mục shim lên đầu `PATH`, rồi kích hoạt một profile tương thích với Codex:
304
+
305
+ ```bash
306
+ switch shim install
307
+ export PATH="$HOME/.llm-switcher/bin:$PATH" # Bash hoặc Zsh
308
+ switch codex <profile>
309
+ switch shim status
310
+ codex
311
+ ```
312
+
313
+ Trên Windows, đặt `%USERPROFILE%\.llm-switcher\bin` trước thư mục Codex thật trong `PATH`. Sau đó, mở terminal mới.
314
+
315
+ Shim không sửa `~/.codex/config.toml`. Khi gateway hoạt động, shim truyền các override chính thức sau vào binary Codex thật:
316
+
317
+ | Vai trò trong profile | Khóa cấu hình Codex | Tên mà CLI nhận được |
318
+ |---|---|---|
319
+ | `main` | `model` | `publicModels[0]` |
320
+ | `review` | `review_model` | `publicModels[1]` |
321
+ | `subagent` | `agents.default_subagent_model` | `publicModels[2]` |
322
+
323
+ **Codex không bao giờ nhận tên nội bộ.** Alias `main`, `review`, `subagent` chỉ tồn tại bên trong gateway. CLI nhận tên model chính thức từ `publicModels`, và `mapModel` phân giải ngược từng tên về đúng slot. Đặt `codexRoles` trong profile nếu muốn ghép khác thứ tự danh sách đó.
324
+
325
+ Shim còn truyền `model_catalog_json`. File này được sinh lại từ `publicModels` mỗi lần đổi profile, và màn `/model` đọc chính nó. Màn `/model` không gọi `/v1/models`. `/v1/models` trả cùng các mục đó, và context window của mỗi mục theo `model1M` của slot.
326
+
327
+ Shim cũng truyền `openai_base_url` để route qua gateway cục bộ. Nếu `main` bật context 1M, shim truyền `model_context_window=1000000` và `model_auto_compact_token_limit=900000`. Override dòng lệnh có độ ưu tiên cao hơn cấu hình người dùng và dự án. Hãy chạy lại `switch shim install` sau khi nâng cấp từ bản cũ.
328
+
329
+ ### Chế độ blindfold (tùy chọn)
330
+
331
+ Khi có override base URL, Codex in một dòng ngay trên màn `/model` của nó:
332
+
333
+ ```
334
+ base URL is overridden to http://127.0.0.1:3456/v1. Selecting models may not be supported or work properly.
335
+ ```
336
+
337
+ Blindfold xóa dòng đó. Codex giữ nguyên endpoint chính thức, switcher chặn ở tầng mạng. Không cần quyền admin, không cài chứng chỉ vào system trust store, không sửa `~/.codex/config.toml`.
338
+
339
+ ```bash
340
+ bash blindfold/make-certs.sh chatgpt.com # chạy một lần
341
+ # rồi đặt "blindfold": true trong profile Codex
342
+ switch codex <profile> # gateway khởi động interceptor
343
+ ```
344
+
345
+ Gateway sở hữu interceptor: nó khởi động interceptor khi boot và sau mỗi thay đổi, còn `switch off` dừng nó. Thiếu certificate, hoặc cổng gateway/interceptor bị tiến trình khác giữ, thì `switch` từ chối kích hoạt và không ghi file nào.
346
+
347
+ Hãy đọc [📖 `docs/codex-blindfold.md`](docs/codex-blindfold.md) trước khi bật. Tài liệu nói rõ phạm vi chặn, rủi ro khi giữ private key của CA, và cách quay lại. Mở đầu là ba sơ đồ:
348
+
349
+ - [Request routing](docs/diagrams/blindfold-request-routing.html) — một request, từ CONNECT tới provider
350
+ - [Model name resolution](docs/diagrams/codex-model-name-resolution.html) — CLI thấy tên nào, và tên đó phân giải ở đâu
351
+ - [Lifecycle under switch](docs/diagrams/blindfold-switch-lifecycle.html) — kích hoạt, từ chối, và tắt
352
+
353
+ ---
354
+
355
+ ## Hoạt động Cùng các Tool Nén Token (RTK, Headroom, Ponytail)
356
+
357
+ Nếu bạn sử dụng các tool cắt tỉa prompt như **Headroom**, **Ponytail** hoặc **RTK (Rust Token Killer)**:
358
+ 1. Cấu hình CLI của bạn (Claude Code / Codex) trỏ vào tool nén đó (ví dụ: `http://127.0.0.1:8787`).
359
+ 2. Cấu hình endpoint upstream trong tool nén đó trỏ về **LLM Switcher** (`http://127.0.0.1:3456`).
360
+ 3. **LLM Switcher** sẽ đóng vai trò là trạm kiểm soát cuối cùng trước khi ra internet:
361
+ - **Tự chữa lành đồ thị tin nhắn:** Cứu các lượt `tool_result` mồ côi và gộp các lượt cùng role liên tiếp do tool nén cắt xén bừa bãi gây ra.
362
+ - **Khôi phục thinking bị xóa:** Tự động phát hiện reasoning model và khôi phục lại tham số thinking nếu bị tool ngoài xóa mất để "tiết kiệm token".
363
+ - **Giữ nguyên 1M Context Window:** Tự động mở khoá 1M context và ngưỡng compact `900,000` tokens.
364
+ - **Chuyển đổi 2 chiều:** Kết nối chuẩn sang 9Router, OpenRouter, Vertex, Anthropic.
365
+
366
+ ### Báo cáo Kiểm thử & Đo lường Khả năng Tương thích
367
+
368
+ | Kịch bản Lỗi do Tool Nén Gây Ra | Gọi Thẳng Upstream (Không qua Switcher) | Đi qua LLM Switcher (Healer Engine) |
369
+ |---|---|---|
370
+ | **Turn `tool_result` mồ côi** (Headroom cắt mất turn `tool_use`) | ❌ **HTTP 400 Crash**: `tool_use_id does not correspond to any tool_use` | ✅ **HTTP 200 OK**: Chữa lành thành block văn bản ngữ cảnh an toàn |
371
+ | **Các turn `user` liên tiếp** (Tool nén bỏ sót turn assistant) | ❌ **HTTP 400 Crash**: `roles must alternate` | ✅ **HTTP 200 OK**: Tự động gộp các turn liền kề mượt mà |
372
+ | **Bị xóa tham số `thinking`** (Tool nén triệt tiêu reasoning) | ⚠️ **AI bị giảm chất lượng**: Mất suy luận, ra code ẩu | ✅ **HTTP 200 OK**: Tự động khôi phục thinking budget an toàn |
373
+ | **Role `tool` mồ côi trong Chat API** | ❌ **HTTP 400 Crash**: `tool role must respond to tool_calls` | ✅ **HTTP 200 OK**: Chuyển thành user context hợp lệ |
374
+ | **Header tracing riêng của tool** (`x-rtk-*`, `traceparent`) | ⚠️ Rớt kết nối / cảnh báo header lạ | ✅ **HTTP 200 OK**: Chuyển tiếp trong suốt 100% |
375
+
376
+ Chạy bộ kiểm thử tự động trên máy bạn:
377
+ ```bash
378
+ # Offline (mock upstream, không cần API key): chuyển đổi giao thức, healer, streaming, bảo mật
379
+ npm test
380
+
381
+ # Live (cần gateway đang chạy và upstream thật; tốn token)
382
+ node tests/live-optimizer-interop.mjs
383
+ ```
384
+
385
+ Đọc báo cáo nghiên cứu kỹ thuật chuyên sâu tại: [📖 `docs/TOKEN-OPTIMIZER-INTEROP.md`](docs/TOKEN-OPTIMIZER-INTEROP.md).
386
+
387
+ ---
388
+
389
+ ## Tích hợp Agent Skill & MCP Server (Chống Chạy Bậy)
390
+
391
+ Để đảm bảo các AI coding agent (Claude Code, Cursor, Windsurf, Opencode) và các tiến trình con (sub-agents) **không bao giờ vượt rào gọi thẳng ra internet**, dự án cung cấp 2 giải pháp điều khiển:
392
+
393
+ ### 1. Agent Skill Chuyên dụng (`skills/llm-switcher/SKILL.md`)
394
+ Một Agent Skill theo chuẩn quốc tế hướng dẫn AI model:
395
+ - **Định tuyến bắt buộc:** Mọi lượt gọi LLM và tool nén (Headroom, RTK, Ponytail) BẮT BUỘC phải trỏ về `http://127.0.0.1:3456`.
396
+ - **Cấm sửa `settings.json`:** Tuyệt đối cấm agent ghi đè endpoint vào `~/.claude/settings.json`.
397
+ - **An toàn cho Sub-process:** Tự động nạp `env.cmd` hoặc `env.sh` trước khi spawn lệnh terminal con.
398
+
399
+ Cài đặt vào thư mục skill:
400
+ ```bash
401
+ # Cho Opencode:
402
+ cp -r skills/llm-switcher ~/.config/opencode/skills/
403
+
404
+ # Cho Claude Code:
405
+ cp -r skills/llm-switcher ~/.claude/skills/
406
+ ```
407
+
408
+ ### 2. MCP Server Chuẩn (`mcp.mjs`)
409
+ Một server Model Context Protocol (MCP) chạy qua `stdio` cực nhẹ (Zero-dependency):
410
+ - `switcher_status`: Đọc trạng thái live của các CLI target và cờ 1M.
411
+ - `switcher_audit`: Quét môi trường máy xem có tool nén nào chạy bậy gọi thẳng ra ngoài không.
412
+ - `switcher_switch_profile`: Cho phép agent tự động chuyển đổi profile theo nhu cầu bài toán.
413
+ - `switcher_recent_logs`: Đọc log gần nhất để tự debug khi output bị cắt cụt.
414
+
415
+ LƯU Ý: `switcher_recent_logs` trả 150 ký tự đầu của mỗi prompt gần đây, từ mọi client đã dùng gateway. Agent gọi tool này đọc được chúng.
416
+
417
+ Thêm vào cấu hình MCP (ví dụ `opencode.jsonc`, `claude_desktop_config.json`, hoặc Cursor):
418
+ ```json
419
+ "mcp": {
420
+ "llm-switcher": {
421
+ "type": "local",
422
+ "command": ["node", "path/to/llm-switcher/mcp.mjs"],
423
+ "enabled": true
424
+ }
425
+ }
426
+ ```
427
+
428
+ ---
429
+
430
+ ## Bảng Tra cứu Lệnh CLI (`switch`)
431
+
432
+ ```bash
433
+ switch ui # Mở giao diện Web UI trên trình duyệt
434
+ switch status # Xem trạng thái kích hoạt của tất cả các CLI
435
+ switch doctor # Quét & thanh tra toàn bộ môi trường, settings và định tuyến
436
+ switch on [profile] # Khởi động gateway và kích hoạt profile cho mọi target tương thích
437
+ switch <profile> # Kích hoạt profile cho tất cả các target tương thích
438
+ switch claude <profile> # Đặt profile kích hoạt riêng cho Claude Code
439
+ switch codex <profile> # Đặt profile kích hoạt riêng cho Codex
440
+ switch openai <profile> # Đặt profile kích hoạt riêng cho OpenAI Chat
441
+ switch vertex <profile> # Đặt profile kích hoạt riêng cho Vertex / Gemini
442
+ switch port <number> # Đổi cổng gateway (tự restart nếu đang chạy)
443
+ switch service install # Cài đặt gateway thành service chạy ngầm tự bật cùng máy
444
+ switch service uninstall # Gỡ bỏ service chạy ngầm
445
+ switch shim install # Route phiên Claude và Codex mới qua gateway
446
+ switch shim status # Kiểm tra shim + phát hiện phiên đang chạy ngoài gateway
447
+ switch shim uninstall # Gỡ shim khỏi launcher
448
+ switch off [target] # Tắt gateway (toàn bộ hoặc từng CLI) và quay về gói Official
449
+ switch contract-probe [--model m] # Chạy các biến thể contract-lab qua gateway
450
+ switch contract-check # Chuyển các findings hợp đồng còn mở thành test case
451
+ ```
452
+
453
+
454
+ Service không chạy trong shell của bạn. Vì vậy `switch service install` chép `CLAUDE_CONFIG_DIR`, `LLM_SWITCHER_CONFIG`, `LLM_SWITCHER_STATE_DIR` và `LLM_SWITCHER_BLINDFOLD_CERTS` vào unit systemd hoặc plist launchd khi các biến này có giá trị. Task Windows không mang được các biến này; hãy đặt chúng thành biến môi trường User. Nếu file định nghĩa đã cài khác file mới (ví dụ đã sửa tay), file cũ được giữ lại thành `<file>.bak`. Trên Windows, task được tạo từ file XML, nên đường dẫn có dấu cách không cần quote thêm và task không bị giới hạn thời gian chạy. Đường Windows này chưa được test trên Windows.
455
+ ### Phiên mở lại (`--resume`) và cơ chế shim — quan trọng
456
+
457
+ `switch on` ghi `env.sh` / `env.cmd` và **chủ động xoá** các biến proxy khỏi
458
+ `~/.claude/settings.json` để Claude Code không hiện banner "custom API". Hệ quả: một CLI
459
+ khởi chạy từ shell **chưa** source `env.sh` sẽ không có `ANTHROPIC_BASE_URL`, nên gọi
460
+ thẳng nhà cung cấp và bỏ qua gateway (mất Healer, mất 1M, mất quota gộp). Trường hợp kinh
461
+ điển là `claude --resume` mở lại phiên cũ trong terminal sạch.
462
+
463
+ Shim bịt đúng lỗ đó. Nó cài wrapper nhỏ vào `~/.llm-switcher/bin`, wrapper source `env.sh`
464
+ rồi `exec` binary thật:
465
+
466
+ ```bash
467
+ switch shim install
468
+ export PATH="$HOME/.llm-switcher/bin:$PATH" # thêm vào ~/.zshrc hoặc ~/.bashrc
469
+ switch shim status # kiểm tra lại
470
+ ```
471
+
472
+ Cách hoạt động:
473
+
474
+ - **Gateway BẬT** → wrapper nạp env, nên mọi lần gọi (kể cả `--resume`) đều qua gateway.
475
+ - Với Codex, wrapper truyền các khóa vai trò model và context chính thức bằng `--config`.
476
+ Wrapper không phụ thuộc vào các biến `CODEX_*` không được hỗ trợ.
477
+ - **Gateway TẮT** (không có `active.flag`) → wrapper trong suốt hoàn toàn, chạy binary thật
478
+ nguyên trạng, không ép định tuyến.
479
+ - Wrapper tìm binary thật sau khi **loại thư mục shim khỏi `PATH`**, nên không bao giờ tự
480
+ gọi đệ quy chính nó. Không tìm thấy binary thật thì thoát mã `127` kèm thông báo rõ ràng,
481
+ không im lặng.
482
+ - Không đụng `settings.json`, nên **không hiện banner cảnh báo**.
483
+
484
+ `switch on` tự cài shim và nhắc nếu `PATH` còn thiếu dòng export. Ngoài ra `switch doctor`
485
+ và `switch shim status` còn quét các tiến trình `claude`/`codex` đang chạy và cảnh báo
486
+ tiến trình nào thiếu `ANTHROPIC_BASE_URL` — phiên đó phải thoát và mở lại từ shell có shim
487
+ trong `PATH`.
488
+
489
+ ---
490
+
491
+ ## Cấu trúc Cấu hình (`config.json`)
492
+
493
+ ```jsonc
494
+ {
495
+ "port": 3456,
496
+ "activeProfile": "9router",
497
+ "activeProfiles": {
498
+ "anthropic": "9router", // Profile active cho Claude Code (/v1/messages)
499
+ "responses": "codex-profile", // Profile active cho Codex (/v1/responses)
500
+ "openai-chat": "9router", // Profile active cho OpenAI Chat
501
+ "vertex": "gemini-profile" // Profile active cho Vertex / Gemini
502
+ },
503
+ "profiles": {
504
+ "9router": {
505
+ "name": "9Router Cloud",
506
+ "mode": "convert", // hybrid | convert | direct
507
+ "inFormat": "auto", // auto | anthropic | openai-chat | responses | vertex
508
+ "outFormat": "openai-chat", // openai-chat | anthropic | vertex
509
+ "thinkingMode": "auto", // auto | native | off (xem Tuỳ chọn Nâng cao)
510
+ "baseURL": "https://api.9router.com/v1",
511
+ "apiKey": "sk-...",
512
+ "defaultModels": {
513
+ "opus": "ag/claude-opus-4-6-thinking",
514
+ "sonnet": "ag/gemini-3.7-flash",
515
+ "haiku": "ag/gemini-3.6-flash-medium",
516
+ "fable": "ag/gemini-3.8-flash"
517
+ },
518
+ "model1M": {
519
+ "opus": true,
520
+ "sonnet": true,
521
+ "haiku": false,
522
+ "fable": true
523
+ }
524
+ }
525
+ },
526
+ "debug": false,
527
+ "contractLab": {
528
+ "url": "https://intact.example.com",
529
+ "apiKey": "sk-...",
530
+ "enabled": false
531
+ }
532
+ }
533
+ ```
534
+
535
+ ### Contract lab
536
+
537
+ Contract lab tìm các field mà converter làm mất. Mặc định tính năng này tắt.
538
+
539
+ - Đặt `contractLab: {url, apiKey, enabled}` trong `config.json`. Nếu `enabled` là `true`, gateway gửi một phần các lượt trao đổi hoàn chỉnh lên intact.
540
+ - `switch contract-probe [--model m]` gửi sáu request thử cho mỗi model và mỗi format qua gateway.
541
+ - `switch contract-check` lấy các finding còn mở từ intact và ghi một file test cho mỗi field bị mất.
542
+
543
+ ---
544
+
545
+ ## Tuỳ chọn Nâng cao
546
+
547
+ | Tuỳ chọn | Mô tả |
548
+ |---|---|
549
+ | `LLM_SWITCHER_CONFIG=/path/config.json` | Dùng file cấu hình nằm ngoài repo (proxy, `switch` và `mcp.mjs` đều hỗ trợ). |
550
+ | `--port <n>` / `LLM_SWITCHER_PORT` | Ghi đè cổng lắng nghe (ưu tiên: flag > env > `config.port`). |
551
+ | Header `x-llm-profile: <key>` (tên khác `x-profile`) hoặc `?profile=<key>` | Định tuyến riêng 1 request qua profile chỉ định. Key không tồn tại trả HTTP 400 thay vì âm thầm dùng profile khác. |
552
+ | `profile.thinkingMode` | `auto` (mặc định, cho gateway như 9Router): phục hồi thinking bị xoá, chèn hướng dẫn `<think>` cho model không có reasoning, gửi `thinking` + `reasoning_effort`. `native` (API OpenAI nghiêm ngặt): chỉ gửi `reasoning_effort` khi client yêu cầu, không sửa prompt, dùng `max_completion_tokens`. `off`: không bao giờ gửi tham số reasoning. |
553
+ | `profile.endpoints.countTokens` | Ghi đè URL `count_tokens` của Anthropic. |
554
+ | `profile.endpoints` | Ghi đè URL upstream theo từng format: `{ "openai-chat": "...", "anthropic": "...", "vertex": "https://.../models/{model}:{action}" }`. |
555
+ | `LLM_SWITCHER_STATE_DIR` | Chuyển file launcher và log ra khỏi thư mục checkout. Test dùng biến này; shim đọc thư mục đã đặt lúc cài shim. |
556
+ | `CLAUDE_CONFIG_DIR` | Được tôn trọng khi tìm `settings.json` của Claude Code. |
557
+
558
+ ## Mô hình Bảo mật
559
+
560
+ - Gateway chỉ lắng nghe `127.0.0.1` và từ chối request có `Host` không phải loopback (chống DNS rebinding) hoặc `Origin` không phải chính dashboard (chống CSRF).
561
+ - Admin API (`/api/*`) bắt buộc header `x-llm-switcher-token`. Gateway tạo token trong file `admin.token`, cạnh `config.json`, với mode 0600. `switch ui` mở dashboard kèm token này, MCP server đọc token từ file. Dashboard giữ token trong `sessionStorage` của đúng tab đó, nên một trang do tài khoản khác phục vụ trên cùng cổng lúc gateway tắt không đọc được token; sau khi khởi động lại trình duyệt, mở dashboard bằng `switch ui` lần nữa. `/v1/*` và `/health` không cần token.
562
+ - API key không bao giờ gửi xuống trình duyệt: `/api/status` trả profile đã che key, dashboard giữ nguyên key đã lưu nếu bạn không nhập key mới. Key đã lưu chỉ được gửi tới `baseURL` và `endpoints` đã lưu của chính profile đó. Lần lưu nào đổi một trong hai thì phải nhập lại key.
563
+ - Mỗi thay đổi từ dashboard mang theo revision của config mà trang đã tải. Nếu tab khác, CLI hoặc MCP server đã lưu trước đó, gateway trả 409 và trang tải lại thay vì ghi đè thay đổi kia.
564
+ - Credential của client (`x-api-key`, `authorization`, `x-goog-api-key`) **không** được chuyển tiếp lên upstream. Các header `x-*` khác, `traceparent` và `tracestate` được chuyển tiếp. Gateway bỏ header điều khiển của chính nó (`x-profile`, `x-llm-profile`) và header định danh mạng (`x-forwarded-*`, `x-real-ip`).
565
+ - `config.json` được ghi atomic với mode 0600. `~/.claude/settings.json` chỉ bị ghi lại để gỡ các giá trị do chính switcher ghi. `ANTHROPIC_AUTH_TOKEN`, các key `*_MODEL_NAME` và giá trị model/URL của bạn được giữ nguyên, và `switch` in tên từng giá trị đã gỡ.
566
+
567
+ ## Ghi chú Tương thích
568
+
569
+ - **Direct passthrough Anthropic:** request hợp lệ được chuyển tiếp nguyên bytes (giữ thinking signature, `cache_control`, document). Request lỗi được healer native của Anthropic sửa tại chỗ: `tool_result` mồ côi → text, thiếu `tool_result` → placeholder, đưa result lên đầu user turn.
570
+ - **Chữ ký thinking:** thinking block sinh ra khi convert mang chữ ký của gateway (`reasoning-sig`, hoặc chữ ký provider khác có prefix `lsw1.`) và bị gỡ trước khi tới Anthropic. Nếu việc gỡ làm vòng tool đang dở thiếu thinking block mà Anthropic bắt buộc, gateway tắt thinking cho riêng request đó thay vì để lỗi.
571
+ - **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ỏ.
572
+ - **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).
573
+ - **`/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).
574
+
575
+ ## Nghiên cứu & Ma trận Giao thức Response
576
+
577
+ Dữ liệu khảo sát chi tiết và kết quả test live 48 biến thể response được ghi lại tại:
578
+ - 📖 [`docs/LLM-RESPONSE-MATRIX.md`](docs/LLM-RESPONSE-MATRIX.md) — Báo cáo khảo sát 48 biến thể live trên 8 họ model.
579
+ - 📊 [`docs/response-matrix.json`](docs/response-matrix.json) — Schema cấu trúc dữ liệu response machine-readable.
580
+
581
+ ---
582
+
583
+ ## Giấy phép
584
+
585
+ Phát hành theo giấy phép MIT © 2026 LLM Switcher Contributors.