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.
@@ -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 ``reasoning`` keys from a choices list, in place.
64
+ """Remove non-standard reasoning keys from a choices list, in place.
53
65
 
54
- v0.5-C: Some OpenRouter free models (confirmed on
55
- ``openai/gpt-oss-120b:free`` 2026-04-20) return a ``reasoning`` field
56
- alongside ``content`` on each choice. The field is not in the OpenAI
57
- Chat Completions spec and strict clients can reject the unknown key.
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 ``reasoning`` key was removed. Callers use
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) and "reasoning" in inner:
79
- inner.pop("reasoning", None)
80
- stripped = True
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 `reasoning` field on choices.
239
- # No-op when the provider opted into passthrough.
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=["reasoning"],
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=["reasoning"],
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
- if resolved.reasoning_passthrough is True:
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": 64,
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": 64,
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
- has_reasoning = bool(msg and "reasoning" in msg)
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.2
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.0-blue" alt="version"></a>
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.0用途別 4 プロファイル + GLM/Gemma 4/Qwen3.6 公式化 + apply 自動化) |
103
+ | **履歴** | [CHANGELOG](./CHANGELOG.md) | 全リリース履歴(最新: v1.8.3tool_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 連携**を追加しました。`uvx` 一発で動きます (Python 3.12 以上必須):
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
- > **v1.8.0 の `--apply` 自動化を使う場合**: `ruamel.yaml` を optional dep として一緒に入れます (`pip install 'coderouter-cli[doctor]'` または `uv pip install ruamel.yaml`)。基本機能には不要です。
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=atYOr73LLI3lKHjhFDY0lea41_0jolfiY2zb15La_O8,68116
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=EC9zNYPGgSOVZyaH1dXRXO1VMN1RjBX5FZ2vEgTkVD8,17100
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.2.dist-info/METADATA,sha256=MBkIOwnySR2wfw-JhBtGFipk_MqbV8J_4aAqWZ65A7g,44136
41
- coderouter_cli-1.8.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
42
- coderouter_cli-1.8.2.dist-info/entry_points.txt,sha256=-dnLfD1YZ2WjH2zSdNCvlO65wYltM9bsHt9Fhg3yGss,51
43
- coderouter_cli-1.8.2.dist-info/licenses/LICENSE,sha256=wkEzoR86jFw33jvfOHjULqmkGEfxTFMgMaJnpR8mPRw,1065
44
- coderouter_cli-1.8.2.dist-info/RECORD,,
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,,