@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,186 @@
1
+ """Anthropic model metadata and Claude Code capability policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable
8
+
9
+
10
+ SUPPORTED_CAPABILITIES = (
11
+ "effort",
12
+ "xhigh_effort",
13
+ "max_effort",
14
+ "thinking",
15
+ "adaptive_thinking",
16
+ "interleaved_thinking",
17
+ )
18
+
19
+
20
+ def model_family(model_id: str) -> str:
21
+ model = (model_id or "").strip().lower()
22
+ for family in ("fable", "mythos", "opus", "sonnet", "haiku"):
23
+ if re.search(rf"(?:^|-)claude-(?:\d+(?:-\d+){{0,2}}-)?{family}(?:-|$)", model) or f"-{family}-" in model:
24
+ return family
25
+ return "claude"
26
+
27
+
28
+ def limit_hints(model_id: str) -> dict[str, Any]:
29
+ model = (model_id or "").strip().lower()
30
+ family = model_family(model)
31
+ if family in ("fable", "mythos") or (
32
+ family == "opus" and re.search(r"(?:^|-)opus-(?:5|4-[678])(?:-|$)", model)
33
+ ):
34
+ return {
35
+ "context_window": 1048576,
36
+ "max_output_tokens": 128000,
37
+ "source": "anthropic-models-overview-current-table",
38
+ }
39
+ if family == "sonnet" and re.search(r"(?:^|-)sonnet-5(?:-|$)", model):
40
+ return {
41
+ "context_window": 1048576,
42
+ "max_output_tokens": 128000,
43
+ "source": "anthropic-models-overview-current-table",
44
+ }
45
+ if family == "sonnet" and re.search(r"(?:^|-)sonnet-4-6(?:-|$)", model):
46
+ return {
47
+ "context_window": 1048576,
48
+ "max_output_tokens": 128000,
49
+ "source": "anthropic-models-overview-current-table",
50
+ }
51
+ if family == "haiku" and re.search(r"(?:^|-)haiku-4-5(?:-|$)", model):
52
+ return {
53
+ "context_window": 200000,
54
+ "max_output_tokens": 64000,
55
+ "source": "anthropic-models-overview-current-table",
56
+ }
57
+ return {"context_window": 200000, "source": "anthropic-default-compatibility"}
58
+
59
+
60
+ def runtime_hints(model_id: str) -> dict[str, Any]:
61
+ model = (model_id or "").strip().lower()
62
+ if model_family(model) in ("fable", "mythos"):
63
+ return {
64
+ "claude_code_default_effort": "high",
65
+ "claude_code_max_effort": "xhigh",
66
+ "thinking_mode": "adaptive",
67
+ "extended_thinking": False,
68
+ "adaptive_thinking_always_on": True,
69
+ "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
70
+ "source": "anthropic-models-overview-current-table",
71
+ }
72
+ if re.search(r"(?:^|-)opus-5(?:-|$)", model):
73
+ return {
74
+ "claude_code_default_effort": "high",
75
+ "claude_code_max_effort": "xhigh",
76
+ "thinking_mode": "adaptive",
77
+ "adaptive_thinking_default_on": True,
78
+ "thinking_disabled_max_effort": "high",
79
+ "extended_thinking": False,
80
+ "fast_mode": {"available": True, "preview": True},
81
+ "prompt_cache_min_tokens": 512,
82
+ "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
83
+ "source": "anthropic-opus-5-launch-notes",
84
+ }
85
+ if re.search(r"(?:^|-)sonnet-5(?:-|$)", model):
86
+ return {
87
+ "claude_code_default_effort": "high",
88
+ "claude_code_max_effort": "xhigh",
89
+ "thinking_mode": "adaptive",
90
+ "adaptive_thinking_default_on": True,
91
+ "extended_thinking": False,
92
+ "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
93
+ "source": "anthropic-sonnet-5-launch-notes",
94
+ }
95
+ if re.search(r"(?:^|-)opus-4-8(?:-|$)", model):
96
+ return {
97
+ "claude_code_default_effort": "high",
98
+ "claude_code_max_effort": "xhigh",
99
+ "thinking_mode": "adaptive",
100
+ "fast_mode": {"available": True, "preview": True},
101
+ "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
102
+ "source": "anthropic-opus-4-8-launch-notes",
103
+ }
104
+ if re.search(r"(?:^|-)opus-4-7(?:-|$)", model):
105
+ return {
106
+ "claude_code_default_effort": "high",
107
+ "claude_code_max_effort": "xhigh",
108
+ "thinking_mode": "adaptive",
109
+ # Fast mode was removed on 4.7: speed="fast" errors, unlike 4.8.
110
+ "fast_mode": {"available": False, "preview": False},
111
+ "unsupported_sampling_parameters": ["temperature", "top_p", "top_k"],
112
+ "source": "anthropic-opus-4-7-launch-notes",
113
+ }
114
+ return {}
115
+
116
+
117
+ def normalize_capabilities(value: Any) -> list[str]:
118
+ if value is None or value is False:
119
+ return []
120
+ if isinstance(value, str):
121
+ raw_items = re.split(r"[,;\s]+", value.strip())
122
+ elif isinstance(value, (list, tuple, set)):
123
+ raw_items = [str(item) for item in value]
124
+ else:
125
+ raw_items = [str(value)]
126
+ allowed = set(SUPPORTED_CAPABILITIES)
127
+ result: list[str] = []
128
+ for raw in raw_items:
129
+ item = str(raw or "").strip().lower().replace("-", "_")
130
+ if item and item in allowed and item not in result:
131
+ result.append(item)
132
+ return result
133
+
134
+
135
+ def infer_capabilities(model_id: str, strip_context_suffix: Callable[[str], str]) -> list[str]:
136
+ model = strip_context_suffix(model_id).strip().lower()
137
+ if re.search(r"(?:^|-)claude-(?:fable|mythos)(?:-|$)", model):
138
+ return ["effort", "xhigh_effort", "max_effort", "thinking", "adaptive_thinking"]
139
+ if re.search(r"(?:^|-)(?:opus-5|sonnet-5|opus-4-[78])(?:-|$)", model):
140
+ return [
141
+ "effort", "xhigh_effort", "max_effort", "thinking",
142
+ "adaptive_thinking", "interleaved_thinking",
143
+ ]
144
+ if re.search(r"(?:^|-)(?:opus-4-6|sonnet-4-6)(?:-|$)", model):
145
+ return ["effort", "max_effort", "thinking", "adaptive_thinking", "interleaved_thinking"]
146
+ if re.search(r"(?:^|-)opus-4-5(?:-|$)", model):
147
+ # Opus 4.5 takes effort at low/medium/high only, and thinks through the
148
+ # pre-4.6 budget_tokens contract rather than adaptive thinking.
149
+ return ["effort", "thinking"]
150
+ return []
151
+
152
+
153
+ def recommended_preset(model_id: str) -> str:
154
+ return "fast" if model_family(model_id) == "haiku" else "balanced"
155
+
156
+
157
+ @dataclass(frozen=True, slots=True)
158
+ class AnthropicModelRecommendations:
159
+ unique_ids: Callable[[str, list[str]], list[str]]
160
+ preset_timeout_ms: Callable[[str], int]
161
+ idle_timeout_ms: Callable[[int], int]
162
+
163
+ def build(self, provider: str, models: list[str]) -> dict[str, Any]:
164
+ if provider != "anthropic":
165
+ return {}
166
+ recommendations: dict[str, Any] = {}
167
+ for model_id in self.unique_ids(provider, models):
168
+ preset = recommended_preset(model_id)
169
+ timeout = self.preset_timeout_ms(preset)
170
+ recommendations[model_id] = {
171
+ "schema": 1,
172
+ "model_family": model_family(model_id),
173
+ "recommended_preset": preset,
174
+ "parameters": {
175
+ "max_output_tokens": 2048 if preset == "fast" else 4096,
176
+ "request_timeout_ms": timeout,
177
+ "stream_idle_timeout_ms": self.idle_timeout_ms(timeout),
178
+ },
179
+ "limits": limit_hints(model_id),
180
+ "runtime": runtime_hints(model_id),
181
+ "notes": [
182
+ "Native Claude Code manages the real context window; ciel-runtime stores context limits as model metadata only.",
183
+ "Recommended max_output_tokens is intentionally lower than the provider hard limit for interactive CLI use.",
184
+ ],
185
+ }
186
+ return recommendations
@@ -0,0 +1,255 @@
1
+ """HTTP/SSE adapter for locally generated Anthropic message responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import time
7
+ from collections.abc import Callable
8
+ from typing import Any
9
+
10
+
11
+ def anthropic_text_response(
12
+ model: str, text: str, stop_reason: str = "end_turn"
13
+ ) -> dict[str, Any]:
14
+ return {
15
+ "id": f"msg_ollama_advisor_{int(time.time() * 1000)}",
16
+ "type": "message",
17
+ "role": "assistant",
18
+ "model": model,
19
+ "content": [{"type": "text", "text": text}],
20
+ "stop_reason": stop_reason,
21
+ "stop_sequence": None,
22
+ "usage": {"input_tokens": 0, "output_tokens": max(1, len(text) // 4)},
23
+ }
24
+
25
+
26
+ def prepend_anthropic_text(
27
+ message: dict[str, Any], text: str
28
+ ) -> dict[str, Any]:
29
+ if not text:
30
+ return message
31
+ projected = dict(message)
32
+ content = projected.get("content")
33
+ blocks = list(content) if isinstance(content, list) else []
34
+ blocks.insert(0, {"type": "text", "text": text})
35
+ projected["content"] = blocks
36
+ return projected
37
+
38
+
39
+ class AnthropicResponseWriter:
40
+ def __init__(self, write_json: Callable[[Any, Any], None]) -> None:
41
+ self.write_json = write_json
42
+
43
+ @staticmethod
44
+ def _event(handler: Any, name: str, payload: dict[str, Any]) -> None:
45
+ data = json.dumps(payload, ensure_ascii=False)
46
+ handler.wfile.write(f"event: {name}\ndata: {data}\n\n".encode())
47
+
48
+ @staticmethod
49
+ def _start_headers(handler: Any) -> None:
50
+ handler.send_response(200)
51
+ handler.send_header("content-type", "text/event-stream")
52
+ handler.send_header("cache-control", "no-cache")
53
+ handler.send_header("connection", "close")
54
+ handler.end_headers()
55
+
56
+ def text(self, handler: Any, model: str, text: str, stream: bool) -> None:
57
+ message = anthropic_text_response(model, text)
58
+ if not stream:
59
+ self.write_json(handler, message)
60
+ return
61
+ self._start_headers(handler)
62
+ started = {
63
+ **message,
64
+ "content": [],
65
+ "stop_reason": None,
66
+ "usage": {"input_tokens": 0, "output_tokens": 0},
67
+ }
68
+ self._event(
69
+ handler,
70
+ "message_start",
71
+ {"type": "message_start", "message": started},
72
+ )
73
+ self.blocks(handler, message["content"], flush=False)
74
+ self.stop(handler, message, flush=False)
75
+ handler.wfile.flush()
76
+
77
+ def message(
78
+ self, handler: Any, message: dict[str, Any], stream: bool
79
+ ) -> None:
80
+ if not stream:
81
+ self.write_json(handler, message)
82
+ return
83
+ self._start_headers(handler)
84
+ self._event(
85
+ handler,
86
+ "message_start",
87
+ {
88
+ "type": "message_start",
89
+ "message": {**message, "content": [], "stop_reason": None},
90
+ },
91
+ )
92
+ self.blocks(handler, message.get("content") or [], flush=False)
93
+ self.stop(handler, message, flush=False)
94
+ handler.wfile.flush()
95
+
96
+ def block(self, handler: Any, index: int, block: dict[str, Any]) -> None:
97
+ block_type = block.get("type")
98
+ if block_type == "text":
99
+ self._event(
100
+ handler,
101
+ "content_block_start",
102
+ {
103
+ "type": "content_block_start",
104
+ "index": index,
105
+ "content_block": {"type": "text", "text": ""},
106
+ },
107
+ )
108
+ self._event(
109
+ handler,
110
+ "content_block_delta",
111
+ {
112
+ "type": "content_block_delta",
113
+ "index": index,
114
+ "delta": {
115
+ "type": "text_delta",
116
+ "text": block.get("text", ""),
117
+ },
118
+ },
119
+ )
120
+ elif block_type == "thinking":
121
+ self._event(
122
+ handler,
123
+ "content_block_start",
124
+ {
125
+ "type": "content_block_start",
126
+ "index": index,
127
+ "content_block": {"type": "thinking", "thinking": ""},
128
+ },
129
+ )
130
+ thinking = str(block.get("thinking") or "")
131
+ if thinking:
132
+ self._event(
133
+ handler,
134
+ "content_block_delta",
135
+ {
136
+ "type": "content_block_delta",
137
+ "index": index,
138
+ "delta": {"type": "thinking_delta", "thinking": thinking},
139
+ },
140
+ )
141
+ signature = str(block.get("signature") or "")
142
+ if signature:
143
+ self._event(
144
+ handler,
145
+ "content_block_delta",
146
+ {
147
+ "type": "content_block_delta",
148
+ "index": index,
149
+ "delta": {"type": "signature_delta", "signature": signature},
150
+ },
151
+ )
152
+ elif block_type == "redacted_thinking":
153
+ self._event(
154
+ handler,
155
+ "content_block_start",
156
+ {
157
+ "type": "content_block_start",
158
+ "index": index,
159
+ "content_block": block,
160
+ },
161
+ )
162
+ elif block_type == "tool_use":
163
+ tool_input = block.get("input") or {}
164
+ self._event(
165
+ handler,
166
+ "content_block_start",
167
+ {
168
+ "type": "content_block_start",
169
+ "index": index,
170
+ "content_block": {**block, "input": {}},
171
+ },
172
+ )
173
+ self._event(
174
+ handler,
175
+ "content_block_delta",
176
+ {
177
+ "type": "content_block_delta",
178
+ "index": index,
179
+ "delta": {
180
+ "type": "input_json_delta",
181
+ "partial_json": json.dumps(tool_input, ensure_ascii=False),
182
+ },
183
+ },
184
+ )
185
+ else:
186
+ return
187
+ self._event(
188
+ handler,
189
+ "content_block_stop",
190
+ {"type": "content_block_stop", "index": index},
191
+ )
192
+
193
+ def start(self, handler: Any, model: str, input_tokens: int = 0) -> None:
194
+ self._start_headers(handler)
195
+ self._event(
196
+ handler,
197
+ "message_start",
198
+ {
199
+ "type": "message_start",
200
+ "message": {
201
+ "id": f"msg_ciel_runtime_{int(time.time() * 1000)}",
202
+ "type": "message",
203
+ "role": "assistant",
204
+ "content": [],
205
+ "model": model,
206
+ "stop_reason": None,
207
+ "stop_sequence": None,
208
+ "usage": {
209
+ "input_tokens": max(0, int(input_tokens or 0)),
210
+ "output_tokens": 0,
211
+ },
212
+ },
213
+ },
214
+ )
215
+ handler.wfile.flush()
216
+
217
+ def blocks(
218
+ self,
219
+ handler: Any,
220
+ blocks: list[dict[str, Any]],
221
+ start_index: int = 0,
222
+ *,
223
+ flush: bool = True,
224
+ ) -> int:
225
+ index = start_index
226
+ for block in blocks:
227
+ self.block(handler, index, block)
228
+ index += 1
229
+ if flush:
230
+ handler.wfile.flush()
231
+ return index
232
+
233
+ def stop(
234
+ self,
235
+ handler: Any,
236
+ message: dict[str, Any] | None = None,
237
+ *,
238
+ flush: bool = True,
239
+ ) -> None:
240
+ message = message or {}
241
+ self._event(
242
+ handler,
243
+ "message_delta",
244
+ {
245
+ "type": "message_delta",
246
+ "delta": {
247
+ "stop_reason": message.get("stop_reason") or "end_turn",
248
+ "stop_sequence": None,
249
+ },
250
+ "usage": message.get("usage") or {"output_tokens": 1},
251
+ },
252
+ )
253
+ self._event(handler, "message_stop", {"type": "message_stop"})
254
+ if flush:
255
+ handler.wfile.flush()
@@ -0,0 +1,130 @@
1
+ """Normalize retained Anthropic tool turns for non-native provider wires."""
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 AnthropicToolTurnServices:
12
+ tool_use_as_text: Callable[[dict[str, Any]], dict[str, str]]
13
+ tool_result_as_text: Callable[[dict[str, Any]], dict[str, str]]
14
+ log: Callable[[str, str], Any]
15
+
16
+
17
+ def normalize_historical_anthropic_tool_turns(
18
+ provider: str,
19
+ body: dict[str, Any],
20
+ services: AnthropicToolTurnServices,
21
+ ) -> dict[str, Any]:
22
+ """Downgrade unmatched historical tool blocks instead of synthesizing success."""
23
+ messages = body.get("messages")
24
+ if not isinstance(messages, list):
25
+ return body
26
+
27
+ changed = False
28
+ converted_tool_uses = 0
29
+ converted_tool_results = 0
30
+ normalized_messages: list[Any] = []
31
+ retained_tool_ids_for_next_user: set[str] = set()
32
+
33
+ for index, message in enumerate(messages):
34
+ if not isinstance(message, dict):
35
+ normalized_messages.append(message)
36
+ retained_tool_ids_for_next_user = set()
37
+ continue
38
+
39
+ role = str(message.get("role") or "")
40
+ content = message.get("content")
41
+ if role == "assistant" and isinstance(content, list):
42
+ tool_ids = _tool_use_ids(message)
43
+ if not tool_ids:
44
+ normalized_messages.append(message)
45
+ retained_tool_ids_for_next_user = set()
46
+ continue
47
+ next_message = messages[index + 1] if index + 1 < len(messages) else None
48
+ next_result_ids = (
49
+ set(_tool_result_ids(next_message))
50
+ if isinstance(next_message, dict) and str(next_message.get("role") or "") == "user"
51
+ else set()
52
+ )
53
+ retained = {tool_id for tool_id in tool_ids if tool_id in next_result_ids}
54
+ next_content: list[Any] = []
55
+ content_changed = False
56
+ for block in content:
57
+ if isinstance(block, dict) and block.get("type") == "tool_use":
58
+ tool_id = str(block.get("id") or "")
59
+ if not tool_id or tool_id not in retained:
60
+ next_content.append(services.tool_use_as_text(block))
61
+ converted_tool_uses += 1
62
+ content_changed = True
63
+ continue
64
+ next_content.append(block)
65
+ normalized_messages.append(_with_content(message, next_content) if content_changed else message)
66
+ changed = changed or content_changed
67
+ retained_tool_ids_for_next_user = retained
68
+ continue
69
+
70
+ if role == "user" and isinstance(content, list):
71
+ next_content = []
72
+ content_changed = False
73
+ for block in content:
74
+ if isinstance(block, dict) and block.get("type") == "tool_result":
75
+ tool_id = str(block.get("tool_use_id") or "")
76
+ if tool_id and tool_id in retained_tool_ids_for_next_user:
77
+ next_content.append(block)
78
+ else:
79
+ next_content.append(services.tool_result_as_text(block))
80
+ converted_tool_results += 1
81
+ content_changed = True
82
+ continue
83
+ next_content.append(block)
84
+ normalized_messages.append(_with_content(message, next_content) if content_changed else message)
85
+ changed = changed or content_changed
86
+ retained_tool_ids_for_next_user = set()
87
+ continue
88
+
89
+ normalized_messages.append(message)
90
+ retained_tool_ids_for_next_user = set()
91
+
92
+ if not changed:
93
+ return body
94
+ out = dict(body)
95
+ out["messages"] = normalized_messages
96
+ services.log(
97
+ "WARN",
98
+ "normalized historical Anthropic tool turns for provider=%s converted_tool_uses=%d converted_tool_results=%d"
99
+ % (provider, converted_tool_uses, converted_tool_results),
100
+ )
101
+ return out
102
+
103
+
104
+ def _content_blocks(message: dict[str, Any]) -> list[Any]:
105
+ content = message.get("content")
106
+ return content if isinstance(content, list) else []
107
+
108
+
109
+ def _tool_use_ids(message: dict[str, Any]) -> list[str]:
110
+ return [
111
+ tool_id
112
+ for block in _content_blocks(message)
113
+ if isinstance(block, dict) and block.get("type") == "tool_use"
114
+ if (tool_id := str(block.get("id") or ""))
115
+ ]
116
+
117
+
118
+ def _tool_result_ids(message: dict[str, Any]) -> list[str]:
119
+ return [
120
+ tool_id
121
+ for block in _content_blocks(message)
122
+ if isinstance(block, dict) and block.get("type") == "tool_result"
123
+ if (tool_id := str(block.get("tool_use_id") or ""))
124
+ ]
125
+
126
+
127
+ def _with_content(message: dict[str, Any], content: list[Any]) -> dict[str, Any]:
128
+ updated = dict(message)
129
+ updated["content"] = content
130
+ return updated