@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,959 @@
1
+ from __future__ import annotations
2
+
3
+ import os
4
+ import sys
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+
8
+ from ciel_runtime_support.llm_presentation_data import LLM_OPTION_TOGGLE_KEYS
9
+ from ciel_runtime_support.prelaunch_launch_preference import remember_launch_action
10
+ from ciel_runtime_support.provider_adapters import PROVIDER_LABELS
11
+ from ciel_runtime_support.runtime_constants import (
12
+ LANGUAGES,
13
+ PRELAUNCH_CANCEL,
14
+ PRELAUNCH_LAUNCH_AGY,
15
+ PRELAUNCH_LAUNCH_CLAUDE,
16
+ PRELAUNCH_LAUNCH_CODEX,
17
+ PRELAUNCH_LAUNCH_CODEX_APP_SERVER,
18
+ PRELAUNCH_RELOAD,
19
+ )
20
+
21
+
22
+ MAIN_MENU_ACTIONS: tuple[str, ...] = (
23
+ "language",
24
+ "provider",
25
+ "api-key",
26
+ "base-url",
27
+ "model",
28
+ "advisor-model",
29
+ "options",
30
+ "log-level",
31
+ "test",
32
+ "launch",
33
+ "launch-codex",
34
+ "launch-codex-app-server",
35
+ "launch-agy",
36
+ "launch-kimi",
37
+ "web-backend",
38
+ "quit",
39
+ )
40
+
41
+
42
+ @dataclass(frozen=True, slots=True)
43
+ class PrelaunchConstants:
44
+ LANGUAGES: Any
45
+ LLM_OPTION_TOGGLE_KEYS: Any
46
+ MAIN_MENU_ACTIONS: Any
47
+ PRELAUNCH_CANCEL: Any
48
+ PRELAUNCH_LAUNCH_AGY: Any
49
+ PRELAUNCH_LAUNCH_CLAUDE: Any
50
+ PRELAUNCH_LAUNCH_CODEX: Any
51
+ PRELAUNCH_LAUNCH_CODEX_APP_SERVER: Any
52
+ PROVIDER_LABELS: Any
53
+
54
+
55
+ def build_default_prelaunch_constants() -> PrelaunchConstants:
56
+ """Build the menu's immutable presentation and dispatch constants."""
57
+ return PrelaunchConstants(
58
+ LANGUAGES=LANGUAGES,
59
+ LLM_OPTION_TOGGLE_KEYS=LLM_OPTION_TOGGLE_KEYS,
60
+ MAIN_MENU_ACTIONS=MAIN_MENU_ACTIONS,
61
+ PRELAUNCH_CANCEL=PRELAUNCH_CANCEL,
62
+ PRELAUNCH_LAUNCH_AGY=PRELAUNCH_LAUNCH_AGY,
63
+ PRELAUNCH_LAUNCH_CLAUDE=PRELAUNCH_LAUNCH_CLAUDE,
64
+ PRELAUNCH_LAUNCH_CODEX=PRELAUNCH_LAUNCH_CODEX,
65
+ PRELAUNCH_LAUNCH_CODEX_APP_SERVER=PRELAUNCH_LAUNCH_CODEX_APP_SERVER,
66
+ PROVIDER_LABELS=PROVIDER_LABELS,
67
+ )
68
+
69
+
70
+ @dataclass(frozen=True, slots=True)
71
+ class PrelaunchChannelQuery:
72
+ _channel_panel_first_selectable: Callable[..., Any]
73
+ _channel_panel_step: Callable[..., Any]
74
+ channel_delivery_panel_rows: Callable[..., Any]
75
+ channel_panel_rows: Callable[..., Any]
76
+ channel_panel_rows_for_menu: Callable[..., Any]
77
+ channel_probe_summary_message: Callable[..., Any]
78
+ channel_specs: Callable[..., Any]
79
+ refresh_channel_probe_cache: Callable[..., Any]
80
+
81
+
82
+ @dataclass(frozen=True, slots=True)
83
+ class PrelaunchChannelCommands:
84
+ add_channel_spec: Callable[..., Any]
85
+ clear_channel_specs: Callable[..., Any]
86
+ remove_channel_spec: Callable[..., Any]
87
+ set_channel_delivery_config: Callable[..., Any]
88
+
89
+
90
+ @dataclass(frozen=True, slots=True)
91
+ class PrelaunchLaunchPolicy:
92
+ agy_launch_enabled_for_provider: Callable[..., Any]
93
+ claude_launch_enabled_for_provider: Callable[..., Any]
94
+ codex_launch_enabled_for_provider: Callable[..., Any]
95
+ launch_blockers_require_api_key: Callable[..., Any]
96
+ launch_readiness_errors: Callable[..., Any]
97
+ launch_kimi: Callable[..., Any]
98
+
99
+
100
+ @dataclass(frozen=True, slots=True)
101
+ class PrelaunchConfig:
102
+ clear_model_cache: Callable[..., Any]
103
+ current_provider_panel_choice: Callable[..., Any]
104
+ default_base_url: Callable[..., Any]
105
+ get_current_provider: Callable[..., Any]
106
+ load_config: Callable[..., Any]
107
+ preflight_lines: Callable[..., Any]
108
+ provider_menu_label: Callable[..., Any]
109
+ save_config: Callable[..., Any]
110
+ settings_ready_except_api_key: Callable[..., Any]
111
+ read_model_list_cache: Callable[..., Any]
112
+
113
+
114
+ @dataclass(frozen=True, slots=True)
115
+ class PrelaunchTerminal:
116
+ default_prelaunch_action: Callable[..., Any]
117
+ enable_ansi: Callable[..., Any]
118
+ main_menu_rows: Callable[..., Any]
119
+ prelaunch_action_index: Callable[..., Any]
120
+ prompt_menu_multiline_value: Callable[..., Any]
121
+ prompt_menu_value: Callable[..., Any]
122
+ read_clipboard_text: Callable[..., Any]
123
+ read_menu_key: Callable[..., Any]
124
+ render_prelaunch_screen: Callable[..., Any]
125
+ self_cmd: Callable[..., Any]
126
+
127
+
128
+ @dataclass(frozen=True, slots=True)
129
+ class PrelaunchPanelRows:
130
+ advisor_model_panel_rows: Callable[..., Any]
131
+ api_key_panel_rows: Callable[..., Any]
132
+ base_url_panel_rows: Callable[..., Any]
133
+ context_setup_panel_rows: Callable[..., Any]
134
+ language_panel_rows: Callable[..., Any]
135
+ llm_option_panel_rows: Callable[..., Any]
136
+ llm_preset_panel_rows: Callable[..., Any]
137
+ log_level_panel_rows: Callable[..., Any]
138
+ model_panel_rows: Callable[..., Any]
139
+ provider_panel_rows: Callable[..., Any]
140
+
141
+
142
+ @dataclass(frozen=True, slots=True)
143
+ class PrelaunchSecrets:
144
+ clear_api_key_config: Callable[..., Any]
145
+ mask_secret: Callable[..., Any]
146
+ parse_api_key_list: Callable[..., Any]
147
+ secret_fingerprint: Callable[..., Any]
148
+ store_api_key_input_config: Callable[..., Any]
149
+ store_api_keys_config: Callable[..., Any]
150
+ copilot_oauth_action: Callable[..., Any]
151
+ kimi_oauth_action: Callable[..., Any]
152
+
153
+
154
+ @dataclass(frozen=True, slots=True)
155
+ class PrelaunchMutations:
156
+ apply_context_setup_config: Callable[..., Any]
157
+ apply_llm_preset_config: Callable[..., Any]
158
+ apply_timeout_profile_to_provider: Callable[..., Any]
159
+ set_advisor_model_config: Callable[..., Any]
160
+ set_base_url_config: Callable[..., Any]
161
+ set_llm_option_config: Callable[..., Any]
162
+ set_log_level_config: Callable[..., Any]
163
+ set_model_config: Callable[..., Any]
164
+ set_provider_choice_config: Callable[..., Any]
165
+
166
+
167
+ @dataclass(frozen=True, slots=True)
168
+ class PrelaunchOptions:
169
+ llm_option_current_bool: Callable[..., Any]
170
+ llm_option_prompt_default: Callable[..., Any]
171
+ timeout_profile_panel_rows: Callable[..., Any]
172
+ web_backend_panel_rows: Callable[..., Any]
173
+ set_web_backend_config: Callable[..., Any]
174
+
175
+
176
+ @dataclass(frozen=True, slots=True)
177
+ class PrelaunchServices:
178
+ constants: PrelaunchConstants
179
+ terminal: PrelaunchTerminal
180
+ config: PrelaunchConfig
181
+ launch_policy: PrelaunchLaunchPolicy
182
+ panel_rows: PrelaunchPanelRows
183
+ channel_query: PrelaunchChannelQuery
184
+ channel_commands: PrelaunchChannelCommands
185
+ mutations: PrelaunchMutations
186
+ secrets: PrelaunchSecrets
187
+ options: PrelaunchOptions
188
+
189
+
190
+ def run_prelaunch_menu(passthrough: list[str] | None = None,
191
+ *,
192
+ services: PrelaunchServices,
193
+ ) -> int:
194
+ LANGUAGES = services.constants.LANGUAGES
195
+ LLM_OPTION_TOGGLE_KEYS = services.constants.LLM_OPTION_TOGGLE_KEYS
196
+ MAIN_MENU_ACTIONS = services.constants.MAIN_MENU_ACTIONS
197
+ PRELAUNCH_CANCEL = services.constants.PRELAUNCH_CANCEL
198
+ PRELAUNCH_LAUNCH_AGY = services.constants.PRELAUNCH_LAUNCH_AGY
199
+ PRELAUNCH_LAUNCH_CLAUDE = services.constants.PRELAUNCH_LAUNCH_CLAUDE
200
+ PRELAUNCH_LAUNCH_CODEX = services.constants.PRELAUNCH_LAUNCH_CODEX
201
+ PRELAUNCH_LAUNCH_CODEX_APP_SERVER = services.constants.PRELAUNCH_LAUNCH_CODEX_APP_SERVER
202
+ PROVIDER_LABELS = services.constants.PROVIDER_LABELS
203
+ _channel_panel_first_selectable = services.channel_query._channel_panel_first_selectable
204
+ _channel_panel_step = services.channel_query._channel_panel_step
205
+ add_channel_spec = services.channel_commands.add_channel_spec
206
+ advisor_model_panel_rows = services.panel_rows.advisor_model_panel_rows
207
+ agy_launch_enabled_for_provider = services.launch_policy.agy_launch_enabled_for_provider
208
+ api_key_panel_rows = services.panel_rows.api_key_panel_rows
209
+ apply_context_setup_config = services.mutations.apply_context_setup_config
210
+ apply_llm_preset_config = services.mutations.apply_llm_preset_config
211
+ apply_timeout_profile_to_provider = services.mutations.apply_timeout_profile_to_provider
212
+ base_url_panel_rows = services.panel_rows.base_url_panel_rows
213
+ channel_delivery_panel_rows = services.channel_query.channel_delivery_panel_rows
214
+ channel_panel_rows = services.channel_query.channel_panel_rows
215
+ channel_panel_rows_for_menu = services.channel_query.channel_panel_rows_for_menu
216
+ channel_probe_summary_message = services.channel_query.channel_probe_summary_message
217
+ channel_specs = services.channel_query.channel_specs
218
+ claude_launch_enabled_for_provider = services.launch_policy.claude_launch_enabled_for_provider
219
+ clear_api_key_config = services.secrets.clear_api_key_config
220
+ clear_channel_specs = services.channel_commands.clear_channel_specs
221
+ clear_model_cache = services.config.clear_model_cache
222
+ codex_launch_enabled_for_provider = services.launch_policy.codex_launch_enabled_for_provider
223
+ context_setup_panel_rows = services.panel_rows.context_setup_panel_rows
224
+ current_provider_panel_choice = services.config.current_provider_panel_choice
225
+ default_base_url = services.config.default_base_url
226
+ default_prelaunch_action = services.terminal.default_prelaunch_action
227
+ enable_ansi = services.terminal.enable_ansi
228
+ get_current_provider = services.config.get_current_provider
229
+ language_panel_rows = services.panel_rows.language_panel_rows
230
+ launch_blockers_require_api_key = services.launch_policy.launch_blockers_require_api_key
231
+ launch_readiness_errors = services.launch_policy.launch_readiness_errors
232
+ launch_kimi = services.launch_policy.launch_kimi
233
+ llm_option_current_bool = services.options.llm_option_current_bool
234
+ llm_option_panel_rows = services.panel_rows.llm_option_panel_rows
235
+ llm_option_prompt_default = services.options.llm_option_prompt_default
236
+ llm_preset_panel_rows = services.panel_rows.llm_preset_panel_rows
237
+ load_config = services.config.load_config
238
+ log_level_panel_rows = services.panel_rows.log_level_panel_rows
239
+ main_menu_rows = services.terminal.main_menu_rows
240
+ mask_secret = services.secrets.mask_secret
241
+ model_panel_rows = services.panel_rows.model_panel_rows
242
+ parse_api_key_list = services.secrets.parse_api_key_list
243
+ preflight_lines = services.config.preflight_lines
244
+ prelaunch_action_index = services.terminal.prelaunch_action_index
245
+ prompt_menu_multiline_value = services.terminal.prompt_menu_multiline_value
246
+ prompt_menu_value = services.terminal.prompt_menu_value
247
+ provider_menu_label = services.config.provider_menu_label
248
+ provider_panel_rows = services.panel_rows.provider_panel_rows
249
+ read_clipboard_text = services.terminal.read_clipboard_text
250
+ read_menu_key = services.terminal.read_menu_key
251
+ read_model_list_cache = services.config.read_model_list_cache
252
+ refresh_channel_probe_cache = services.channel_query.refresh_channel_probe_cache
253
+ remove_channel_spec = services.channel_commands.remove_channel_spec
254
+ render_prelaunch_screen = services.terminal.render_prelaunch_screen
255
+ save_config = services.config.save_config
256
+ secret_fingerprint = services.secrets.secret_fingerprint
257
+ self_cmd = services.terminal.self_cmd
258
+ set_advisor_model_config = services.mutations.set_advisor_model_config
259
+ set_base_url_config = services.mutations.set_base_url_config
260
+ set_channel_delivery_config = services.channel_commands.set_channel_delivery_config
261
+ set_llm_option_config = services.mutations.set_llm_option_config
262
+ set_log_level_config = services.mutations.set_log_level_config
263
+ set_model_config = services.mutations.set_model_config
264
+ set_provider_choice_config = services.mutations.set_provider_choice_config
265
+ settings_ready_except_api_key = services.config.settings_ready_except_api_key
266
+ store_api_key_input_config = services.secrets.store_api_key_input_config
267
+ store_api_keys_config = services.secrets.store_api_keys_config
268
+ copilot_oauth_action = services.secrets.copilot_oauth_action
269
+ kimi_oauth_action = services.secrets.kimi_oauth_action
270
+ timeout_profile_panel_rows = services.options.timeout_profile_panel_rows
271
+ web_backend_panel_rows = services.options.web_backend_panel_rows
272
+ set_web_backend_config = services.options.set_web_backend_config
273
+ passthrough = list(passthrough or [])
274
+ enable_ansi()
275
+ cfg = load_config()
276
+ provider, _pcfg = get_current_provider(cfg)
277
+ initial_action = default_prelaunch_action(provider)
278
+ main_idx = prelaunch_action_index(initial_action) if settings_ready_except_api_key() else 0
279
+ panel: str | None = None
280
+ panel_idx = 0
281
+ panel_rows: list[str] = []
282
+ panel_values: list[str] = []
283
+ panel_last_idx: dict[str, int] = {}
284
+ checks = preflight_lines()
285
+ messages: list[str] = []
286
+ first_render = True
287
+
288
+ def open_panel(name: str) -> None:
289
+ nonlocal panel, panel_idx, panel_rows, panel_values, messages, first_render
290
+ cfg = load_config()
291
+ provider, pcfg = get_current_provider(cfg)
292
+ panel = name
293
+ panel_idx = panel_last_idx.get(name, 0)
294
+ if name == "language":
295
+ panel_rows, panel_values = language_panel_rows(cfg)
296
+ panel_idx = panel_values.index(cfg.get("language", "en"))
297
+ elif name == "provider":
298
+ panel_rows, panel_values = provider_panel_rows(cfg)
299
+ current_choice = current_provider_panel_choice(provider, pcfg)
300
+ panel_idx = panel_values.index(current_choice) if current_choice in panel_values else 0
301
+ elif name == "api-key":
302
+ panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
303
+ elif name == "base-url":
304
+ panel_rows, panel_values = base_url_panel_rows(provider, pcfg)
305
+ elif name == "model":
306
+ try:
307
+ panel_rows, panel_values = model_panel_rows(
308
+ provider,
309
+ pcfg,
310
+ fetch=provider == "anthropic" and read_model_list_cache(provider, pcfg) is None,
311
+ )
312
+ except Exception as exc:
313
+ panel_rows, panel_values = [f"Model list failed: {type(exc).__name__}: {exc}", "+ Custom model id..."], []
314
+ elif name == "advisor-model":
315
+ try:
316
+ panel_rows, panel_values = advisor_model_panel_rows(
317
+ provider,
318
+ pcfg,
319
+ fetch=provider == "anthropic" and read_model_list_cache(provider, pcfg) is None,
320
+ )
321
+ except Exception as exc:
322
+ panel_rows, panel_values = [f"Advisor model list failed: {type(exc).__name__}: {exc}", "+ Custom advisor model id..."], []
323
+ elif name == "test":
324
+ panel_rows, panel_values = ["Run compatibility test", "Back"], ["run", "back"]
325
+ elif name == "options":
326
+ panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
327
+ elif name == "channel-delivery":
328
+ panel_rows, panel_values = channel_delivery_panel_rows(cfg)
329
+ elif name == "log-level":
330
+ panel_rows, panel_values = log_level_panel_rows(cfg)
331
+ elif name == "channels":
332
+ panel_rows, panel_values, probe_messages = channel_panel_rows_for_menu(cfg, passthrough)
333
+ if probe_messages:
334
+ messages = probe_messages
335
+ if panel_values:
336
+ panel_idx = _channel_panel_first_selectable(panel_values)
337
+ elif name == "context":
338
+ panel_rows, panel_values = context_setup_panel_rows(provider, pcfg, cfg.get("language", "en"))
339
+ elif name == "preset":
340
+ panel_rows, panel_values = llm_preset_panel_rows(provider, pcfg, cfg.get("language", "en"))
341
+ elif name == "timeout":
342
+ panel_rows, panel_values = timeout_profile_panel_rows(pcfg, cfg.get("language", "en"))
343
+ elif name == "web-backend":
344
+ panel_rows, panel_values = web_backend_panel_rows(cfg)
345
+ if panel_rows:
346
+ panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
347
+
348
+ def close_panel(next_idx: int | None = None) -> None:
349
+ nonlocal panel, panel_idx, panel_rows, panel_values, main_idx
350
+ if panel:
351
+ panel_last_idx[panel] = panel_idx
352
+ panel = None
353
+ panel_idx = 0
354
+ panel_rows = []
355
+ panel_values = []
356
+ if next_idx is not None:
357
+ main_idx = next_idx
358
+
359
+ def refresh_checks() -> None:
360
+ nonlocal checks
361
+ checks = preflight_lines()
362
+
363
+ def persist_launch_action(action: str) -> None:
364
+ launch_config = load_config()
365
+ if remember_launch_action(launch_config, action):
366
+ save_config(launch_config)
367
+
368
+ fd = sys.stdin.fileno()
369
+ old_settings = None
370
+ if os.name != "nt" and os.isatty(fd):
371
+ try:
372
+ import termios
373
+ old_settings = termios.tcgetattr(fd)
374
+ new = termios.tcgetattr(fd)
375
+ new[3] = new[3] & ~(termios.ECHO | termios.ICANON)
376
+ new[6][termios.VMIN] = 1
377
+ new[6][termios.VTIME] = 0
378
+ termios.tcsetattr(fd, termios.TCSANOW, new)
379
+ except Exception:
380
+ fd = -1
381
+ if sys.stdout.isatty():
382
+ sys.stdout.write("\033[?25l")
383
+ sys.stdout.flush()
384
+ def restore_line_mode() -> None:
385
+ if old_settings is not None and fd >= 0:
386
+ try:
387
+ import termios
388
+ termios.tcsetattr(fd, termios.TCSANOW, old_settings)
389
+ except Exception:
390
+ pass
391
+
392
+ def restore_raw_mode() -> None:
393
+ if old_settings is not None and fd >= 0:
394
+ try:
395
+ import termios
396
+ new = termios.tcgetattr(fd)
397
+ new[3] = new[3] & ~(termios.ECHO | termios.ICANON)
398
+ new[6][termios.VMIN] = 1
399
+ new[6][termios.VTIME] = 0
400
+ termios.tcsetattr(fd, termios.TCSANOW, new)
401
+ except Exception:
402
+ pass
403
+
404
+ try:
405
+ while True:
406
+ first_render = render_prelaunch_screen(main_idx, panel, panel_idx, panel_rows, checks, messages, first_render)
407
+ key = read_menu_key(fd) if fd >= 0 else read_menu_key()
408
+ if panel:
409
+ panel_name = panel
410
+ if key in ("up", "k"):
411
+ if panel == "channels":
412
+ panel_idx = _channel_panel_step(panel_values, panel_idx, -1)
413
+ else:
414
+ panel_idx = (panel_idx - 1) % max(1, len(panel_rows))
415
+ panel_last_idx[panel_name] = panel_idx
416
+ continue
417
+ if key in ("down", "j"):
418
+ if panel == "channels":
419
+ panel_idx = _channel_panel_step(panel_values, panel_idx, 1)
420
+ else:
421
+ panel_idx = (panel_idx + 1) % max(1, len(panel_rows))
422
+ panel_last_idx[panel_name] = panel_idx
423
+ continue
424
+ if key in ("pageup", "pagedown", "home", "end"):
425
+ if key == "home":
426
+ panel_idx = 0
427
+ elif key == "end":
428
+ panel_idx = max(0, len(panel_rows) - 1)
429
+ else:
430
+ step = 10 if key == "pagedown" else -10
431
+ panel_idx = max(0, min(len(panel_rows) - 1, panel_idx + step))
432
+ panel_last_idx[panel_name] = panel_idx
433
+ continue
434
+ if key in ("esc", "left", "q"):
435
+ close_panel()
436
+ continue
437
+ if key != "enter":
438
+ continue
439
+ cfg = load_config()
440
+ provider, pcfg = get_current_provider(cfg)
441
+ value = panel_values[panel_idx] if panel_idx < len(panel_values) else ""
442
+ if panel == "language" and value:
443
+ cfg["language"] = value
444
+ save_config(cfg)
445
+ messages = [f"Language set to {value} ({LANGUAGES[value]})."]
446
+ refresh_checks()
447
+ close_panel(1)
448
+ elif panel == "provider" and value:
449
+ messages = set_provider_choice_config(value)
450
+ refresh_checks()
451
+ cfg = load_config()
452
+ provider, _pcfg = get_current_provider(cfg)
453
+ if provider == "codex":
454
+ close_panel(prelaunch_action_index(default_prelaunch_action(provider)))
455
+ else:
456
+ main_idx = 4
457
+ open_panel("model")
458
+ elif panel == "model":
459
+ if value == "back":
460
+ close_panel()
461
+ continue
462
+ if value == "__refresh_models__":
463
+ panel_rows, panel_values = ["Refreshing provider model list..."], []
464
+ first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
465
+ try:
466
+ panel_rows, panel_values = model_panel_rows(provider, pcfg, fetch=True, force_refresh=True)
467
+ messages = [f"Model list refreshed: {max(0, len(panel_values) - 3)} model(s)."]
468
+ except Exception as exc:
469
+ messages = [f"Model list refresh failed: {type(exc).__name__}: {exc}"]
470
+ panel_rows, panel_values = model_panel_rows(provider, pcfg, fetch=False)
471
+ panel_idx = 0
472
+ panel_last_idx["model"] = 0
473
+ refresh_checks()
474
+ continue
475
+ if value == "__custom__" or panel_idx >= len(panel_values):
476
+ model_value = prompt_menu_value("Model id or alias", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
477
+ else:
478
+ model_value = value
479
+ if model_value:
480
+ messages = set_model_config(model_value)
481
+ refresh_checks()
482
+ close_panel(5)
483
+ elif panel == "advisor-model":
484
+ if value == "back":
485
+ close_panel()
486
+ continue
487
+ if value == "__refresh_models__":
488
+ panel_rows, panel_values = ["Refreshing provider model list..."], []
489
+ first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
490
+ try:
491
+ panel_rows, panel_values = advisor_model_panel_rows(provider, pcfg, fetch=True, force_refresh=True)
492
+ messages = [f"Model list refreshed: {max(0, len(panel_values) - 4)} advisor model(s)."]
493
+ except Exception as exc:
494
+ messages = [f"Model list refresh failed: {type(exc).__name__}: {exc}"]
495
+ panel_rows, panel_values = advisor_model_panel_rows(provider, pcfg, fetch=False)
496
+ panel_idx = 0
497
+ panel_last_idx["advisor-model"] = 0
498
+ refresh_checks()
499
+ continue
500
+ if value == "__custom__" or panel_idx >= len(panel_values):
501
+ advisor_value = prompt_menu_value("Advisor model id", "deepseek-v4-pro", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
502
+ else:
503
+ advisor_value = value
504
+ messages = set_advisor_model_config(advisor_value)
505
+ refresh_checks()
506
+ close_panel(6)
507
+ elif panel == "api-key":
508
+ if value == "back":
509
+ close_panel()
510
+ elif value == "kimi-oauth-login":
511
+ messages = kimi_oauth_action("login")
512
+ refresh_checks()
513
+ cfg = load_config()
514
+ provider, pcfg = get_current_provider(cfg)
515
+ panel_rows, panel_values = api_key_panel_rows(provider, pcfg)
516
+ panel_idx = 0
517
+ elif value.startswith("oauth-"):
518
+ messages = copilot_oauth_action(
519
+ value.removeprefix("oauth-")
520
+ )
521
+ refresh_checks()
522
+ close_panel(3)
523
+ elif value == "input":
524
+ key_value = prompt_menu_value(f"API key for {provider}", secret=True, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
525
+ if key_value:
526
+ messages = store_api_key_input_config(provider, key_value)
527
+ refresh_checks()
528
+ close_panel(3)
529
+ elif value == "multi-input":
530
+ key_value = prompt_menu_multiline_value(
531
+ f"API keys for {provider} (comma/newline separated)",
532
+ restore_tty=restore_line_mode,
533
+ raw_tty=restore_raw_mode,
534
+ )
535
+ if key_value:
536
+ messages = store_api_keys_config(provider, parse_api_key_list(key_value))
537
+ refresh_checks()
538
+ close_panel(3)
539
+ elif value == "env":
540
+ default_env = {
541
+ "anthropic": "ANTHROPIC_API_KEY",
542
+ "deepseek": "DEEPSEEK_API_KEY",
543
+ "opencode": "OPENCODE_API_KEY",
544
+ "opencode-go": "OPENCODE_API_KEY",
545
+ "kimi": "KIMI_API_KEY",
546
+ "nvidia-hosted": "NVIDIA_API_KEY",
547
+ "ollama-cloud": "OLLAMA_API_KEY",
548
+ "openrouter": "OPENROUTER_API_KEY",
549
+ "fireworks": "FIREWORKS_API_KEY",
550
+ "meta": "MODEL_API_KEY",
551
+ }.get(provider, "API_KEY")
552
+ env_name = prompt_menu_value("Environment variable name", default_env, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
553
+ key_value = os.environ.get(env_name, "").strip()
554
+ if key_value:
555
+ messages = store_api_key_input_config(provider, key_value)
556
+ else:
557
+ messages = [f"Environment variable {env_name} is empty or not set."]
558
+ refresh_checks()
559
+ close_panel(3)
560
+ elif value == "multi-env":
561
+ default_env = {
562
+ "anthropic": "ANTHROPIC_API_KEYS",
563
+ "deepseek": "DEEPSEEK_API_KEYS",
564
+ "opencode": "OPENCODE_API_KEYS",
565
+ "opencode-go": "OPENCODE_API_KEYS",
566
+ "kimi": "KIMI_API_KEYS",
567
+ "nvidia-hosted": "NVIDIA_API_KEYS",
568
+ "ollama-cloud": "OLLAMA_API_KEYS",
569
+ "openrouter": "OPENROUTER_API_KEYS",
570
+ "fireworks": "FIREWORKS_API_KEYS",
571
+ "meta": "MODEL_API_KEYS",
572
+ }.get(provider, "API_KEYS")
573
+ env_name = prompt_menu_value("Environment variable name", default_env, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
574
+ key_value = os.environ.get(env_name, "").strip()
575
+ if key_value:
576
+ messages = store_api_keys_config(provider, parse_api_key_list(key_value))
577
+ else:
578
+ messages = [f"Environment variable {env_name} is empty or not set."]
579
+ refresh_checks()
580
+ close_panel(3)
581
+ elif value == "clipboard":
582
+ key_value = read_clipboard_text()
583
+ if not key_value:
584
+ messages = ["Clipboard did not contain readable text."]
585
+ else:
586
+ confirm = prompt_menu_value(f"Clipboard contains {mask_secret(key_value)}. Store it? y/N", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
587
+ if confirm.lower().startswith("y"):
588
+ messages = store_api_key_input_config(provider, key_value)
589
+ else:
590
+ messages = ["Clipboard API key was not stored."]
591
+ refresh_checks()
592
+ close_panel(3)
593
+ elif value == "multi-clipboard":
594
+ key_value = read_clipboard_text()
595
+ keys = parse_api_key_list(key_value)
596
+ if not keys:
597
+ messages = ["Clipboard did not contain readable API keys."]
598
+ else:
599
+ primary = f"{mask_secret(keys[0])}; fp {secret_fingerprint(keys[0])}"
600
+ confirm = prompt_menu_value(
601
+ f"Clipboard contains {len(keys)} key(s); primary {primary}. Store with round-robin? y/N",
602
+ restore_tty=restore_line_mode,
603
+ raw_tty=restore_raw_mode,
604
+ )
605
+ if confirm.lower().startswith("y"):
606
+ messages = store_api_keys_config(provider, keys)
607
+ else:
608
+ messages = ["Clipboard API keys were not stored."]
609
+ refresh_checks()
610
+ close_panel(3)
611
+ elif value == "clear":
612
+ messages = clear_api_key_config(provider)
613
+ refresh_checks()
614
+ close_panel(3)
615
+ elif panel == "base-url":
616
+ if value == "back":
617
+ close_panel()
618
+ elif value == "default":
619
+ messages = set_base_url_config(provider, default_base_url(provider))
620
+ refresh_checks()
621
+ close_panel(4)
622
+ elif value == "edit":
623
+ default = pcfg.get("base_url") or default_base_url(provider)
624
+ url = prompt_menu_value(f"Base URL for {provider}", default, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
625
+ if url:
626
+ messages = set_base_url_config(provider, url)
627
+ refresh_checks()
628
+ close_panel(4)
629
+ elif panel == "test":
630
+ if value == "back":
631
+ close_panel()
632
+ else:
633
+ panel_rows, panel_values = ["Testing current provider/model..."], []
634
+ first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
635
+ _, out = self_cmd(["test"])
636
+ lines = [line for line in out.splitlines() if line.strip()]
637
+ messages = lines[-8:] if lines else ["Test produced no output."]
638
+ test_ok = "Compatibility: OK" in out
639
+ refresh_checks()
640
+ close_panel(9 if test_ok else 4)
641
+ elif panel == "log-level":
642
+ if value == "back":
643
+ close_panel()
644
+ elif value:
645
+ messages = set_log_level_config(value)
646
+ refresh_checks()
647
+ cfg = load_config()
648
+ panel_rows, panel_values = log_level_panel_rows(cfg)
649
+ panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
650
+ elif panel == "web-backend":
651
+ if value == "back":
652
+ close_panel()
653
+ continue
654
+ web_config = cfg.get("web_backend", {})
655
+ if not isinstance(web_config, dict):
656
+ web_config = {}
657
+ if value == "enabled":
658
+ messages = set_web_backend_config(
659
+ "enabled",
660
+ not bool(web_config.get("enabled", False)),
661
+ )
662
+ elif value == "host":
663
+ entered = prompt_menu_value(
664
+ "Web bind address",
665
+ str(web_config.get("host") or "127.0.0.1"),
666
+ restore_tty=restore_line_mode,
667
+ raw_tty=restore_raw_mode,
668
+ )
669
+ if not entered:
670
+ continue
671
+ messages = set_web_backend_config("host", entered)
672
+ elif value == "port":
673
+ entered = prompt_menu_value(
674
+ "Web port",
675
+ str(web_config.get("port") or ""),
676
+ restore_tty=restore_line_mode,
677
+ raw_tty=restore_raw_mode,
678
+ )
679
+ if not entered:
680
+ continue
681
+ messages = set_web_backend_config("port", entered)
682
+ elif value == "tailscale":
683
+ messages = set_web_backend_config(
684
+ "tailscale",
685
+ not bool(web_config.get("tailscale_https", False)),
686
+ )
687
+ else:
688
+ continue
689
+ return PRELAUNCH_RELOAD
690
+ elif panel == "channel-delivery":
691
+ if value == "back":
692
+ close_panel()
693
+ elif value:
694
+ messages = set_channel_delivery_config(value)
695
+ refresh_checks()
696
+ cfg = load_config()
697
+ panel_rows, panel_values = channel_delivery_panel_rows(cfg)
698
+ panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
699
+ elif panel == "channels":
700
+ if value == "back":
701
+ close_panel()
702
+ elif value in ("__heading__", "__noop__"):
703
+ continue
704
+ elif value == "__reprobe__":
705
+ panel_rows, panel_values = ["Re-probing MCP channel capability..."], []
706
+ first_render = render_prelaunch_screen(main_idx, panel, 0, panel_rows, checks, messages, first_render)
707
+ try:
708
+ result = refresh_channel_probe_cache(passthrough)
709
+ messages = [channel_probe_summary_message("Probe complete", result)]
710
+ except Exception as exc:
711
+ messages = [f"Re-probe failed: {type(exc).__name__}: {exc}"]
712
+ cfg = load_config()
713
+ panel_rows, panel_values = channel_panel_rows(cfg)
714
+ if panel_values:
715
+ panel_idx = _channel_panel_first_selectable(panel_values)
716
+ elif value == "__add_custom__":
717
+ spec = prompt_menu_value("Channel spec (for example plugin:ainet@local or server:ainet)", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
718
+ if spec:
719
+ messages = add_channel_spec(spec)
720
+ cfg = load_config()
721
+ panel_rows, panel_values = channel_panel_rows(cfg)
722
+ if panel_values:
723
+ panel_idx = _channel_panel_first_selectable(panel_values)
724
+ elif value == "__remove__":
725
+ spec = prompt_menu_value("Channel spec to remove", "", restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
726
+ if spec:
727
+ messages = remove_channel_spec(spec)
728
+ cfg = load_config()
729
+ panel_rows, panel_values = channel_panel_rows(cfg)
730
+ if panel_values:
731
+ panel_idx = _channel_panel_first_selectable(panel_values)
732
+ elif value == "__clear__":
733
+ messages = clear_channel_specs()
734
+ cfg = load_config()
735
+ panel_rows, panel_values = channel_panel_rows(cfg)
736
+ if panel_values:
737
+ panel_idx = _channel_panel_first_selectable(panel_values)
738
+ elif value:
739
+ if value in channel_specs(cfg):
740
+ messages = remove_channel_spec(value)
741
+ else:
742
+ messages = add_channel_spec(value)
743
+ cfg = load_config()
744
+ panel_rows, panel_values = channel_panel_rows(cfg)
745
+ refresh_checks()
746
+ elif panel == "options":
747
+ if value == "back":
748
+ close_panel()
749
+ elif value == "context_setup":
750
+ open_panel("context")
751
+ elif value == "preset":
752
+ open_panel("preset")
753
+ elif value == "timeout_profile":
754
+ open_panel("timeout")
755
+ elif value in LLM_OPTION_TOGGLE_KEYS:
756
+ # Boolean toggles flip on Enter — no input prompt.
757
+ current = llm_option_current_bool(provider, pcfg, value)
758
+ try:
759
+ messages = set_llm_option_config(provider, value, "false" if current else "true")
760
+ except Exception as exc:
761
+ messages = [f"Option update failed: {type(exc).__name__}: {exc}"]
762
+ refresh_checks()
763
+ cfg = load_config()
764
+ provider, pcfg = get_current_provider(cfg)
765
+ old_idx = panel_idx
766
+ panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
767
+ panel_idx = max(0, min(old_idx, len(panel_rows) - 1))
768
+ panel_last_idx["options"] = panel_idx
769
+ else:
770
+ default = llm_option_prompt_default(provider, pcfg, value)
771
+ entered = prompt_menu_value(f"{value} for {provider} (default/unset clears)", default, restore_tty=restore_line_mode, raw_tty=restore_raw_mode)
772
+ try:
773
+ messages = set_llm_option_config(provider, value, entered)
774
+ except Exception as exc:
775
+ messages = [f"Option update failed: {type(exc).__name__}: {exc}"]
776
+ refresh_checks()
777
+ cfg = load_config()
778
+ provider, pcfg = get_current_provider(cfg)
779
+ old_idx = panel_idx
780
+ panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
781
+ panel_idx = max(0, min(old_idx, len(panel_rows) - 1))
782
+ panel_last_idx["options"] = panel_idx
783
+ elif panel == "context":
784
+ if value == "back":
785
+ open_panel("options")
786
+ elif value == "__info__":
787
+ continue
788
+ else:
789
+ try:
790
+ messages = apply_context_setup_config(provider, value)
791
+ except Exception as exc:
792
+ messages = [f"Context setup failed: {type(exc).__name__}: {exc}"]
793
+ refresh_checks()
794
+ cfg = load_config()
795
+ provider, pcfg = get_current_provider(cfg)
796
+ panel = "options"
797
+ panel_idx = panel_last_idx.get("options", 0)
798
+ panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
799
+ panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
800
+ panel_last_idx["options"] = panel_idx
801
+ elif panel == "preset":
802
+ if value == "back":
803
+ open_panel("options")
804
+ elif value == "__info__":
805
+ continue
806
+ else:
807
+ try:
808
+ messages = apply_llm_preset_config(provider, value)
809
+ except Exception as exc:
810
+ messages = [f"Preset failed: {type(exc).__name__}: {exc}"]
811
+ refresh_checks()
812
+ cfg = load_config()
813
+ provider, pcfg = get_current_provider(cfg)
814
+ panel = "options"
815
+ panel_idx = panel_last_idx.get("options", 0)
816
+ panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
817
+ panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
818
+ panel_last_idx["options"] = panel_idx
819
+ elif panel == "timeout":
820
+ if value == "back":
821
+ open_panel("options")
822
+ elif value == "__info__":
823
+ continue
824
+ else:
825
+ try:
826
+ cfg = load_config()
827
+ provider, pcfg = get_current_provider(cfg)
828
+ messages = apply_timeout_profile_to_provider(pcfg, value, cfg.get("language", "en"))
829
+ save_config(cfg)
830
+ clear_model_cache()
831
+ except Exception as exc:
832
+ messages = [f"Timeout preset failed: {type(exc).__name__}: {exc}"]
833
+ refresh_checks()
834
+ cfg = load_config()
835
+ provider, pcfg = get_current_provider(cfg)
836
+ panel = "options"
837
+ panel_idx = panel_last_idx.get("options", 0)
838
+ panel_rows, panel_values = llm_option_panel_rows(provider, pcfg, cfg.get("language", "en"))
839
+ panel_idx = max(0, min(panel_idx, len(panel_rows) - 1))
840
+ panel_last_idx["options"] = panel_idx
841
+ continue
842
+
843
+ if key in ("up", "k"):
844
+ cfg = load_config()
845
+ provider, pcfg = get_current_provider(cfg)
846
+ main_idx = (main_idx - 1) % len(main_menu_rows(cfg, provider, pcfg, cfg.get("language", "en")))
847
+ elif key in ("down", "j"):
848
+ cfg = load_config()
849
+ provider, pcfg = get_current_provider(cfg)
850
+ main_idx = (main_idx + 1) % len(main_menu_rows(cfg, provider, pcfg, cfg.get("language", "en")))
851
+ elif key in ("esc", "q"):
852
+ return PRELAUNCH_CANCEL
853
+ elif key == "enter":
854
+ actions = list(MAIN_MENU_ACTIONS)
855
+ action = actions[main_idx]
856
+ if action == "launch":
857
+ cfg = load_config()
858
+ provider, _ = get_current_provider(cfg)
859
+ if not claude_launch_enabled_for_provider(provider):
860
+ provider_label = provider_menu_label(provider, cfg.get("providers", {}).get(provider, {}))
861
+ messages = [f"Launch Claude Code is disabled while {provider_label} provider is selected."]
862
+ refresh_checks()
863
+ continue
864
+ blockers = launch_readiness_errors()
865
+ if blockers:
866
+ messages = blockers
867
+ if launch_blockers_require_api_key(blockers):
868
+ cfg = load_config()
869
+ provider, _ = get_current_provider(cfg)
870
+ main_idx = actions.index("api-key")
871
+ open_panel("api-key")
872
+ if "input" in panel_values:
873
+ panel_idx = panel_values.index("input")
874
+ messages = [
875
+ *blockers,
876
+ f"Opening API key setup for {PROVIDER_LABELS.get(provider, provider)}.",
877
+ ]
878
+ refresh_checks()
879
+ continue
880
+ persist_launch_action(action)
881
+ return PRELAUNCH_LAUNCH_CLAUDE
882
+ if action == "launch-agy":
883
+ cfg = load_config()
884
+ provider, _ = get_current_provider(cfg)
885
+ if not agy_launch_enabled_for_provider(provider):
886
+ messages = ["Launch AGY is disabled until you select AGY or AGY Routed as the provider."]
887
+ refresh_checks()
888
+ continue
889
+ blockers = launch_readiness_errors()
890
+ if blockers:
891
+ messages = blockers
892
+ refresh_checks()
893
+ continue
894
+ return PRELAUNCH_LAUNCH_AGY
895
+ if action == "launch-kimi":
896
+ cfg = load_config()
897
+ provider, _ = get_current_provider(cfg)
898
+ if provider != "kimi":
899
+ messages = ["Launch Kimi Code is disabled until Kimi Native or Kimi Routed is selected."]
900
+ refresh_checks()
901
+ continue
902
+ launch_kimi([])
903
+ return PRELAUNCH_CANCEL
904
+ if action == "launch-codex":
905
+ cfg = load_config()
906
+ provider, pcfg = get_current_provider(cfg)
907
+ if not codex_launch_enabled_for_provider(provider):
908
+ messages = [f"Launch Codex is disabled while {provider_menu_label(provider, pcfg)} provider is selected."]
909
+ refresh_checks()
910
+ continue
911
+ blockers = launch_readiness_errors()
912
+ if blockers:
913
+ messages = blockers
914
+ refresh_checks()
915
+ continue
916
+ persist_launch_action(action)
917
+ return PRELAUNCH_LAUNCH_CODEX
918
+ if action == "launch-codex-app-server":
919
+ cfg = load_config()
920
+ provider, pcfg = get_current_provider(cfg)
921
+ if not codex_launch_enabled_for_provider(provider):
922
+ messages = [f"Launch Codex App Server is disabled while {provider_menu_label(provider, pcfg)} provider is selected."]
923
+ refresh_checks()
924
+ continue
925
+ blockers = launch_readiness_errors()
926
+ if blockers:
927
+ messages = blockers
928
+ refresh_checks()
929
+ continue
930
+ persist_launch_action(action)
931
+ return PRELAUNCH_LAUNCH_CODEX_APP_SERVER
932
+ if action == "quit":
933
+ return PRELAUNCH_CANCEL
934
+ open_panel(action)
935
+ finally:
936
+ if old_settings is not None:
937
+ try:
938
+ import termios
939
+ termios.tcsetattr(fd, termios.TCSANOW, old_settings)
940
+ except Exception:
941
+ pass
942
+ sys.stdout.write("\033[?25h")
943
+ sys.stdout.flush()
944
+
945
+
946
+ __all__ = [
947
+ "PrelaunchChannelCommands",
948
+ "PrelaunchChannelQuery",
949
+ "PrelaunchConfig",
950
+ "PrelaunchConstants",
951
+ "PrelaunchLaunchPolicy",
952
+ "PrelaunchMutations",
953
+ "PrelaunchOptions",
954
+ "PrelaunchPanelRows",
955
+ "PrelaunchSecrets",
956
+ "PrelaunchServices",
957
+ "PrelaunchTerminal",
958
+ "run_prelaunch_menu",
959
+ ]