@oneciel-ai/ciel-runtime 0.2.2 → 0.2.3

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 (126) hide show
  1. package/ciel_runtime.py +2553 -9635
  2. package/ciel_runtime_support/advisor_request_builder.py +8 -21
  3. package/ciel_runtime_support/anthropic_tool_turns.py +13 -8
  4. package/ciel_runtime_support/architecture.py +68 -0
  5. package/ciel_runtime_support/architecture_budget.py +1 -1
  6. package/ciel_runtime_support/channel_connection_context.py +233 -0
  7. package/ciel_runtime_support/channel_delivery_context.py +332 -0
  8. package/ciel_runtime_support/channel_mcp_context.py +313 -0
  9. package/ciel_runtime_support/channel_mcp_discovery.py +47 -0
  10. package/ciel_runtime_support/channel_mcp_transport.py +5 -1
  11. package/ciel_runtime_support/channel_message_context.py +212 -0
  12. package/ciel_runtime_support/channel_message_repository.py +14 -3
  13. package/ciel_runtime_support/channel_pending_injection.py +9 -0
  14. package/ciel_runtime_support/channel_probe_launch_context.py +213 -0
  15. package/ciel_runtime_support/channel_replay_policy.py +38 -0
  16. package/ciel_runtime_support/channel_runtime_environment.py +8 -0
  17. package/ciel_runtime_support/channel_session_context.py +236 -0
  18. package/ciel_runtime_support/channel_terminal_context.py +350 -0
  19. package/ciel_runtime_support/channel_wake_context.py +532 -0
  20. package/ciel_runtime_support/claude_environment.py +60 -0
  21. package/ciel_runtime_support/claude_launch_assembly.py +249 -0
  22. package/ciel_runtime_support/claude_router.py +62 -12
  23. package/ciel_runtime_support/cli_application_context.py +132 -0
  24. package/ciel_runtime_support/cli_assembly.py +50 -0
  25. package/ciel_runtime_support/codex_backend_context.py +363 -0
  26. package/ciel_runtime_support/codex_config.py +13 -1
  27. package/ciel_runtime_support/codex_launch_assembly.py +213 -0
  28. package/ciel_runtime_support/codex_launch_configuration.py +30 -1
  29. package/ciel_runtime_support/codex_mcp_integration.py +90 -8
  30. package/ciel_runtime_support/codex_model_catalog.py +4 -1
  31. package/ciel_runtime_support/codex_reasoning_rejects.py +225 -0
  32. package/ciel_runtime_support/codex_router.py +38 -8
  33. package/ciel_runtime_support/codex_turn_recovery.py +154 -0
  34. package/ciel_runtime_support/config_migrations.py +103 -0
  35. package/ciel_runtime_support/configuration_cli.py +38 -0
  36. package/ciel_runtime_support/context_compaction.py +9 -4
  37. package/ciel_runtime_support/credential_management.py +12 -0
  38. package/ciel_runtime_support/credentials.py +12 -0
  39. package/ciel_runtime_support/github_copilot_oauth.py +2 -2
  40. package/ciel_runtime_support/hosted_formula_tools.py +216 -0
  41. package/ciel_runtime_support/kimi_runtime_context.py +208 -0
  42. package/ciel_runtime_support/llm_preset_context.py +338 -0
  43. package/ciel_runtime_support/managed_mcp_config.py +8 -4
  44. package/ciel_runtime_support/mcp_configuration_context.py +291 -0
  45. package/ciel_runtime_support/mcp_http_proxy.py +14 -8
  46. package/ciel_runtime_support/mcp_probe_transport.py +47 -15
  47. package/ciel_runtime_support/mcp_transport.py +14 -1
  48. package/ciel_runtime_support/native_context_recovery.py +72 -0
  49. package/ciel_runtime_support/ollama_catalog_context.py +213 -0
  50. package/ciel_runtime_support/ollama_stream_collection.py +103 -0
  51. package/ciel_runtime_support/ollama_thinking.py +6 -1
  52. package/ciel_runtime_support/ollama_wire_projection.py +157 -0
  53. package/ciel_runtime_support/openai_forwarding.py +32 -10
  54. package/ciel_runtime_support/openai_responses_router.py +12 -0
  55. package/ciel_runtime_support/package_lifecycle.py +39 -0
  56. package/ciel_runtime_support/prelaunch_assembly.py +37 -0
  57. package/ciel_runtime_support/prelaunch_panel_context.py +418 -0
  58. package/ciel_runtime_support/prelaunch_shell_context.py +394 -0
  59. package/ciel_runtime_support/prompt_compaction.py +144 -0
  60. package/ciel_runtime_support/prompt_injection.py +45 -0
  61. package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +1 -1
  62. package/ciel_runtime_support/protocols/chat_projection.py +85 -5
  63. package/ciel_runtime_support/protocols/conversation_turn_policy.py +43 -0
  64. package/ciel_runtime_support/protocols/ollama_chat.py +31 -0
  65. package/ciel_runtime_support/protocols/ollama_response.py +57 -5
  66. package/ciel_runtime_support/protocols/openai_reasoning.py +5 -2
  67. package/ciel_runtime_support/protocols/openai_responses.py +61 -15
  68. package/ciel_runtime_support/provider_adapters.py +26 -0
  69. package/ciel_runtime_support/provider_administration_context.py +207 -0
  70. package/ciel_runtime_support/provider_config_mutations.py +3 -0
  71. package/ciel_runtime_support/provider_model_catalog_context.py +137 -0
  72. package/ciel_runtime_support/provider_model_context.py +107 -0
  73. package/ciel_runtime_support/provider_model_metadata_context.py +197 -0
  74. package/ciel_runtime_support/provider_model_selection.py +10 -3
  75. package/ciel_runtime_support/provider_models.py +45 -2
  76. package/ciel_runtime_support/provider_option_cli.py +19 -0
  77. package/ciel_runtime_support/provider_policy.py +1 -1
  78. package/ciel_runtime_support/provider_readiness_context.py +189 -0
  79. package/ciel_runtime_support/provider_request_builder.py +64 -28
  80. package/ciel_runtime_support/provider_responses_passthrough.py +21 -2
  81. package/ciel_runtime_support/provider_timeout_policy.py +54 -0
  82. package/ciel_runtime_support/provider_tool_policy.py +9 -1
  83. package/ciel_runtime_support/providers/__init__.py +6 -0
  84. package/ciel_runtime_support/providers/alibaba.py +634 -0
  85. package/ciel_runtime_support/providers/catalog.py +24 -16
  86. package/ciel_runtime_support/providers/deepseek.py +73 -0
  87. package/ciel_runtime_support/providers/github_copilot_oauth.py +22 -1
  88. package/ciel_runtime_support/providers/kimi.py +69 -9
  89. package/ciel_runtime_support/providers/ollama.py +8 -0
  90. package/ciel_runtime_support/providers/ollama_context.py +21 -2
  91. package/ciel_runtime_support/providers/vllm.py +7 -1
  92. package/ciel_runtime_support/response_collection.py +68 -18
  93. package/ciel_runtime_support/response_collection_context.py +391 -0
  94. package/ciel_runtime_support/response_stream_context.py +555 -0
  95. package/ciel_runtime_support/responses_input_compatibility.py +121 -0
  96. package/ciel_runtime_support/responses_usage_observer.py +83 -0
  97. package/ciel_runtime_support/router_client_lifecycle.py +1 -0
  98. package/ciel_runtime_support/router_http.py +239 -3
  99. package/ciel_runtime_support/router_observability_context.py +251 -0
  100. package/ciel_runtime_support/router_process_context.py +200 -0
  101. package/ciel_runtime_support/router_process_lifecycle.py +2 -0
  102. package/ciel_runtime_support/router_request_assembly.py +399 -0
  103. package/ciel_runtime_support/router_request_context.py +215 -0
  104. package/ciel_runtime_support/router_server_context.py +82 -0
  105. package/ciel_runtime_support/runaway_output_guard.py +488 -0
  106. package/ciel_runtime_support/runtime_asset_assembly.py +147 -0
  107. package/ciel_runtime_support/runtime_asset_context.py +297 -0
  108. package/ciel_runtime_support/runtime_constants.py +16 -1
  109. package/ciel_runtime_support/runtime_launch.py +9 -5
  110. package/ciel_runtime_support/runtime_launch_context.py +130 -0
  111. package/ciel_runtime_support/runtime_maintenance_assembly.py +60 -0
  112. package/ciel_runtime_support/runtime_maintenance_context.py +309 -0
  113. package/ciel_runtime_support/runtime_maintenance_services.py +265 -0
  114. package/ciel_runtime_support/runtime_paths.py +60 -40
  115. package/ciel_runtime_support/runtime_primitives.py +78 -0
  116. package/ciel_runtime_support/sse_stream_collection.py +236 -0
  117. package/ciel_runtime_support/statusline_script.py +57 -8
  118. package/ciel_runtime_support/streaming_anthropic.py +361 -24
  119. package/ciel_runtime_support/tool_schema.py +40 -2
  120. package/ciel_runtime_support/tool_side_effect_dedupe.py +117 -12
  121. package/ciel_runtime_support/upstream_dump.py +68 -0
  122. package/ciel_runtime_support/upstream_retry_context.py +259 -0
  123. package/ciel_runtime_support/workspace_router_selection.py +86 -0
  124. package/docs/Configuration.md +50 -0
  125. package/docs/Test-Suite.md +1 -0
  126. package/package.json +1 -1
@@ -0,0 +1,249 @@
1
+ """Assemble Claude Code launch services from bounded, typed dependency groups."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, Callable
7
+
8
+ from . import runtime_launch
9
+
10
+
11
+ Callback = Callable[..., Any]
12
+
13
+
14
+ @dataclass(frozen=True, slots=True)
15
+ class ClaudeLaunchProcessPorts:
16
+ log_command: Callback
17
+ call_capturing_stderr: Callback
18
+ env_bool: Callback
19
+ env_vars: Callback
20
+ file_size: Callback
21
+ runtime_path: Callback
22
+ print_exit_diagnostics: Callback
23
+ call_with_channel_wake: Callback
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class ClaudeLaunchInstallationPorts:
28
+ find_executable: Callback
29
+ install_commands: Callback
30
+ install_statusline: Callback
31
+ install_claude: Callback
32
+ install_tool_guard: Callback
33
+ disable_commands: Callback
34
+ readiness_errors: Callback
35
+ warn_multiple_installs: Callback
36
+
37
+
38
+ @dataclass(frozen=True, slots=True)
39
+ class ClaudeLaunchDispatchPorts:
40
+ launch_agy: Callback
41
+ launch_codex: Callback
42
+ launch_codex_app_server: Callback
43
+ materialize_command: Callback
44
+ run_runtime_update: Callback
45
+ run_claude_update: Callback
46
+ run_prelaunch_menu: Callback
47
+ launch_enabled: Callback
48
+
49
+
50
+ @dataclass(frozen=True, slots=True)
51
+ class ClaudeLaunchConfigPorts:
52
+ load_config: Callback
53
+ save_config: Callback
54
+ current_provider: Callback
55
+ ensure_current_model: Callback
56
+ ensure_model_cache: Callback
57
+ apply_endpoint_policy: Callback
58
+ provider_menu_label: Callback
59
+ launch_mode_name: Callback
60
+ current_launch_cwd_key: Callback
61
+
62
+
63
+ @dataclass(frozen=True, slots=True)
64
+ class ClaudeLaunchRoutingPorts:
65
+ routed_enabled: Callback
66
+ direct_native_enabled: Callback
67
+ cleanup_services: Callback
68
+ ensure_router_running: Callback
69
+ reset_zai_mcp: Callback
70
+ health_summary: Callback
71
+ log: Callback
72
+ start_router: Callback
73
+ run_with_router_lifetime: Callback
74
+ record_launch_state: Callback
75
+
76
+
77
+ @dataclass(frozen=True, slots=True)
78
+ class ClaudeLaunchPolicyPorts:
79
+ append_runtime_settings: Callback
80
+ supports_permission_mode: Callback
81
+ has_noninteractive_args: Callback
82
+ has_passthrough_option: Callback
83
+ should_append_compat_prompt: Callback
84
+ should_attach_web_search: Callback
85
+ should_disallow_server_web_tools: Callback
86
+ should_fork_native_session: Callback
87
+ should_insert_option_boundary: Callback
88
+ strip_mcp_config: Callback
89
+
90
+
91
+ @dataclass(frozen=True, slots=True)
92
+ class ClaudeLaunchDiscoveryPorts:
93
+ auto_import_channels: Callback
94
+ cached_capable_names: Callback
95
+ cached_source_paths: Callback
96
+ candidate_names: Callback
97
+ channel_specs: Callback
98
+ channels_requested: Callback
99
+ auth_available: Callback
100
+ ensure_probe_cache: Callback
101
+ external_server_names: Callback
102
+ read_probe_cache: Callback
103
+
104
+
105
+ @dataclass(frozen=True, slots=True)
106
+ class ClaudeLaunchDeliveryPorts:
107
+ auto_start_sse: Callback
108
+ channel_args: Callback
109
+ native_passthrough_requested: Callback
110
+ normalize_passthrough: Callback
111
+ should_start_sse: Callback
112
+ should_use_llm_delivery: Callback
113
+ should_use_stdin_proxy: Callback
114
+ should_use_native_bridge: Callback
115
+ write_channel_config: Callback
116
+
117
+
118
+ @dataclass(frozen=True, slots=True)
119
+ class ClaudeLaunchMcpConfigPorts:
120
+ write_duckduckgo: Callback
121
+ write_proxy: Callback
122
+ write_native_discovery: Callback
123
+ write_zai: Callback
124
+
125
+
126
+ @dataclass(frozen=True, slots=True)
127
+ class ClaudeLaunchAssembly:
128
+ process: ClaudeLaunchProcessPorts
129
+ installation: ClaudeLaunchInstallationPorts
130
+ dispatch: ClaudeLaunchDispatchPorts
131
+ config: ClaudeLaunchConfigPorts
132
+ routing: ClaudeLaunchRoutingPorts
133
+ policy: ClaudeLaunchPolicyPorts
134
+ discovery: ClaudeLaunchDiscoveryPorts
135
+ delivery: ClaudeLaunchDeliveryPorts
136
+ mcp_config: ClaudeLaunchMcpConfigPorts
137
+
138
+ def services(self) -> runtime_launch.ClaudeLaunchServices:
139
+ return runtime_launch.ClaudeLaunchServices(
140
+ constants=runtime_launch.build_default_claude_launch_constants(),
141
+ process=runtime_launch.ClaudeLaunchProcess(
142
+ _log_claude_command_for_diagnostics=self.process.log_command,
143
+ _subprocess_call_capturing_stderr=self.process.call_capturing_stderr,
144
+ env_bool=self.process.env_bool,
145
+ env_vars=self.process.env_vars,
146
+ file_size_or_zero=self.process.file_size,
147
+ path_with_ciel_runtime_user_dirs=self.process.runtime_path,
148
+ print_routed_claude_exit_diagnostics=self.process.print_exit_diagnostics,
149
+ subprocess_call_with_channel_wake_proxy=self.process.call_with_channel_wake,
150
+ ),
151
+ installation=runtime_launch.ClaudeLaunchInstallation(
152
+ find_executable=self.installation.find_executable,
153
+ install_ciel_runtime_slash_commands=self.installation.install_commands,
154
+ install_ciel_runtime_statusline=self.installation.install_statusline,
155
+ install_claude_code_if_missing=self.installation.install_claude,
156
+ install_tool_guard_hooks=self.installation.install_tool_guard,
157
+ disable_ciel_runtime_slash_commands_for_native=self.installation.disable_commands,
158
+ launch_readiness_errors=self.installation.readiness_errors,
159
+ warn_if_multiple_ciel_runtime_installs=self.installation.warn_multiple_installs,
160
+ ),
161
+ dispatch=runtime_launch.ClaudeLaunchDispatch(
162
+ launch_agy=self.dispatch.launch_agy,
163
+ launch_codex=self.dispatch.launch_codex,
164
+ launch_codex_app_server=self.dispatch.launch_codex_app_server,
165
+ materialize_runtime_command=self.dispatch.materialize_command,
166
+ run_ciel_runtime_update_check=self.dispatch.run_runtime_update,
167
+ run_claude_update_check=self.dispatch.run_claude_update,
168
+ run_prelaunch_menu=self.dispatch.run_prelaunch_menu,
169
+ claude_launch_enabled_for_provider=self.dispatch.launch_enabled,
170
+ ),
171
+ config=runtime_launch.ClaudeLaunchConfig(
172
+ load_config=self.config.load_config,
173
+ save_config=self.config.save_config,
174
+ get_current_provider=self.config.current_provider,
175
+ ensure_current_model_from_provider_list=self.config.ensure_current_model,
176
+ ensure_model_cache_for_launch=self.config.ensure_model_cache,
177
+ apply_launch_endpoint_policy=self.config.apply_endpoint_policy,
178
+ provider_menu_label=self.config.provider_menu_label,
179
+ launch_mode_name=self.config.launch_mode_name,
180
+ current_launch_cwd_key=self.config.current_launch_cwd_key,
181
+ ),
182
+ routing=runtime_launch.ClaudeLaunchRouting(
183
+ anthropic_routed_enabled=self.routing.routed_enabled,
184
+ direct_native_anthropic_enabled=self.routing.direct_native_enabled,
185
+ cleanup_managed_services_for_provider=self.routing.cleanup_services,
186
+ ensure_managed_router_running_for_client=self.routing.ensure_router_running,
187
+ reset_zai_mcp_config_if_inactive=self.routing.reset_zai_mcp,
188
+ router_health_summary=self.routing.health_summary,
189
+ router_log=self.routing.log,
190
+ start_router_if_needed=self.routing.start_router,
191
+ run_with_router_lifetime=self.routing.run_with_router_lifetime,
192
+ record_launch_state_for_cwd=self.routing.record_launch_state,
193
+ ),
194
+ policy=runtime_launch.ClaudeLaunchPolicy(
195
+ append_claude_code_runtime_settings_args=self.policy.append_runtime_settings,
196
+ claude_supports_permission_mode_arg=self.policy.supports_permission_mode,
197
+ has_noninteractive_claude_args=self.policy.has_noninteractive_args,
198
+ has_passthrough_option=self.policy.has_passthrough_option,
199
+ should_append_compat_prompt=self.policy.should_append_compat_prompt,
200
+ should_attach_web_search=self.policy.should_attach_web_search,
201
+ should_disallow_claude_server_side_web_tools=self.policy.should_disallow_server_web_tools,
202
+ should_fork_native_session_after_mode_switch=self.policy.should_fork_native_session,
203
+ should_insert_passthrough_option_boundary=self.policy.should_insert_option_boundary,
204
+ strip_mcp_config_passthrough=self.policy.strip_mcp_config,
205
+ ),
206
+ channel_discovery=runtime_launch.ClaudeLaunchChannelDiscovery(
207
+ auto_import_passthrough_channels=self.discovery.auto_import_channels,
208
+ cached_channel_capable_server_names=self.discovery.cached_capable_names,
209
+ cached_channel_source_paths_for_specs=self.discovery.cached_source_paths,
210
+ channel_candidate_server_names_for_launch=self.discovery.candidate_names,
211
+ channel_specs_for_launch=self.discovery.channel_specs,
212
+ claude_channels_requested=self.discovery.channels_requested,
213
+ claude_code_channels_auth_available=self.discovery.auth_available,
214
+ ensure_channel_probe_cache_for_launch=self.discovery.ensure_probe_cache,
215
+ external_mcp_channel_server_names_from_configs=self.discovery.external_server_names,
216
+ read_channel_probe_cache=self.discovery.read_probe_cache,
217
+ ),
218
+ channel_delivery=runtime_launch.ClaudeLaunchChannelDelivery(
219
+ auto_start_sse_channels_from_mcp_configs=self.delivery.auto_start_sse,
220
+ claude_channel_args=self.delivery.channel_args,
221
+ native_channel_passthrough_requested=self.delivery.native_passthrough_requested,
222
+ normalize_channel_passthrough=self.delivery.normalize_passthrough,
223
+ should_launch_process_start_channel_sse=self.delivery.should_start_sse,
224
+ should_use_channel_llm_delivery=self.delivery.should_use_llm_delivery,
225
+ should_use_channel_stdin_proxy=self.delivery.should_use_stdin_proxy,
226
+ should_use_native_channel_bridge=self.delivery.should_use_native_bridge,
227
+ write_channel_mcp_config=self.delivery.write_channel_config,
228
+ ),
229
+ mcp_config=runtime_launch.ClaudeLaunchMcpConfig(
230
+ write_duckduckgo_mcp_config=self.mcp_config.write_duckduckgo,
231
+ write_mcp_proxy_config=self.mcp_config.write_proxy,
232
+ write_native_mcp_config_from_discovery=self.mcp_config.write_native_discovery,
233
+ write_zai_mcp_config=self.mcp_config.write_zai,
234
+ ),
235
+ )
236
+
237
+
238
+ __all__ = [
239
+ "ClaudeLaunchAssembly",
240
+ "ClaudeLaunchConfigPorts",
241
+ "ClaudeLaunchDeliveryPorts",
242
+ "ClaudeLaunchDiscoveryPorts",
243
+ "ClaudeLaunchDispatchPorts",
244
+ "ClaudeLaunchInstallationPorts",
245
+ "ClaudeLaunchMcpConfigPorts",
246
+ "ClaudeLaunchPolicyPorts",
247
+ "ClaudeLaunchProcessPorts",
248
+ "ClaudeLaunchRoutingPorts",
249
+ ]
@@ -115,6 +115,11 @@ class ClaudeRouterResponse:
115
115
  key_from_headers: Callable[..., Any]
116
116
 
117
117
 
118
+ @dataclass(frozen=True, slots=True)
119
+ class ClaudeRouterContextRecovery:
120
+ recover_output_budget: Callable[..., Any]
121
+
122
+
118
123
  @dataclass(frozen=True, slots=True)
119
124
  class ClaudeRouterServices:
120
125
  core: ClaudeRouterCore
@@ -126,6 +131,7 @@ class ClaudeRouterServices:
126
131
  normalization: ClaudeRouterNativeNormalization
127
132
  transport: ClaudeRouterTransport
128
133
  response: ClaudeRouterResponse
134
+ context_recovery: ClaudeRouterContextRecovery
129
135
 
130
136
 
131
137
  def handle_claude_count_tokens_post(
@@ -158,6 +164,7 @@ def handle_claude_messages_post(
158
164
  normalization = services.normalization
159
165
  transport = services.transport
160
166
  response = services.response
167
+ context_recovery = services.context_recovery
161
168
  _rebatch_anthropic_sse_text = response.rebatch_sse
162
169
  _update_tool_schema_registry = pipeline.update_tool_schema_registry
163
170
  append_synthetic_tasklist_to_message = response.append_tasklist
@@ -313,7 +320,7 @@ def handle_claude_messages_post(
313
320
  )
314
321
  return
315
322
  body = normalize_thinking_for_non_anthropic_provider(provider, pcfg, body)
316
- body = normalize_anthropic_system_role_messages(body)
323
+ body = normalize_anthropic_system_role_messages(provider, pcfg, body)
317
324
  body = cap_anthropic_body_for_provider(provider, pcfg, body)
318
325
  body = apply_provider_request_options(provider, pcfg, body)
319
326
  body = rehydrate_suppressed_thinking_passback(provider, pcfg, body)
@@ -339,16 +346,56 @@ def handle_claude_messages_post(
339
346
  waited, rpm_used, rpm_limit = apply_router_rate_limit(provider, pcfg, upstream_model)
340
347
  try:
341
348
  event_bus.publish(level="info", category="upstream.request", message="forwarding to Anthropic-compatible provider", request_id=request_id, provider=provider, model=upstream_model, data={"url": url, "stream": bool(body.get("stream", stream_enabled))})
342
- resp = open_provider_request_with_key_retry(
343
- url,
344
- upstream_body,
345
- headers,
346
- provider_request_timeout_seconds(pcfg),
347
- provider,
348
- pcfg,
349
- upstream_model,
350
- stream=bool(body.get("stream", stream_enabled)),
351
- )
349
+ try:
350
+ resp = open_provider_request_with_key_retry(
351
+ url,
352
+ upstream_body,
353
+ headers,
354
+ provider_request_timeout_seconds(pcfg),
355
+ provider,
356
+ pcfg,
357
+ upstream_model,
358
+ stream=bool(body.get("stream", stream_enabled)),
359
+ )
360
+ except urllib.error.HTTPError as initial_error:
361
+ raw_error = initial_error.read()
362
+ recovered_body = (
363
+ context_recovery.recover_output_budget(
364
+ upstream_body, raw_error, pcfg
365
+ )
366
+ if initial_error.code == 400
367
+ else None
368
+ )
369
+ if recovered_body is None:
370
+ initial_error.ciel_runtime_body = raw_error
371
+ raise
372
+ previous_output = upstream_body.get("max_tokens")
373
+ upstream_body = recovered_body
374
+ body["max_tokens"] = recovered_body["max_tokens"]
375
+ router_log(
376
+ "WARN",
377
+ "native_context_output_retry "
378
+ f"provider={provider} model={upstream_model} "
379
+ f"max_tokens={previous_output}->{recovered_body['max_tokens']}",
380
+ )
381
+ write_router_activity(
382
+ "retry",
383
+ provider,
384
+ upstream_model,
385
+ code=400,
386
+ reason="context_output_budget",
387
+ max_tokens=recovered_body["max_tokens"],
388
+ )
389
+ resp = open_provider_request_with_key_retry(
390
+ url,
391
+ upstream_body,
392
+ headers,
393
+ provider_request_timeout_seconds(pcfg),
394
+ provider,
395
+ pcfg,
396
+ upstream_model,
397
+ stream=bool(body.get("stream", stream_enabled)),
398
+ )
352
399
  if bool(body.get("stream", stream_enabled)):
353
400
  set_upstream_stream_read_timeout(resp, provider_stream_idle_timeout_seconds(pcfg))
354
401
  status = getattr(resp, "status", 200)
@@ -395,7 +442,9 @@ def handle_claude_messages_post(
395
442
  mark_pending_channel_delivery_success(self, "anthropic_json")
396
443
  commit_pending_channel_delivery_cursors(body, self)
397
444
  except urllib.error.HTTPError as e:
398
- err = e.read()
445
+ err = getattr(e, "ciel_runtime_body", None)
446
+ if err is None:
447
+ err = e.read()
399
448
  if e.code == 429:
400
449
  register_api_key_cooldown(provider, pcfg, key_from_request_headers(headers), e.headers)
401
450
  event_bus.publish(level="error", category="upstream.error", message=f"upstream HTTP {e.code}", request_id=request_id, provider=provider, model=upstream_model, data={"status": e.code})
@@ -518,6 +567,7 @@ assert all(any(capability.name == required for capability in ClaudeRouter.capabi
518
567
 
519
568
  __all__ = [
520
569
  "ClaudeRouter",
570
+ "ClaudeRouterContextRecovery",
521
571
  "ClaudeRouterCore",
522
572
  "ClaudeRouterCountTokens",
523
573
  "ClaudeRouterDelivery",
@@ -0,0 +1,132 @@
1
+ """CLI dispatch, parser construction, and process entrypoint context."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ from dataclasses import dataclass
7
+ from typing import Any, Callable, Sequence
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class CliApplicationDispatchPorts:
12
+ dispatch: Callable[..., int]
13
+ services: Callable[[], Any]
14
+ mcp_proxy: Callable[[list[str]], int]
15
+ launch_claude: Callable[[list[str]], int]
16
+ launch_codex: Callable[[list[str]], int]
17
+ launch_codex_app_server: Callable[[list[str]], int]
18
+ launch_agy: Callable[[list[str]], int]
19
+ launch_kimi: Callable[[list[str]], int]
20
+ kimi_login: Callable[[], int]
21
+
22
+
23
+ @dataclass(frozen=True, slots=True)
24
+ class CliApplicationPresentationPorts:
25
+ parser_builder: Callable[[Any], argparse.ArgumentParser]
26
+ parser_services: Callable[[], Any]
27
+ version: str
28
+ output: Callable[[str], None]
29
+ arguments: Callable[[], Sequence[str]]
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class CliApplicationContext:
34
+ dispatch: CliApplicationDispatchPorts
35
+ presentation: CliApplicationPresentationPorts
36
+
37
+ def run_cli(self, argv: list[str]) -> int:
38
+ return self.dispatch.dispatch(argv, self.dispatch.services())
39
+
40
+ def build_parser(self) -> argparse.ArgumentParser:
41
+ return self.presentation.parser_builder(
42
+ self.presentation.parser_services()
43
+ )
44
+
45
+ def cmd_cli(self, args: argparse.Namespace) -> None:
46
+ raise SystemExit(self.run_cli(args.argv))
47
+
48
+ def cmd_launch(self, args: argparse.Namespace) -> None:
49
+ raise SystemExit(self.dispatch.launch_claude(args.argv))
50
+
51
+ def cmd_launch_codex(self, args: argparse.Namespace) -> None:
52
+ raise SystemExit(self.dispatch.launch_codex(args.argv))
53
+
54
+ def cmd_launch_codex_app_server(self, args: argparse.Namespace) -> None:
55
+ raise SystemExit(self.dispatch.launch_codex_app_server(args.argv))
56
+
57
+ def cmd_launch_agy(self, args: argparse.Namespace) -> None:
58
+ raise SystemExit(self.dispatch.launch_agy(args.argv))
59
+
60
+ def cmd_version(self, _: argparse.Namespace) -> None:
61
+ self.presentation.output(f"ciel-runtime {self.presentation.version}")
62
+
63
+ def main(self) -> None:
64
+ arguments = list(self.presentation.arguments())
65
+ if len(arguments) >= 2 and arguments[1] == "mcp-proxy":
66
+ raise SystemExit(self.dispatch.mcp_proxy(arguments[2:]))
67
+ if len(arguments) >= 2 and arguments[1] == "cli":
68
+ raise SystemExit(self.run_cli(arguments[2:]))
69
+ routes = {
70
+ "launch": self.dispatch.launch_claude,
71
+ "codex": self.dispatch.launch_codex,
72
+ "launch-codex": self.dispatch.launch_codex,
73
+ "codex-app": self.dispatch.launch_codex_app_server,
74
+ "codex-app-server": self.dispatch.launch_codex_app_server,
75
+ "codex-appserver": self.dispatch.launch_codex_app_server,
76
+ "launch-codex-app-server": self.dispatch.launch_codex_app_server,
77
+ "agy": self.dispatch.launch_agy,
78
+ "launch-agy": self.dispatch.launch_agy,
79
+ "antigravity": self.dispatch.launch_agy,
80
+ "kimi": self.dispatch.launch_kimi,
81
+ "kimi-code": self.dispatch.launch_kimi,
82
+ "launch-kimi": self.dispatch.launch_kimi,
83
+ }
84
+ if len(arguments) >= 2 and arguments[1] in routes:
85
+ raise SystemExit(routes[arguments[1]](arguments[2:]))
86
+ if len(arguments) >= 2 and arguments[1] in (
87
+ "kimi-login",
88
+ "kimi-oauth-login",
89
+ ):
90
+ raise SystemExit(self.dispatch.kimi_login())
91
+ args = self.build_parser().parse_args(arguments[1:])
92
+ args.func(args)
93
+
94
+
95
+ @dataclass(frozen=True, slots=True)
96
+ class CliApplicationCompatibilityApi:
97
+ context: Callable[[], CliApplicationContext]
98
+
99
+ def run_cli(self, argv: list[str]) -> int:
100
+ return self.context().run_cli(argv)
101
+
102
+ def build_parser(self) -> argparse.ArgumentParser:
103
+ return self.context().build_parser()
104
+
105
+ def cmd_cli(self, args: argparse.Namespace) -> None:
106
+ self.context().cmd_cli(args)
107
+
108
+ def cmd_launch(self, args: argparse.Namespace) -> None:
109
+ self.context().cmd_launch(args)
110
+
111
+ def cmd_launch_codex(self, args: argparse.Namespace) -> None:
112
+ self.context().cmd_launch_codex(args)
113
+
114
+ def cmd_launch_codex_app_server(self, args: argparse.Namespace) -> None:
115
+ self.context().cmd_launch_codex_app_server(args)
116
+
117
+ def cmd_launch_agy(self, args: argparse.Namespace) -> None:
118
+ self.context().cmd_launch_agy(args)
119
+
120
+ def cmd_version(self, args: argparse.Namespace) -> None:
121
+ self.context().cmd_version(args)
122
+
123
+ def main(self) -> None:
124
+ self.context().main()
125
+
126
+
127
+ __all__ = [
128
+ "CliApplicationCompatibilityApi",
129
+ "CliApplicationContext",
130
+ "CliApplicationDispatchPorts",
131
+ "CliApplicationPresentationPorts",
132
+ ]
@@ -0,0 +1,50 @@
1
+ """Compose CLI dispatch and parser services from their bounded port groups."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+
7
+ from . import cli_dispatch, cli_parser
8
+
9
+
10
+ @dataclass(frozen=True, slots=True)
11
+ class CliServiceAssembly:
12
+ core: cli_dispatch.CliCore
13
+ runtime: cli_dispatch.CliRuntime
14
+ provider_commands: cli_dispatch.CliProviderCommands
15
+ channel_commands: cli_dispatch.CliChannelCommands
16
+ special_commands: cli_dispatch.CliSpecialCommands
17
+ operations: cli_dispatch.CliOperations
18
+ configuration: cli_dispatch.CliConfiguration
19
+
20
+ def services(self) -> cli_dispatch.CliServices:
21
+ return cli_dispatch.CliServices(
22
+ core=self.core,
23
+ runtime=self.runtime,
24
+ provider_commands=self.provider_commands,
25
+ channel_commands=self.channel_commands,
26
+ special_commands=self.special_commands,
27
+ operations=self.operations,
28
+ configuration=self.configuration,
29
+ )
30
+
31
+
32
+ @dataclass(frozen=True, slots=True)
33
+ class CliParserAssembly:
34
+ launch: cli_parser.CliParserLaunch
35
+ runtime: cli_parser.CliParserRuntime
36
+ settings: cli_parser.CliParserSettings
37
+ provider: cli_parser.CliParserProvider
38
+ models: cli_parser.CliParserModels
39
+
40
+ def services(self) -> cli_parser.CliParserServices:
41
+ return cli_parser.CliParserServices(
42
+ launch=self.launch,
43
+ runtime=self.runtime,
44
+ settings=self.settings,
45
+ provider=self.provider,
46
+ models=self.models,
47
+ )
48
+
49
+
50
+ __all__ = ["CliParserAssembly", "CliServiceAssembly"]