@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,308 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Callable
5
+
6
+ from .architecture import ProviderContextPolicy
7
+
8
+
9
+ @dataclass(frozen=True)
10
+ class ProviderContextServices:
11
+ positive_int: Callable[[Any], int | None]
12
+ model_context_hint: Callable[[str], int | None]
13
+ anthropic_context_hint: Callable[[str], int | None]
14
+ nvidia_context_default: Callable[[str], int | None]
15
+ upstream_context_limit: Callable[..., int | None]
16
+ ollama_context_limit: Callable[[dict[str, Any]], int | None]
17
+
18
+
19
+ @dataclass(frozen=True)
20
+ class ContextPresetServices:
21
+ positive_int: Callable[[Any], int | None]
22
+ ollama_options: Callable[[dict[str, Any]], dict[str, Any]]
23
+ ollama_thinking_enabled: Callable[[str, dict[str, Any]], bool]
24
+
25
+
26
+ def resolve_context_capacity(
27
+ provider: str,
28
+ config: dict[str, Any],
29
+ policy: ProviderContextPolicy,
30
+ services: ProviderContextServices,
31
+ ) -> int | None:
32
+ strategy = policy.capacity_strategy
33
+ model = str(config.get("current_model") or "")
34
+ positive_int = services.positive_int
35
+
36
+ def hint() -> int | None:
37
+ return services.model_context_hint(model)
38
+
39
+ def configured() -> int | None:
40
+ return positive_int(config.get("context_window"))
41
+
42
+ def maximum() -> int | None:
43
+ return positive_int(config.get("max_model_len"))
44
+ if strategy == "managed":
45
+ return None
46
+ if strategy == "nvidia":
47
+ return services.nvidia_context_default(model)
48
+ if strategy == "remote_first":
49
+ return (
50
+ services.upstream_context_limit(provider, config, timeout=1.0)
51
+ or maximum()
52
+ or hint()
53
+ or configured()
54
+ )
55
+ if strategy == "hint_first":
56
+ return hint() or maximum() or configured()
57
+ if strategy == "configured_first":
58
+ return maximum() or hint() or configured()
59
+ if strategy == "ollama":
60
+ return (
61
+ services.ollama_context_limit(config)
62
+ or hint()
63
+ or positive_int(config.get("num_ctx_max"))
64
+ or positive_int(config.get("num_ctx"))
65
+ )
66
+ if strategy == "anthropic_hint":
67
+ return configured() or maximum() or services.anthropic_context_hint(model)
68
+ return hint() or configured() or maximum()
69
+
70
+
71
+ def cap_context_settings(
72
+ config: dict[str, Any],
73
+ capacity: int | None,
74
+ policy: ProviderContextPolicy,
75
+ *,
76
+ positive_int: Callable[[Any], int | None],
77
+ ) -> list[str]:
78
+ if not capacity:
79
+ return []
80
+ if policy.settings_strategy == "ollama":
81
+ maximum = positive_int(config.get("num_ctx_max"))
82
+ messages: list[str] = []
83
+ if maximum and maximum > capacity:
84
+ config["num_ctx_max"] = capacity
85
+ messages.append(f"Context max capped to selected model limit: {capacity:,} tokens.")
86
+ for key in ("num_ctx_min", "num_ctx"):
87
+ value = positive_int(config.get(key))
88
+ if value and value > capacity:
89
+ config[key] = capacity
90
+ return messages
91
+ if policy.settings_strategy == "standard":
92
+ context_window = positive_int(config.get("context_window"))
93
+ if context_window and context_window > capacity:
94
+ config["context_window"] = capacity
95
+ return [f"Context window capped to selected model limit: {capacity:,} tokens."]
96
+ return []
97
+
98
+
99
+ def small_context_output_token_cap(
100
+ context_window: int | None,
101
+ *,
102
+ positive_int: Callable[[Any], int | None],
103
+ ) -> int | None:
104
+ context = positive_int(context_window)
105
+ if not context or context > 262144:
106
+ return None
107
+ divisor = 16 if context <= 131072 else 32
108
+ cap = max(1024, min(8192, context // divisor))
109
+ return max(1024, (cap // 1024) * 1024)
110
+
111
+
112
+ def cap_output_tokens(
113
+ configured: int | None,
114
+ policy: ProviderContextPolicy,
115
+ context_window: int | None,
116
+ *,
117
+ positive_int: Callable[[Any], int | None],
118
+ ) -> int | None:
119
+ value = positive_int(configured)
120
+ if not value or policy.settings_strategy == "managed":
121
+ return value
122
+ cap = small_context_output_token_cap(
123
+ context_window,
124
+ positive_int=positive_int,
125
+ )
126
+ return min(value, cap) if cap else value
127
+
128
+
129
+ def cap_output_settings(
130
+ config: dict[str, Any],
131
+ policy: ProviderContextPolicy,
132
+ context_window: int | None,
133
+ *,
134
+ positive_int: Callable[[Any], int | None],
135
+ format_context: Callable[[int | None], str],
136
+ ) -> list[str]:
137
+ if policy.settings_strategy == "managed":
138
+ return []
139
+ cap = small_context_output_token_cap(
140
+ context_window,
141
+ positive_int=positive_int,
142
+ )
143
+ if not cap:
144
+ return []
145
+ if policy.settings_strategy == "ollama":
146
+ options = config.setdefault("ollama_options", {})
147
+ current = positive_int(options.get("num_predict")) or positive_int(
148
+ config.get("max_output_tokens")
149
+ )
150
+ if current and current > cap:
151
+ options["num_predict"] = cap
152
+ config["max_output_tokens"] = cap
153
+ return [_output_cap_message(cap, context_window, format_context)]
154
+ elif policy.settings_strategy == "standard":
155
+ current = positive_int(config.get("max_output_tokens"))
156
+ if current and current > cap:
157
+ config["max_output_tokens"] = cap
158
+ return [_output_cap_message(cap, context_window, format_context)]
159
+ return []
160
+
161
+
162
+ def _output_cap_message(
163
+ cap: int,
164
+ context_window: int | None,
165
+ format_context: Callable[[int | None], str],
166
+ ) -> str:
167
+ return (
168
+ f"Max output capped to {cap:,} tokens for context "
169
+ f"{format_context(context_window)}."
170
+ )
171
+
172
+
173
+ def infer_context_preset(
174
+ config: dict[str, Any],
175
+ policy: ProviderContextPolicy,
176
+ services: ContextPresetServices,
177
+ ) -> str | None:
178
+ positive_int = services.positive_int
179
+ if policy.settings_strategy == "managed" and not policy.managed_preset_inference:
180
+ return None
181
+ if policy.settings_strategy == "ollama":
182
+ options = services.ollama_options(config)
183
+ output = positive_int(options.get("num_predict")) or 0
184
+ fixed_context = positive_int(config.get("num_ctx")) or 0
185
+ minimum = positive_int(config.get("num_ctx_min")) or 0
186
+ context = positive_int(config.get("num_ctx_max")) or 0
187
+ if services.ollama_thinking_enabled(str(config.get("current_model") or ""), config):
188
+ return "reasoning"
189
+ else:
190
+ output = positive_int(config.get("max_output_tokens")) or 0
191
+ fixed_context = 0
192
+ minimum = 0
193
+ context = positive_int(config.get("context_window")) or 0
194
+ if bool(config.get("think", False)):
195
+ return "reasoning"
196
+ if context >= 1_000_000:
197
+ return "million-context-1m"
198
+ if context >= 524288:
199
+ return "long-context-512k"
200
+ if context >= 307200:
201
+ return "long-context-300k"
202
+ if context >= 262144:
203
+ return "long-context-256k"
204
+ if context >= 131072 and output >= 8192:
205
+ return "long-context-128k"
206
+ if output >= 8192:
207
+ return "large-output"
208
+ if minimum >= 65536 or context >= 65536:
209
+ return "long-context-65k"
210
+ if fixed_context and fixed_context <= 32768 and output and output <= 2048:
211
+ return "fast"
212
+ if policy.settings_strategy != "ollama" and output and output <= 2048:
213
+ return "fast"
214
+ return None
215
+
216
+
217
+ def classify_model_family(
218
+ config: dict[str, Any],
219
+ policy: ProviderContextPolicy,
220
+ capacity: int | None,
221
+ services: ContextPresetServices,
222
+ ) -> str:
223
+ model = str(config.get("current_model") or "").lower()
224
+ if any(marker in model for marker in ("coder", "codegemma", "starcoder", "devstral")):
225
+ return "coding"
226
+ if any(marker in model for marker in ("reason", "thinking", "r1", "qwq")):
227
+ return "reasoning"
228
+ if policy.context_family_before_size_markers:
229
+ return _context_family(capacity)
230
+ if any(marker in model for marker in ("1m", "v4-pro", "million")):
231
+ return "million-context"
232
+ if any(marker in model for marker in ("70b", "120b", "253b", "405b", "480b", "large", "ultra", "pro")):
233
+ return "large"
234
+ if policy.settings_strategy == "standard":
235
+ return _context_family(capacity)
236
+ if policy.settings_strategy == "ollama":
237
+ context = (
238
+ services.positive_int(config.get("num_ctx_max"))
239
+ or services.positive_int(config.get("num_ctx"))
240
+ )
241
+ return _context_family(context)
242
+ return "general"
243
+
244
+
245
+ def recommended_preset(family: str, capacity: int | None) -> str:
246
+ if family in {"reasoning", "coding"}:
247
+ return family
248
+ if family == "million-context":
249
+ return "million-context-1m"
250
+ if family == "long-context":
251
+ if capacity and capacity >= 524288:
252
+ return "long-context-512k"
253
+ if capacity and capacity >= 307200:
254
+ return "long-context-300k"
255
+ if capacity and capacity >= 262144:
256
+ return "long-context-256k"
257
+ if capacity and capacity >= 131072:
258
+ return "long-context-128k"
259
+ return "long-context-65k"
260
+ return "balanced"
261
+
262
+
263
+ def required_context_for_preset(preset_id: str, policy: ProviderContextPolicy) -> int | None:
264
+ profile = policy.preset_context_profile
265
+ if preset_id == "million-context-1m":
266
+ return 1_000_000
267
+ if preset_id == "humanities-researcher":
268
+ return 524288 if profile == "ollama" else 262144
269
+ if preset_id in {"novelist", "mathematician", "product-architect"}:
270
+ return 262144
271
+ if preset_id == "teacher":
272
+ return 131072
273
+ if preset_id == "reasoning":
274
+ return 262144 if profile == "nvidia" else 131072
275
+ if preset_id == "large-output":
276
+ if profile == "nvidia":
277
+ return 262144
278
+ return 131072 if profile == "ollama" else 65536
279
+ return {
280
+ "long-context-512k": 524288,
281
+ "long-context-300k": 307200,
282
+ "long-context-256k": 262144,
283
+ "long-context-128k": 131072,
284
+ "long-context-65k": 131072 if profile == "nvidia" else 65536,
285
+ }.get(preset_id)
286
+
287
+
288
+ def _context_family(capacity: int | None) -> str:
289
+ if capacity and capacity >= 1_000_000:
290
+ return "million-context"
291
+ if capacity and capacity >= 65536:
292
+ return "long-context"
293
+ return "general"
294
+
295
+
296
+ __all__ = [
297
+ "ContextPresetServices",
298
+ "ProviderContextServices",
299
+ "cap_context_settings",
300
+ "cap_output_settings",
301
+ "cap_output_tokens",
302
+ "infer_context_preset",
303
+ "classify_model_family",
304
+ "recommended_preset",
305
+ "required_context_for_preset",
306
+ "resolve_context_capacity",
307
+ "small_context_output_token_cap",
308
+ ]
@@ -0,0 +1,73 @@
1
+ """Provider-neutral projection of registered adapter contracts."""
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
+ from .architecture import (
10
+ ProviderAdapter,
11
+ ProviderConfig,
12
+ ProviderConfigurationPolicy,
13
+ ProviderContextPolicy,
14
+ ProviderModelCatalogPolicy,
15
+ ProviderRequestPolicy,
16
+ )
17
+
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class ProviderContractProjectionApi:
21
+ """Project provider contracts without provider-name dispatch in the facade."""
22
+
23
+ adapter: Callable[[str, dict[str, Any]], ProviderAdapter]
24
+ contract: Callable[[str, dict[str, Any]], ProviderConfig]
25
+ request_base: Callable[[str, dict[str, Any]], str]
26
+ join_url: Callable[[str, str], str]
27
+
28
+ def _parts(self, provider: str, pcfg: dict[str, Any]) -> tuple[ProviderAdapter, ProviderConfig]:
29
+ return self.adapter(provider, pcfg), self.contract(provider, pcfg)
30
+
31
+ def endpoint(self, provider: str, pcfg: dict[str, Any], operation: str) -> str:
32
+ adapter, contract = self._parts(provider, pcfg)
33
+ return self.join_url(self.request_base(provider, pcfg), adapter.resolve_endpoint(operation, contract))
34
+
35
+ def model_paths(self, provider: str, pcfg: dict[str, Any]) -> tuple[str, ...]:
36
+ adapter, contract = self._parts(provider, pcfg)
37
+ return adapter.model_paths(contract)
38
+
39
+ def request_policy(self, provider: str, pcfg: dict[str, Any]) -> ProviderRequestPolicy:
40
+ adapter, contract = self._parts(provider, pcfg)
41
+ return adapter.request_policy(contract)
42
+
43
+ def model_catalog_policy(self, provider: str, pcfg: dict[str, Any]) -> ProviderModelCatalogPolicy:
44
+ adapter, contract = self._parts(provider, pcfg)
45
+ return adapter.model_catalog_policy(contract)
46
+
47
+ def preserves_anthropic_thinking_contract(self, provider: str, pcfg: dict[str, Any]) -> bool:
48
+ adapter, contract = self._parts(provider, pcfg)
49
+ return adapter.preserves_anthropic_thinking(contract)
50
+
51
+ def context_compaction_available(self, provider: str, pcfg: dict[str, Any]) -> bool:
52
+ adapter, contract = self._parts(provider, pcfg)
53
+ return adapter.context_compaction_available(contract)
54
+
55
+ def context_policy(self, provider: str, pcfg: dict[str, Any]) -> ProviderContextPolicy:
56
+ adapter, contract = self._parts(provider, pcfg)
57
+ return adapter.context_policy(contract)
58
+
59
+ def configuration_policy(self, provider: str, pcfg: dict[str, Any]) -> ProviderConfigurationPolicy:
60
+ adapter, contract = self._parts(provider, pcfg)
61
+ return adapter.configuration_policy(contract)
62
+
63
+ def model_panel_badge(self, provider: str, pcfg: dict[str, Any], model: str) -> str:
64
+ adapter, contract = self._parts(provider, pcfg)
65
+ return adapter.model_panel_badge(contract, model)
66
+
67
+ def advisor_panel_notice(self, provider: str, pcfg: dict[str, Any]) -> tuple[tuple[str, ...], tuple[str, ...]] | None:
68
+ adapter, contract = self._parts(provider, pcfg)
69
+ return adapter.advisor_panel_notice(contract)
70
+
71
+ def advisor_model_badge(self, provider: str, pcfg: dict[str, Any], model: str) -> str:
72
+ adapter, contract = self._parts(provider, pcfg)
73
+ return adapter.advisor_model_badge(contract, model)
@@ -0,0 +1,82 @@
1
+ """Data-driven provider registration descriptors.
2
+
3
+ Descriptors contain discovery metadata and adapter construction only. Runtime
4
+ behavior remains in concrete adapters, so complex providers can use specialized
5
+ implementations without growing a central conditional router.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+ from collections.abc import Callable
12
+ from typing import Any
13
+
14
+ from ciel_runtime_support.architecture import ProviderAdapter
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class ProviderDescriptor:
19
+ """Stable provider identity and its concrete adapter factory type."""
20
+
21
+ name: str
22
+ label: str
23
+ adapter_type: Callable[..., ProviderAdapter]
24
+ aliases: tuple[str, ...] = ()
25
+
26
+ def __post_init__(self) -> None:
27
+ if not self.normalized_name:
28
+ raise ValueError("provider descriptor name cannot be empty")
29
+ if not self.label.strip():
30
+ raise ValueError(f"provider descriptor label cannot be empty: {self.name}")
31
+
32
+ @property
33
+ def normalized_name(self) -> str:
34
+ return normalize_provider_name(self.name)
35
+
36
+ def create(self, **kwargs: Any) -> ProviderAdapter:
37
+ base_url = str(kwargs.get("base_url") or "").strip()
38
+ return self.adapter_type(**({"base_url": base_url} if base_url else {}))
39
+
40
+
41
+ def normalize_provider_name(name: str) -> str:
42
+ return str(name or "").strip().lower().replace("_", "-")
43
+
44
+
45
+ class ProviderDescriptorRegistry:
46
+ """Read-only discovery index with normalized alias lookup."""
47
+
48
+ def __init__(self, descriptors: tuple[ProviderDescriptor, ...]) -> None:
49
+ self._descriptors: dict[str, ProviderDescriptor] = {}
50
+ self._canonical: dict[str, ProviderDescriptor] = {}
51
+ for descriptor in descriptors:
52
+ name = descriptor.normalized_name
53
+ if name in self._canonical:
54
+ raise ValueError(f"provider descriptor already registered: {name}")
55
+ self._canonical[name] = descriptor
56
+ for candidate in (name, *descriptor.aliases):
57
+ key = normalize_provider_name(candidate)
58
+ if not key:
59
+ raise ValueError(f"provider alias cannot be empty: {name}")
60
+ if key in self._descriptors:
61
+ raise ValueError(f"provider descriptor alias already registered: {key}")
62
+ self._descriptors[key] = descriptor
63
+
64
+ def get(self, name: str) -> ProviderDescriptor:
65
+ try:
66
+ return self._descriptors[normalize_provider_name(name)]
67
+ except KeyError as exc:
68
+ raise KeyError(f"unknown provider descriptor: {name}") from exc
69
+
70
+ def descriptors(self) -> tuple[ProviderDescriptor, ...]:
71
+ return tuple(self._canonical[name] for name in sorted(self._canonical))
72
+
73
+ def names(self) -> tuple[str, ...]:
74
+ return tuple(sorted(self._canonical))
75
+
76
+ def aliases(self) -> dict[str, str]:
77
+ """Project every registered lookup token to its canonical provider name."""
78
+
79
+ return {
80
+ alias: descriptor.normalized_name
81
+ for alias, descriptor in self._descriptors.items()
82
+ }
@@ -0,0 +1,130 @@
1
+ """Provider protocol endpoint override and presentation policy."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+ from ciel_runtime_support.runtime_constants import OPENCODE_ENDPOINT_ALIASES
10
+
11
+
12
+ @dataclass(frozen=True, slots=True)
13
+ class ProviderEndpointPorts:
14
+ normalize_model_id: Callable[[str, str], str]
15
+ strip_context_suffix: Callable[[str], str]
16
+ alias_for: Callable[[str, str], str]
17
+ select_protocol: Callable[[str, dict[str, Any], str], str]
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class ProviderEndpointPresentation:
22
+ aliases: Mapping[str, str]
23
+ labels: Mapping[str, str]
24
+ routed_protocols: frozenset[str]
25
+
26
+
27
+ def build_default_provider_endpoint_policy(
28
+ ports: ProviderEndpointPorts,
29
+ ) -> ProviderEndpointPolicy:
30
+ """Compose endpoint behavior with the runtime's standard presentation."""
31
+ return ProviderEndpointPolicy(
32
+ ports=ports,
33
+ presentation=ProviderEndpointPresentation(
34
+ aliases=OPENCODE_ENDPOINT_ALIASES,
35
+ labels={
36
+ "anthropic-messages": "messages",
37
+ "openai-chat": "chat",
38
+ "openai-responses": "responses",
39
+ "google-generative": "gemini",
40
+ },
41
+ routed_protocols=frozenset(
42
+ {"anthropic-messages", "openai-chat", "openai-responses"}
43
+ ),
44
+ ),
45
+ )
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class ProviderEndpointPolicy:
50
+ ports: ProviderEndpointPorts
51
+ presentation: ProviderEndpointPresentation
52
+
53
+ def normalize_endpoint_kind(self, value: Any) -> str | None:
54
+ key = str(value or "").strip().lower().replace("_", "-")
55
+ return self.presentation.aliases.get(key)
56
+
57
+ def endpoint_override(
58
+ self,
59
+ provider: str,
60
+ model_id: str,
61
+ provider_config: dict[str, Any] | None = None,
62
+ ) -> str | None:
63
+ if not provider_config:
64
+ return None
65
+ overrides = provider_config.get("model_endpoints")
66
+ if not isinstance(overrides, dict):
67
+ return None
68
+ normalized = self.ports.normalize_model_id(provider, model_id)
69
+ candidates = (
70
+ model_id,
71
+ normalized,
72
+ self.ports.strip_context_suffix(model_id),
73
+ self.ports.alias_for(provider, normalized),
74
+ )
75
+ for candidate in candidates:
76
+ endpoint = self.normalize_endpoint_kind(overrides.get(candidate))
77
+ if endpoint:
78
+ return endpoint
79
+ return None
80
+
81
+ def endpoint_kind(
82
+ self,
83
+ provider: str,
84
+ model_id: str,
85
+ provider_config: dict[str, Any] | None = None,
86
+ ) -> str:
87
+ override = self.endpoint_override(provider, model_id, provider_config)
88
+ if override:
89
+ return override
90
+ config = provider_config or {}
91
+ protocol = self.ports.select_protocol(
92
+ provider,
93
+ config,
94
+ model_id,
95
+ )
96
+ return str(protocol).replace("_", "-")
97
+
98
+ def model_supported(
99
+ self,
100
+ provider: str,
101
+ model_id: str,
102
+ provider_config: dict[str, Any] | None = None,
103
+ ) -> bool:
104
+ return (
105
+ self.endpoint_kind(provider, model_id, provider_config)
106
+ in self.presentation.routed_protocols
107
+ )
108
+
109
+ def endpoint_display(
110
+ self,
111
+ provider: str,
112
+ model_id: str,
113
+ provider_config: dict[str, Any] | None = None,
114
+ ) -> str:
115
+ endpoint = self.endpoint_kind(provider, model_id, provider_config)
116
+ text = self.presentation.labels.get(endpoint, endpoint)
117
+ if endpoint not in self.presentation.routed_protocols:
118
+ text += " unsupported"
119
+ if self.endpoint_override(provider, model_id, provider_config):
120
+ text += " override"
121
+ return text
122
+
123
+ def zen_endpoint_kind(self, model_id: str) -> str:
124
+ return self.endpoint_kind("opencode", model_id)
125
+
126
+ def zen_model_supported(self, model_id: str) -> bool:
127
+ return self.model_supported("opencode", model_id)
128
+
129
+ def go_endpoint_kind(self, model_id: str) -> str:
130
+ return self.endpoint_kind("opencode-go", model_id)