@oneciel-ai/claude-any 0.1.36 → 0.1.37

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/README.md CHANGED
@@ -48,7 +48,7 @@ arguments through unchanged.
48
48
 
49
49
  Credits: One Ciel LLC
50
50
 
51
- Current version: `0.1.36`
51
+ Current version: `0.1.37`
52
52
 
53
53
  ## Why This Exists
54
54
 
@@ -381,6 +381,14 @@ steps under that larger model's supervision.
381
381
 
382
382
  ## Changelog
383
383
 
384
+ ### 0.1.37
385
+
386
+ - **Pseudo tool-call recovery**: the NVIDIA/OpenAI-compatible stream path now
387
+ suppresses `<|tool_calls_section_begin|>...` pseudo tool-call text and
388
+ converts it back into Claude `tool_use` blocks when possible.
389
+ - **Streaming defaults**: provider streaming defaults to on; NVIDIA hosted
390
+ remains forced to the streaming upstream path for stability.
391
+
384
392
  ### 0.1.36
385
393
 
386
394
  - **NVIDIA upstream streaming**: NVIDIA hosted router calls now use upstream
package/claude_any.py CHANGED
@@ -85,7 +85,7 @@ PROVIDER_LABELS = {
85
85
  "self-hosted-nim": "Self Hosted NIM",
86
86
  }
87
87
  APP_NAME = "Claude Any"
88
- VERSION = "0.1.36"
88
+ VERSION = "0.1.37"
89
89
  CREDITS = "Credits: One Ciel LLC"
90
90
 
91
91
  LOG_LEVELS = {"SILENT": 0, "ERROR": 1, "WARN": 2, "INFO": 3, "DEBUG": 4, "TRACE": 5}
@@ -790,6 +790,13 @@ def apply_config_migrations(cfg: dict[str, Any]) -> None:
790
790
  if isinstance(pcfg, dict) and not positive_int(pcfg.get("context_window")):
791
791
  pcfg["context_window"] = 32768
792
792
  migrations[marker] = True
793
+
794
+ marker = "stream_enabled_default_true_20260513"
795
+ if not migrations.get(marker):
796
+ for pcfg in (cfg.get("providers") or {}).values():
797
+ if isinstance(pcfg, dict) and "stream_enabled" not in pcfg:
798
+ pcfg["stream_enabled"] = True
799
+ migrations[marker] = True
793
800
 
794
801
 
795
802
  _config_cache: dict[str, Any] | None = None
@@ -3988,7 +3995,7 @@ def maybe_handle_advisor_request(handler: BaseHTTPRequestHandler, provider: str,
3988
3995
  return True
3989
3996
 
3990
3997
 
3991
- def normalize_tool_arguments(tool_name: str, args: Any) -> dict[str, Any]:
3998
+ def normalize_tool_arguments(tool_name: str, args: Any) -> dict[str, Any]:
3992
3999
  if isinstance(args, dict):
3993
4000
  return args
3994
4001
  if isinstance(args, str):
@@ -4003,17 +4010,86 @@ def normalize_tool_arguments(tool_name: str, args: Any) -> dict[str, Any]:
4003
4010
  pass
4004
4011
  if tool_name == "Bash":
4005
4012
  return {"command": text}
4006
- return {}
4007
-
4008
-
4009
- def ollama_chat_to_anthropic(data: dict[str, Any], model: str, source_body: dict[str, Any] | None = None) -> dict[str, Any]:
4010
- message = data.get("message") if isinstance(data.get("message"), dict) else {}
4011
- content: list[dict[str, Any]] = []
4012
- text = message.get("content") or ""
4013
- if text:
4014
- content.append({"type": "text", "text": text})
4015
- tool_id_prefix = f"toolu_ollama_{int(time.time() * 1000)}_{os.getpid()}"
4016
- for i, call in enumerate(message.get("tool_calls") or []):
4013
+ return {}
4014
+
4015
+
4016
+ PSEUDO_TOOL_START = "<|tool_calls_section_begin|>"
4017
+ PSEUDO_TOOL_END = "<|tool_calls_section_end|>"
4018
+ PSEUDO_CALL_BEGIN = "<|tool_call_begin|>"
4019
+ PSEUDO_ARG_BEGIN = "<|tool_call_argument_begin|>"
4020
+ PSEUDO_CALL_END = "<|tool_call_end|>"
4021
+
4022
+
4023
+ def infer_tool_name_from_args(args: dict[str, Any]) -> str:
4024
+ keys = set(args)
4025
+ if "command" in keys:
4026
+ return "Bash"
4027
+ if {"file_path", "content"}.issubset(keys):
4028
+ return "Write"
4029
+ if {"file_path", "old_string", "new_string"}.issubset(keys):
4030
+ return "Edit"
4031
+ if "file_path" in keys:
4032
+ return "Read"
4033
+ if "taskId" in keys and "status" in keys:
4034
+ return "TaskUpdate"
4035
+ return "TaskList" if not args else "Write"
4036
+
4037
+
4038
+ def parse_pseudo_tool_calls(text: str) -> tuple[str, list[dict[str, Any]]]:
4039
+ if PSEUDO_TOOL_START not in text:
4040
+ return text, []
4041
+ visible_parts: list[str] = []
4042
+ calls: list[dict[str, Any]] = []
4043
+ pos = 0
4044
+ while True:
4045
+ start = text.find(PSEUDO_TOOL_START, pos)
4046
+ if start < 0:
4047
+ visible_parts.append(text[pos:])
4048
+ break
4049
+ visible_parts.append(text[pos:start])
4050
+ end = text.find(PSEUDO_TOOL_END, start)
4051
+ if end < 0:
4052
+ section = text[start + len(PSEUDO_TOOL_START):]
4053
+ pos = len(text)
4054
+ else:
4055
+ section = text[start + len(PSEUDO_TOOL_START):end]
4056
+ pos = end + len(PSEUDO_TOOL_END)
4057
+ for match in re.finditer(
4058
+ re.escape(PSEUDO_CALL_BEGIN) + r"(.*?)" + re.escape(PSEUDO_ARG_BEGIN) + r"(.*?)" + re.escape(PSEUDO_CALL_END),
4059
+ section,
4060
+ flags=re.DOTALL,
4061
+ ):
4062
+ raw_header = match.group(1).strip()
4063
+ raw_args = match.group(2).strip()
4064
+ try:
4065
+ args = json.loads(raw_args)
4066
+ except Exception:
4067
+ continue
4068
+ if not isinstance(args, dict):
4069
+ continue
4070
+ name = ""
4071
+ for part in re.split(r"[\s:|,]+", raw_header):
4072
+ candidate = _fuzzy_match_tool_name(part)
4073
+ if candidate:
4074
+ name = candidate
4075
+ break
4076
+ if not name:
4077
+ name = infer_tool_name_from_args(args)
4078
+ calls.append({"function": {"name": name, "arguments": args}, "id": raw_header})
4079
+ if end < 0:
4080
+ break
4081
+ return "".join(visible_parts), calls
4082
+
4083
+
4084
+ def ollama_chat_to_anthropic(data: dict[str, Any], model: str, source_body: dict[str, Any] | None = None) -> dict[str, Any]:
4085
+ message = data.get("message") if isinstance(data.get("message"), dict) else {}
4086
+ content: list[dict[str, Any]] = []
4087
+ text = message.get("content") or ""
4088
+ text, pseudo_tool_calls = parse_pseudo_tool_calls(text)
4089
+ if text:
4090
+ content.append({"type": "text", "text": text})
4091
+ tool_id_prefix = f"toolu_ollama_{int(time.time() * 1000)}_{os.getpid()}"
4092
+ for i, call in enumerate(list(message.get("tool_calls") or []) + pseudo_tool_calls):
4017
4093
  fn = call.get("function") if isinstance(call, dict) else {}
4018
4094
  if not isinstance(fn, dict) or not fn.get("name"):
4019
4095
  continue
@@ -4609,6 +4685,8 @@ def stream_openai_chat_to_anthropic_sse(
4609
4685
  text_suppressed_for_plan = False
4610
4686
  text_index: int | None = None
4611
4687
  text_so_far = ""
4688
+ pseudo_text = ""
4689
+ pseudo_mode = False
4612
4690
  text_buffer = ""
4613
4691
  tool_fragments: dict[int, dict[str, Any]] = {}
4614
4692
  output_tokens = 0
@@ -4667,6 +4745,21 @@ def stream_openai_chat_to_anthropic_sse(
4667
4745
  delta = choice.get("delta") if isinstance(choice.get("delta"), dict) else {}
4668
4746
  text_chunk = delta.get("content") or ""
4669
4747
  if text_chunk:
4748
+ if pseudo_mode or PSEUDO_TOOL_START in text_chunk:
4749
+ before, sep, after = text_chunk.partition(PSEUDO_TOOL_START)
4750
+ if before and not pseudo_mode:
4751
+ text_so_far += before
4752
+ if word_chunking:
4753
+ text_buffer += before
4754
+ to_flush, text_buffer = _split_word_buffer(text_buffer, force=False)
4755
+ emit_text_delta(to_flush)
4756
+ else:
4757
+ emit_text_delta(before)
4758
+ pseudo_mode = True
4759
+ pseudo_text += (sep + after) if sep else text_chunk
4760
+ if PSEUDO_TOOL_END in pseudo_text:
4761
+ pseudo_mode = False
4762
+ continue
4670
4763
  if source_body is not None and not text_started and not tool_fragments and should_auto_enter_plan_mode(source_body, text_so_far + text_chunk, []):
4671
4764
  text_so_far += text_chunk
4672
4765
  text_suppressed_for_plan = True
@@ -4698,6 +4791,15 @@ def stream_openai_chat_to_anthropic_sse(
4698
4791
  emit_text_delta(to_flush)
4699
4792
 
4700
4793
  tool_calls: list[dict[str, Any]] = []
4794
+ _, pseudo_tool_calls = parse_pseudo_tool_calls(pseudo_text)
4795
+ for i, pseudo in enumerate(pseudo_tool_calls):
4796
+ fn = pseudo.get("function") if isinstance(pseudo, dict) else {}
4797
+ if isinstance(fn, dict):
4798
+ tool_fragments.setdefault(100000 + i, {
4799
+ "id": str(pseudo.get("id") or ""),
4800
+ "name": str(fn.get("name") or ""),
4801
+ "arguments": json.dumps(fn.get("arguments") or {}, ensure_ascii=False),
4802
+ })
4701
4803
  for _, fragment in sorted(tool_fragments.items()):
4702
4804
  raw_name = str(fragment.get("name") or "")
4703
4805
  if not raw_name:
@@ -4964,7 +5066,8 @@ def forward_openai_compatible_chat(handler: BaseHTTPRequestHandler, provider: st
4964
5066
  model = ncp_model_id_for_nvidia_hosted(model)
4965
5067
  url = join_url(provider_upstream_request_base(provider, pcfg), "/chat/completions")
4966
5068
  waited, rpm_used, rpm_limit = apply_router_rate_limit(provider, pcfg, model)
4967
- stream = bool(body.get("stream", True))
5069
+ stream_enabled = bool(pcfg.get("stream_enabled", True))
5070
+ stream = True if provider == "nvidia-hosted" else bool(body.get("stream", stream_enabled)) and stream_enabled
4968
5071
  notice = rate_limit_notice(waited, rpm_used, rpm_limit, bool(pcfg.get("rate_limit_status", True)))
4969
5072
  if stream:
4970
5073
  req_body = openai_compatible_chat_request(provider, model, body, pcfg, stream=True)
package/docs/README.ja.md CHANGED
@@ -47,7 +47,7 @@ vLLM、NVIDIA hosted、self-hosted NIM を選択し、通常の Claude Code 引
47
47
 
48
48
  Credits: One Ciel LLC
49
49
 
50
- 現在のバージョン: `0.1.36`
50
+ 現在のバージョン: `0.1.37`
51
51
 
52
52
  ## 作られた理由
53
53
 
@@ -351,6 +351,14 @@ Windows/Linux 管理、クリーンアップスクリプト、定期的なセキ
351
351
 
352
352
  ## 変更履歴
353
353
 
354
+ ### 0.1.37
355
+
356
+ - **Pseudo tool-call recovery**: NVIDIA/OpenAI-compatible stream 経路で
357
+ `<|tool_calls_section_begin|>...` pseudo tool-call テキストを画面に出さず、
358
+ 可能な場合は Claude `tool_use` ブロックへ復元します。
359
+ - **Streaming defaults**: provider streaming の既定値は on です。NVIDIA hosted
360
+ は安定性のため upstream streaming 経路に固定されます。
361
+
354
362
  ### 0.1.36
355
363
 
356
364
  - **NVIDIA upstream streaming**: NVIDIA hosted router 呼び出しは upstream にも
package/docs/README.ko.md CHANGED
@@ -47,7 +47,7 @@ NVIDIA hosted, self-hosted NIM을 선택하고, Claude Code의 일반 인자는
47
47
 
48
48
  Credits: One Ciel LLC
49
49
 
50
- 현재 버전: `0.1.36`
50
+ 현재 버전: `0.1.37`
51
51
 
52
52
  ## 왜 만들었나
53
53
 
@@ -351,6 +351,14 @@ Windows 이벤트 로그 리뷰, 바이러스/랜섬웨어 침입 시도 정리,
351
351
 
352
352
  ## 변경 이력
353
353
 
354
+ ### 0.1.37
355
+
356
+ - **Pseudo tool-call recovery**: NVIDIA/OpenAI-compatible stream 경로에서
357
+ `<|tool_calls_section_begin|>...` pseudo tool-call 텍스트를 화면에 출력하지
358
+ 않고 가능한 경우 Claude `tool_use` 블록으로 복구합니다.
359
+ - **Streaming defaults**: provider streaming 기본값은 on이며, NVIDIA hosted는
360
+ 안정성을 위해 upstream streaming 경로로 고정됩니다.
361
+
354
362
  ### 0.1.36
355
363
 
356
364
  - **NVIDIA upstream streaming**: NVIDIA hosted router 호출은 이제 upstream에도
package/docs/README.zh.md CHANGED
@@ -47,7 +47,7 @@ NIM,并把普通 Claude Code 参数原样传递。
47
47
 
48
48
  Credits: One Ciel LLC
49
49
 
50
- 当前版本: `0.1.36`
50
+ 当前版本: `0.1.37`
51
51
 
52
52
  ## 为什么存在
53
53
 
@@ -337,6 +337,14 @@ Hermes 格式模型或部分较旧的 Qwen tool template。
337
337
 
338
338
  ## 更新日志
339
339
 
340
+ ### 0.1.37
341
+
342
+ - **Pseudo tool-call recovery**:NVIDIA/OpenAI-compatible stream 路径现在会
343
+ 隐藏 `<|tool_calls_section_begin|>...` pseudo tool-call 文本,并尽可能恢复为
344
+ Claude `tool_use` block。
345
+ - **Streaming defaults**:provider streaming 默认开启;NVIDIA hosted 为了稳定性
346
+ 固定使用 upstream streaming 路径。
347
+
340
348
  ### 0.1.36
341
349
 
342
350
  - **NVIDIA upstream streaming**:NVIDIA hosted router 调用现在也会向 upstream
package/docs/manual.md CHANGED
@@ -10,7 +10,7 @@ Code starts, while passing normal Claude Code arguments through unchanged.
10
10
 
11
11
  Credits: One Ciel LLC
12
12
 
13
- Current version: `0.1.36`
13
+ Current version: `0.1.37`
14
14
 
15
15
  ## Install
16
16
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/claude-any",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Claude Code provider selector for Anthropic, Ollama, Ollama Cloud, vLLM, NVIDIA hosted, and self-hosted NIM.",
5
5
  "license": "MIT",
6
6
  "author": "One Ciel LLC",