router-maestro 0.1.3__tar.gz → 0.1.5__tar.gz
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.
- router_maestro-0.1.5/.markdownlint.json +4 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/CLAUDE.md +2 -2
- {router_maestro-0.1.3 → router_maestro-0.1.5}/PKG-INFO +26 -25
- {router_maestro-0.1.3 → router_maestro-0.1.5}/README.md +25 -24
- {router_maestro-0.1.3 → router_maestro-0.1.5}/pyproject.toml +1 -1
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/__init__.py +1 -1
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/config.py +55 -10
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/routing/router.py +2 -2
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/routes/anthropic.py +6 -5
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/utils/tokens.py +10 -2
- {router_maestro-0.1.3 → router_maestro-0.1.5}/uv.lock +1 -1
- {router_maestro-0.1.3 → router_maestro-0.1.5}/.env.example +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/.gitignore +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/Dockerfile +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/LICENSE +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/Makefile +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/docker-compose.yml +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/docs/deployment.md +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/__main__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/auth/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/auth/github_oauth.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/auth/manager.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/auth/storage.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/auth.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/client.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/context.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/main.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/model.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/cli/server.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/contexts.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/paths.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/priorities.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/providers.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/server.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/config/settings.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/providers/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/providers/anthropic.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/providers/base.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/providers/copilot.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/providers/openai.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/providers/openai_compat.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/routing/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/app.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/middleware/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/middleware/auth.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/oauth_sessions.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/routes/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/routes/admin.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/routes/chat.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/routes/models.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/schemas/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/schemas/admin.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/schemas/anthropic.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/schemas/openai.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/translation.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/utils/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/utils/logging.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/__init__.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/test_auth.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/test_config.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/test_providers.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/test_router.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/test_translation.py +0 -0
- {router_maestro-0.1.3 → router_maestro-0.1.5}/tests/test_utils.py +0 -0
|
@@ -57,7 +57,7 @@ Router-Maestro is a multi-model routing system that exposes both OpenAI-compatib
|
|
|
57
57
|
- `schemas/` - Pydantic models for both API formats
|
|
58
58
|
|
|
59
59
|
**CLI (`src/router_maestro/cli/`)**
|
|
60
|
-
- Typer-based CLI with subcommands: `server`, `auth`, `model`, `context`, `config
|
|
60
|
+
- Typer-based CLI with subcommands: `server`, `auth`, `model`, `context`, `config`
|
|
61
61
|
- Each subcommand in its own module registered in `main.py`
|
|
62
62
|
|
|
63
63
|
### Data Flow
|
|
@@ -72,7 +72,7 @@ Router-Maestro is a multi-model routing system that exposes both OpenAI-compatib
|
|
|
72
72
|
|
|
73
73
|
Configuration and data files follow XDG conventions:
|
|
74
74
|
- **Config** (`~/.config/router-maestro/`): `providers.json`, `priorities.json`, `contexts.json`
|
|
75
|
-
- **Data** (`~/.local/share/router-maestro/`): `auth.json`, `server.json
|
|
75
|
+
- **Data** (`~/.local/share/router-maestro/`): `auth.json`, `server.json`
|
|
76
76
|
|
|
77
77
|
### Model Identification
|
|
78
78
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: router-maestro
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: Multi-model routing and load balancing system with OpenAI-compatible API
|
|
5
5
|
Author-email: Kanwen Li <likanwen@icloud.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -132,8 +132,8 @@ router-maestro config claude-code
|
|
|
132
132
|
|
|
133
133
|
Models are identified using the format `{provider}/{model-id}`:
|
|
134
134
|
|
|
135
|
-
| Example
|
|
136
|
-
|
|
135
|
+
| Example | Description |
|
|
136
|
+
| --------------------------------- | ----------------------------------- |
|
|
137
137
|
| `github-copilot/gpt-4o` | GPT-4o via GitHub Copilot |
|
|
138
138
|
| `github-copilot/claude-sonnet-4` | Claude Sonnet 4 via GitHub Copilot |
|
|
139
139
|
| `openai/gpt-4-turbo` | GPT-4 Turbo via OpenAI |
|
|
@@ -164,8 +164,8 @@ router-maestro model priority list
|
|
|
164
164
|
|
|
165
165
|
**Fallback** triggers when a request fails with a retryable error (429, 5xx):
|
|
166
166
|
|
|
167
|
-
| Strategy
|
|
168
|
-
|
|
167
|
+
| Strategy | Behavior |
|
|
168
|
+
| ------------ | ------------------------------------ |
|
|
169
169
|
| `priority` | Try next model in priorities list |
|
|
170
170
|
| `same-model` | Try same model on different provider |
|
|
171
171
|
| `none` | Fail immediately |
|
|
@@ -195,8 +195,8 @@ POST /v1/chat/completions {"model": "anthropic/claude-3-5-sonnet", ...}
|
|
|
195
195
|
|
|
196
196
|
A **context** is a named connection profile that stores an endpoint URL and API key. Contexts let you manage multiple Router-Maestro deployments from a single CLI.
|
|
197
197
|
|
|
198
|
-
| Context
|
|
199
|
-
|
|
198
|
+
| Context | Use Case |
|
|
199
|
+
| -------- | ------------------------------------------ |
|
|
200
200
|
| `local` | Default context for `router-maestro server start` |
|
|
201
201
|
| `docker` | Connect to a local Docker container |
|
|
202
202
|
| `my-vps` | Connect to a remote VPS deployment |
|
|
@@ -216,25 +216,25 @@ router-maestro model list
|
|
|
216
216
|
|
|
217
217
|
### Server
|
|
218
218
|
|
|
219
|
-
| Command
|
|
220
|
-
|
|
221
|
-
| `server start --port 8080` | Start the server
|
|
219
|
+
| Command | Description |
|
|
220
|
+
| -------------------------- | ------------------ |
|
|
221
|
+
| `server start --port 8080` | Start the server |
|
|
222
222
|
| `server stop` | Stop the server |
|
|
223
223
|
| `server info` | Show server status |
|
|
224
224
|
|
|
225
225
|
### Authentication
|
|
226
226
|
|
|
227
|
-
| Command
|
|
228
|
-
|
|
229
|
-
| `auth login [provider]` | Authenticate with a provider
|
|
227
|
+
| Command | Description |
|
|
228
|
+
| ----------------------- | ------------------------------ |
|
|
229
|
+
| `auth login [provider]` | Authenticate with a provider |
|
|
230
230
|
| `auth logout <provider>` | Remove authentication |
|
|
231
231
|
| `auth list` | List authenticated providers |
|
|
232
232
|
|
|
233
233
|
### Models
|
|
234
234
|
|
|
235
|
-
| Command
|
|
236
|
-
|
|
237
|
-
| `model list`
|
|
235
|
+
| Command | Description |
|
|
236
|
+
| ---------------------------------- | ---------------------- |
|
|
237
|
+
| `model list` | List available models |
|
|
238
238
|
| `model refresh` | Refresh models cache |
|
|
239
239
|
| `model priority list` | Show priorities |
|
|
240
240
|
| `model priority <model> --position <n>` | Set priority |
|
|
@@ -242,9 +242,9 @@ router-maestro model list
|
|
|
242
242
|
|
|
243
243
|
### Contexts (Remote Management)
|
|
244
244
|
|
|
245
|
-
| Command
|
|
246
|
-
|
|
247
|
-
| `context show`
|
|
245
|
+
| Command | Description |
|
|
246
|
+
| ---------------------------------------------------- | -------------------- |
|
|
247
|
+
| `context show` | Show current context |
|
|
248
248
|
| `context list` | List all contexts |
|
|
249
249
|
| `context set <name>` | Switch context |
|
|
250
250
|
| `context add <name> --endpoint <url> --api-key <key>` | Add remote context |
|
|
@@ -252,8 +252,8 @@ router-maestro model list
|
|
|
252
252
|
|
|
253
253
|
### Other
|
|
254
254
|
|
|
255
|
-
| Command
|
|
256
|
-
|
|
255
|
+
| Command | Description |
|
|
256
|
+
| -------------------- | ----------------------------- |
|
|
257
257
|
| `config claude-code` | Generate Claude Code settings |
|
|
258
258
|
|
|
259
259
|
## API Reference
|
|
@@ -301,8 +301,8 @@ POST /api/admin/models/refresh # Refresh model cache
|
|
|
301
301
|
|
|
302
302
|
Following XDG Base Directory specification:
|
|
303
303
|
|
|
304
|
-
| Type
|
|
305
|
-
|
|
304
|
+
| Type | Path | Contents |
|
|
305
|
+
| ---------- | ---------------------------------- | ---------------------------- |
|
|
306
306
|
| **Config** | `~/.config/router-maestro/` | |
|
|
307
307
|
| | `providers.json` | Custom provider definitions |
|
|
308
308
|
| | `priorities.json` | Model priorities and fallback |
|
|
@@ -340,8 +340,8 @@ export OLLAMA_API_KEY="sk-..."
|
|
|
340
340
|
|
|
341
341
|
Configuration files are automatically reloaded every 5 minutes:
|
|
342
342
|
|
|
343
|
-
| File
|
|
344
|
-
|
|
343
|
+
| File | Auto-Reload |
|
|
344
|
+
| ------------------ | ---------------- |
|
|
345
345
|
| `priorities.json` | ✓ (5 min) |
|
|
346
346
|
| `providers.json` | ✓ (5 min) |
|
|
347
347
|
| `auth.json` | Requires restart |
|
|
@@ -405,6 +405,7 @@ router-maestro model list
|
|
|
405
405
|
The Docker Compose setup includes Traefik for automatic HTTPS via Let's Encrypt with DNS challenge.
|
|
406
406
|
|
|
407
407
|
For detailed configuration options including:
|
|
408
|
+
|
|
408
409
|
- Other DNS providers (Route53, DigitalOcean, etc.)
|
|
409
410
|
- HTTP challenge setup
|
|
410
411
|
- Traefik dashboard configuration
|
|
@@ -95,8 +95,8 @@ router-maestro config claude-code
|
|
|
95
95
|
|
|
96
96
|
Models are identified using the format `{provider}/{model-id}`:
|
|
97
97
|
|
|
98
|
-
| Example
|
|
99
|
-
|
|
98
|
+
| Example | Description |
|
|
99
|
+
| --------------------------------- | ----------------------------------- |
|
|
100
100
|
| `github-copilot/gpt-4o` | GPT-4o via GitHub Copilot |
|
|
101
101
|
| `github-copilot/claude-sonnet-4` | Claude Sonnet 4 via GitHub Copilot |
|
|
102
102
|
| `openai/gpt-4-turbo` | GPT-4 Turbo via OpenAI |
|
|
@@ -127,8 +127,8 @@ router-maestro model priority list
|
|
|
127
127
|
|
|
128
128
|
**Fallback** triggers when a request fails with a retryable error (429, 5xx):
|
|
129
129
|
|
|
130
|
-
| Strategy
|
|
131
|
-
|
|
130
|
+
| Strategy | Behavior |
|
|
131
|
+
| ------------ | ------------------------------------ |
|
|
132
132
|
| `priority` | Try next model in priorities list |
|
|
133
133
|
| `same-model` | Try same model on different provider |
|
|
134
134
|
| `none` | Fail immediately |
|
|
@@ -158,8 +158,8 @@ POST /v1/chat/completions {"model": "anthropic/claude-3-5-sonnet", ...}
|
|
|
158
158
|
|
|
159
159
|
A **context** is a named connection profile that stores an endpoint URL and API key. Contexts let you manage multiple Router-Maestro deployments from a single CLI.
|
|
160
160
|
|
|
161
|
-
| Context
|
|
162
|
-
|
|
161
|
+
| Context | Use Case |
|
|
162
|
+
| -------- | ------------------------------------------ |
|
|
163
163
|
| `local` | Default context for `router-maestro server start` |
|
|
164
164
|
| `docker` | Connect to a local Docker container |
|
|
165
165
|
| `my-vps` | Connect to a remote VPS deployment |
|
|
@@ -179,25 +179,25 @@ router-maestro model list
|
|
|
179
179
|
|
|
180
180
|
### Server
|
|
181
181
|
|
|
182
|
-
| Command
|
|
183
|
-
|
|
184
|
-
| `server start --port 8080` | Start the server
|
|
182
|
+
| Command | Description |
|
|
183
|
+
| -------------------------- | ------------------ |
|
|
184
|
+
| `server start --port 8080` | Start the server |
|
|
185
185
|
| `server stop` | Stop the server |
|
|
186
186
|
| `server info` | Show server status |
|
|
187
187
|
|
|
188
188
|
### Authentication
|
|
189
189
|
|
|
190
|
-
| Command
|
|
191
|
-
|
|
192
|
-
| `auth login [provider]` | Authenticate with a provider
|
|
190
|
+
| Command | Description |
|
|
191
|
+
| ----------------------- | ------------------------------ |
|
|
192
|
+
| `auth login [provider]` | Authenticate with a provider |
|
|
193
193
|
| `auth logout <provider>` | Remove authentication |
|
|
194
194
|
| `auth list` | List authenticated providers |
|
|
195
195
|
|
|
196
196
|
### Models
|
|
197
197
|
|
|
198
|
-
| Command
|
|
199
|
-
|
|
200
|
-
| `model list`
|
|
198
|
+
| Command | Description |
|
|
199
|
+
| ---------------------------------- | ---------------------- |
|
|
200
|
+
| `model list` | List available models |
|
|
201
201
|
| `model refresh` | Refresh models cache |
|
|
202
202
|
| `model priority list` | Show priorities |
|
|
203
203
|
| `model priority <model> --position <n>` | Set priority |
|
|
@@ -205,9 +205,9 @@ router-maestro model list
|
|
|
205
205
|
|
|
206
206
|
### Contexts (Remote Management)
|
|
207
207
|
|
|
208
|
-
| Command
|
|
209
|
-
|
|
210
|
-
| `context show`
|
|
208
|
+
| Command | Description |
|
|
209
|
+
| ---------------------------------------------------- | -------------------- |
|
|
210
|
+
| `context show` | Show current context |
|
|
211
211
|
| `context list` | List all contexts |
|
|
212
212
|
| `context set <name>` | Switch context |
|
|
213
213
|
| `context add <name> --endpoint <url> --api-key <key>` | Add remote context |
|
|
@@ -215,8 +215,8 @@ router-maestro model list
|
|
|
215
215
|
|
|
216
216
|
### Other
|
|
217
217
|
|
|
218
|
-
| Command
|
|
219
|
-
|
|
218
|
+
| Command | Description |
|
|
219
|
+
| -------------------- | ----------------------------- |
|
|
220
220
|
| `config claude-code` | Generate Claude Code settings |
|
|
221
221
|
|
|
222
222
|
## API Reference
|
|
@@ -264,8 +264,8 @@ POST /api/admin/models/refresh # Refresh model cache
|
|
|
264
264
|
|
|
265
265
|
Following XDG Base Directory specification:
|
|
266
266
|
|
|
267
|
-
| Type
|
|
268
|
-
|
|
267
|
+
| Type | Path | Contents |
|
|
268
|
+
| ---------- | ---------------------------------- | ---------------------------- |
|
|
269
269
|
| **Config** | `~/.config/router-maestro/` | |
|
|
270
270
|
| | `providers.json` | Custom provider definitions |
|
|
271
271
|
| | `priorities.json` | Model priorities and fallback |
|
|
@@ -303,8 +303,8 @@ export OLLAMA_API_KEY="sk-..."
|
|
|
303
303
|
|
|
304
304
|
Configuration files are automatically reloaded every 5 minutes:
|
|
305
305
|
|
|
306
|
-
| File
|
|
307
|
-
|
|
306
|
+
| File | Auto-Reload |
|
|
307
|
+
| ------------------ | ---------------- |
|
|
308
308
|
| `priorities.json` | ✓ (5 min) |
|
|
309
309
|
| `providers.json` | ✓ (5 min) |
|
|
310
310
|
| `auth.json` | Requires restart |
|
|
@@ -368,6 +368,7 @@ router-maestro model list
|
|
|
368
368
|
The Docker Compose setup includes Traefik for automatic HTTPS via Let's Encrypt with DNS challenge.
|
|
369
369
|
|
|
370
370
|
For detailed configuration options including:
|
|
371
|
+
|
|
371
372
|
- Other DNS providers (Route53, DigitalOcean, etc.)
|
|
372
373
|
- HTTP challenge setup
|
|
373
374
|
- Traefik dashboard configuration
|
|
@@ -15,9 +15,17 @@ from rich.table import Table
|
|
|
15
15
|
from router_maestro.cli.client import ServerNotRunningError, get_admin_client
|
|
16
16
|
from router_maestro.config.server import get_current_context_api_key
|
|
17
17
|
|
|
18
|
-
app = typer.Typer(
|
|
18
|
+
app = typer.Typer(invoke_without_command=True)
|
|
19
19
|
console = Console()
|
|
20
20
|
|
|
21
|
+
# Available CLI tools for configuration
|
|
22
|
+
CLI_TOOLS = {
|
|
23
|
+
"claude-code": {
|
|
24
|
+
"name": "Claude Code",
|
|
25
|
+
"description": "Generate settings.json for Claude Code CLI",
|
|
26
|
+
},
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
|
|
22
30
|
def get_claude_code_paths() -> dict[str, Path]:
|
|
23
31
|
"""Get Claude Code settings paths."""
|
|
@@ -27,6 +35,33 @@ def get_claude_code_paths() -> dict[str, Path]:
|
|
|
27
35
|
}
|
|
28
36
|
|
|
29
37
|
|
|
38
|
+
@app.callback(invoke_without_command=True)
|
|
39
|
+
def config_callback(ctx: typer.Context) -> None:
|
|
40
|
+
"""Generate configuration for CLI tools (interactive selection if not specified)."""
|
|
41
|
+
if ctx.invoked_subcommand is not None:
|
|
42
|
+
return
|
|
43
|
+
|
|
44
|
+
# Interactive selection
|
|
45
|
+
console.print("\n[bold]Available CLI tools:[/bold]")
|
|
46
|
+
tools = list(CLI_TOOLS.items())
|
|
47
|
+
for i, (key, info) in enumerate(tools, 1):
|
|
48
|
+
console.print(f" {i}. {info['name']} - {info['description']}")
|
|
49
|
+
|
|
50
|
+
console.print()
|
|
51
|
+
choice = Prompt.ask(
|
|
52
|
+
"Select tool to configure",
|
|
53
|
+
choices=[str(i) for i in range(1, len(tools) + 1)],
|
|
54
|
+
default="1",
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
idx = int(choice) - 1
|
|
58
|
+
tool_key = tools[idx][0]
|
|
59
|
+
|
|
60
|
+
# Dispatch to the appropriate command
|
|
61
|
+
if tool_key == "claude-code":
|
|
62
|
+
claude_code_config()
|
|
63
|
+
|
|
64
|
+
|
|
30
65
|
@app.command(name="claude-code")
|
|
31
66
|
def claude_code_config() -> None:
|
|
32
67
|
"""Generate Claude Code CLI settings.json for router-maestro."""
|
|
@@ -104,19 +139,29 @@ def claude_code_config() -> None:
|
|
|
104
139
|
)
|
|
105
140
|
anthropic_url = f"{base_url}/api/anthropic"
|
|
106
141
|
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
114
|
-
}
|
|
142
|
+
env_config = {
|
|
143
|
+
"ANTHROPIC_BASE_URL": anthropic_url,
|
|
144
|
+
"ANTHROPIC_AUTH_TOKEN": auth_token,
|
|
145
|
+
"ANTHROPIC_MODEL": main_model,
|
|
146
|
+
"ANTHROPIC_SMALL_FAST_MODEL": fast_model,
|
|
147
|
+
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
|
|
115
148
|
}
|
|
116
149
|
|
|
150
|
+
# Load existing settings to preserve other sections (e.g., MCP servers)
|
|
151
|
+
existing_config: dict = {}
|
|
152
|
+
if settings_path.exists():
|
|
153
|
+
try:
|
|
154
|
+
with open(settings_path, encoding="utf-8") as f:
|
|
155
|
+
existing_config = json.load(f)
|
|
156
|
+
except (json.JSONDecodeError, OSError):
|
|
157
|
+
pass # If file is corrupted, start fresh
|
|
158
|
+
|
|
159
|
+
# Merge: update env section while preserving other sections
|
|
160
|
+
existing_config["env"] = env_config
|
|
161
|
+
|
|
117
162
|
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
118
163
|
with open(settings_path, "w", encoding="utf-8") as f:
|
|
119
|
-
json.dump(
|
|
164
|
+
json.dump(existing_config, f, indent=2)
|
|
120
165
|
|
|
121
166
|
console.print(
|
|
122
167
|
Panel(
|
|
@@ -382,7 +382,7 @@ class Router:
|
|
|
382
382
|
try:
|
|
383
383
|
await provider.ensure_token()
|
|
384
384
|
if is_stream:
|
|
385
|
-
stream = provider.chat_completion_stream(actual_request)
|
|
385
|
+
stream = await provider.chat_completion_stream(actual_request)
|
|
386
386
|
logger.info("Stream request routed to %s", provider_name)
|
|
387
387
|
return stream, provider_name
|
|
388
388
|
else:
|
|
@@ -417,7 +417,7 @@ class Router:
|
|
|
417
417
|
try:
|
|
418
418
|
await other_provider.ensure_token()
|
|
419
419
|
if is_stream:
|
|
420
|
-
stream = other_provider.chat_completion_stream(fallback_request)
|
|
420
|
+
stream = await other_provider.chat_completion_stream(fallback_request)
|
|
421
421
|
logger.info("Stream fallback succeeded via %s", other_name)
|
|
422
422
|
return stream, other_name
|
|
423
423
|
else:
|
|
@@ -26,6 +26,7 @@ from router_maestro.utils import (
|
|
|
26
26
|
get_logger,
|
|
27
27
|
map_openai_stop_reason_to_anthropic,
|
|
28
28
|
)
|
|
29
|
+
from router_maestro.utils.tokens import AnthropicStopReason
|
|
29
30
|
|
|
30
31
|
logger = get_logger("server.routes.anthropic")
|
|
31
32
|
|
|
@@ -106,7 +107,7 @@ async def count_tokens(request: AnthropicCountTokensRequest):
|
|
|
106
107
|
|
|
107
108
|
# Count messages
|
|
108
109
|
for msg in request.messages:
|
|
109
|
-
content = msg.content
|
|
110
|
+
content = msg.content
|
|
110
111
|
if isinstance(content, str):
|
|
111
112
|
total_chars += len(content)
|
|
112
113
|
elif isinstance(content, list):
|
|
@@ -115,12 +116,12 @@ async def count_tokens(request: AnthropicCountTokensRequest):
|
|
|
115
116
|
if block.get("type") == "text":
|
|
116
117
|
total_chars += len(block.get("text", ""))
|
|
117
118
|
elif hasattr(block, "text"):
|
|
118
|
-
total_chars += len(block.text)
|
|
119
|
+
total_chars += len(block.text) # type: ignore[union-attr]
|
|
119
120
|
|
|
120
121
|
return {"input_tokens": estimate_tokens_from_char_count(total_chars)}
|
|
121
122
|
|
|
122
123
|
|
|
123
|
-
def _map_finish_reason(reason: str | None) ->
|
|
124
|
+
def _map_finish_reason(reason: str | None) -> AnthropicStopReason | None:
|
|
124
125
|
"""Map OpenAI finish reason to Anthropic stop reason."""
|
|
125
126
|
return map_openai_stop_reason_to_anthropic(reason)
|
|
126
127
|
|
|
@@ -144,7 +145,7 @@ def _estimate_input_tokens(request: AnthropicMessagesRequest) -> int:
|
|
|
144
145
|
|
|
145
146
|
# Count messages
|
|
146
147
|
for msg in request.messages:
|
|
147
|
-
content = msg.content
|
|
148
|
+
content = msg.content
|
|
148
149
|
if isinstance(content, str):
|
|
149
150
|
total_chars += len(content)
|
|
150
151
|
elif isinstance(content, list):
|
|
@@ -161,7 +162,7 @@ def _estimate_input_tokens(request: AnthropicMessagesRequest) -> int:
|
|
|
161
162
|
if isinstance(tc, dict) and tc.get("type") == "text":
|
|
162
163
|
total_chars += len(tc.get("text", ""))
|
|
163
164
|
elif hasattr(block, "text"):
|
|
164
|
-
total_chars += len(block.text)
|
|
165
|
+
total_chars += len(block.text) # type: ignore[union-attr]
|
|
165
166
|
|
|
166
167
|
# Count tools definitions if present
|
|
167
168
|
if request.tools:
|
|
@@ -1,8 +1,14 @@
|
|
|
1
1
|
"""Token estimation utilities."""
|
|
2
2
|
|
|
3
|
+
from typing import Literal
|
|
4
|
+
|
|
3
5
|
# Approximate characters per token for English text
|
|
4
6
|
CHARS_PER_TOKEN = 4
|
|
5
7
|
|
|
8
|
+
AnthropicStopReason = Literal[
|
|
9
|
+
"end_turn", "max_tokens", "stop_sequence", "tool_use", "pause_turn", "refusal"
|
|
10
|
+
]
|
|
11
|
+
|
|
6
12
|
|
|
7
13
|
def estimate_tokens(text: str) -> int:
|
|
8
14
|
"""Estimate token count from text.
|
|
@@ -31,7 +37,9 @@ def estimate_tokens_from_char_count(char_count: int) -> int:
|
|
|
31
37
|
return char_count // CHARS_PER_TOKEN
|
|
32
38
|
|
|
33
39
|
|
|
34
|
-
def map_openai_stop_reason_to_anthropic(
|
|
40
|
+
def map_openai_stop_reason_to_anthropic(
|
|
41
|
+
openai_reason: str | None,
|
|
42
|
+
) -> AnthropicStopReason | None:
|
|
35
43
|
"""Map OpenAI finish reason to Anthropic stop reason.
|
|
36
44
|
|
|
37
45
|
Args:
|
|
@@ -42,7 +50,7 @@ def map_openai_stop_reason_to_anthropic(openai_reason: str | None) -> str | None
|
|
|
42
50
|
"""
|
|
43
51
|
if openai_reason is None:
|
|
44
52
|
return None
|
|
45
|
-
mapping = {
|
|
53
|
+
mapping: dict[str, AnthropicStopReason] = {
|
|
46
54
|
"stop": "end_turn",
|
|
47
55
|
"length": "max_tokens",
|
|
48
56
|
"tool_calls": "tool_use",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/middleware/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{router_maestro-0.1.3 → router_maestro-0.1.5}/src/router_maestro/server/schemas/anthropic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|