coderouter-cli 1.8.2__py3-none-any.whl → 1.8.3__py3-none-any.whl
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.
- coderouter/adapters/openai_compat.py +30 -14
- coderouter/doctor.py +31 -6
- {coderouter_cli-1.8.2.dist-info → coderouter_cli-1.8.3.dist-info}/METADATA +5 -5
- {coderouter_cli-1.8.2.dist-info → coderouter_cli-1.8.3.dist-info}/RECORD +7 -7
- {coderouter_cli-1.8.2.dist-info → coderouter_cli-1.8.3.dist-info}/WHEEL +0 -0
- {coderouter_cli-1.8.2.dist-info → coderouter_cli-1.8.3.dist-info}/entry_points.txt +0 -0
- {coderouter_cli-1.8.2.dist-info → coderouter_cli-1.8.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -48,14 +48,25 @@ logger = get_logger(__name__)
|
|
|
48
48
|
_RETRYABLE_STATUSES = {404, 408, 425, 429, 500, 502, 503, 504}
|
|
49
49
|
|
|
50
50
|
|
|
51
|
+
# v1.8.3: non-standard reasoning fields emitted by various upstreams.
|
|
52
|
+
# Different runtimes use different field names for the same concept:
|
|
53
|
+
# * ``reasoning`` — OpenRouter free models (gpt-oss-120b:free
|
|
54
|
+
# confirmed 2026-04-20), Ollama
|
|
55
|
+
# * ``reasoning_content`` — llama.cpp ``llama-server`` (Qwen3.6 etc.,
|
|
56
|
+
# confirmed 2026-04-26 with Unsloth GGUF)
|
|
57
|
+
# Strict OpenAI clients reject either as an unknown key. The strip
|
|
58
|
+
# function below removes both at the adapter boundary so downstream
|
|
59
|
+
# layers never see them, regardless of which runtime fronts the model.
|
|
60
|
+
_NON_STANDARD_REASONING_KEYS = ("reasoning", "reasoning_content")
|
|
61
|
+
|
|
62
|
+
|
|
51
63
|
def _strip_reasoning_field(choices: list[dict[str, Any]] | None, *, delta_key: bool) -> bool:
|
|
52
|
-
"""Remove non-standard
|
|
64
|
+
"""Remove non-standard reasoning keys from a choices list, in place.
|
|
53
65
|
|
|
54
|
-
v0.5-C
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
We strip it at the adapter boundary so downstream layers never see it.
|
|
66
|
+
v0.5-C originally targeted OpenRouter's ``reasoning`` field. v1.8.3
|
|
67
|
+
extends the strip to ``reasoning_content`` (llama.cpp ``llama-server``
|
|
68
|
+
naming) since both denote the same hidden chain-of-thought trace and
|
|
69
|
+
neither is part of the OpenAI Chat Completions spec.
|
|
59
70
|
|
|
60
71
|
Args:
|
|
61
72
|
choices: The ``choices`` list from the response body or stream chunk.
|
|
@@ -64,7 +75,7 @@ def _strip_reasoning_field(choices: list[dict[str, Any]] | None, *, delta_key: b
|
|
|
64
75
|
``False`` for non-streaming responses (look in ``choice["message"]``).
|
|
65
76
|
|
|
66
77
|
Returns:
|
|
67
|
-
True iff at least one
|
|
78
|
+
True iff at least one reasoning key was removed. Callers use
|
|
68
79
|
this to decide whether to emit a one-shot log line.
|
|
69
80
|
"""
|
|
70
81
|
if not choices:
|
|
@@ -75,9 +86,12 @@ def _strip_reasoning_field(choices: list[dict[str, Any]] | None, *, delta_key: b
|
|
|
75
86
|
if not isinstance(choice, dict):
|
|
76
87
|
continue
|
|
77
88
|
inner = choice.get(inner_key)
|
|
78
|
-
if isinstance(inner, dict)
|
|
79
|
-
|
|
80
|
-
|
|
89
|
+
if not isinstance(inner, dict):
|
|
90
|
+
continue
|
|
91
|
+
for key in _NON_STANDARD_REASONING_KEYS:
|
|
92
|
+
if key in inner:
|
|
93
|
+
inner.pop(key, None)
|
|
94
|
+
stripped = True
|
|
81
95
|
return stripped
|
|
82
96
|
|
|
83
97
|
|
|
@@ -235,15 +249,17 @@ class OpenAICompatAdapter(BaseAdapter):
|
|
|
235
249
|
retryable=False,
|
|
236
250
|
) from exc
|
|
237
251
|
|
|
238
|
-
# v0.5-C: passive strip of non-standard
|
|
239
|
-
#
|
|
252
|
+
# v0.5-C / v1.8.3: passive strip of non-standard reasoning fields
|
|
253
|
+
# on choices (covers both Ollama/OpenRouter ``reasoning`` and
|
|
254
|
+
# llama.cpp ``reasoning_content``). No-op when the provider opted
|
|
255
|
+
# into passthrough.
|
|
240
256
|
if not self.config.capabilities.reasoning_passthrough and _strip_reasoning_field(
|
|
241
257
|
data.get("choices"), delta_key=False
|
|
242
258
|
):
|
|
243
259
|
log_capability_degraded(
|
|
244
260
|
logger,
|
|
245
261
|
provider=self.name,
|
|
246
|
-
dropped=
|
|
262
|
+
dropped=list(_NON_STANDARD_REASONING_KEYS),
|
|
247
263
|
reason="non-standard-field",
|
|
248
264
|
)
|
|
249
265
|
|
|
@@ -344,7 +360,7 @@ class OpenAICompatAdapter(BaseAdapter):
|
|
|
344
360
|
log_capability_degraded(
|
|
345
361
|
logger,
|
|
346
362
|
provider=self.name,
|
|
347
|
-
dropped=
|
|
363
|
+
dropped=list(_NON_STANDARD_REASONING_KEYS),
|
|
348
364
|
reason="non-standard-field",
|
|
349
365
|
)
|
|
350
366
|
reasoning_logged = True
|
coderouter/doctor.py
CHANGED
|
@@ -458,6 +458,18 @@ _NUM_CTX_PROBE_MAX_TOKENS_DEFAULT = 256
|
|
|
458
458
|
_NUM_CTX_PROBE_MAX_TOKENS_THINKING = 1024
|
|
459
459
|
_STREAMING_PROBE_MAX_TOKENS_DEFAULT = 512
|
|
460
460
|
_STREAMING_PROBE_MAX_TOKENS_THINKING = 1024
|
|
461
|
+
# v1.8.3: tool_calls probe also needs thinking-aware budget. The
|
|
462
|
+
# pre-v1.8.3 default of 64 was tight even for non-thinking models
|
|
463
|
+
# (the assistant often emits a brief preamble before the JSON tool
|
|
464
|
+
# call), and on thinking models (Qwen3.6, Gemma 4, gpt-oss, deepseek-r1)
|
|
465
|
+
# the entire 64-token budget gets consumed by ``reasoning_content``
|
|
466
|
+
# before any ``tool_calls`` can surface — producing a false-positive
|
|
467
|
+
# NEEDS_TUNING with the WRONG remediation (suggested patch flips
|
|
468
|
+
# ``tools`` to false even though the model supports them perfectly).
|
|
469
|
+
# 256/1024 brings the budget into line with the num_ctx / streaming
|
|
470
|
+
# probes (same _is_reasoning_model gate).
|
|
471
|
+
_TOOL_CALLS_PROBE_MAX_TOKENS_DEFAULT = 256
|
|
472
|
+
_TOOL_CALLS_PROBE_MAX_TOKENS_THINKING = 1024
|
|
461
473
|
# Default ``num_predict`` suggested in the emitted patch. -1 would be
|
|
462
474
|
# optimal (uncapped) but "4096" communicates intent more clearly to
|
|
463
475
|
# operators unfamiliar with Ollama's sentinel value, and covers Claude
|
|
@@ -531,9 +543,7 @@ def _is_reasoning_model(
|
|
|
531
543
|
return True
|
|
532
544
|
if resolved.thinking is True:
|
|
533
545
|
return True
|
|
534
|
-
|
|
535
|
-
return True
|
|
536
|
-
return False
|
|
546
|
+
return resolved.reasoning_passthrough is True
|
|
537
547
|
|
|
538
548
|
|
|
539
549
|
_PROBE_BASIC_USER_PROMPT = "Reply with exactly the single word: PONG"
|
|
@@ -1093,6 +1103,16 @@ async def _probe_tool_calls(
|
|
|
1093
1103
|
If declaration says True → NEEDS_TUNING (flip to False). If
|
|
1094
1104
|
False → OK.
|
|
1095
1105
|
"""
|
|
1106
|
+
# v1.8.3: thinking-aware budget — the pre-v1.8.3 default of 64 was
|
|
1107
|
+
# consumed by ``reasoning_content`` on thinking models (Qwen3.6,
|
|
1108
|
+
# Gemma 4, gpt-oss, deepseek-r1) before any ``tool_calls`` could
|
|
1109
|
+
# surface, producing a false-positive NEEDS_TUNING that recommended
|
|
1110
|
+
# flipping ``tools`` to false — the exact opposite of what's needed.
|
|
1111
|
+
max_tokens = (
|
|
1112
|
+
_TOOL_CALLS_PROBE_MAX_TOKENS_THINKING
|
|
1113
|
+
if _is_reasoning_model(provider, resolved)
|
|
1114
|
+
else _TOOL_CALLS_PROBE_MAX_TOKENS_DEFAULT
|
|
1115
|
+
)
|
|
1096
1116
|
if provider.kind == "anthropic":
|
|
1097
1117
|
# Anthropic native tools use a different wire shape; we probe
|
|
1098
1118
|
# via the messages API. A capable model returns content blocks
|
|
@@ -1104,7 +1124,7 @@ async def _probe_tool_calls(
|
|
|
1104
1124
|
"messages": [
|
|
1105
1125
|
{"role": "user", "content": _PROBE_TOOLS_USER_PROMPT},
|
|
1106
1126
|
],
|
|
1107
|
-
"max_tokens":
|
|
1127
|
+
"max_tokens": max_tokens,
|
|
1108
1128
|
"tools": [_PROBE_TOOL_SPEC_ANTHROPIC],
|
|
1109
1129
|
}
|
|
1110
1130
|
else:
|
|
@@ -1115,7 +1135,7 @@ async def _probe_tool_calls(
|
|
|
1115
1135
|
"messages": [
|
|
1116
1136
|
{"role": "user", "content": _PROBE_TOOLS_USER_PROMPT},
|
|
1117
1137
|
],
|
|
1118
|
-
"max_tokens":
|
|
1138
|
+
"max_tokens": max_tokens,
|
|
1119
1139
|
"temperature": 0,
|
|
1120
1140
|
"tools": [_PROBE_TOOL_SPEC_OPENAI],
|
|
1121
1141
|
}
|
|
@@ -1439,7 +1459,12 @@ async def _probe_reasoning_leak(
|
|
|
1439
1459
|
)
|
|
1440
1460
|
|
|
1441
1461
|
msg = _extract_openai_assistant_choice(parsed)
|
|
1442
|
-
|
|
1462
|
+
# v1.8.3: detect llama.cpp's ``reasoning_content`` alongside Ollama /
|
|
1463
|
+
# OpenRouter's ``reasoning`` — they're the same concept under different
|
|
1464
|
+
# field names, and the openai_compat adapter strips both since v1.8.3.
|
|
1465
|
+
has_reasoning = bool(
|
|
1466
|
+
msg and ("reasoning" in msg or "reasoning_content" in msg)
|
|
1467
|
+
)
|
|
1443
1468
|
|
|
1444
1469
|
# v1.0-A: content-embedded marker detection.
|
|
1445
1470
|
content = (msg.get("content") if isinstance(msg, dict) else None) or ""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coderouter-cli
|
|
3
|
-
Version: 1.8.
|
|
3
|
+
Version: 1.8.3
|
|
4
4
|
Summary: Local-first, free-first, fallback-built-in LLM router. Claude Code / OpenAI compatible.
|
|
5
5
|
Project-URL: Homepage, https://github.com/zephel01/CodeRouter
|
|
6
6
|
Project-URL: Repository, https://github.com/zephel01/CodeRouter
|
|
@@ -60,7 +60,7 @@ Description-Content-Type: text/markdown
|
|
|
60
60
|
<p align="center">
|
|
61
61
|
<a href="https://github.com/zephel01/CodeRouter/actions/workflows/ci.yml"><img src="https://github.com/zephel01/CodeRouter/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI"></a>
|
|
62
62
|
<a href=""><img src="https://img.shields.io/badge/status-stable-brightgreen" alt="status"></a>
|
|
63
|
-
<a href=""><img src="https://img.shields.io/badge/version-1.8.
|
|
63
|
+
<a href=""><img src="https://img.shields.io/badge/version-1.8.3-blue" alt="version"></a>
|
|
64
64
|
<a href=""><img src="https://img.shields.io/badge/python-3.12%2B-blue" alt="python"></a>
|
|
65
65
|
<a href=""><img src="https://img.shields.io/badge/runtime%20deps-5-brightgreen" alt="deps"></a>
|
|
66
66
|
<a href=""><img src="https://img.shields.io/badge/license-MIT-yellow" alt="license"></a>
|
|
@@ -100,7 +100,7 @@ Description-Content-Type: text/markdown
|
|
|
100
100
|
| **要るか判断する** | [要否判定ガイド](./docs/when-do-i-need-coderouter.md) | エージェント × モデルの詳細マトリクスで「そもそも自分に必要か」を決める |
|
|
101
101
|
| **詰まったとき** | [トラブルシューティング](./docs/troubleshooting.md) | `doctor` の使い方、`.env` の export 必須、Ollama サイレント失敗 5 症状、Claude Code 連携の罠 |
|
|
102
102
|
| **安全に使う** | [セキュリティ方針](./docs/security.md) | 脅威モデル・秘密情報の扱い・脆弱性報告経路 |
|
|
103
|
-
| **履歴** | [CHANGELOG](./CHANGELOG.md) | 全リリース履歴(最新: v1.8.
|
|
103
|
+
| **履歴** | [CHANGELOG](./CHANGELOG.md) | 全リリース履歴(最新: v1.8.3 — tool_calls probe も thinking 対応 + adapter で `reasoning_content` strip / llama.cpp 直叩き対応) |
|
|
104
104
|
| **設計を追う** | [plan.md](./plan.md) | 設計不変項・マイルストーン・今後のロードマップ |
|
|
105
105
|
|
|
106
106
|
English versions: [Quickstart](./docs/quickstart.en.md) · [Usage guide](./docs/usage-guide.en.md) · [Free-tier guide](./docs/free-tier-guide.en.md) · [When you need it](./docs/when-do-i-need-coderouter.en.md) · [Troubleshooting](./docs/troubleshooting.en.md) · [Security](./docs/security.en.md)
|
|
@@ -175,7 +175,7 @@ OpenAI 互換エージェント + お行儀の良いモデル + フォールバ
|
|
|
175
175
|
|
|
176
176
|
## クイックスタート(3 コマンド)
|
|
177
177
|
|
|
178
|
-
**v1.7.0 で PyPI 公開**、**v1.8.0 で用途別 4 プロファイル + Z.AI/GLM
|
|
178
|
+
**v1.7.0 で PyPI 公開**、**v1.8.0 で用途別 4 プロファイル + Z.AI/GLM 連携**を追加、**v1.8.2 で doctor probe を thinking モデル対応**にしました。`uvx` 一発で動きます (Python 3.12 以上必須):
|
|
179
179
|
|
|
180
180
|
```bash
|
|
181
181
|
# 1. サンプル設定を置く
|
|
@@ -205,7 +205,7 @@ uv run coderouter serve --port 8088
|
|
|
205
205
|
|
|
206
206
|
> **注**: PyPI 上のパッケージ名は `coderouter-cli` ですが、コマンド名と Python import 名は `coderouter` のままです。詳しくは [CHANGELOG `[v1.7.0]`](./CHANGELOG.md#v170--2026-04-25-pypi-公開-uvx-coderouter-cli-一発で動く) 参照。
|
|
207
207
|
>
|
|
208
|
-
>
|
|
208
|
+
> **`--apply` 自動化を使う場合** (v1.8.0+): `ruamel.yaml` を optional dep として一緒に入れます (`pip install 'coderouter-cli[doctor]'` または `uv pip install ruamel.yaml`)。基本機能には不要です。
|
|
209
209
|
|
|
210
210
|
あとは任意の OpenAI クライアントを `http://127.0.0.1:8088` に向けるだけです:
|
|
211
211
|
|
|
@@ -2,7 +2,7 @@ coderouter/__init__.py,sha256=ghdjPrLtnRzY8fyQ4CJZI1UJKADyNTLtA3G7se8H7Ns,696
|
|
|
2
2
|
coderouter/__main__.py,sha256=-LCgxJnvgUV240HjQKv7ly-mn2NuKHpC4nCpvTHjeSU,130
|
|
3
3
|
coderouter/cli.py,sha256=vI1-dv10t4-xG6Zpt7zi_3U8xGgq54Qa8XIMUYpfOV8,19859
|
|
4
4
|
coderouter/cli_stats.py,sha256=ae20xUr_hjX09Ms3fBZGZsUS52o44JC57EpbWLBOCO0,27750
|
|
5
|
-
coderouter/doctor.py,sha256=
|
|
5
|
+
coderouter/doctor.py,sha256=F6f9vl99KTGgCja6N9w_QlDIFFCEqY0mxoAhKEI5yTI,69643
|
|
6
6
|
coderouter/doctor_apply.py,sha256=r_J6xbu5-HivofPNriw4_vjNYs_VRs7GsGTS0oMEX10,24209
|
|
7
7
|
coderouter/env_security.py,sha256=FEBZnXfJ0xE39kmMMn39zk0W_DRRnmcB_REmP9f4xWo,14796
|
|
8
8
|
coderouter/errors.py,sha256=Xmq67lheyw8iv3Ox39jh2c4tvNI5RcUR4QkoxVDN6l4,1130
|
|
@@ -11,7 +11,7 @@ coderouter/output_filters.py,sha256=rI4YgKVv5vviDBl3Xkf7rp6LaSSkdWyEV004q6HrkB0,
|
|
|
11
11
|
coderouter/adapters/__init__.py,sha256=7dIDSZ-FE_0iSqLSDc_lK1idRdLTKcM2hP9tCJipgPI,463
|
|
12
12
|
coderouter/adapters/anthropic_native.py,sha256=qfdjxy4YyLt-0Fj7hUYn1oi1SFjEEbSvpaRBUC2hMf4,21903
|
|
13
13
|
coderouter/adapters/base.py,sha256=H4uM6r_-95Xs1hCM_X4Zv3tq-xN3cXWLj83F-QjPNLw,8265
|
|
14
|
-
coderouter/adapters/openai_compat.py,sha256=
|
|
14
|
+
coderouter/adapters/openai_compat.py,sha256=9qoJfLR2vVnyM8isb9G4j-Dk5QBHFlneOaBSY-P4UAg,17941
|
|
15
15
|
coderouter/adapters/registry.py,sha256=Syt3eDljWZAK5mfiJGvUMKaZYAfCRScp7PvV6pYt7mc,683
|
|
16
16
|
coderouter/config/__init__.py,sha256=FODEn74fN-qZnt4INPSHswqhOlEgpL6-_onxsitSx8g,274
|
|
17
17
|
coderouter/config/capability_registry.py,sha256=oypl6Z-YjvNoC87AdSIm1C7XE_MZoFq_7Ivm3eRH3cI,14379
|
|
@@ -37,8 +37,8 @@ coderouter/translation/__init__.py,sha256=PYXN7XVEwpG1uC8RLy6fvnGbzEZhhrEuUapH8I
|
|
|
37
37
|
coderouter/translation/anthropic.py,sha256=JpvIWNXHUPVqOGvps7o_6ZADhXuJuvpU7RdMqQFtwwM,6421
|
|
38
38
|
coderouter/translation/convert.py,sha256=-qyzFzmmr9hhQV6_Sg75kJnvCZvHe3n7vRdaZtk_JqQ,47269
|
|
39
39
|
coderouter/translation/tool_repair.py,sha256=fyxDb4kWHytO5JWq5y0i4tinJUtWqhMCkyfoCf5BjeM,8314
|
|
40
|
-
coderouter_cli-1.8.
|
|
41
|
-
coderouter_cli-1.8.
|
|
42
|
-
coderouter_cli-1.8.
|
|
43
|
-
coderouter_cli-1.8.
|
|
44
|
-
coderouter_cli-1.8.
|
|
40
|
+
coderouter_cli-1.8.3.dist-info/METADATA,sha256=Nm6kOVjXop9D5aISl3H0OcH-gi4fkGT0fmhuyoKTcHU,44221
|
|
41
|
+
coderouter_cli-1.8.3.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
42
|
+
coderouter_cli-1.8.3.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
|
|
43
|
+
coderouter_cli-1.8.3.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
|
|
44
|
+
coderouter_cli-1.8.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|