@oneciel-ai/ciel-runtime 0.1.1 → 0.2.0

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 (287) hide show
  1. package/README.md +132 -0
  2. package/ciel-runtime-menu.py +56 -6
  3. package/ciel_runtime.py +9379 -35909
  4. package/ciel_runtime_support/advisor_client.py +193 -0
  5. package/ciel_runtime_support/advisor_policy.py +320 -0
  6. package/ciel_runtime_support/advisor_refinement.py +160 -0
  7. package/ciel_runtime_support/advisor_request_builder.py +261 -0
  8. package/ciel_runtime_support/agy_installer.py +169 -0
  9. package/ciel_runtime_support/agy_mcp_restore.py +182 -0
  10. package/ciel_runtime_support/anthropic_model_policy.py +186 -0
  11. package/ciel_runtime_support/anthropic_response_writer.py +255 -0
  12. package/ciel_runtime_support/anthropic_tool_turns.py +130 -0
  13. package/ciel_runtime_support/api_key_cooldown.py +159 -0
  14. package/ciel_runtime_support/architecture.py +488 -1
  15. package/ciel_runtime_support/architecture_budget.py +42 -0
  16. package/ciel_runtime_support/channel_backlog.py +90 -0
  17. package/ciel_runtime_support/channel_cli.py +119 -0
  18. package/ciel_runtime_support/channel_compact_injection.py +82 -0
  19. package/ciel_runtime_support/channel_compact_poll.py +67 -0
  20. package/ciel_runtime_support/channel_compact_request_repository.py +113 -0
  21. package/ciel_runtime_support/channel_config_service.py +281 -0
  22. package/ciel_runtime_support/channel_connection_lifecycle.py +180 -0
  23. package/ciel_runtime_support/channel_connection_registry.py +128 -0
  24. package/ciel_runtime_support/channel_connection_worker.py +284 -0
  25. package/ciel_runtime_support/channel_cursor_recovery.py +92 -0
  26. package/ciel_runtime_support/channel_cursor_repository.py +89 -0
  27. package/ciel_runtime_support/channel_cursor_service.py +178 -0
  28. package/ciel_runtime_support/channel_event_identity.py +212 -0
  29. package/ciel_runtime_support/channel_event_projection.py +315 -0
  30. package/ciel_runtime_support/channel_inflight.py +127 -0
  31. package/ciel_runtime_support/channel_injection.py +115 -0
  32. package/ciel_runtime_support/channel_launch_guard_repository.py +58 -0
  33. package/ciel_runtime_support/channel_launch_policy.py +180 -0
  34. package/ciel_runtime_support/channel_llm_context.py +156 -0
  35. package/ciel_runtime_support/channel_mcp_discovery.py +186 -0
  36. package/ciel_runtime_support/channel_mcp_http_controller.py +239 -0
  37. package/ciel_runtime_support/channel_mcp_ownership.py +148 -0
  38. package/ciel_runtime_support/channel_mcp_tools.py +240 -0
  39. package/ciel_runtime_support/channel_mcp_transport.py +394 -0
  40. package/ciel_runtime_support/channel_message_dedupe.py +65 -0
  41. package/ciel_runtime_support/channel_message_policy.py +256 -0
  42. package/ciel_runtime_support/channel_message_prompt.py +305 -0
  43. package/ciel_runtime_support/channel_message_repository.py +234 -0
  44. package/ciel_runtime_support/channel_notification_projection.py +217 -0
  45. package/ciel_runtime_support/channel_panel.py +162 -0
  46. package/ciel_runtime_support/channel_pending_injection.py +209 -0
  47. package/ciel_runtime_support/channel_pending_poll.py +109 -0
  48. package/ciel_runtime_support/channel_probe_cache.py +433 -0
  49. package/ciel_runtime_support/channel_probe_report.py +101 -0
  50. package/ciel_runtime_support/channel_runtime_environment.py +181 -0
  51. package/ciel_runtime_support/channel_session_lifecycle.py +114 -0
  52. package/ciel_runtime_support/channel_session_repository.py +90 -0
  53. package/ciel_runtime_support/channel_terminal_dispatch.py +115 -0
  54. package/ciel_runtime_support/channel_terminal_input.py +277 -0
  55. package/ciel_runtime_support/channel_terminal_proxy.py +447 -0
  56. package/ciel_runtime_support/channel_tool_context.py +166 -0
  57. package/ciel_runtime_support/channel_transcript.py +414 -0
  58. package/ciel_runtime_support/channel_transcript_repository.py +96 -0
  59. package/ciel_runtime_support/channel_wake_claim_repository.py +126 -0
  60. package/ciel_runtime_support/channel_wake_delivery_repository.py +88 -0
  61. package/ciel_runtime_support/chat_files.py +138 -0
  62. package/ciel_runtime_support/chat_http_controller.py +235 -0
  63. package/ciel_runtime_support/claude_environment.py +375 -0
  64. package/ciel_runtime_support/claude_router.py +247 -193
  65. package/ciel_runtime_support/cli_dispatch.py +792 -0
  66. package/ciel_runtime_support/cli_parser.py +165 -0
  67. package/ciel_runtime_support/cli_usage.py +100 -0
  68. package/ciel_runtime_support/codex_app_server.py +20 -5
  69. package/ciel_runtime_support/codex_channel_sse_launch.py +87 -0
  70. package/ciel_runtime_support/codex_cli.py +42 -6
  71. package/ciel_runtime_support/codex_config.py +323 -0
  72. package/ciel_runtime_support/codex_launch_configuration.py +240 -0
  73. package/ciel_runtime_support/codex_launch_policy.py +66 -0
  74. package/ciel_runtime_support/codex_mcp_integration.py +195 -0
  75. package/ciel_runtime_support/codex_mcp_restore.py +304 -0
  76. package/ciel_runtime_support/codex_model_catalog.py +133 -0
  77. package/ciel_runtime_support/codex_process_lifecycle.py +271 -0
  78. package/ciel_runtime_support/codex_router.py +147 -1
  79. package/ciel_runtime_support/codex_session_repository.py +115 -0
  80. package/ciel_runtime_support/codex_session_selection.py +114 -0
  81. package/ciel_runtime_support/command_asset_installer.py +103 -0
  82. package/ciel_runtime_support/compatibility_probe.py +295 -0
  83. package/ciel_runtime_support/compatibility_protocol.py +251 -0
  84. package/ciel_runtime_support/compatibility_runtime.py +166 -0
  85. package/ciel_runtime_support/compatibility_test.py +370 -0
  86. package/ciel_runtime_support/config_migrations.py +307 -0
  87. package/ciel_runtime_support/config_repository.py +175 -0
  88. package/ciel_runtime_support/config_value_codec.py +64 -0
  89. package/ciel_runtime_support/configuration_cli.py +374 -0
  90. package/ciel_runtime_support/context_compaction.py +280 -0
  91. package/ciel_runtime_support/context_setup.py +208 -0
  92. package/ciel_runtime_support/context_summary_policy.py +392 -0
  93. package/ciel_runtime_support/credential_cli.py +104 -0
  94. package/ciel_runtime_support/credential_management.py +261 -0
  95. package/ciel_runtime_support/credentials.py +269 -0
  96. package/ciel_runtime_support/executable_discovery.py +141 -0
  97. package/ciel_runtime_support/github_copilot_oauth.py +335 -0
  98. package/ciel_runtime_support/github_copilot_oauth_runtime.py +213 -0
  99. package/ciel_runtime_support/header_forwarding.py +73 -0
  100. package/ciel_runtime_support/headless_config.py +221 -0
  101. package/ciel_runtime_support/http_response.py +129 -0
  102. package/ciel_runtime_support/install_diagnostics.py +149 -0
  103. package/ciel_runtime_support/kimi_identity.py +123 -0
  104. package/ciel_runtime_support/launch_diagnostics.py +204 -0
  105. package/ciel_runtime_support/launch_state.py +127 -0
  106. package/ciel_runtime_support/live_api_key_controller.py +58 -0
  107. package/ciel_runtime_support/llm_config_http.py +148 -0
  108. package/ciel_runtime_support/llm_option_config.py +259 -0
  109. package/ciel_runtime_support/llm_presentation_data.py +447 -0
  110. package/ciel_runtime_support/llm_presets.py +773 -0
  111. package/ciel_runtime_support/lm_studio_runtime.py +401 -0
  112. package/ciel_runtime_support/managed_mcp_config.py +144 -0
  113. package/ciel_runtime_support/managed_mcp_discovery.py +151 -0
  114. package/ciel_runtime_support/managed_service_cleanup.py +89 -0
  115. package/ciel_runtime_support/mcp_config_reader.py +230 -0
  116. package/ciel_runtime_support/mcp_http_proxy.py +607 -0
  117. package/ciel_runtime_support/mcp_inventory.py +59 -0
  118. package/ciel_runtime_support/mcp_notification_wait_policy.py +159 -0
  119. package/ciel_runtime_support/mcp_probe_codec.py +136 -0
  120. package/ciel_runtime_support/mcp_probe_transport.py +328 -0
  121. package/ciel_runtime_support/mcp_proxy_codec.py +345 -0
  122. package/ciel_runtime_support/mcp_proxy_config.py +107 -0
  123. package/ciel_runtime_support/mcp_proxy_notifications.py +261 -0
  124. package/ciel_runtime_support/mcp_proxy_process.py +560 -0
  125. package/ciel_runtime_support/mcp_split_proxy_http.py +165 -0
  126. package/ciel_runtime_support/mcp_stdio_probe.py +240 -0
  127. package/ciel_runtime_support/mcp_transport.py +146 -0
  128. package/ciel_runtime_support/model_cache_lifecycle.py +78 -0
  129. package/ciel_runtime_support/model_catalog_projection.py +61 -0
  130. package/ciel_runtime_support/model_context_hints.py +109 -0
  131. package/ciel_runtime_support/model_panel.py +147 -0
  132. package/ciel_runtime_support/model_registry_repository.py +231 -0
  133. package/ciel_runtime_support/npm_runtime.py +191 -0
  134. package/ciel_runtime_support/ollama_catalog.py +462 -0
  135. package/ciel_runtime_support/ollama_catalog_cli.py +41 -0
  136. package/ciel_runtime_support/ollama_catalog_repository.py +75 -0
  137. package/ciel_runtime_support/ollama_context_sync.py +87 -0
  138. package/ciel_runtime_support/ollama_forwarding.py +449 -0
  139. package/ciel_runtime_support/openai_chat_passthrough.py +67 -0
  140. package/ciel_runtime_support/openai_chat_router.py +64 -0
  141. package/ciel_runtime_support/openai_forwarding.py +194 -0
  142. package/ciel_runtime_support/openai_responses_router.py +291 -0
  143. package/ciel_runtime_support/openai_responses_stream.py +135 -0
  144. package/ciel_runtime_support/output_budget.py +89 -0
  145. package/ciel_runtime_support/package_lifecycle.py +217 -0
  146. package/ciel_runtime_support/plan_artifact_controller.py +104 -0
  147. package/ciel_runtime_support/prelaunch.py +959 -0
  148. package/ciel_runtime_support/prelaunch_launch_preference.py +75 -0
  149. package/ciel_runtime_support/prelaunch_panel_projection.py +396 -0
  150. package/ciel_runtime_support/prelaunch_terminal.py +764 -0
  151. package/ciel_runtime_support/process_control.py +708 -0
  152. package/ciel_runtime_support/prompt_compaction.py +322 -0
  153. package/ciel_runtime_support/prompt_injection.py +176 -0
  154. package/ciel_runtime_support/protocols/__init__.py +24 -0
  155. package/ciel_runtime_support/protocols/anthropic_content.py +29 -0
  156. package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +336 -0
  157. package/ciel_runtime_support/protocols/chat_projection.py +315 -0
  158. package/ciel_runtime_support/protocols/conversation_policy.py +217 -0
  159. package/ciel_runtime_support/protocols/conversation_turn_policy.py +972 -0
  160. package/ciel_runtime_support/protocols/ollama_chat.py +111 -0
  161. package/ciel_runtime_support/protocols/ollama_response.py +231 -0
  162. package/ciel_runtime_support/protocols/openai_reasoning.py +75 -0
  163. package/ciel_runtime_support/protocols/openai_responses.py +271 -0
  164. package/ciel_runtime_support/protocols/pseudo_tool_history.py +248 -0
  165. package/ciel_runtime_support/protocols/tool_result_projection.py +112 -0
  166. package/ciel_runtime_support/provider_adapters.py +160 -0
  167. package/ciel_runtime_support/provider_catalog_sources.py +316 -0
  168. package/ciel_runtime_support/provider_choice.py +201 -0
  169. package/ciel_runtime_support/provider_compatibility.py +165 -0
  170. package/ciel_runtime_support/provider_config_mutations.py +361 -0
  171. package/ciel_runtime_support/provider_configuration_service.py +162 -0
  172. package/ciel_runtime_support/provider_context.py +308 -0
  173. package/ciel_runtime_support/provider_contract_projection.py +73 -0
  174. package/ciel_runtime_support/provider_descriptor.py +82 -0
  175. package/ciel_runtime_support/provider_endpoint_policy.py +130 -0
  176. package/ciel_runtime_support/provider_endpoint_probe.py +165 -0
  177. package/ciel_runtime_support/provider_launch_endpoint.py +109 -0
  178. package/ciel_runtime_support/provider_limits.py +457 -0
  179. package/ciel_runtime_support/provider_model_identity.py +139 -0
  180. package/ciel_runtime_support/provider_model_selection.py +431 -0
  181. package/ciel_runtime_support/provider_model_specs.py +142 -0
  182. package/ciel_runtime_support/provider_models.py +263 -0
  183. package/ciel_runtime_support/provider_network.py +176 -0
  184. package/ciel_runtime_support/provider_option_cli.py +238 -0
  185. package/ciel_runtime_support/provider_option_panel.py +275 -0
  186. package/ciel_runtime_support/provider_option_status.py +192 -0
  187. package/ciel_runtime_support/provider_policy.py +101 -0
  188. package/ciel_runtime_support/provider_query_policy.py +67 -0
  189. package/ciel_runtime_support/provider_readiness.py +112 -0
  190. package/ciel_runtime_support/provider_request_access.py +131 -0
  191. package/ciel_runtime_support/provider_request_builder.py +250 -0
  192. package/ciel_runtime_support/provider_responses_passthrough.py +81 -0
  193. package/ciel_runtime_support/provider_runtime_info.py +113 -0
  194. package/ciel_runtime_support/provider_runtime_modes.py +150 -0
  195. package/ciel_runtime_support/provider_sampling_policy.py +46 -0
  196. package/ciel_runtime_support/provider_status.py +145 -0
  197. package/ciel_runtime_support/provider_timeout_policy.py +184 -0
  198. package/ciel_runtime_support/provider_tool_policy.py +145 -0
  199. package/ciel_runtime_support/providers/__init__.py +65 -0
  200. package/ciel_runtime_support/providers/anthropic.py +160 -0
  201. package/ciel_runtime_support/providers/anthropic_catalog.py +119 -0
  202. package/ciel_runtime_support/providers/base.py +232 -0
  203. package/ciel_runtime_support/providers/catalog.py +326 -0
  204. package/ciel_runtime_support/providers/cloud.py +194 -0
  205. package/ciel_runtime_support/providers/constants.py +54 -0
  206. package/ciel_runtime_support/providers/deepseek.py +115 -0
  207. package/ciel_runtime_support/providers/fireworks.py +158 -0
  208. package/ciel_runtime_support/providers/github_copilot_oauth.py +199 -0
  209. package/ciel_runtime_support/providers/kimi.py +297 -0
  210. package/ciel_runtime_support/providers/lm_studio.py +76 -0
  211. package/ciel_runtime_support/providers/meta.py +257 -0
  212. package/ciel_runtime_support/providers/native.py +194 -0
  213. package/ciel_runtime_support/providers/nim.py +69 -0
  214. package/ciel_runtime_support/providers/nvidia.py +158 -0
  215. package/ciel_runtime_support/providers/nvidia_runtime.py +285 -0
  216. package/ciel_runtime_support/providers/ollama.py +181 -0
  217. package/ciel_runtime_support/providers/ollama_context.py +195 -0
  218. package/ciel_runtime_support/providers/ollama_runtime.py +213 -0
  219. package/ciel_runtime_support/providers/opencode.py +220 -0
  220. package/ciel_runtime_support/providers/opencode_go.py +37 -0
  221. package/ciel_runtime_support/providers/openrouter.py +57 -0
  222. package/ciel_runtime_support/providers/vllm.py +65 -0
  223. package/ciel_runtime_support/providers/zai.py +119 -0
  224. package/ciel_runtime_support/pseudo_tool_parser.py +115 -0
  225. package/ciel_runtime_support/rate_limit_policy.py +117 -0
  226. package/ciel_runtime_support/rate_limit_repository.py +154 -0
  227. package/ciel_runtime_support/registry.py +46 -0
  228. package/ciel_runtime_support/request_shortcuts.py +253 -0
  229. package/ciel_runtime_support/request_trace.py +323 -0
  230. package/ciel_runtime_support/response_collection.py +209 -0
  231. package/ciel_runtime_support/router_access.py +238 -0
  232. package/ciel_runtime_support/router_client_lifecycle.py +366 -0
  233. package/ciel_runtime_support/router_health_policy.py +101 -0
  234. package/ciel_runtime_support/router_http.py +513 -0
  235. package/ciel_runtime_support/router_process_lifecycle.py +401 -0
  236. package/ciel_runtime_support/router_rate_limit_service.py +285 -0
  237. package/ciel_runtime_support/router_server_runtime.py +103 -0
  238. package/ciel_runtime_support/router_shortcuts.py +201 -0
  239. package/ciel_runtime_support/routing_fallback.py +73 -0
  240. package/ciel_runtime_support/runtime_activity_repository.py +143 -0
  241. package/ciel_runtime_support/runtime_adapters.py +104 -0
  242. package/ciel_runtime_support/runtime_command_factory.py +73 -0
  243. package/ciel_runtime_support/runtime_compatibility.py +50 -0
  244. package/ciel_runtime_support/runtime_constants.py +178 -0
  245. package/ciel_runtime_support/runtime_launch.py +1602 -0
  246. package/ciel_runtime_support/runtime_llm_options.py +312 -0
  247. package/ciel_runtime_support/runtime_logging.py +161 -0
  248. package/ciel_runtime_support/runtime_paths.py +157 -0
  249. package/ciel_runtime_support/runtime_restart.py +84 -0
  250. package/ciel_runtime_support/runtime_upgrade.py +149 -0
  251. package/ciel_runtime_support/secure_json_repository.py +55 -0
  252. package/ciel_runtime_support/session_import.py +356 -0
  253. package/ciel_runtime_support/settings_repository.py +8 -0
  254. package/ciel_runtime_support/slash_command_assets.py +211 -0
  255. package/ciel_runtime_support/sse_stream.py +57 -0
  256. package/ciel_runtime_support/sse_trace.py +225 -0
  257. package/ciel_runtime_support/statusline_script.py +593 -0
  258. package/ciel_runtime_support/statusline_settings.py +53 -0
  259. package/ciel_runtime_support/stream_chunk_policy.py +18 -0
  260. package/ciel_runtime_support/streaming_anthropic.py +1955 -0
  261. package/ciel_runtime_support/synthetic_tool_policy.py +105 -0
  262. package/ciel_runtime_support/terminal_platform_io.py +127 -0
  263. package/ciel_runtime_support/timeout_profile.py +196 -0
  264. package/ciel_runtime_support/tool_dialects.py +85 -0
  265. package/ciel_runtime_support/tool_exposure_policy.py +63 -0
  266. package/ciel_runtime_support/tool_guard_hooks.py +218 -0
  267. package/ciel_runtime_support/tool_request_projection.py +96 -0
  268. package/ciel_runtime_support/tool_schema.py +483 -0
  269. package/ciel_runtime_support/tool_side_effect_dedupe.py +95 -0
  270. package/ciel_runtime_support/ui_text.py +266 -0
  271. package/ciel_runtime_support/upstream_error_policy.py +104 -0
  272. package/ciel_runtime_support/upstream_retry.py +419 -0
  273. package/ciel_runtime_support/upstream_stream_io.py +106 -0
  274. package/ciel_runtime_support/usage_events.py +96 -0
  275. package/ciel_runtime_support/visible_stream_filters.py +130 -0
  276. package/ciel_runtime_support/web_endpoints.py +447 -0
  277. package/ciel_runtime_support/web_ui.py +915 -0
  278. package/ciel_runtime_support/web_ui_controller.py +189 -0
  279. package/ciel_runtime_support/windows_console_input.py +137 -0
  280. package/ciel_runtime_support/windows_console_mode.py +112 -0
  281. package/docs/Architecture.md +54 -0
  282. package/docs/Configuration.md +17 -0
  283. package/docs/Module-Map.md +1093 -26
  284. package/docs/Providers.md +46 -1
  285. package/docs/adr/0001-runtime-bounded-contexts.md +295 -0
  286. package/npm-bin/run-ciel-runtime.js +22 -2
  287. package/package.json +9 -2
@@ -0,0 +1,166 @@
1
+ """Runtime diagnostics and cache repository for compatibility probes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class CompatibilityRuntimePorts:
12
+ provider_policy: Callable[[str], Any]
13
+ runtime_info: Callable[..., dict[str, Any] | None]
14
+ positive_int: Callable[[Any], int | None]
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class CompatibilityCachePorts:
19
+ save_config: Callable[[dict[str, Any]], None]
20
+ timestamp: Callable[[], int]
21
+
22
+
23
+ class CompatibilityRuntimeProjection:
24
+ def __init__(self, ports: CompatibilityRuntimePorts) -> None:
25
+ self.ports = ports
26
+
27
+ @staticmethod
28
+ def vllm_tool_parser_hint(model: str) -> str | None:
29
+ normalized = model.lower()
30
+ if "qwen3-coder" in normalized or "qwen3_coder" in normalized:
31
+ return "vLLM hint: Qwen3-Coder models should be served with --enable-auto-tool-choice --tool-call-parser qwen3_xml."
32
+ if any(marker in normalized for marker in ("qwen2.5", "qwen2_5", "qwq")):
33
+ return "vLLM hint: Qwen2.5/QwQ tool templates usually use --enable-auto-tool-choice --tool-call-parser hermes."
34
+ if "glm-4.7" in normalized or "glm4.7" in normalized:
35
+ return "vLLM hint: GLM-4.7 models should be served with --enable-auto-tool-choice --tool-call-parser glm47."
36
+ if any(
37
+ marker in normalized
38
+ for marker in ("glm-4.5", "glm4.5", "glm-4.6", "glm4.6")
39
+ ):
40
+ return "vLLM hint: GLM-4.5/4.6 models should be served with --enable-auto-tool-choice --tool-call-parser glm45."
41
+ if "deepseek-v3.1" in normalized:
42
+ return "vLLM hint: DeepSeek-V3.1 models should be served with --enable-auto-tool-choice --tool-call-parser deepseek_v31."
43
+ if "deepseek-v3" in normalized or "deepseek-r1" in normalized:
44
+ return "vLLM hint: DeepSeek-V3/R1 models require the matching DeepSeek tool parser and chat template from vLLM examples."
45
+ if "llama-3" in normalized or "llama3" in normalized:
46
+ return "vLLM hint: Llama 3.x models usually need --enable-auto-tool-choice --tool-call-parser llama3_json and the matching tool chat template."
47
+ if "hermes" in normalized:
48
+ return "vLLM hint: Hermes models should be served with --enable-auto-tool-choice --tool-call-parser hermes."
49
+ if "qwen3" in normalized or "qwen-3" in normalized:
50
+ return (
51
+ "vLLM hint: this looks like a Qwen3-family model. Verify its model card/tool format; "
52
+ "Qwen3-Coder uses qwen3_xml, while older Hermes-style Qwen templates use hermes."
53
+ )
54
+ return None
55
+
56
+ def lines(
57
+ self,
58
+ provider: str,
59
+ config: dict[str, Any],
60
+ native: bool,
61
+ ) -> list[str]:
62
+ policy = self.ports.provider_policy(provider)
63
+ if not policy.exposes_runtime_info:
64
+ return []
65
+ lines: list[str] = []
66
+ info = self.ports.runtime_info(provider, config, timeout=4.0)
67
+ configured_context = self.ports.positive_int(config.get("context_window"))
68
+ configured_output = self.ports.positive_int(config.get("max_output_tokens"))
69
+ if info:
70
+ lines.append(f"Runtime models URL: {info.get('models_url')}")
71
+ if info.get("runtime_model"):
72
+ lines.append(f"Runtime model id: {info.get('runtime_model')}")
73
+ runtime_limit = self.ports.positive_int(info.get("max_model_len"))
74
+ if runtime_limit:
75
+ lines.append(f"Runtime max_model_len: {runtime_limit}")
76
+ else:
77
+ lines.append("Runtime max_model_len: not reported by /v1/models")
78
+ lines.extend(policy.runtime_metadata(info))
79
+ else:
80
+ runtime_limit = None
81
+ lines.append(
82
+ "Runtime max_model_len: unavailable (/v1/models did not return model metadata)"
83
+ )
84
+ if configured_context:
85
+ lines.append(f"Configured context_window: {configured_context}")
86
+ if configured_output:
87
+ lines.append(f"Configured max_output_tokens: {configured_output}")
88
+ if runtime_limit and configured_context and configured_context != runtime_limit:
89
+ lines.append(
90
+ f"Context warning: configured context_window {configured_context} "
91
+ f"differs from runtime max_model_len {runtime_limit}."
92
+ )
93
+ if runtime_limit and configured_output and configured_output >= runtime_limit:
94
+ lines.append(
95
+ "Context warning: max_output_tokens is greater than or equal to the full runtime context length."
96
+ )
97
+ if native:
98
+ lines.append(
99
+ "Runtime mode note: native mode sends Claude Code requests directly; "
100
+ "ciel-runtime cannot shrink max_tokens per request."
101
+ )
102
+ else:
103
+ lines.append(
104
+ "Runtime mode note: router mode can cap max_tokens based on configured context_window."
105
+ )
106
+ return lines
107
+
108
+
109
+ class CompatibilityCacheRepository:
110
+ def __init__(self, ports: CompatibilityCachePorts) -> None:
111
+ self.ports = ports
112
+
113
+ def record(
114
+ self,
115
+ config: dict[str, Any],
116
+ provider: str,
117
+ model: str,
118
+ ok: bool,
119
+ code: int | None = None,
120
+ message: str = "",
121
+ diagnosis: str = "",
122
+ ) -> None:
123
+ cache = config.setdefault("compatibility_cache", {})
124
+ if not isinstance(cache, dict):
125
+ cache = {}
126
+ config["compatibility_cache"] = cache
127
+ provider_cache = cache.setdefault(provider, {})
128
+ if not isinstance(provider_cache, dict):
129
+ provider_cache = {}
130
+ cache[provider] = provider_cache
131
+ provider_cache[model] = {
132
+ "ok": ok,
133
+ "code": code,
134
+ "message": message[:500],
135
+ "diagnosis": diagnosis[:500],
136
+ "tested_at": self.ports.timestamp(),
137
+ }
138
+ self.ports.save_config(config)
139
+
140
+
141
+ @dataclass(frozen=True, slots=True)
142
+ class ClaudeCliCapabilityProbe:
143
+ cache: dict[str, bool]
144
+ run: Callable[..., Any]
145
+
146
+ def supports_permission_mode(self, executable: str) -> bool:
147
+ cache_key = str(executable or "")
148
+ if cache_key in self.cache:
149
+ return self.cache[cache_key]
150
+ try:
151
+ process = self.run(
152
+ [executable, "--help"],
153
+ capture_output=True,
154
+ text=True,
155
+ timeout=5,
156
+ check=False,
157
+ )
158
+ help_text = process.stdout or ""
159
+ supported = (
160
+ "--permission-mode" in help_text
161
+ and "bypassPermissions" in help_text
162
+ )
163
+ except Exception:
164
+ supported = False
165
+ self.cache[cache_key] = supported
166
+ return supported
@@ -0,0 +1,370 @@
1
+ """Provider compatibility-test application service."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from dataclasses import dataclass
7
+ import sys
8
+ from typing import Any, Callable
9
+ import urllib.error
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class CompatibilityTestConstants:
14
+ api_key_probe_error: type[Exception]
15
+ compatibility_test_header: str
16
+ lm_studio_min_context: int
17
+ opencode_provider_names: tuple[str, ...]
18
+ router_base: str
19
+
20
+
21
+ @dataclass(frozen=True, slots=True)
22
+ class CompatibilityTestConfig:
23
+ current_alias: Callable[..., Any]
24
+ current_upstream_model_id: Callable[..., Any]
25
+ ensure_current_model: Callable[..., Any]
26
+ get_current_provider: Callable[..., Any]
27
+ launch_model_id: Callable[..., Any]
28
+ load_config: Callable[..., Any]
29
+ normalize_model_id: Callable[..., Any]
30
+ positive_int: Callable[..., Any]
31
+ save_config: Callable[..., Any]
32
+ upstream_model_runtime_info: Callable[..., Any]
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class CompatibilityTestMode:
37
+ ensure_lm_studio_model_loaded: Callable[..., Any]
38
+ lm_studio_native_enabled: Callable[..., Any]
39
+ native_anthropic_base_url: Callable[..., Any]
40
+ nim_native_enabled: Callable[..., Any]
41
+ nvidia_native_enabled: Callable[..., Any]
42
+ ollama_native_enabled: Callable[..., Any]
43
+ provider_native_enabled: Callable[..., Any]
44
+ upstream_api_model_id: Callable[..., Any]
45
+ vllm_native_enabled: Callable[..., Any]
46
+ vllm_tool_parser_hint: Callable[..., Any]
47
+
48
+
49
+ @dataclass(frozen=True, slots=True)
50
+ class CompatibilityTestRequest:
51
+ compatibility_endpoint_probe_lines: Callable[..., Any]
52
+ compatibility_failure_diagnosis: Callable[..., Any]
53
+ compatibility_http_error_message: Callable[..., Any]
54
+ post_json: Callable[..., Any]
55
+ provider_headers: Callable[..., Any]
56
+ provider_ip_family_probe_lines: Callable[..., Any]
57
+ run_api_key_probes: Callable[..., Any]
58
+ start_router: Callable[..., Any]
59
+ stop_router: Callable[..., Any]
60
+
61
+
62
+ @dataclass(frozen=True, slots=True)
63
+ class CompatibilityTestProtocol:
64
+ compatibility_text_request: Callable[..., Any]
65
+ compatibility_tool_request: Callable[..., Any]
66
+ compatibility_tool_result_request: Callable[..., Any]
67
+ find_compat_tool_use: Callable[..., Any]
68
+ known_tool_use_blocker: Callable[..., Any]
69
+ normalize_thinking: Callable[..., Any]
70
+ normalize_tool_choice: Callable[..., Any]
71
+ ollama_chat_request: Callable[..., Any]
72
+ resolve_requested_model: Callable[..., Any]
73
+ response_text_preview: Callable[..., Any]
74
+
75
+
76
+ @dataclass(frozen=True, slots=True)
77
+ class CompatibilityTestOutput:
78
+ compatibility_runtime_lines: Callable[..., Any]
79
+ join_url: Callable[..., Any]
80
+ set_compatibility_cache: Callable[..., Any]
81
+ summarize_compat_response: Callable[..., Any]
82
+
83
+
84
+ @dataclass(frozen=True, slots=True)
85
+ class CompatibilityTestServices:
86
+ constants: CompatibilityTestConstants
87
+ config: CompatibilityTestConfig
88
+ mode: CompatibilityTestMode
89
+ request: CompatibilityTestRequest
90
+ protocol: CompatibilityTestProtocol
91
+ output: CompatibilityTestOutput
92
+
93
+
94
+ def run_compatibility_test(
95
+ args: argparse.Namespace, *, services: CompatibilityTestServices
96
+ ) -> None:
97
+ constants = services.constants
98
+ config = services.config
99
+ mode = services.mode
100
+ request = services.request
101
+ protocol = services.protocol
102
+ output = services.output
103
+ COMPATIBILITY_TEST_HEADER = constants.compatibility_test_header
104
+ CompatibilityApiKeyProbeError = constants.api_key_probe_error
105
+ LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT = constants.lm_studio_min_context
106
+ OPENCODE_PROVIDER_NAMES = constants.opencode_provider_names
107
+ ROUTER_BASE = constants.router_base
108
+ current_alias = config.current_alias
109
+ current_upstream_model_id = config.current_upstream_model_id
110
+ ensure_current_model_from_provider_list = config.ensure_current_model
111
+ get_current_provider = config.get_current_provider
112
+ launch_model_id = config.launch_model_id
113
+ load_config = config.load_config
114
+ normalize_model_id = config.normalize_model_id
115
+ positive_int = config.positive_int
116
+ save_config = config.save_config
117
+ upstream_model_runtime_info = config.upstream_model_runtime_info
118
+ ensure_lm_studio_model_loaded_for_context = mode.ensure_lm_studio_model_loaded
119
+ lm_studio_native_compat_enabled = mode.lm_studio_native_enabled
120
+ native_anthropic_base_url = mode.native_anthropic_base_url
121
+ nim_native_compat_enabled = mode.nim_native_enabled
122
+ nvidia_hosted_native_compat_enabled = mode.nvidia_native_enabled
123
+ ollama_native_compat_enabled = mode.ollama_native_enabled
124
+ provider_native_compat_enabled = mode.provider_native_enabled
125
+ upstream_api_model_id = mode.upstream_api_model_id
126
+ vllm_native_compat_enabled = mode.vllm_native_enabled
127
+ vllm_tool_parser_hint = mode.vllm_tool_parser_hint
128
+ compatibility_endpoint_probe_lines = request.compatibility_endpoint_probe_lines
129
+ compatibility_failure_diagnosis = request.compatibility_failure_diagnosis
130
+ compatibility_http_error_message = request.compatibility_http_error_message
131
+ post_json = request.post_json
132
+ provider_headers = request.provider_headers
133
+ provider_ip_family_probe_lines = request.provider_ip_family_probe_lines
134
+ run_compatibility_api_key_probes = request.run_api_key_probes
135
+ start_router_if_needed = request.start_router
136
+ stop_router_processes = request.stop_router
137
+ compatibility_text_request = protocol.compatibility_text_request
138
+ compatibility_tool_request = protocol.compatibility_tool_request
139
+ compatibility_tool_result_request = protocol.compatibility_tool_result_request
140
+ find_compat_tool_use = protocol.find_compat_tool_use
141
+ known_compatibility_tool_use_blocker = protocol.known_tool_use_blocker
142
+ normalize_thinking_for_non_anthropic_provider = protocol.normalize_thinking
143
+ normalize_tool_choice_for_provider = protocol.normalize_tool_choice
144
+ ollama_chat_request = protocol.ollama_chat_request
145
+ resolve_requested_model = protocol.resolve_requested_model
146
+ response_text_preview = protocol.response_text_preview
147
+ compatibility_runtime_lines = output.compatibility_runtime_lines
148
+ join_url = output.join_url
149
+ set_compatibility_cache = output.set_compatibility_cache
150
+ summarize_compat_response = output.summarize_compat_response
151
+ cfg = load_config()
152
+ provider, pcfg = get_current_provider(cfg)
153
+ test_mode = getattr(args, "mode", "auto") or "auto"
154
+ if test_mode not in ("auto", "quick", "smoke", "full"):
155
+ raise SystemExit("test mode must be auto, quick, smoke, or full")
156
+ effective_mode = "quick" if test_mode == "auto" and provider == "nvidia-hosted" else ("full" if test_mode == "auto" else test_mode)
157
+ lm_studio_preflight_lines: list[str] = []
158
+ if provider == "lm-studio":
159
+ try:
160
+ lm_studio_preflight_lines = ensure_lm_studio_model_loaded_for_context(pcfg, timeout=1.5)
161
+ save_config(cfg)
162
+ except Exception as exc:
163
+ print("Compatibility: FAIL")
164
+ print("Reason: CielRuntime could not automatically load the selected LM Studio model with the recommended context.")
165
+ print(f"Diagnosis: LM Studio load failed ({type(exc).__name__}: {exc}).")
166
+ sys.exit(1)
167
+ selected, selection_lines = ensure_current_model_from_provider_list(provider, pcfg)
168
+ for line in selection_lines:
169
+ print(line)
170
+ if selection_lines:
171
+ save_config(cfg)
172
+ if not selected:
173
+ print("Compatibility: FAIL")
174
+ print("Reason: No concrete provider model is selected.")
175
+ print("Diagnosis: choose a model from the provider model list, then retry the compatibility test.")
176
+ set_compatibility_cache(cfg, provider, normalize_model_id(provider, str(pcfg.get("current_model") or "")) or "(unset)", False, None, "No concrete provider model is selected.", "Choose a model from the provider model list.")
177
+ raise SystemExit(1)
178
+ ollama_native = ollama_native_compat_enabled(provider, pcfg)
179
+ provider_native = provider_native_compat_enabled(provider, pcfg)
180
+ native = ollama_native or provider_native
181
+ model = current_upstream_model_id(provider, pcfg) if provider_native else (launch_model_id(provider, pcfg) if ollama_native else current_alias(cfg))
182
+ request_model = upstream_api_model_id(provider, model) if native else model
183
+ base = native_anthropic_base_url(provider, pcfg) if native else ROUTER_BASE
184
+ if not native:
185
+ # Compatibility tests must exercise the currently installed router.
186
+ # Older long-running routers can keep stale NVIDIA proxy code alive
187
+ # across npm upgrades, producing false nvd-claude-proxy failures.
188
+ stop_router_processes(quiet=True)
189
+ start_router_if_needed()
190
+ url = join_url(base, "/v1/messages")
191
+ headers = provider_headers(provider, pcfg)
192
+ headers[COMPATIBILITY_TEST_HEADER] = "1"
193
+ if ollama_native:
194
+ headers = {
195
+ "content-type": "application/json",
196
+ "anthropic-version": "2023-06-01",
197
+ "authorization": "Bearer ollama",
198
+ "x-api-key": "ollama",
199
+ COMPATIBILITY_TEST_HEADER: "1",
200
+ }
201
+ text_body = normalize_tool_choice_for_provider(
202
+ provider,
203
+ pcfg,
204
+ normalize_thinking_for_non_anthropic_provider(provider, pcfg, compatibility_text_request(request_model)),
205
+ )
206
+ tool_body = normalize_tool_choice_for_provider(
207
+ provider,
208
+ pcfg,
209
+ normalize_thinking_for_non_anthropic_provider(provider, pcfg, compatibility_tool_request(request_model)),
210
+ )
211
+ print(f"Testing provider: {provider}")
212
+ print(f"Test mode: {effective_mode}")
213
+ if ollama_native:
214
+ mode = "ollama-native"
215
+ elif vllm_native_compat_enabled(provider, pcfg):
216
+ mode = "vllm-native"
217
+ elif lm_studio_native_compat_enabled(provider, pcfg):
218
+ mode = "lm-studio-native"
219
+ elif nim_native_compat_enabled(provider, pcfg):
220
+ mode = "nim-native"
221
+ elif nvidia_hosted_native_compat_enabled(provider, pcfg):
222
+ mode = "nvidia-native"
223
+ else:
224
+ mode = "ciel-runtime-router"
225
+ print(f"Mode: {mode}")
226
+ print(f"Claude API URL: {url}")
227
+ if not native:
228
+ print(f"Upstream base URL: {pcfg.get('base_url')}")
229
+ for line in provider_ip_family_probe_lines(provider, pcfg):
230
+ print(line)
231
+ if provider in ("ollama", "ollama-cloud"):
232
+ req_preview = ollama_chat_request(resolve_requested_model(provider, pcfg, model), tool_body, pcfg, stream=False, provider=provider)
233
+ print(f"Ollama num_ctx: {req_preview.get('options', {}).get('num_ctx', 'default')}")
234
+ elif provider in OPENCODE_PROVIDER_NAMES:
235
+ for line in provider_ip_family_probe_lines(provider, pcfg):
236
+ print(line)
237
+ print(f"Model: {model}")
238
+ if request_model != model:
239
+ print(f"API model: {request_model}")
240
+ for line in lm_studio_preflight_lines:
241
+ print(line)
242
+ for line in compatibility_runtime_lines(provider, pcfg, native):
243
+ print(line)
244
+ for line in compatibility_endpoint_probe_lines(provider, pcfg, timeout=min(float(args.timeout or 1.5), 3.0)):
245
+ print(line)
246
+ if provider == "lm-studio":
247
+ info = upstream_model_runtime_info(provider, pcfg, timeout=1.5)
248
+ loaded = positive_int(info.get("loaded_context_len")) if info else None
249
+ state = str(info.get("state") or "") if info else ""
250
+ if loaded and loaded < LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:
251
+ print("Compatibility: FAIL")
252
+ print(
253
+ "Reason: LM Studio loaded context is "
254
+ f"{loaded:,} tokens; Claude Code needs at least {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,}."
255
+ )
256
+ print("Diagnosis: reload the model in LM Studio with a larger context length, then retry.")
257
+ sys.exit(1)
258
+ if state and state != "loaded":
259
+ print("Compatibility: FAIL")
260
+ print("Reason: the selected LM Studio model is not loaded, so the active context length cannot be verified.")
261
+ print(f"Diagnosis: load the model in LM Studio with at least {LM_STUDIO_MIN_CLAUDE_CODE_CONTEXT:,} context tokens, then retry.")
262
+ sys.exit(1)
263
+ if provider == "vllm":
264
+ hint = vllm_tool_parser_hint(model)
265
+ if hint:
266
+ print(hint)
267
+
268
+ def fail(message: str, code: int | None = None, diagnosis: str = "") -> None:
269
+ print("Compatibility: FAIL")
270
+ if code is not None:
271
+ print(f"HTTP: {code}")
272
+ print(f"Reason: {message[:1000]}")
273
+ if diagnosis:
274
+ print(diagnosis)
275
+ set_compatibility_cache(cfg, provider, model, False, code, message, diagnosis)
276
+ raise SystemExit(1)
277
+
278
+ def run_phase(label: str, request_body: dict[str, Any]) -> Any:
279
+ print(f"{label}: running")
280
+ try:
281
+ return post_json(
282
+ url,
283
+ request_body,
284
+ headers=headers,
285
+ timeout=args.timeout,
286
+ provider=provider if native else None,
287
+ pcfg=pcfg if native else None,
288
+ )
289
+ except urllib.error.HTTPError as exc:
290
+ msg = compatibility_http_error_message(exc)
291
+ diagnosis = compatibility_failure_diagnosis(provider, exc.code, msg)
292
+ fail(f"{label}: {msg}", exc.code, diagnosis or "")
293
+ except TimeoutError:
294
+ print("Compatibility: TIMEOUT")
295
+ print(f"Reason: {label} did not respond before the {args.timeout:g}s compatibility-test timeout.")
296
+ print("Diagnosis: this timeout was not saved as a model failure. Retry the test or choose another model if it repeats.")
297
+ sys.stdout.flush()
298
+ sys.exit(1)
299
+ except Exception as exc:
300
+ msg = f"{type(exc).__name__}: {exc}"
301
+ if "timed out" in msg.lower() or "timeout" in msg.lower():
302
+ print("Compatibility: TIMEOUT")
303
+ print(f"Reason: {label}: {msg}")
304
+ print("Diagnosis: this timeout was not saved as a model failure. Retry the test or choose another model if it repeats.")
305
+ sys.stdout.flush()
306
+ sys.exit(1)
307
+ fail(f"{label}: {msg}")
308
+
309
+ try:
310
+ for line in run_compatibility_api_key_probes(provider, pcfg, model, text_body, args.timeout):
311
+ print(line)
312
+ except CompatibilityApiKeyProbeError as exc:
313
+ fail(f"API key check: {exc}", exc.code, exc.diagnosis)
314
+
315
+ text_data = run_phase("Text response", text_body)
316
+ for line in summarize_compat_response(text_data, "Text response"):
317
+ print(line)
318
+
319
+ if effective_mode == "quick":
320
+ set_compatibility_cache(cfg, provider, model, True, 200, "text quick OK", "")
321
+ print("Compatibility: OK")
322
+ print("Note: quick mode checked text only; run `ciel-runtime test 120 smoke` for tool_use or `ciel-runtime test 180 full` for tool_result.")
323
+ return
324
+
325
+ tool_blocker = known_compatibility_tool_use_blocker(provider, request_model)
326
+ if tool_blocker:
327
+ fail(
328
+ f"Tool use: {tool_blocker}",
329
+ diagnosis=(
330
+ "Diagnosis: the selected model is not suitable for Claude Code through the Anthropic "
331
+ "compatibility path because Claude Code depends on reliable tool_use responses."
332
+ ),
333
+ )
334
+
335
+ tool_data = run_phase("Tool use", tool_body)
336
+ tool_use, tool_error = find_compat_tool_use(tool_data)
337
+ if not tool_use:
338
+ diagnosis = (
339
+ "Diagnosis: the model/server did not return a valid Anthropic tool_use block. "
340
+ "Claude Code can fail with 'tool call could not be parsed' on this provider/model."
341
+ )
342
+ if provider == "vllm":
343
+ hint = vllm_tool_parser_hint(model)
344
+ if hint:
345
+ diagnosis = f"{diagnosis} {hint}"
346
+ fail(f"Tool use: {tool_error}", diagnosis=diagnosis)
347
+ for line in summarize_compat_response(tool_data, "Tool use"):
348
+ print(line)
349
+
350
+ if effective_mode == "smoke":
351
+ set_compatibility_cache(cfg, provider, model, True, 200, "text/tool_use smoke OK", "")
352
+ print("Compatibility: OK")
353
+ print("Note: smoke mode checked text and tool_use only; run `ciel-runtime test 180 full` for tool_result round trip.")
354
+ return
355
+
356
+ result_body = compatibility_tool_result_request(request_model, tool_use)
357
+ result_data = run_phase("Tool result", result_body)
358
+ result_preview = response_text_preview(result_data)
359
+ if not result_preview:
360
+ fail(
361
+ "Tool result: no final text response after tool_result.",
362
+ diagnosis="Diagnosis: the provider accepted tool_use but did not complete the tool_result round trip.",
363
+ )
364
+ for line in summarize_compat_response(result_data, "Tool result"):
365
+ print(line)
366
+ print(f"Tool result text: {result_preview[:120]}")
367
+
368
+ set_compatibility_cache(cfg, provider, model, True, 200, "text/tool_use/tool_result OK", "")
369
+ print("Compatibility: OK")
370
+