@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,75 @@
1
+ """Persistence policy for the prelaunch menu's runtime choice."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+
9
+ LAST_LAUNCH_ACTION_KEY = "last_launch_action"
10
+ REMEMBERED_LAUNCH_ACTIONS = frozenset(
11
+ {
12
+ "launch",
13
+ "launch-codex",
14
+ "launch-codex-app-server",
15
+ }
16
+ )
17
+
18
+
19
+ def preferred_launch_action(
20
+ config: dict[str, Any],
21
+ provider: str,
22
+ *,
23
+ fallback: Callable[[str], str],
24
+ supports_claude: Callable[[str], bool],
25
+ supports_codex: Callable[[str], bool],
26
+ ) -> str:
27
+ """Return the remembered launch action when the provider can run it."""
28
+ remembered = str(config.get(LAST_LAUNCH_ACTION_KEY) or "").strip()
29
+ if remembered == "launch" and supports_claude(provider):
30
+ return remembered
31
+ if remembered in {"launch-codex", "launch-codex-app-server"} and supports_codex(provider):
32
+ return remembered
33
+ return fallback(provider)
34
+
35
+
36
+ def preferred_provider_launch_action(
37
+ config: dict[str, Any],
38
+ provider: str,
39
+ supports_agy: Callable[[str], bool],
40
+ supports_claude: Callable[[str], bool],
41
+ supports_codex: Callable[[str], bool],
42
+ ) -> str:
43
+ """Resolve a remembered action with the provider's natural fallback."""
44
+
45
+ def fallback(selected_provider: str) -> str:
46
+ if supports_agy(selected_provider):
47
+ return "launch-agy"
48
+ return "launch-codex" if supports_codex(selected_provider) else "launch"
49
+
50
+ return preferred_launch_action(
51
+ config,
52
+ provider,
53
+ fallback=fallback,
54
+ supports_claude=supports_claude,
55
+ supports_codex=supports_codex,
56
+ )
57
+
58
+
59
+ def remember_launch_action(config: dict[str, Any], action: str) -> bool:
60
+ """Record a concrete menu launch choice and report whether it changed."""
61
+ if action not in REMEMBERED_LAUNCH_ACTIONS:
62
+ return False
63
+ if config.get(LAST_LAUNCH_ACTION_KEY) == action:
64
+ return False
65
+ config[LAST_LAUNCH_ACTION_KEY] = action
66
+ return True
67
+
68
+
69
+ __all__ = [
70
+ "LAST_LAUNCH_ACTION_KEY",
71
+ "REMEMBERED_LAUNCH_ACTIONS",
72
+ "preferred_launch_action",
73
+ "preferred_provider_launch_action",
74
+ "remember_launch_action",
75
+ ]
@@ -0,0 +1,396 @@
1
+ """Prelaunch main-menu and configuration-panel projections."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable, Mapping, Protocol
7
+
8
+ from ciel_runtime_support.web_endpoints import web_backend_summary
9
+
10
+
11
+ ProviderConfig = dict[str, Any]
12
+ RuntimeConfig = dict[str, Any]
13
+
14
+
15
+ class ProviderUiProjection(Protocol):
16
+ model_placeholder: str
17
+ advisor_placeholder: str
18
+
19
+
20
+ @dataclass(frozen=True, slots=True)
21
+ class MainMenuProjectionPorts:
22
+ languages: Mapping[str, str]
23
+ ui_text: Callable[[str, str], str]
24
+ compact_text: Callable[[Any, int], str]
25
+ provider_label: Callable[[str, ProviderConfig], str]
26
+ stored_api_key_mask: Callable[[str, ProviderConfig], str]
27
+ llm_options_status: Callable[[str, ProviderConfig], str]
28
+ log_level_status: Callable[[], str]
29
+ supports_runtime: Callable[[str, str], bool]
30
+ provider_family: Callable[[str, str], str]
31
+ provider_ui_policy: Callable[
32
+ [str, ProviderConfig],
33
+ ProviderUiProjection,
34
+ ]
35
+
36
+
37
+ @dataclass(frozen=True, slots=True)
38
+ class MainMenuProjection:
39
+ ports: MainMenuProjectionPorts
40
+
41
+ def rows(
42
+ self,
43
+ config: RuntimeConfig,
44
+ provider: str,
45
+ provider_config: ProviderConfig,
46
+ language: str,
47
+ ) -> list[str]:
48
+ policy = self.ports.provider_ui_policy(provider, provider_config)
49
+ policy_model = policy.model_placeholder
50
+ policy_advisor = policy.advisor_placeholder
51
+ model_text = (
52
+ str(policy_model)
53
+ if policy_model and not provider_config.get("current_model")
54
+ else self.ports.compact_text(
55
+ provider_config.get("current_model", "unset"),
56
+ 62,
57
+ )
58
+ )
59
+ advisor_text = (
60
+ str(policy_advisor)
61
+ if policy_advisor
62
+ else self.ports.compact_text(
63
+ provider_config.get("advisor_model") or "off",
64
+ 62,
65
+ )
66
+ )
67
+ launch_label = self.ports.ui_text("launch", language)
68
+ if not self.ports.supports_runtime("claude", provider):
69
+ family = self._provider_family(provider, provider_config)
70
+ launch_label += f" [disabled: {family} provider selected]"
71
+ launch_agy_label = self.ports.ui_text("launch_agy", language)
72
+ if not self.ports.supports_runtime("agy", provider):
73
+ launch_agy_label += " [disabled: select AGY provider]"
74
+ launch_codex_label = self.ports.ui_text("launch_codex", language)
75
+ if not self.ports.supports_runtime("codex", provider):
76
+ family = self._provider_family(provider, provider_config)
77
+ launch_codex_label += f" [disabled: {family} provider selected]"
78
+ launch_app_label = self.ports.ui_text(
79
+ "launch_codex_app_server",
80
+ language,
81
+ )
82
+ if not self.ports.supports_runtime("codex", provider):
83
+ family = self._provider_family(provider, provider_config)
84
+ launch_app_label += f" [disabled: {family} provider selected]"
85
+ launch_kimi_label = "Launch Kimi Code"
86
+ if not self.ports.supports_runtime("kimi", provider):
87
+ launch_kimi_label += " [disabled: select Kimi provider]"
88
+ return [
89
+ f"0. {self.ports.ui_text('language', language)} "
90
+ f"[{self.ports.languages.get(language, language)}]",
91
+ f"1. {self.ports.ui_text('provider', language)} "
92
+ f"[{self.ports.provider_label(provider, provider_config)}]",
93
+ f"2. {self.ports.ui_text('api_key', language)} "
94
+ f"[{self.ports.stored_api_key_mask(provider, provider_config)}]",
95
+ f"3. {self.ports.ui_text('base_url', language)} "
96
+ f"[{self.ports.compact_text(provider_config.get('base_url', 'unset'), 62)}]",
97
+ f"4. {self.ports.ui_text('model', language)} [{model_text}]",
98
+ f"5. {self.ports.ui_text('advisor_model', language)} "
99
+ f"[{advisor_text}]",
100
+ f"6. {self.ports.ui_text('options', language)} "
101
+ f"[{self.ports.compact_text(self.ports.llm_options_status(provider, provider_config), 62)}]",
102
+ f"7. {self.ports.ui_text('log_level', language)} "
103
+ f"[{self.ports.log_level_status()}]",
104
+ f"8. {self.ports.ui_text('test', language)}",
105
+ f"9. {launch_label}",
106
+ f"10. {launch_codex_label}",
107
+ f"11. {launch_app_label}",
108
+ f"12. {launch_agy_label}",
109
+ f"13. {launch_kimi_label}",
110
+ f"14. {self.ports.ui_text('web_backend', language)} "
111
+ f"[{web_backend_summary(config, 0)}]",
112
+ self.ports.ui_text("quit", language),
113
+ ]
114
+
115
+ def _provider_family(
116
+ self,
117
+ provider: str,
118
+ provider_config: ProviderConfig,
119
+ ) -> str:
120
+ return self.ports.provider_family(
121
+ provider,
122
+ self.ports.provider_label(provider, provider_config),
123
+ )
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class ProviderPanelConstants:
128
+ labels: Mapping[str, str]
129
+ anthropic_native_choice: str
130
+ anthropic_routed_choice: str
131
+ agy_native_choice: str
132
+ agy_routed_choice: str
133
+ codex_native_choice: str
134
+ codex_routed_choice: str
135
+
136
+
137
+ @dataclass(frozen=True, slots=True)
138
+ class ProviderPanelPorts:
139
+ anthropic_routed: Callable[[str, ProviderConfig], bool]
140
+ agy_routed: Callable[[str, ProviderConfig], bool]
141
+ codex_routed: Callable[[str, ProviderConfig], bool]
142
+ has_api_key: Callable[[str, ProviderConfig], bool]
143
+ compact_text: Callable[[Any, int], str]
144
+
145
+
146
+ @dataclass(frozen=True, slots=True)
147
+ class ProviderPanelProjection:
148
+ constants: ProviderPanelConstants
149
+ ports: ProviderPanelPorts
150
+
151
+ def rows(self, config: RuntimeConfig) -> tuple[list[str], list[str]]:
152
+ entries: list[tuple[str, str, str]] = []
153
+ current = config.get("current_provider", "nvidia-hosted")
154
+ providers = config.get("providers", {})
155
+ provider_configs = providers if isinstance(providers, dict) else {}
156
+ for key, label in self.constants.labels.items():
157
+ raw_config = provider_configs.get(key, {})
158
+ provider_config = raw_config if isinstance(raw_config, dict) else {}
159
+ if key == "anthropic":
160
+ routed = self.ports.anthropic_routed(key, provider_config)
161
+ entries.extend(
162
+ self._anthropic_rows(
163
+ key,
164
+ provider_config,
165
+ current,
166
+ routed,
167
+ )
168
+ )
169
+ continue
170
+ if key == "agy":
171
+ routed = self.ports.agy_routed(key, provider_config)
172
+ entries.extend(self._agy_rows(current, routed))
173
+ continue
174
+ if key == "codex":
175
+ routed = self.ports.codex_routed(key, provider_config)
176
+ entries.extend(self._codex_rows(current, routed))
177
+ continue
178
+ if key == "kimi":
179
+ routed = bool(provider_config.get("route_through_router"))
180
+ entries.extend(
181
+ (
182
+ ("Kimi Native", f"{'*' if current == key and not routed else ' '} {'Kimi Native':<16} {'kimi:native':<15} official OAuth/config", "kimi:native"),
183
+ ("Kimi Routed", f"{'*' if current == key and routed else ' '} {'Kimi Routed':<16} {'kimi:routed':<15} via ciel-runtime", "kimi:routed"),
184
+ )
185
+ )
186
+ continue
187
+ mark = "*" if key == current else " "
188
+ entries.append(
189
+ (
190
+ label,
191
+ f"{mark} {label:<16} {key:<15} "
192
+ f"{self.ports.compact_text(provider_config.get('base_url', ''), 54)}",
193
+ key,
194
+ )
195
+ )
196
+ entries.sort(key=lambda item: (item[0].casefold(), item[2].casefold()))
197
+ return (
198
+ [row for _label, row, _value in entries],
199
+ [value for _label, _row, value in entries],
200
+ )
201
+
202
+ def _anthropic_rows(
203
+ self,
204
+ provider: str,
205
+ config: ProviderConfig,
206
+ current: Any,
207
+ routed: bool,
208
+ ) -> list[tuple[str, str, str]]:
209
+ native_mark = "*" if current == provider and not routed else " "
210
+ routed_mark = "*" if current == provider and routed else " "
211
+ suffix = (
212
+ "router features"
213
+ if self.ports.has_api_key(provider, config)
214
+ else "router via Claude Code auth"
215
+ )
216
+ return [
217
+ (
218
+ "Claude Native",
219
+ f"{native_mark} {'Claude Native':<16} {'anthropic-native':<17} "
220
+ f"{self.ports.compact_text(config.get('base_url', ''), 52)}",
221
+ self.constants.anthropic_native_choice,
222
+ ),
223
+ (
224
+ "Anthropic routed",
225
+ f"{routed_mark} {'Anthropic routed':<16} "
226
+ f"{'anthropic-routed':<17} {suffix}",
227
+ self.constants.anthropic_routed_choice,
228
+ ),
229
+ ]
230
+
231
+ def _agy_rows(
232
+ self,
233
+ current: Any,
234
+ routed: bool,
235
+ ) -> list[tuple[str, str, str]]:
236
+ native_mark = "*" if current == "agy" and not routed else " "
237
+ routed_mark = "*" if current == "agy" and routed else " "
238
+ return [
239
+ (
240
+ "AGY",
241
+ f"{native_mark} {'AGY':<16} {'agy-native':<17} "
242
+ "native Antigravity settings",
243
+ self.constants.agy_native_choice,
244
+ ),
245
+ (
246
+ "AGY Routed",
247
+ f"{routed_mark} {'AGY Routed':<16} {'agy-routed':<17} "
248
+ "channel/PTY wake support",
249
+ self.constants.agy_routed_choice,
250
+ ),
251
+ ]
252
+
253
+ def _codex_rows(
254
+ self,
255
+ current: Any,
256
+ routed: bool,
257
+ ) -> list[tuple[str, str, str]]:
258
+ native_mark = "*" if current == "codex" and not routed else " "
259
+ routed_mark = "*" if current == "codex" and routed else " "
260
+ return [
261
+ (
262
+ "Codex Native",
263
+ f"{native_mark} {'Codex Native':<16} {'codex-native':<17} "
264
+ "native Codex settings",
265
+ self.constants.codex_native_choice,
266
+ ),
267
+ (
268
+ "Codex routed",
269
+ f"{routed_mark} {'Codex routed':<16} {'codex-routed':<17} "
270
+ "router via native Codex auth",
271
+ self.constants.codex_routed_choice,
272
+ ),
273
+ ]
274
+
275
+
276
+ @dataclass(frozen=True, slots=True)
277
+ class ConfigurationPanelPorts:
278
+ languages: Mapping[str, str]
279
+ log_level_names: Mapping[int, str]
280
+ log_level_name: Callable[[], str]
281
+ log_level_status: Callable[[], str]
282
+ ui_text: Callable[[str, str], str]
283
+ compact_text: Callable[[Any, int], str]
284
+ default_base_url: Callable[[str], str]
285
+ api_key_count: Callable[[str, ProviderConfig], int]
286
+ platform_name: str
287
+
288
+
289
+ @dataclass(frozen=True, slots=True)
290
+ class ConfigurationPanelProjection:
291
+ ports: ConfigurationPanelPorts
292
+
293
+ def language_rows(
294
+ self,
295
+ config: RuntimeConfig,
296
+ ) -> tuple[list[str], list[str]]:
297
+ current = config.get("language", "en")
298
+ rows = [
299
+ f"{'*' if code == current else ' '} {code:<2} {label}"
300
+ for code, label in self.ports.languages.items()
301
+ ]
302
+ return rows, list(self.ports.languages)
303
+
304
+ def log_level_rows(
305
+ self,
306
+ config: RuntimeConfig,
307
+ ) -> tuple[list[str], list[str]]:
308
+ current = self.ports.log_level_name()
309
+ descriptions = {
310
+ "SILENT": "no router log writes",
311
+ "ERROR": "errors only",
312
+ "WARN": "warnings and errors",
313
+ "INFO": "normal diagnostics",
314
+ "DEBUG": "verbose diagnostics",
315
+ "TRACE": "request/response trace detail",
316
+ }
317
+ rows: list[str] = []
318
+ values: list[str] = []
319
+ for numeric in sorted(self.ports.log_level_names):
320
+ name = self.ports.log_level_names[numeric]
321
+ mark = "*" if name == current else " "
322
+ rows.append(
323
+ f"{mark} {name:<6} {numeric} {descriptions.get(name, '')}"
324
+ )
325
+ values.append(name)
326
+ rows.append(f"Reset to default/env [{self.ports.log_level_status()}]")
327
+ values.append("DEFAULT")
328
+ rows.append(
329
+ self.ports.ui_text(
330
+ "back",
331
+ str(config.get("language", "en")),
332
+ )
333
+ )
334
+ values.append("back")
335
+ return rows, values
336
+
337
+ def api_key_rows(
338
+ self,
339
+ provider: str,
340
+ provider_config: ProviderConfig | None = None,
341
+ ) -> tuple[list[str], list[str]]:
342
+ rows = [
343
+ "Type or paste API key as hidden input",
344
+ "Type or paste multiple API keys (comma/newline separated)",
345
+ "Read API key from an environment variable",
346
+ "Read API keys from an environment variable",
347
+ "Read API key from clipboard",
348
+ "Read API keys from clipboard",
349
+ "Back",
350
+ ]
351
+ values = [
352
+ "input",
353
+ "multi-input",
354
+ "env",
355
+ "multi-env",
356
+ "clipboard",
357
+ "multi-clipboard",
358
+ "back",
359
+ ]
360
+ if self.ports.platform_name != "nt":
361
+ rows[4] = "Read API key from desktop clipboard if available"
362
+ rows[5] = "Read API keys from desktop clipboard if available"
363
+ if (
364
+ provider_config is not None
365
+ and self.ports.api_key_count(provider, provider_config)
366
+ ):
367
+ rows.insert(-1, "Clear stored API key(s)")
368
+ values.insert(-1, "clear")
369
+ return rows, values
370
+
371
+ def base_url_rows(
372
+ self,
373
+ provider: str,
374
+ provider_config: ProviderConfig,
375
+ ) -> tuple[list[str], list[str]]:
376
+ default = self.ports.default_base_url(provider)
377
+ current = provider_config.get("base_url") or default
378
+ return (
379
+ [
380
+ f"Edit Base URL [{self.ports.compact_text(current, 72)}]",
381
+ f"Reset to provider default [{default}]",
382
+ "Back",
383
+ ],
384
+ ["edit", "default", "back"],
385
+ )
386
+
387
+
388
+ __all__ = [
389
+ "ConfigurationPanelPorts",
390
+ "ConfigurationPanelProjection",
391
+ "MainMenuProjection",
392
+ "MainMenuProjectionPorts",
393
+ "ProviderPanelConstants",
394
+ "ProviderPanelPorts",
395
+ "ProviderPanelProjection",
396
+ ]