router-maestro 0.1.4__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.
Files changed (67) hide show
  1. {router_maestro-0.1.4 → router_maestro-0.1.5}/CLAUDE.md +2 -2
  2. {router_maestro-0.1.4 → router_maestro-0.1.5}/PKG-INFO +1 -1
  3. {router_maestro-0.1.4 → router_maestro-0.1.5}/pyproject.toml +1 -1
  4. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/__init__.py +1 -1
  5. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/config.py +19 -9
  6. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/routes/anthropic.py +6 -5
  7. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/utils/tokens.py +10 -2
  8. {router_maestro-0.1.4 → router_maestro-0.1.5}/uv.lock +1 -1
  9. {router_maestro-0.1.4 → router_maestro-0.1.5}/.env.example +0 -0
  10. {router_maestro-0.1.4 → router_maestro-0.1.5}/.gitignore +0 -0
  11. {router_maestro-0.1.4 → router_maestro-0.1.5}/.markdownlint.json +0 -0
  12. {router_maestro-0.1.4 → router_maestro-0.1.5}/Dockerfile +0 -0
  13. {router_maestro-0.1.4 → router_maestro-0.1.5}/LICENSE +0 -0
  14. {router_maestro-0.1.4 → router_maestro-0.1.5}/Makefile +0 -0
  15. {router_maestro-0.1.4 → router_maestro-0.1.5}/README.md +0 -0
  16. {router_maestro-0.1.4 → router_maestro-0.1.5}/docker-compose.yml +0 -0
  17. {router_maestro-0.1.4 → router_maestro-0.1.5}/docs/deployment.md +0 -0
  18. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/__main__.py +0 -0
  19. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/auth/__init__.py +0 -0
  20. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/auth/github_oauth.py +0 -0
  21. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/auth/manager.py +0 -0
  22. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/auth/storage.py +0 -0
  23. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/__init__.py +0 -0
  24. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/auth.py +0 -0
  25. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/client.py +0 -0
  26. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/context.py +0 -0
  27. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/main.py +0 -0
  28. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/model.py +0 -0
  29. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/cli/server.py +0 -0
  30. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/__init__.py +0 -0
  31. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/contexts.py +0 -0
  32. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/paths.py +0 -0
  33. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/priorities.py +0 -0
  34. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/providers.py +0 -0
  35. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/server.py +0 -0
  36. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/config/settings.py +0 -0
  37. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/providers/__init__.py +0 -0
  38. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/providers/anthropic.py +0 -0
  39. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/providers/base.py +0 -0
  40. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/providers/copilot.py +0 -0
  41. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/providers/openai.py +0 -0
  42. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/providers/openai_compat.py +0 -0
  43. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/routing/__init__.py +0 -0
  44. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/routing/router.py +0 -0
  45. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/__init__.py +0 -0
  46. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/app.py +0 -0
  47. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/middleware/__init__.py +0 -0
  48. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/middleware/auth.py +0 -0
  49. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/oauth_sessions.py +0 -0
  50. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/routes/__init__.py +0 -0
  51. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/routes/admin.py +0 -0
  52. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/routes/chat.py +0 -0
  53. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/routes/models.py +0 -0
  54. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/schemas/__init__.py +0 -0
  55. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/schemas/admin.py +0 -0
  56. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/schemas/anthropic.py +0 -0
  57. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/schemas/openai.py +0 -0
  58. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/server/translation.py +0 -0
  59. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/utils/__init__.py +0 -0
  60. {router_maestro-0.1.4 → router_maestro-0.1.5}/src/router_maestro/utils/logging.py +0 -0
  61. {router_maestro-0.1.4 → router_maestro-0.1.5}/tests/__init__.py +0 -0
  62. {router_maestro-0.1.4 → router_maestro-0.1.5}/tests/test_auth.py +0 -0
  63. {router_maestro-0.1.4 → router_maestro-0.1.5}/tests/test_config.py +0 -0
  64. {router_maestro-0.1.4 → router_maestro-0.1.5}/tests/test_providers.py +0 -0
  65. {router_maestro-0.1.4 → router_maestro-0.1.5}/tests/test_router.py +0 -0
  66. {router_maestro-0.1.4 → router_maestro-0.1.5}/tests/test_translation.py +0 -0
  67. {router_maestro-0.1.4 → 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`, `stats`
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`, `stats.db`
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.4
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "router-maestro"
3
- version = "0.1.4"
3
+ version = "0.1.5"
4
4
  description = "Multi-model routing and load balancing system with OpenAI-compatible API"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,3 +1,3 @@
1
1
  """Router-Maestro: Multi-model routing and load balancing system."""
2
2
 
3
- __version__ = "0.1.4"
3
+ __version__ = "0.1.5"
@@ -139,19 +139,29 @@ def claude_code_config() -> None:
139
139
  )
140
140
  anthropic_url = f"{base_url}/api/anthropic"
141
141
 
142
- config = {
143
- "env": {
144
- "ANTHROPIC_BASE_URL": anthropic_url,
145
- "ANTHROPIC_AUTH_TOKEN": auth_token,
146
- "ANTHROPIC_MODEL": main_model,
147
- "ANTHROPIC_SMALL_FAST_MODEL": fast_model,
148
- "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
149
- }
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",
150
148
  }
151
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
+
152
162
  settings_path.parent.mkdir(parents=True, exist_ok=True)
153
163
  with open(settings_path, "w", encoding="utf-8") as f:
154
- json.dump(config, f, indent=2)
164
+ json.dump(existing_config, f, indent=2)
155
165
 
156
166
  console.print(
157
167
  Panel(
@@ -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 if hasattr(msg, "content") else msg.get("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) -> 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 if hasattr(msg, "content") else msg.get("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(openai_reason: str | None) -> str | None:
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",
@@ -925,7 +925,7 @@ wheels = [
925
925
 
926
926
  [[package]]
927
927
  name = "router-maestro"
928
- version = "0.1.4"
928
+ version = "0.1.5"
929
929
  source = { editable = "." }
930
930
  dependencies = [
931
931
  { name = "aiosqlite" },
File without changes
File without changes
File without changes