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.
- package/.gitattributes +16 -0
- package/LICENSE +21 -0
- package/README.md +587 -0
- package/README.vi.md +585 -0
- package/blindfold/blindfold.mjs +633 -0
- package/blindfold/make-certs.sh +88 -0
- package/blindfold/wsframe.mjs +176 -0
- package/codex-catalog-template.json +1 -0
- package/config.example.json +84 -0
- package/contract-exclusions.json +41 -0
- package/contract.mjs +561 -0
- package/docs/LLM-RESPONSE-MATRIX.md +165 -0
- package/docs/TOKEN-OPTIMIZER-INTEROP.md +110 -0
- package/docs/codex-blindfold.md +214 -0
- package/docs/cross-platform.md +136 -0
- package/docs/diagrams/blindfold-request-routing.html +14972 -0
- package/docs/diagrams/blindfold-request-routing.sequence.json +175 -0
- package/docs/diagrams/blindfold-switch-lifecycle.html +14958 -0
- package/docs/diagrams/blindfold-switch-lifecycle.lifecycle.json +159 -0
- package/docs/diagrams/codex-model-name-resolution.html +15005 -0
- package/docs/diagrams/codex-model-name-resolution.workflow.json +71 -0
- package/docs/response-matrix.json +1131 -0
- package/formats.mjs +2308 -0
- package/mcp.mjs +340 -0
- package/package.json +36 -0
- package/proxy.mjs +1743 -0
- package/service.mjs +132 -0
- package/shim.mjs +292 -0
- package/skills/llm-switcher/SKILL.md +88 -0
- package/state.mjs +978 -0
- package/switch +5 -0
- package/switch.cmd +2 -0
- package/switch.mjs +930 -0
- package/tests/blindfold.test.mjs +307 -0
- package/tests/blindfold.wire.test.mjs +170 -0
- package/tests/contract/run.test.mjs +214 -0
- package/tests/contract-check.test.mjs +458 -0
- package/tests/contract-lab.test.mjs +755 -0
- package/tests/datadir.test.mjs +37 -0
- package/tests/formats.test.mjs +794 -0
- package/tests/gateway.e2e.test.mjs +999 -0
- package/tests/helpers.mjs +24 -0
- package/tests/lifecycle.test.mjs +416 -0
- package/tests/live-optimizer-interop.mjs +205 -0
- package/tests/mcp.test.mjs +91 -0
- package/tests/service.test.mjs +69 -0
- package/tests/shim.test.mjs +228 -0
- package/tests/state.test.mjs +675 -0
- package/tests/switch.test.mjs +156 -0
- package/tests/wsframe.test.mjs +154 -0
- 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.
|