@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,323 @@
1
+ """Codex configuration discovery and TOML projection policies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import re
8
+ from collections.abc import Callable
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ def toml_string(value: str) -> str:
14
+ return json.dumps(str(value))
15
+
16
+
17
+ def codex_config_override_keys(passthrough: list[str]) -> set[str]:
18
+ keys: set[str] = set()
19
+ index = 0
20
+ while index < len(passthrough):
21
+ argument = str(passthrough[index])
22
+ value = ""
23
+ if argument in ("-c", "--config") and index + 1 < len(passthrough):
24
+ value = str(passthrough[index + 1])
25
+ index += 2
26
+ elif argument.startswith("--config="):
27
+ value = argument.split("=", 1)[1]
28
+ index += 1
29
+ else:
30
+ index += 1
31
+ continue
32
+ if "=" in value:
33
+ keys.add(value.split("=", 1)[0].strip())
34
+ return keys
35
+
36
+
37
+ def toml_scalar_without_comment(raw: str) -> str:
38
+ in_single = False
39
+ in_double = False
40
+ escaped = False
41
+ out: list[str] = []
42
+ for character in raw:
43
+ if escaped:
44
+ out.append(character)
45
+ escaped = False
46
+ continue
47
+ if character == "\\" and in_double:
48
+ out.append(character)
49
+ escaped = True
50
+ continue
51
+ if character == '"' and not in_single:
52
+ in_double = not in_double
53
+ elif character == "'" and not in_double:
54
+ in_single = not in_single
55
+ elif character == "#" and not in_single and not in_double:
56
+ break
57
+ out.append(character)
58
+ return "".join(out).strip()
59
+
60
+
61
+ def unquote_toml_string(raw: str) -> str:
62
+ value = raw.strip()
63
+ if len(value) >= 2 and value[0] == value[-1] and value[0] in ("'", '"'):
64
+ return value[1:-1].strip()
65
+ return value
66
+
67
+
68
+ def codex_alternate_screen_value_from_config_text(text: str) -> str | None:
69
+ table = ""
70
+ for line in text.splitlines():
71
+ stripped = toml_scalar_without_comment(line)
72
+ if not stripped:
73
+ continue
74
+ table_match = re.fullmatch(r"\[([A-Za-z0-9_.-]+)\]", stripped)
75
+ if table_match:
76
+ table = table_match.group(1).strip()
77
+ continue
78
+ match = re.match(r"alternate_screen\s*=\s*(.+)$", stripped) if table == "tui" else None
79
+ if match is None:
80
+ match = re.match(r"tui\.alternate_screen\s*=\s*(.+)$", stripped)
81
+ if match is None:
82
+ continue
83
+ value = unquote_toml_string(match.group(1)).casefold()
84
+ if value in ("false", "0", "off", "no", "disabled", "disable"):
85
+ return "never"
86
+ if value in ("true", "1", "on", "yes", "enabled", "enable"):
87
+ return "always"
88
+ if value in ("auto", "always", "never"):
89
+ return None
90
+ return "auto"
91
+ return None
92
+
93
+
94
+ def codex_config_paths_for_launch(
95
+ passthrough: list[str],
96
+ env: dict[str, str] | None = None,
97
+ cwd: Path | None = None,
98
+ ) -> list[Path]:
99
+ env = env or os.environ
100
+ configured_home = str(env.get("CODEX_HOME") or "").strip()
101
+ default_home = Path.home() / ".codex"
102
+ home = Path(configured_home or default_home).expanduser()
103
+ paths = [home / "config.toml"]
104
+ profiles: list[str] = []
105
+ index = 0
106
+ while index < len(passthrough):
107
+ argument = str(passthrough[index])
108
+ if argument in ("-p", "--profile") and index + 1 < len(passthrough):
109
+ profiles.append(str(passthrough[index + 1]))
110
+ index += 2
111
+ continue
112
+ if argument.startswith("--profile="):
113
+ profiles.append(argument.split("=", 1)[1])
114
+ index += 1
115
+ for profile in profiles:
116
+ if re.fullmatch(r"[A-Za-z0-9_-]+", profile or ""):
117
+ paths.append(home / f"{profile}.config.toml")
118
+ current = (cwd or Path.cwd()).resolve()
119
+ for parent in (current, *current.parents):
120
+ path = parent / ".codex" / "config.toml"
121
+ # An explicit CODEX_HOME is an isolation boundary. When cwd happens
122
+ # to live below the OS home directory, parent discovery must not pull
123
+ # the default ~/.codex/config.toml back into that isolated config.
124
+ if configured_home and path.resolve() == (default_home / "config.toml").resolve():
125
+ continue
126
+ if path not in paths:
127
+ paths.append(path)
128
+ return paths
129
+
130
+
131
+ def normalize_codex_mcp_server(
132
+ raw_name: Any, raw_server: Any
133
+ ) -> tuple[str, dict[str, Any]] | None:
134
+ name = str(raw_name or "").strip()
135
+ if not name or not isinstance(raw_server, dict):
136
+ return None
137
+ url = str(raw_server.get("url") or raw_server.get("endpoint") or "").strip()
138
+ if not url.startswith(("http://", "https://")):
139
+ return None
140
+ explicit_type = raw_server.get("type") is not None or raw_server.get("transport") is not None
141
+ server_type = str(raw_server.get("type") or raw_server.get("transport") or "http").strip().lower()
142
+ if server_type in {"streamable-http", "streamable_http"}:
143
+ server_type = "http"
144
+ if server_type not in {"http", "sse"}:
145
+ server_type = "http"
146
+ out: dict[str, Any] = {"type": server_type, "url": url}
147
+ if explicit_type:
148
+ out["_ciel_runtime_explicit_type"] = True
149
+ for key in (
150
+ "headers",
151
+ "env_http_headers",
152
+ "bearer_token_env_var",
153
+ "token_env_var",
154
+ "bearer_token",
155
+ "token",
156
+ "streamable_requires_session",
157
+ "require_session",
158
+ "mcp_session_required",
159
+ "mcp_protocol_version",
160
+ "protocolVersion",
161
+ "protocol_version",
162
+ "mcp_timeout_seconds",
163
+ "timeout",
164
+ ):
165
+ value = raw_server.get(key)
166
+ if value is not None:
167
+ out[key] = value
168
+ return name, out
169
+
170
+
171
+ def codex_mcp_servers_from_toml_data(data: Any) -> dict[str, dict[str, Any]]:
172
+ if not isinstance(data, dict) or not isinstance(data.get("mcp_servers"), dict):
173
+ return {}
174
+ out: dict[str, dict[str, Any]] = {}
175
+ for raw_name, raw_server in data["mcp_servers"].items():
176
+ normalized = normalize_codex_mcp_server(raw_name, raw_server)
177
+ if normalized is not None:
178
+ name, server = normalized
179
+ out[name] = server
180
+ return out
181
+
182
+
183
+ def toml_table_parts(raw: str) -> list[str]:
184
+ parts: list[str] = []
185
+ current: list[str] = []
186
+ quote = ""
187
+ escaped = False
188
+ for character in raw:
189
+ if escaped:
190
+ current.append(character)
191
+ escaped = False
192
+ continue
193
+ if character == "\\" and quote == '"':
194
+ current.append(character)
195
+ escaped = True
196
+ continue
197
+ if character in {"'", '"'}:
198
+ quote = "" if quote == character else character if not quote else quote
199
+ current.append(character)
200
+ continue
201
+ if character == "." and not quote:
202
+ part = unquote_toml_string("".join(current).strip())
203
+ if part:
204
+ parts.append(part)
205
+ current = []
206
+ continue
207
+ current.append(character)
208
+ part = unquote_toml_string("".join(current).strip())
209
+ if part:
210
+ parts.append(part)
211
+ return parts
212
+
213
+
214
+ def parse_simple_toml_value(raw: str) -> Any:
215
+ value = toml_scalar_without_comment(raw).strip()
216
+ if not value:
217
+ return ""
218
+ if len(value) >= 2 and value[0] == value[-1] and value[0] in {"'", '"'}:
219
+ return unquote_toml_string(value)
220
+ low = value.lower()
221
+ if low in {"true", "false"}:
222
+ return low == "true"
223
+ if value.startswith("[") and value.endswith("]"):
224
+ items: list[str] = []
225
+ current: list[str] = []
226
+ quote = ""
227
+ escaped = False
228
+ for character in value[1:-1]:
229
+ if escaped:
230
+ current.append(character)
231
+ escaped = False
232
+ continue
233
+ if character == "\\" and quote == '"':
234
+ current.append(character)
235
+ escaped = True
236
+ continue
237
+ if character in {"'", '"'}:
238
+ quote = "" if quote == character else character if not quote else quote
239
+ current.append(character)
240
+ continue
241
+ if character == "," and not quote:
242
+ item = unquote_toml_string("".join(current).strip())
243
+ if item:
244
+ items.append(item)
245
+ current = []
246
+ continue
247
+ current.append(character)
248
+ item = unquote_toml_string("".join(current).strip())
249
+ if item:
250
+ items.append(item)
251
+ return items
252
+ try:
253
+ return int(value)
254
+ except ValueError:
255
+ return value
256
+
257
+
258
+ def fallback_codex_mcp_servers_from_config_text(text: str) -> dict[str, dict[str, Any]]:
259
+ raw_servers: dict[str, dict[str, Any]] = {}
260
+ current_name = ""
261
+ current_subtable = ""
262
+ for line in text.splitlines():
263
+ stripped = toml_scalar_without_comment(line)
264
+ if not stripped:
265
+ continue
266
+ table_match = re.fullmatch(r"\[(.+)\]", stripped)
267
+ if table_match:
268
+ parts = toml_table_parts(table_match.group(1).strip())
269
+ current_name = ""
270
+ current_subtable = ""
271
+ if len(parts) >= 2 and parts[0] == "mcp_servers":
272
+ current_name = parts[1]
273
+ current_subtable = parts[2] if len(parts) >= 3 else ""
274
+ raw_servers.setdefault(current_name, {})
275
+ continue
276
+ if not current_name or "=" not in stripped:
277
+ continue
278
+ key, raw_value = stripped.split("=", 1)
279
+ value = parse_simple_toml_value(raw_value)
280
+ server = raw_servers.setdefault(current_name, {})
281
+ if current_subtable:
282
+ nested = server.setdefault(current_subtable, {})
283
+ if isinstance(nested, dict):
284
+ nested[key.strip()] = value
285
+ else:
286
+ server[key.strip()] = value
287
+ return codex_mcp_servers_from_toml_data({"mcp_servers": raw_servers})
288
+
289
+
290
+ def codex_mcp_servers_from_config_text(text: str) -> dict[str, dict[str, Any]]:
291
+ try:
292
+ import tomllib
293
+
294
+ parsed = codex_mcp_servers_from_toml_data(tomllib.loads(text))
295
+ if parsed:
296
+ return parsed
297
+ except (ImportError, TypeError, ValueError):
298
+ pass
299
+ return fallback_codex_mcp_servers_from_config_text(text)
300
+
301
+
302
+ def discover_codex_mcp_servers(
303
+ passthrough: list[str] | None = None,
304
+ env: dict[str, str] | None = None,
305
+ cwd: Path | None = None,
306
+ *,
307
+ log: Callable[[str, str], None],
308
+ ) -> dict[str, dict[str, Any]]:
309
+ servers: dict[str, dict[str, Any]] = {}
310
+ for path in codex_config_paths_for_launch(passthrough or [], env=env, cwd=cwd):
311
+ if not path.is_file():
312
+ continue
313
+ try:
314
+ parsed = codex_mcp_servers_from_config_text(path.read_text(encoding="utf-8"))
315
+ except (OSError, UnicodeError) as exc:
316
+ log(
317
+ "WARN",
318
+ f"codex_mcp_config_read_failed path={path} "
319
+ f"error={type(exc).__name__}: {exc}",
320
+ )
321
+ continue
322
+ servers.update(parsed)
323
+ return servers
@@ -0,0 +1,240 @@
1
+ """Codex launch configuration and routed model catalog orchestration."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+ from dataclasses import dataclass
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from ciel_runtime_support.codex_config import (
11
+ codex_alternate_screen_value_from_config_text,
12
+ codex_config_override_keys,
13
+ codex_config_paths_for_launch,
14
+ toml_string,
15
+ )
16
+ from ciel_runtime_support.codex_model_catalog import CodexModelCatalogSpec
17
+ from ciel_runtime_support.runtime_constants import (
18
+ CODEX_NATIVE_PROVIDER_ID_ENV,
19
+ CODEX_ROUTED_PROVIDER_ID,
20
+ CODEX_RUNTIME_API_KEY_ENV,
21
+ CODEX_RUNTIME_PROVIDER_ID,
22
+ CODEX_TUI_ALTERNATE_SCREEN_KEY,
23
+ )
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class CodexLaunchConfigurationConstants:
28
+ runtime_provider_id: str
29
+ runtime_api_key_env: str
30
+ native_provider_id_env: str
31
+ routed_provider_id: str
32
+ alternate_screen_key: str
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class CodexLaunchPolicyPorts:
37
+ has_option: Callable[..., bool]
38
+ config_override_keys: Callable[[list[str]], set[str]]
39
+ config_paths: Callable[..., list[Path]]
40
+ alternate_screen_value: Callable[[str], str | None]
41
+ toml_string: Callable[[str], str]
42
+
43
+
44
+ def build_default_codex_launch_constants() -> CodexLaunchConfigurationConstants:
45
+ """Build immutable routed Codex provider configuration constants."""
46
+ return CodexLaunchConfigurationConstants(
47
+ runtime_provider_id=CODEX_RUNTIME_PROVIDER_ID,
48
+ runtime_api_key_env=CODEX_RUNTIME_API_KEY_ENV,
49
+ native_provider_id_env=CODEX_NATIVE_PROVIDER_ID_ENV,
50
+ routed_provider_id=CODEX_ROUTED_PROVIDER_ID,
51
+ alternate_screen_key=CODEX_TUI_ALTERNATE_SCREEN_KEY,
52
+ )
53
+
54
+
55
+ def build_default_codex_launch_policy(
56
+ has_option: Callable[..., bool],
57
+ ) -> CodexLaunchPolicyPorts:
58
+ """Build the standard Codex config-file and CLI projection policy."""
59
+ return CodexLaunchPolicyPorts(
60
+ has_option=has_option,
61
+ config_override_keys=codex_config_override_keys,
62
+ config_paths=codex_config_paths_for_launch,
63
+ alternate_screen_value=codex_alternate_screen_value_from_config_text,
64
+ toml_string=toml_string,
65
+ )
66
+
67
+
68
+ @dataclass(frozen=True, slots=True)
69
+ class CodexLaunchModelPorts:
70
+ current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
71
+ native_enabled: Callable[[str], bool]
72
+ current_alias: Callable[[dict[str, Any]], str]
73
+ context_limit: Callable[[str, dict[str, Any]], int | None]
74
+ context_capacity: Callable[[str, dict[str, Any]], int | None]
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class CodexLaunchCatalogPorts:
79
+ write: Callable[[str, CodexModelCatalogSpec, dict[str, str]], Path | None]
80
+ provider_label: Callable[[str], str]
81
+ path_value: Callable[[dict[str, str]], str]
82
+ current_model_args: Callable[..., list[str]]
83
+ native_routed_args: Callable[..., list[str]]
84
+
85
+
86
+ @dataclass(frozen=True, slots=True)
87
+ class CodexLaunchConfigurationEffects:
88
+ environ: Callable[[], Mapping[str, str]]
89
+ router_base: Callable[[], str]
90
+ read_text: Callable[[Path], str]
91
+ log: Callable[[str, str], None]
92
+ output: Callable[[str], None]
93
+
94
+
95
+ @dataclass(frozen=True, slots=True)
96
+ class CodexLaunchConfigurationService:
97
+ constants: CodexLaunchConfigurationConstants
98
+ policy: CodexLaunchPolicyPorts
99
+ model: CodexLaunchModelPorts
100
+ catalog: CodexLaunchCatalogPorts
101
+ effects: CodexLaunchConfigurationEffects
102
+
103
+ def alternate_screen_compat_args(
104
+ self,
105
+ passthrough: list[str],
106
+ env: dict[str, str] | None = None,
107
+ cwd: Path | None = None,
108
+ ) -> list[str]:
109
+ key = self.constants.alternate_screen_key
110
+ if self.policy.has_option(passthrough, "--no-alt-screen"):
111
+ return []
112
+ if key in self.policy.config_override_keys(passthrough):
113
+ return []
114
+ for path in self.policy.config_paths(passthrough, env=env, cwd=cwd):
115
+ try:
116
+ text = self.effects.read_text(path)
117
+ except Exception:
118
+ continue
119
+ value = self.policy.alternate_screen_value(text)
120
+ if value:
121
+ self.effects.log(
122
+ "WARN",
123
+ f"codex_compat_alternate_screen_override path={path} value={value}",
124
+ )
125
+ self.effects.output(
126
+ "Ciel Runtime warning: applying Codex config compatibility "
127
+ f'override {key}="{value}".'
128
+ )
129
+ return ["-c", f"{key}={self.policy.toml_string(value)}"]
130
+ return []
131
+
132
+ def runtime_config_args(self, router_base: str | None = None) -> list[str]:
133
+ provider = self.constants.runtime_provider_id
134
+ configured_base = (
135
+ self.effects.router_base() if router_base is None else router_base
136
+ )
137
+ base = configured_base.rstrip("/") + "/v1"
138
+ toml = self.policy.toml_string
139
+ return [
140
+ "-c",
141
+ f"model_provider={toml(provider)}",
142
+ "-c",
143
+ f"model_providers.{provider}.name={toml('Ciel Runtime')}",
144
+ "-c",
145
+ f"model_providers.{provider}.base_url={toml(base)}",
146
+ "-c",
147
+ f"model_providers.{provider}.wire_api={toml('responses')}",
148
+ "-c",
149
+ f"model_providers.{provider}.env_key={toml(self.constants.runtime_api_key_env)}",
150
+ "-c",
151
+ f"model_providers.{provider}.request_max_retries=0",
152
+ "-c",
153
+ f"model_providers.{provider}.stream_max_retries=0",
154
+ ]
155
+
156
+ def write_runtime_model_catalog(
157
+ self, codex: str, cfg: dict[str, Any]
158
+ ) -> Path | None:
159
+ provider, provider_config = self.model.current_provider(cfg)
160
+ if self.model.native_enabled(provider):
161
+ return None
162
+ alias = self.model.current_alias(cfg)
163
+ if not alias:
164
+ return None
165
+ context_window = (
166
+ self.model.context_limit(provider, provider_config)
167
+ or self.model.context_capacity(provider, provider_config)
168
+ or 272000
169
+ )
170
+ catalog_env = dict(self.effects.environ())
171
+ catalog_env["PATH"] = self.catalog.path_value(catalog_env)
172
+ configured_auto_compact = provider_config.get(
173
+ "codex_auto_compact_window"
174
+ )
175
+ try:
176
+ auto_compact_token_limit = int(configured_auto_compact)
177
+ except (TypeError, ValueError):
178
+ auto_compact_token_limit = None
179
+ if auto_compact_token_limit is not None and auto_compact_token_limit <= 0:
180
+ auto_compact_token_limit = None
181
+ return self.catalog.write(
182
+ codex,
183
+ CodexModelCatalogSpec(
184
+ alias=alias,
185
+ provider_label=self.catalog.provider_label(provider),
186
+ context_window=context_window,
187
+ effort=str(provider_config.get("effort_level") or "").strip().lower(),
188
+ auto_compact_token_limit=auto_compact_token_limit,
189
+ ),
190
+ catalog_env,
191
+ )
192
+
193
+ def runtime_model_catalog_args(
194
+ self, codex: str, cfg: dict[str, Any]
195
+ ) -> list[str]:
196
+ path = self.write_runtime_model_catalog(codex, cfg)
197
+ if path is None:
198
+ return []
199
+ value = self.policy.toml_string(str(path.resolve()))
200
+ return ["-c", f"model_catalog_json={value}"]
201
+
202
+ def native_routed_config_args(
203
+ self, router_base: str | None = None
204
+ ) -> list[str]:
205
+ env = self.effects.environ()
206
+ provider = str(env.get(self.constants.native_provider_id_env) or "").strip()
207
+ provider = provider or self.constants.routed_provider_id
208
+ configured_base = (
209
+ self.effects.router_base() if router_base is None else router_base
210
+ )
211
+ return self.catalog.native_routed_args(
212
+ configured_base,
213
+ provider,
214
+ toml_string=self.policy.toml_string,
215
+ )
216
+
217
+ def passthrough_has_model_override(self, passthrough: list[str]) -> bool:
218
+ return self.policy.has_option(
219
+ passthrough, "-m", "--model"
220
+ ) or "model" in self.policy.config_override_keys(passthrough)
221
+
222
+ def current_model_cli_args(
223
+ self, provider_config: dict[str, Any], passthrough: list[str]
224
+ ) -> list[str]:
225
+ return self.catalog.current_model_args(
226
+ provider_config,
227
+ passthrough,
228
+ overridden=self.passthrough_has_model_override,
229
+ )
230
+
231
+ def current_model_config_args(
232
+ self, provider_config: dict[str, Any], passthrough: list[str]
233
+ ) -> list[str]:
234
+ return self.catalog.current_model_args(
235
+ provider_config,
236
+ passthrough,
237
+ overridden=self.passthrough_has_model_override,
238
+ config_style=True,
239
+ toml_string=self.policy.toml_string,
240
+ )
@@ -0,0 +1,66 @@
1
+ """Pure Codex command-line launch argument policies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable
6
+ from typing import Any
7
+
8
+
9
+ def native_routed_config_args(
10
+ router_base: str,
11
+ provider: str,
12
+ *,
13
+ toml_string: Callable[[str], str],
14
+ ) -> list[str]:
15
+ base = router_base.rstrip("/") + "/backend-api/codex"
16
+ if provider == "openai":
17
+ return ["-c", 'model_provider="openai"', "-c", f"openai_base_url={toml_string(base)}"]
18
+ return [
19
+ "-c",
20
+ f"model_provider={toml_string(provider)}",
21
+ "-c",
22
+ f"model_providers.{provider}.name={toml_string('Ciel Runtime Codex')}",
23
+ "-c",
24
+ f"model_providers.{provider}.base_url={toml_string(base)}",
25
+ "-c",
26
+ f"model_providers.{provider}.wire_api={toml_string('responses')}",
27
+ "-c",
28
+ f"model_providers.{provider}.requires_openai_auth=true",
29
+ "-c",
30
+ f"model_providers.{provider}.supports_websockets=false",
31
+ ]
32
+
33
+
34
+ def passthrough_has_model_override(
35
+ passthrough: list[str],
36
+ *,
37
+ has_option: Callable[..., bool],
38
+ config_override_keys: Callable[[list[str]], set[str]],
39
+ ) -> bool:
40
+ return has_option(passthrough, "-m", "--model") or "model" in config_override_keys(
41
+ passthrough
42
+ )
43
+
44
+
45
+ def current_model_args(
46
+ config: dict[str, Any],
47
+ passthrough: list[str],
48
+ *,
49
+ overridden: Callable[[list[str]], bool],
50
+ config_style: bool = False,
51
+ toml_string: Callable[[str], str] = repr,
52
+ ) -> list[str]:
53
+ model = str(config.get("current_model") or "").strip()
54
+ if not model or overridden(passthrough):
55
+ return []
56
+ return ["-c", f"model={toml_string(model)}"] if config_style else ["-m", model]
57
+
58
+
59
+ def help_requested(passthrough: list[str]) -> bool:
60
+ return any(argument in ("help", "--help", "-h") for argument in passthrough)
61
+
62
+
63
+ def yolo_launch_args(
64
+ passthrough: list[str], *, has_option: Callable[..., bool]
65
+ ) -> list[str]:
66
+ return [] if has_option(passthrough, "--yolo") else ["--yolo"]