@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,189 @@
1
+ """Router web UI projection and GET application controller."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Any, Callable
8
+
9
+
10
+ RuntimeConfig = dict[str, Any]
11
+ ProviderConfig = dict[str, Any]
12
+
13
+
14
+ @dataclass(frozen=True, slots=True)
15
+ class WebUiConstants:
16
+ version: str
17
+ activity_path: Path
18
+ context_usage_path: Path
19
+ default_timeout_ms: int
20
+
21
+
22
+ @dataclass(frozen=True, slots=True)
23
+ class WebUiProjectionPorts:
24
+ current_alias: Callable[[RuntimeConfig], str]
25
+ read_json: Callable[[Path], dict[str, Any]]
26
+ rate_limit_usage: Callable[
27
+ [str, ProviderConfig],
28
+ tuple[int, int | None],
29
+ ]
30
+ positive_int: Callable[[Any], int | None]
31
+ idle_timeout_ms: Callable[[int], int]
32
+ context_limit: Callable[[str, ProviderConfig], int | None]
33
+
34
+
35
+ @dataclass(frozen=True, slots=True)
36
+ class WebUiDisplayPorts:
37
+ render_home: Callable[..., str]
38
+ render_chat: Callable[..., str]
39
+ provider_mode: Callable[[str, ProviderConfig], str]
40
+ api_key_status: Callable[[str, ProviderConfig], str]
41
+
42
+
43
+ @dataclass(frozen=True, slots=True)
44
+ class WebUiHttpPorts:
45
+ load_config: Callable[[], RuntimeConfig]
46
+ current_provider: Callable[
47
+ [RuntimeConfig],
48
+ tuple[str, ProviderConfig],
49
+ ]
50
+ write_text: Callable[..., None]
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class WebUiController:
55
+ constants: WebUiConstants
56
+ projection: WebUiProjectionPorts
57
+ display: WebUiDisplayPorts
58
+ http: WebUiHttpPorts
59
+
60
+ def render_router_home(
61
+ self,
62
+ config: RuntimeConfig,
63
+ provider: str,
64
+ provider_config: ProviderConfig,
65
+ ) -> str:
66
+ upstream = self.projection.read_json(
67
+ self.constants.activity_path
68
+ )
69
+ context = self.projection.read_json(
70
+ self.constants.context_usage_path
71
+ )
72
+ used, rpm_limit = self.projection.rate_limit_usage(
73
+ provider,
74
+ provider_config,
75
+ )
76
+ rpm_text = self._rpm_text(provider_config, used, rpm_limit)
77
+ timeout_ms = (
78
+ self.projection.positive_int(
79
+ provider_config.get("request_timeout_ms")
80
+ )
81
+ or self.constants.default_timeout_ms
82
+ )
83
+ idle_ms = (
84
+ self.projection.positive_int(
85
+ provider_config.get("stream_idle_timeout_ms")
86
+ )
87
+ or self.projection.idle_timeout_ms(timeout_ms)
88
+ )
89
+ context_limit = self.projection.context_limit(
90
+ provider,
91
+ provider_config,
92
+ )
93
+ context_text = self._context_text(context, context_limit)
94
+ upstream_text = " · ".join(
95
+ value
96
+ for value in (
97
+ str(upstream.get("event") or "idle"),
98
+ str(upstream.get("provider") or provider),
99
+ str(
100
+ upstream.get("model")
101
+ or provider_config.get("current_model")
102
+ or ""
103
+ ),
104
+ )
105
+ if value
106
+ )
107
+ return self.display.render_home(
108
+ version=self.constants.version,
109
+ provider=provider,
110
+ model=self.projection.current_alias(config),
111
+ context_text=context_text,
112
+ timeout_ms=timeout_ms,
113
+ idle_ms=idle_ms,
114
+ rpm_text=rpm_text,
115
+ upstream_text=upstream_text,
116
+ )
117
+
118
+ def render_web_chat(
119
+ self,
120
+ config: RuntimeConfig,
121
+ provider: str,
122
+ provider_config: ProviderConfig,
123
+ ) -> str:
124
+ timeout_ms = (
125
+ self.projection.positive_int(
126
+ provider_config.get("request_timeout_ms")
127
+ )
128
+ or self.constants.default_timeout_ms
129
+ )
130
+ return self.display.render_chat(
131
+ model=self.projection.current_alias(config),
132
+ provider=provider,
133
+ mode=self.display.provider_mode(provider, provider_config),
134
+ api_status=self.display.api_key_status(
135
+ provider,
136
+ provider_config,
137
+ ),
138
+ timeout_ms=timeout_ms,
139
+ )
140
+
141
+ def handle_get(self, handler: Any, path: str) -> bool:
142
+ if path not in {"/ca/web/chat", "/ca/web/chat/"}:
143
+ return False
144
+ config = self.http.load_config()
145
+ provider, provider_config = self.http.current_provider(config)
146
+ self.http.write_text(
147
+ handler,
148
+ self.render_web_chat(
149
+ config,
150
+ provider,
151
+ provider_config,
152
+ ),
153
+ content_type="text/html; charset=utf-8",
154
+ )
155
+ return True
156
+
157
+ @staticmethod
158
+ def _rpm_text(
159
+ provider_config: ProviderConfig,
160
+ used: int,
161
+ limit: int | None,
162
+ ) -> str:
163
+ if not bool(provider_config.get("rate_limit_status", False)):
164
+ return "off"
165
+ if limit == 0:
166
+ return f"{used}/min unmanaged"
167
+ return f"{used}/{limit}" if limit else "unknown"
168
+
169
+ def _context_text(
170
+ self,
171
+ context: dict[str, Any],
172
+ limit: int | None,
173
+ ) -> str:
174
+ tokens = self.projection.positive_int(context.get("tokens"))
175
+ percent = context.get("percent")
176
+ if isinstance(percent, (int, float)):
177
+ return f"{tokens or 0:,}/{limit or 0:,} tok ({percent}%)"
178
+ if limit:
179
+ return f"{tokens or 0:,}/{limit:,} tok"
180
+ return "unknown"
181
+
182
+
183
+ __all__ = [
184
+ "WebUiConstants",
185
+ "WebUiController",
186
+ "WebUiDisplayPorts",
187
+ "WebUiHttpPorts",
188
+ "WebUiProjectionPorts",
189
+ ]
@@ -0,0 +1,137 @@
1
+ """Windows console input queue adapter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import time
6
+ from typing import Any, Callable, Iterable
7
+
8
+
9
+ def _windows_console_utf16_units(chars: Iterable[str]) -> list[str]:
10
+ """Expand non-BMP code points into WCHAR-sized UTF-16 surrogate units."""
11
+ units: list[str] = []
12
+ for char in chars:
13
+ codepoint = ord(char)
14
+ if codepoint > 0xFFFF:
15
+ codepoint -= 0x10000
16
+ units.extend((chr(0xD800 + (codepoint >> 10)), chr(0xDC00 + (codepoint & 0x3FF))))
17
+ else:
18
+ units.append(char)
19
+ return units
20
+
21
+
22
+ class WindowsConsoleInputWriter:
23
+ """Inject keystrokes into the active Windows console input queue."""
24
+
25
+ def __init__(self, input_handle: Callable[[], Any], mouse_filter_factory: Callable[[], Any]) -> None:
26
+ self.handle = input_handle()
27
+ if self.handle is None:
28
+ raise RuntimeError("Windows console input handle is not available")
29
+ self._mouse_filter = mouse_filter_factory()
30
+ self._queue_baseline: int | None = None
31
+
32
+ def wait_until_input_consumed(self, timeout_seconds: float = 2.0) -> bool:
33
+ """Wait until the most recently written console-input batch leaves the queue."""
34
+ if self._queue_baseline is None:
35
+ return True
36
+ import ctypes
37
+ from ctypes import wintypes
38
+
39
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
40
+ kernel32.GetNumberOfConsoleInputEvents.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)]
41
+ kernel32.GetNumberOfConsoleInputEvents.restype = wintypes.BOOL
42
+ deadline = time.monotonic() + max(0.0, timeout_seconds)
43
+ while True:
44
+ pending = wintypes.DWORD(0)
45
+ if not kernel32.GetNumberOfConsoleInputEvents(self.handle, ctypes.byref(pending)):
46
+ return False
47
+ if int(pending.value) <= self._queue_baseline:
48
+ return True
49
+ if time.monotonic() >= deadline:
50
+ return False
51
+ time.sleep(0.01)
52
+
53
+ def write(self, data: bytes) -> None:
54
+ if not data:
55
+ return
56
+ data = self._mouse_filter.feed(data)
57
+ if not data:
58
+ return
59
+ text = data.decode("utf-8", errors="replace")
60
+ chars: list[str] = []
61
+ index = 0
62
+ while index < len(text):
63
+ char = text[index]
64
+ if char == "\r" and index + 1 < len(text) and text[index + 1] == "\n":
65
+ chars.append("\r")
66
+ index += 2
67
+ continue
68
+ chars.append("\r" if char == "\n" else char)
69
+ index += 1
70
+ self._write_chars(chars)
71
+
72
+ def _write_chars(self, chars: list[str]) -> None:
73
+ import ctypes
74
+ from ctypes import wintypes
75
+
76
+ if not chars:
77
+ return
78
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
79
+ user32 = ctypes.WinDLL("user32", use_last_error=True)
80
+
81
+ class KEY_EVENT_RECORD(ctypes.Structure):
82
+ _fields_ = [
83
+ ("bKeyDown", wintypes.BOOL),
84
+ ("wRepeatCount", wintypes.WORD),
85
+ ("wVirtualKeyCode", wintypes.WORD),
86
+ ("wVirtualScanCode", wintypes.WORD),
87
+ ("uChar", wintypes.WCHAR),
88
+ ("dwControlKeyState", wintypes.DWORD),
89
+ ]
90
+
91
+ class EVENT_UNION(ctypes.Union):
92
+ _fields_ = [("KeyEvent", KEY_EVENT_RECORD)]
93
+
94
+ class INPUT_RECORD(ctypes.Structure):
95
+ _fields_ = [("EventType", wintypes.WORD), ("Event", EVENT_UNION)]
96
+
97
+ user32.MapVirtualKeyW.argtypes = [wintypes.UINT, wintypes.UINT]
98
+ user32.MapVirtualKeyW.restype = wintypes.UINT
99
+ kernel32.WriteConsoleInputW.argtypes = [
100
+ wintypes.HANDLE,
101
+ ctypes.POINTER(INPUT_RECORD),
102
+ wintypes.DWORD,
103
+ ctypes.POINTER(wintypes.DWORD),
104
+ ]
105
+ kernel32.WriteConsoleInputW.restype = wintypes.BOOL
106
+ kernel32.GetNumberOfConsoleInputEvents.argtypes = [wintypes.HANDLE, ctypes.POINTER(wintypes.DWORD)]
107
+ kernel32.GetNumberOfConsoleInputEvents.restype = wintypes.BOOL
108
+ key_event = 0x0001
109
+ left_ctrl_pressed = 0x0008
110
+ map_vk_to_vsc = 0
111
+ vk_map = {"\r": 0x0D, "\x1b": 0x1B, "\x08": 0x08, "\t": 0x09, "\x15": 0x55}
112
+ records: list[Any] = []
113
+ for char in _windows_console_utf16_units(chars):
114
+ virtual_key = vk_map.get(char)
115
+ if virtual_key is None and len(char) == 1 and "A" <= char.upper() <= "Z":
116
+ virtual_key = ord(char.upper())
117
+ elif virtual_key is None:
118
+ virtual_key = 0
119
+ control = left_ctrl_pressed if char == "\x15" else 0
120
+ scan_code = int(user32.MapVirtualKeyW(int(virtual_key), map_vk_to_vsc)) if virtual_key else 0
121
+ for key_down in (True, False):
122
+ record = INPUT_RECORD()
123
+ record.EventType = key_event
124
+ record.Event.KeyEvent = KEY_EVENT_RECORD(
125
+ bool(key_down), 1, int(virtual_key), scan_code, char, control
126
+ )
127
+ records.append(record)
128
+ array_type = INPUT_RECORD * len(records)
129
+ written = wintypes.DWORD(0)
130
+ pending_before = wintypes.DWORD(0)
131
+ if kernel32.GetNumberOfConsoleInputEvents(self.handle, ctypes.byref(pending_before)):
132
+ self._queue_baseline = int(pending_before.value)
133
+ else:
134
+ self._queue_baseline = None
135
+ ok = kernel32.WriteConsoleInputW(self.handle, array_type(*records), len(records), ctypes.byref(written))
136
+ if not ok or int(written.value) != len(records):
137
+ raise OSError(f"WriteConsoleInputW failed: written={int(written.value)} expected={len(records)}")
@@ -0,0 +1,112 @@
1
+ """Windows Console mode adapter and mouse-input lifecycle guard."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Callable, Mapping
6
+ from dataclasses import dataclass
7
+ from typing import Any
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class WindowsConsoleModePorts:
12
+ input_handle: Callable[[], Any]
13
+ parse_bool: Callable[..., bool]
14
+ environment: Mapping[str, str]
15
+
16
+
17
+ @dataclass(frozen=True, slots=True)
18
+ class WindowsConsoleModeService:
19
+ ports: WindowsConsoleModePorts
20
+
21
+ def input_supported(self) -> bool:
22
+ return self.current() is not None
23
+
24
+ def mouse_filter_enabled(self) -> bool:
25
+ return self.ports.parse_bool(
26
+ self.ports.environment.get(
27
+ "CIEL_RUNTIME_WINDOWS_CONSOLE_MOUSE_FILTER"
28
+ ),
29
+ True,
30
+ )
31
+
32
+ def current(self) -> int | None:
33
+ handle = self.ports.input_handle()
34
+ if handle is None:
35
+ return None
36
+ try:
37
+ import ctypes
38
+ from ctypes import wintypes
39
+
40
+ mode = wintypes.DWORD(0)
41
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
42
+ kernel32.GetConsoleMode.argtypes = [
43
+ wintypes.HANDLE,
44
+ ctypes.POINTER(wintypes.DWORD),
45
+ ]
46
+ kernel32.GetConsoleMode.restype = wintypes.BOOL
47
+ if not kernel32.GetConsoleMode(handle, ctypes.byref(mode)):
48
+ return None
49
+ return int(mode.value)
50
+ except Exception:
51
+ return None
52
+
53
+ def set(self, mode: int) -> bool:
54
+ handle = self.ports.input_handle()
55
+ if handle is None:
56
+ return False
57
+ try:
58
+ import ctypes
59
+ from ctypes import wintypes
60
+
61
+ kernel32 = ctypes.WinDLL("kernel32", use_last_error=True)
62
+ kernel32.SetConsoleMode.argtypes = [
63
+ wintypes.HANDLE,
64
+ wintypes.DWORD,
65
+ ]
66
+ kernel32.SetConsoleMode.restype = wintypes.BOOL
67
+ return bool(
68
+ kernel32.SetConsoleMode(handle, wintypes.DWORD(int(mode)))
69
+ )
70
+ except Exception:
71
+ return False
72
+
73
+
74
+ class WindowsConsoleMouseInputGuard:
75
+ ENABLE_MOUSE_INPUT = 0x0010
76
+
77
+ def __init__(
78
+ self,
79
+ *,
80
+ platform_name: str,
81
+ filter_enabled: Callable[[], bool],
82
+ current_mode: Callable[[], int | None],
83
+ set_mode: Callable[[int], bool],
84
+ log: Callable[[str, str], None],
85
+ ) -> None:
86
+ self._platform_name = platform_name
87
+ self._filter_enabled = filter_enabled
88
+ self._current_mode = current_mode
89
+ self._set_mode = set_mode
90
+ self._log = log
91
+ self.original_mode: int | None = None
92
+
93
+ def apply(self) -> None:
94
+ if self._platform_name != "nt" or not self._filter_enabled():
95
+ return
96
+ current = self._current_mode()
97
+ if current is None:
98
+ return
99
+ if self.original_mode is None:
100
+ self.original_mode = current
101
+ filtered = current & ~self.ENABLE_MOUSE_INPUT
102
+ if filtered != current and self._set_mode(filtered):
103
+ self._log(
104
+ "INFO",
105
+ "windows_console_mouse_input_disabled "
106
+ f"mode={current:#x}->{filtered:#x}",
107
+ )
108
+
109
+ def restore(self) -> None:
110
+ if self._platform_name != "nt" or self.original_mode is None:
111
+ return
112
+ self._set_mode(self.original_mode)
@@ -14,6 +14,11 @@
14
14
  | **Protocol Adapter** | 요청/응답 Wire 형식 변환 | — |
15
15
  | **Tool Dialect** | 런타임별 툴 이름 및 수정 | — |
16
16
  | **Runtime Router** | 런타임별 HTTP 경로와 라우터 기능 소유 | 다른 런타임의 경로 직접 처리 금지 |
17
+ | **Channel Injection** | 런타임 입력 정책과 터미널 전송 조정 | SSE/MCP 수집, cursor 저장, subprocess 생명주기 금지 |
18
+ | **Credential Source** | API key, inbound OAuth, 향후 계정 저장소의 인증 해석 | provider wire 변환 및 retry 금지 |
19
+ | **Prompt Strategy** | protocol별 system context 배치와 원본 불변성 보장 | provider 이름 분기 금지 |
20
+ | **Fallback Policy** | 실패 원인별 provider/model 후보 계획 | HTTP 실행 및 credential 접근 금지 |
21
+ | **Usage Sink** | provider 중립 token usage event 저장·집계 | prompt/credential 저장 금지 |
17
22
 
18
23
  ---
19
24
 
@@ -111,6 +116,13 @@ class RateLimitState:
111
116
  - `default_base_url()` → `str`
112
117
  - `list_models(config: ProviderConfig)` → `list[ModelInfo]`
113
118
  - `build_headers(config, api_key)` → `dict[str, str]`
119
+ - `capabilities(config)` → upstream protocol 및 tools/thinking/local 특성
120
+ - `supported_protocols(config, model)` → 모델별 지원 protocol 집합
121
+ - `select_protocol(operation, config, model)` → 요청 목적별 upstream protocol 선택
122
+ - `request_policy(config)` → chat/models/model-info endpoint와 timeout 기본값
123
+ - `resolve_endpoint(operation, config)` → provider-owned endpoint 선택
124
+ - `model_paths(config)` → provider별 모델 discovery fallback 순서
125
+ - `model_catalog_policy(config)` → configured/Anthropic/OpenAI/Ollama/LM Studio/NVIDIA/Fireworks catalog 전략 선택
114
126
 
115
127
  ### `ToolDialect` (ABC)
116
128
  런타임별 툴 이름 정규화 인터페이스.
@@ -136,6 +148,21 @@ class RateLimitState:
136
148
 
137
149
  `tests/test_runtime_routers.py`가 Claude/Codex 라우터가 공통 기능을 모두 제공하는지와 경로 소유권이 섞이지 않는지 확인한다.
138
150
 
151
+ ## Channel Injection 구조
152
+
153
+ `ciel_runtime_support/channel_injection.py`는 외부 메시지를 대화 입력으로 제출하는 마지막 단계만 담당한다.
154
+
155
+ | 구성요소 | 패턴 | 책임 |
156
+ |----------|------|------|
157
+ | `InputTransport` | Port / Adapter | PTY 또는 Windows Console 입력 전송 계약 |
158
+ | `CallableInputTransport` | Adapter | 기존 descriptor/writer를 전송 계약에 연결 |
159
+ | `RuntimeInjectionPolicy` | Strategy value | clear, paste, Enter, 지연, 재시도 등 런타임 의미 |
160
+ | `PromptInjection` | Command | 변경 불가능한 한 번의 주입 요청 |
161
+ | `ChannelPromptInjector` | Application Service | 정책과 transport를 조정하며 제출 순서를 보장 |
162
+
163
+ 의존성 방향은 `channel source -> delivery coordinator -> prompt injector -> input transport`이다.
164
+ `ChannelPromptInjector`는 SSE, MCP, cursor, transcript 또는 subprocess를 참조하지 않는다. 플랫폼별 transport도 메시지 의미를 해석하지 않는다. 이 경계를 통해 Claude, Codex, AGY 정책과 Windows/PTY 구현을 독립적으로 테스트하고 교체한다.
165
+
139
166
  ---
140
167
 
141
168
  ## LaunchMode 흐름
@@ -152,3 +179,30 @@ class RateLimitState:
152
179
  - [[Providers]] — 제공자 목록
153
180
  - [[Router]] — HTTP 라우터
154
181
  - [[Configuration]] — 설정 시스템
182
+
183
+ ## 운영 경로 적용
184
+
185
+ 아키텍처 계약은 테스트용 모델에 머무르지 않고 운영 경로의 composition root에서 실제로 적용된다.
186
+
187
+ - `ciel_runtime_support/provider_adapters.py`는 전용 Adapter와 선언형 hosted-provider factory를 하나의 Descriptor Registry에 등록한다. 표준 OpenAI Chat 제공자는 `providers/catalog.py`, Anthropic Messages 호환 제공자는 `providers/anthropic_catalog.py`, 동적 cloud endpoint는 `providers/cloud.py`가 소유한다. `HttpBearerProviderAdapter`는 공통 인증 기반일 뿐 Provider 선택 단위가 아니다.
188
+ - `provider_descriptor.py`는 provider 이름·표시명·별칭·구체 Adapter 타입의 단일 등록 원천이다. 단순 provider는 descriptor 데이터만 추가하고, 복잡한 protocol/OAuth 동작은 계속 전용 Adapter에 둔다.
189
+ - `prompt_injection.py`는 Anthropic Messages, OpenAI Chat/Responses, Ollama Chat, Google Generative의 system prompt 배치를 Strategy로 분리한다. Anthropic의 첫 identity/cache block 순서는 변경하지 않는다.
190
+ - `credentials.py`는 API key와 inbound OAuth header를 Chain of Responsibility로 해석한다. allowlist 밖의 cookie/secret header는 전달하지 않으며 OAuth token 발급·갱신은 향후 별도 infrastructure adapter가 구현한다.
191
+ - `routing_fallback.py`는 rate limit, unavailable, timeout, model error별 immutable 후보 계획만 담당한다. 기존 `upstream_retry.py`와 API-key cooldown이 transport 실행을 계속 소유하므로 정책과 재시도 효과가 섞이지 않는다.
192
+ - `usage_events.py`는 token usage를 prompt나 key 없이 JSONL로 기록하는 Port/Adapter다. UI 또는 특정 database에 결합하지 않아 analytics backend를 교체할 수 있다.
193
+ - wire profile, thinking passback 및 tool-choice 정책은 Provider 이름 조건문 대신 구체 Adapter의 protocol/capability 메서드를 사용한다. Kimi·Fireworks의 dual protocol과 OpenCode의 모델별 endpoint도 Adapter가 선택한다.
194
+ - 모델 조회 Application Service는 Provider 이름을 분기하지 않고 Adapter의 `ProviderModelCatalogPolicy`와 `model_paths()`를 사용한다. 새 Provider는 기존 catalog 전략을 조합하면 모델 서비스 수정 없이 등록할 수 있다.
195
+ - `provider_policy.py`도 Provider 이름을 분기하지 않는다. 모델별 protocol, request option, tool-choice 및 thinking 보존은 구체 Adapter 메서드로 선택한다.
196
+ - `ciel_runtime_support/runtime_adapters.py`의 `CliRuntimeAdapter`가 Claude, Codex, AGY의 정규화된 `LaunchSpec`을 `RuntimeCommand`로 변환한다.
197
+ - `ciel_runtime_support/protocols/openai_responses.py`는 설정이나 네트워크에 의존하지 않고 OpenAI Responses와 Anthropic Messages 사이를 변환한다.
198
+ - `ciel_runtime_support/registry.py`는 provider, runtime, protocol, tool dialect 구현을 조건문 대신 이름 기반 factory로 선택한다.
199
+ - `ciel_runtime_support/tool_dialects.py`는 Claude 도구 이름과 MCP 서버 이름 변형을 `ToolDialect` 구현으로 정규화한다.
200
+ - `ciel_runtime_support/provider_limits.py`, `provider_models.py`, `provider_policy.py`는 키 순환·rate limit·모델 카탈로그·wire 정규화를 Provider 계층에 둔다.
201
+ - `ciel_runtime_support/runtime_launch.py`는 네 런타임의 프로세스 및 라우터·채널 수명주기를 조정하고, `runtime_adapters.py`가 최종 CLI 문법을 소유한다.
202
+ - `ciel_runtime_support/streaming_anthropic.py`는 Anthropic/Ollama/OpenAI 스트림 변환을 명시적 dependency object로 실행한다.
203
+ - `ciel_runtime_support/config_repository.py`, `cli_dispatch.py`, `prelaunch.py`, `web_ui.py`는 각각 Repository, Application Service, UI Controller, Presentation 경계를 형성한다.
204
+ - `ciel_runtime_support/tool_schema.py`는 도구 schema registry와 입력 보정을, `tool_dialects.py`는 런타임별 이름 dialect를 분리해 담당한다.
205
+
206
+ `ciel_runtime.py`는 기존 공개 함수 호환성을 유지하는 composition root다. 각 래퍼는 호출 시점의 의존성을 typed service object로 조립해 하위 모듈에 위임하므로 테스트 patch 호환성을 유지하면서 숨은 전역 service locator를 만들지 않는다. 지원 모듈은 composition root를 역참조하지 않으며, 이 의존성 방향은 아키텍처 계약 테스트가 검증한다.
207
+
208
+ 외부 gateway 프로젝트에서 검증된 data-driven provider registry, multi-account fallback 개념, protocol별 prompt injection, usage analytics는 위 경계로 흡수한다. 반면 client fingerprint 위장, subscription token 추출, 기본 활성 lossy prompt compression처럼 보안·호환성 위험이 큰 기법은 architecture capability로 취급하지 않는다.
@@ -23,10 +23,12 @@
23
23
  | `router.log` | 라우터 로그 |
24
24
  | `log-level` | 로그 레벨 파일 |
25
25
  | `router.pid` | 라우터 프로세스 PID |
26
+ | `router-external-token` | 외부 디버그 접근용 Bearer 토큰 (`0600`) |
26
27
  | `router-activity.json` | 라우터 활동 상태 |
27
28
  | `rate-limit-state.json` | 레이트 리밋 상태 |
28
29
  | `requests.jsonl` | 요청 덤프 (디버그용, 최대 5MB) |
29
30
  | `responses.jsonl` | 응답 덤프 (디버그용, 최대 5MB) |
31
+ | `usage-events.jsonl` | provider/model별 token usage event (10MB에서 1세대 회전) |
30
32
  | `router-sse-trace.jsonl` | SSE 트레이스 (최대 2MB) |
31
33
  | `model-list-cache.json` | 모델 목록 캐시 (TTL: 300초) |
32
34
  | `model-registry.json` | 모델 레지스트리 |
@@ -78,10 +80,12 @@
78
80
  | `CIEL_RUNTIME_CONFIG_DIR` | 설정 디렉터리 재정의 |
79
81
  | `CIEL_RUNTIME_ROUTER_PORT` | 라우터 포트 재정의 |
80
82
  | `CIEL_RUNTIME_ROUTER_CLIENT_HOST` | 라우터 클라이언트 바인드 호스트 |
83
+ | `CIEL_RUNTIME_ROUTER_EXTERNAL_TOKEN` | 외부 디버그 접근용 Bearer 토큰 재정의 |
81
84
  | `CIEL_RUNTIME_UPSTREAM_USER_AGENT` | 업스트림 User-Agent (기본: `claude-cli`) |
82
85
  | `CIEL_RUNTIME_EVENT_LOG` | 이벤트 로그 활성화 (기본: `true`) |
83
86
  | `CIEL_RUNTIME_EVENT_LEVEL` | 이벤트 로그 레벨 (기본: `info`) |
84
87
  | `CIEL_RUNTIME_EVENT_BUFFER` | 이벤트 버퍼 크기 (기본: `1000`) |
88
+ | `CIEL_RUNTIME_USAGE_LOG` | token usage JSONL 기록 활성화 (기본: `true`) |
85
89
  | `CIEL_RUNTIME_THINKING_PASSBACK_MAX` | Thinking 패스백 최대 토큰 (기본: `4096`) |
86
90
  | `CIEL_RUNTIME_PYTHON` | 사용할 Python 실행 파일 경로 |
87
91
  | `CIEL_RUNTIME_SKIP_POSTINSTALL_STOP` | npm 설치 후 stop 건너뜀 |
@@ -121,6 +125,19 @@ ciel-runtimectl log-level DEBUG
121
125
  파일 `log-level`에 레벨 문자열을 저장하거나,
122
126
  `config.json`의 `"log_level"` 키로도 설정 가능.
123
127
 
128
+ ## 외부 라우터 접근
129
+
130
+ 라우터는 기본적으로 `127.0.0.1`에만 바인딩된다. 외부 디버그 접근을 활성화하면
131
+ `router-external-token` 파일에 제한된 권한으로 토큰을 생성한다. 외부 클라이언트는
132
+ 모든 요청에 다음 헤더를 보내야 한다.
133
+
134
+ ```http
135
+ Authorization: Bearer <token>
136
+ ```
137
+
138
+ `CIEL_RUNTIME_ROUTER_EXTERNAL_TOKEN` 환경 변수로 파일 토큰을 재정의할 수 있다.
139
+ 루프백 요청은 기존 로컬 CLI 호환성을 위해 토큰 없이 허용된다.
140
+
124
141
  ---
125
142
 
126
143
  ## 언어 설정