@oneciel-ai/ciel-runtime 0.2.2 → 0.2.4
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.
- package/ciel_runtime.py +2555 -9635
- package/ciel_runtime_support/advisor_request_builder.py +8 -21
- package/ciel_runtime_support/anthropic_tool_turns.py +13 -8
- package/ciel_runtime_support/architecture.py +68 -0
- package/ciel_runtime_support/architecture_budget.py +1 -1
- package/ciel_runtime_support/channel_connection_context.py +233 -0
- package/ciel_runtime_support/channel_delivery_context.py +332 -0
- package/ciel_runtime_support/channel_mcp_context.py +313 -0
- package/ciel_runtime_support/channel_mcp_discovery.py +47 -0
- package/ciel_runtime_support/channel_mcp_transport.py +5 -1
- package/ciel_runtime_support/channel_message_context.py +212 -0
- package/ciel_runtime_support/channel_message_repository.py +14 -3
- package/ciel_runtime_support/channel_pending_injection.py +9 -0
- package/ciel_runtime_support/channel_probe_launch_context.py +213 -0
- package/ciel_runtime_support/channel_replay_policy.py +38 -0
- package/ciel_runtime_support/channel_runtime_environment.py +8 -0
- package/ciel_runtime_support/channel_session_context.py +236 -0
- package/ciel_runtime_support/channel_terminal_context.py +350 -0
- package/ciel_runtime_support/channel_wake_context.py +532 -0
- package/ciel_runtime_support/claude_environment.py +60 -0
- package/ciel_runtime_support/claude_launch_assembly.py +249 -0
- package/ciel_runtime_support/claude_router.py +62 -12
- package/ciel_runtime_support/cli_application_context.py +132 -0
- package/ciel_runtime_support/cli_assembly.py +50 -0
- package/ciel_runtime_support/codex_backend_context.py +363 -0
- package/ciel_runtime_support/codex_config.py +13 -1
- package/ciel_runtime_support/codex_launch_assembly.py +213 -0
- package/ciel_runtime_support/codex_launch_configuration.py +30 -1
- package/ciel_runtime_support/codex_mcp_integration.py +90 -8
- package/ciel_runtime_support/codex_model_catalog.py +4 -1
- package/ciel_runtime_support/codex_reasoning_rejects.py +225 -0
- package/ciel_runtime_support/codex_router.py +38 -8
- package/ciel_runtime_support/codex_turn_recovery.py +154 -0
- package/ciel_runtime_support/config_migrations.py +103 -0
- package/ciel_runtime_support/config_repository.py +30 -0
- package/ciel_runtime_support/configuration_cli.py +38 -0
- package/ciel_runtime_support/context_compaction.py +9 -4
- package/ciel_runtime_support/credential_management.py +12 -0
- package/ciel_runtime_support/credentials.py +12 -0
- package/ciel_runtime_support/github_copilot_oauth.py +2 -2
- package/ciel_runtime_support/hosted_formula_tools.py +216 -0
- package/ciel_runtime_support/kimi_runtime_context.py +208 -0
- package/ciel_runtime_support/llm_preset_context.py +338 -0
- package/ciel_runtime_support/managed_mcp_config.py +8 -4
- package/ciel_runtime_support/mcp_configuration_context.py +291 -0
- package/ciel_runtime_support/mcp_http_proxy.py +14 -8
- package/ciel_runtime_support/mcp_probe_transport.py +47 -15
- package/ciel_runtime_support/mcp_transport.py +14 -1
- package/ciel_runtime_support/native_context_recovery.py +72 -0
- package/ciel_runtime_support/ollama_catalog_context.py +213 -0
- package/ciel_runtime_support/ollama_stream_collection.py +103 -0
- package/ciel_runtime_support/ollama_thinking.py +6 -1
- package/ciel_runtime_support/ollama_wire_projection.py +157 -0
- package/ciel_runtime_support/openai_forwarding.py +32 -10
- package/ciel_runtime_support/openai_responses_router.py +12 -0
- package/ciel_runtime_support/package_lifecycle.py +39 -0
- package/ciel_runtime_support/prelaunch_assembly.py +37 -0
- package/ciel_runtime_support/prelaunch_panel_context.py +418 -0
- package/ciel_runtime_support/prelaunch_shell_context.py +394 -0
- package/ciel_runtime_support/prompt_compaction.py +144 -0
- package/ciel_runtime_support/prompt_injection.py +45 -0
- package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +1 -1
- package/ciel_runtime_support/protocols/chat_projection.py +85 -5
- package/ciel_runtime_support/protocols/conversation_turn_policy.py +43 -0
- package/ciel_runtime_support/protocols/ollama_chat.py +31 -0
- package/ciel_runtime_support/protocols/ollama_response.py +57 -5
- package/ciel_runtime_support/protocols/openai_reasoning.py +5 -2
- package/ciel_runtime_support/protocols/openai_responses.py +61 -15
- package/ciel_runtime_support/provider_adapters.py +26 -0
- package/ciel_runtime_support/provider_administration_context.py +207 -0
- package/ciel_runtime_support/provider_config_mutations.py +3 -0
- package/ciel_runtime_support/provider_model_catalog_context.py +137 -0
- package/ciel_runtime_support/provider_model_context.py +107 -0
- package/ciel_runtime_support/provider_model_metadata_context.py +197 -0
- package/ciel_runtime_support/provider_model_selection.py +10 -3
- package/ciel_runtime_support/provider_models.py +45 -2
- package/ciel_runtime_support/provider_option_cli.py +19 -0
- package/ciel_runtime_support/provider_policy.py +1 -1
- package/ciel_runtime_support/provider_readiness_context.py +189 -0
- package/ciel_runtime_support/provider_request_builder.py +64 -28
- package/ciel_runtime_support/provider_responses_passthrough.py +21 -2
- package/ciel_runtime_support/provider_timeout_policy.py +54 -0
- package/ciel_runtime_support/provider_tool_policy.py +9 -1
- package/ciel_runtime_support/providers/__init__.py +6 -0
- package/ciel_runtime_support/providers/alibaba.py +634 -0
- package/ciel_runtime_support/providers/catalog.py +24 -16
- package/ciel_runtime_support/providers/deepseek.py +73 -0
- package/ciel_runtime_support/providers/github_copilot_oauth.py +22 -1
- package/ciel_runtime_support/providers/kimi.py +69 -9
- package/ciel_runtime_support/providers/ollama.py +8 -0
- package/ciel_runtime_support/providers/ollama_context.py +21 -2
- package/ciel_runtime_support/providers/vllm.py +7 -1
- package/ciel_runtime_support/response_collection.py +68 -18
- package/ciel_runtime_support/response_collection_context.py +391 -0
- package/ciel_runtime_support/response_stream_context.py +555 -0
- package/ciel_runtime_support/responses_input_compatibility.py +121 -0
- package/ciel_runtime_support/responses_usage_observer.py +83 -0
- package/ciel_runtime_support/router_client_lifecycle.py +1 -0
- package/ciel_runtime_support/router_http.py +245 -3
- package/ciel_runtime_support/router_observability_context.py +251 -0
- package/ciel_runtime_support/router_process_context.py +200 -0
- package/ciel_runtime_support/router_process_lifecycle.py +2 -0
- package/ciel_runtime_support/router_request_assembly.py +399 -0
- package/ciel_runtime_support/router_request_context.py +215 -0
- package/ciel_runtime_support/router_server_context.py +84 -0
- package/ciel_runtime_support/runaway_output_guard.py +488 -0
- package/ciel_runtime_support/runtime_asset_assembly.py +147 -0
- package/ciel_runtime_support/runtime_asset_context.py +297 -0
- package/ciel_runtime_support/runtime_constants.py +16 -1
- package/ciel_runtime_support/runtime_launch.py +9 -5
- package/ciel_runtime_support/runtime_launch_context.py +130 -0
- package/ciel_runtime_support/runtime_maintenance_assembly.py +60 -0
- package/ciel_runtime_support/runtime_maintenance_context.py +309 -0
- package/ciel_runtime_support/runtime_maintenance_services.py +265 -0
- package/ciel_runtime_support/runtime_paths.py +60 -40
- package/ciel_runtime_support/runtime_primitives.py +78 -0
- package/ciel_runtime_support/speech_http_controller.py +335 -0
- package/ciel_runtime_support/sse_stream_collection.py +236 -0
- package/ciel_runtime_support/statusline_script.py +57 -8
- package/ciel_runtime_support/streaming_anthropic.py +361 -24
- package/ciel_runtime_support/tool_schema.py +40 -2
- package/ciel_runtime_support/tool_side_effect_dedupe.py +117 -12
- package/ciel_runtime_support/upstream_dump.py +68 -0
- package/ciel_runtime_support/upstream_retry_context.py +259 -0
- package/ciel_runtime_support/web_ui.py +248 -1
- package/ciel_runtime_support/workspace_router_selection.py +86 -0
- package/docs/COLAB_SPEECH.md +32 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +4 -1
- package/scripts/colab/__pycache__/bootstrap_moss_tts.cpython-311.pyc +0 -0
- package/scripts/colab/__pycache__/bootstrap_qwen_asr.cpython-311.pyc +0 -0
- package/scripts/colab/bootstrap_moss_tts.py +106 -0
- package/scripts/colab/bootstrap_qwen_asr.py +106 -0
- package/scripts/configure_speech_workers.py +37 -0
- package/scripts/deploy_colab_speech.ps1 +47 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Codex backend and provider-native passthrough bounded context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from http.server import BaseHTTPRequestHandler
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from .codex_router import CodexChannelContextPorts, CodexChannelContextProjector
|
|
10
|
+
from .openai_chat_passthrough import OpenAIChatPassthrough, OpenAIChatPassthroughPorts
|
|
11
|
+
from .provider_responses_passthrough import (
|
|
12
|
+
ProviderResponsesPassthrough,
|
|
13
|
+
ProviderResponsesPassthroughPorts,
|
|
14
|
+
)
|
|
15
|
+
from .router_http import (
|
|
16
|
+
CodexBackendHttpAdapter,
|
|
17
|
+
CodexBackendRequestPorts,
|
|
18
|
+
CodexBackendRetryPorts,
|
|
19
|
+
CodexRoutedHeaderPolicy,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass(frozen=True, slots=True)
|
|
24
|
+
class CodexBackendChannelPorts:
|
|
25
|
+
responses_to_anthropic: Callable[..., dict[str, Any]]
|
|
26
|
+
inject_pending: Callable[..., dict[str, Any]]
|
|
27
|
+
inject_tool_results: Callable[..., dict[str, Any]]
|
|
28
|
+
content_to_text: Callable[..., str]
|
|
29
|
+
begin_delivery: Callable[..., Any]
|
|
30
|
+
mark_delivery_success: Callable[..., Any]
|
|
31
|
+
commit_delivery: Callable[..., Any]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class CodexBackendTransportPorts:
|
|
36
|
+
upstream_base: str
|
|
37
|
+
decorate_headers: Callable[..., dict[str, str]]
|
|
38
|
+
urlopen: Callable[..., Any]
|
|
39
|
+
timeout_seconds: Callable[..., float]
|
|
40
|
+
read_preamble: Callable[..., Any]
|
|
41
|
+
retry_wait_seconds: Callable[..., float]
|
|
42
|
+
log: Callable[..., Any]
|
|
43
|
+
publish_event: Callable[..., Any]
|
|
44
|
+
sleep: Callable[[float], None]
|
|
45
|
+
env_get: Callable[..., str | None]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class CodexBackendReplayPorts:
|
|
50
|
+
"""Durable upstream verdicts about replayed turns."""
|
|
51
|
+
|
|
52
|
+
rejected_reasoning_contains: Callable[[str], bool] = lambda _sealed: False
|
|
53
|
+
rejected_reasoning_record: Callable[[str], Any] = lambda _sealed: None
|
|
54
|
+
estimate_tokens: Callable[[Any], int] = lambda _body: 0
|
|
55
|
+
compact_responses: Callable[..., dict[str, Any]] = lambda body, _budget, **_kw: body
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(frozen=True, slots=True)
|
|
59
|
+
class ProviderPassthroughProjectionPorts:
|
|
60
|
+
provider_headers: Callable[..., dict[str, str]]
|
|
61
|
+
chat_headers: Callable[..., dict[str, str]]
|
|
62
|
+
responses_headers: Callable[..., dict[str, str]]
|
|
63
|
+
upstream_model: Callable[..., str]
|
|
64
|
+
resolve_model: Callable[..., str]
|
|
65
|
+
apply_request_policy: Callable[..., dict[str, Any]]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass(frozen=True, slots=True)
|
|
69
|
+
class ProviderPassthroughTransportPorts:
|
|
70
|
+
upstream_base: Callable[..., str]
|
|
71
|
+
join_url: Callable[[str, str], str]
|
|
72
|
+
urlopen: Callable[..., Any]
|
|
73
|
+
timeout_seconds: Callable[..., float]
|
|
74
|
+
copy_response_headers: Callable[..., None]
|
|
75
|
+
write_activity: Callable[..., None]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(frozen=True, slots=True)
|
|
79
|
+
class CodexBackendContext:
|
|
80
|
+
channel: CodexBackendChannelPorts
|
|
81
|
+
transport: CodexBackendTransportPorts
|
|
82
|
+
provider_projection: ProviderPassthroughProjectionPorts
|
|
83
|
+
provider_transport: ProviderPassthroughTransportPorts
|
|
84
|
+
replay: CodexBackendReplayPorts = CodexBackendReplayPorts()
|
|
85
|
+
|
|
86
|
+
def routed_headers(
|
|
87
|
+
self,
|
|
88
|
+
pcfg: dict[str, Any],
|
|
89
|
+
inbound_headers: Any | None = None,
|
|
90
|
+
) -> dict[str, str]:
|
|
91
|
+
del pcfg
|
|
92
|
+
return CodexRoutedHeaderPolicy(
|
|
93
|
+
decorate=self.transport.decorate_headers
|
|
94
|
+
).project(inbound_headers)
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def routed_auth_error_message(message: str) -> str:
|
|
98
|
+
low = str(message or "").lower()
|
|
99
|
+
markers = (
|
|
100
|
+
"api.responses.write",
|
|
101
|
+
"insufficient permissions",
|
|
102
|
+
"unauthorized",
|
|
103
|
+
)
|
|
104
|
+
if not any(marker in low for marker in markers):
|
|
105
|
+
return message
|
|
106
|
+
guidance = (
|
|
107
|
+
" Codex routed is expected to forward Codex CLI native auth to the "
|
|
108
|
+
"ChatGPT Codex backend. If this mentions api.responses.write, the "
|
|
109
|
+
"request is still using the OpenAI Platform /v1 endpoint; upgrade "
|
|
110
|
+
"ciel-runtime and relaunch Codex routed so the local base URL is "
|
|
111
|
+
"/backend-api/codex."
|
|
112
|
+
)
|
|
113
|
+
return f"{message}{guidance}"
|
|
114
|
+
|
|
115
|
+
def project_channel_context(
|
|
116
|
+
self, body: dict[str, Any]
|
|
117
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
118
|
+
return CodexChannelContextProjector(
|
|
119
|
+
CodexChannelContextPorts(
|
|
120
|
+
self.channel.responses_to_anthropic,
|
|
121
|
+
self.channel.inject_pending,
|
|
122
|
+
self.channel.inject_tool_results,
|
|
123
|
+
self.channel.content_to_text,
|
|
124
|
+
)
|
|
125
|
+
).project(body)
|
|
126
|
+
|
|
127
|
+
@staticmethod
|
|
128
|
+
def responses_input_as_list(value: Any) -> list[Any]:
|
|
129
|
+
return CodexChannelContextProjector.input_items(value)
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def copy_response_headers(
|
|
133
|
+
handler: BaseHTTPRequestHandler,
|
|
134
|
+
headers: Any,
|
|
135
|
+
) -> None:
|
|
136
|
+
CodexBackendHttpAdapter.copy_response_headers(handler, headers)
|
|
137
|
+
|
|
138
|
+
def capacity_retry_limit(self) -> int:
|
|
139
|
+
raw = str(
|
|
140
|
+
self.transport.env_get("CIEL_RUNTIME_CODEX_CAPACITY_RETRIES") or "3"
|
|
141
|
+
).strip()
|
|
142
|
+
try:
|
|
143
|
+
return max(0, min(10, int(raw)))
|
|
144
|
+
except ValueError:
|
|
145
|
+
return 3
|
|
146
|
+
|
|
147
|
+
def backend_adapter(self) -> CodexBackendHttpAdapter:
|
|
148
|
+
return CodexBackendHttpAdapter(
|
|
149
|
+
self.transport.upstream_base,
|
|
150
|
+
CodexBackendRequestPorts(
|
|
151
|
+
self.project_channel_context,
|
|
152
|
+
self.channel.begin_delivery,
|
|
153
|
+
self.routed_headers,
|
|
154
|
+
self.transport.urlopen,
|
|
155
|
+
self.transport.timeout_seconds,
|
|
156
|
+
),
|
|
157
|
+
CodexBackendRetryPorts(
|
|
158
|
+
self.capacity_retry_limit,
|
|
159
|
+
self.transport.read_preamble,
|
|
160
|
+
self.transport.retry_wait_seconds,
|
|
161
|
+
self.transport.log,
|
|
162
|
+
self.transport.publish_event,
|
|
163
|
+
self.transport.sleep,
|
|
164
|
+
rejected_reasoning_contains=self.replay.rejected_reasoning_contains,
|
|
165
|
+
rejected_reasoning_record=self.replay.rejected_reasoning_record,
|
|
166
|
+
estimate_tokens=self.replay.estimate_tokens,
|
|
167
|
+
compact_responses=self.replay.compact_responses,
|
|
168
|
+
),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def provider_responses_headers(
|
|
172
|
+
self,
|
|
173
|
+
provider: str,
|
|
174
|
+
pcfg: dict[str, Any],
|
|
175
|
+
inbound_headers: Any | None = None,
|
|
176
|
+
) -> dict[str, str]:
|
|
177
|
+
headers = self.provider_projection.provider_headers(
|
|
178
|
+
provider,
|
|
179
|
+
pcfg,
|
|
180
|
+
inbound_headers,
|
|
181
|
+
"openai_responses",
|
|
182
|
+
)
|
|
183
|
+
if inbound_headers is None:
|
|
184
|
+
headers = {
|
|
185
|
+
name: value
|
|
186
|
+
for name, value in headers.items()
|
|
187
|
+
if str(name).casefold() != "anthropic-version"
|
|
188
|
+
}
|
|
189
|
+
return headers
|
|
190
|
+
|
|
191
|
+
def provider_chat_headers(
|
|
192
|
+
self,
|
|
193
|
+
provider: str,
|
|
194
|
+
pcfg: dict[str, Any],
|
|
195
|
+
inbound_headers: Any | None = None,
|
|
196
|
+
) -> dict[str, str]:
|
|
197
|
+
return self.provider_projection.provider_headers(
|
|
198
|
+
provider,
|
|
199
|
+
pcfg,
|
|
200
|
+
inbound_headers,
|
|
201
|
+
"openai_chat",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
def normalize_model(
|
|
205
|
+
self,
|
|
206
|
+
provider: str,
|
|
207
|
+
pcfg: dict[str, Any],
|
|
208
|
+
model: Any,
|
|
209
|
+
) -> str:
|
|
210
|
+
return self.provider_projection.upstream_model(
|
|
211
|
+
provider,
|
|
212
|
+
pcfg,
|
|
213
|
+
self.provider_projection.resolve_model(provider, pcfg, model),
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def normalize_request(
|
|
217
|
+
self,
|
|
218
|
+
provider: str,
|
|
219
|
+
pcfg: dict[str, Any],
|
|
220
|
+
body: dict[str, Any],
|
|
221
|
+
) -> dict[str, Any]:
|
|
222
|
+
return self.provider_projection.apply_request_policy(
|
|
223
|
+
provider, pcfg, dict(body)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def chat_passthrough(self) -> OpenAIChatPassthrough:
|
|
227
|
+
return OpenAIChatPassthrough(
|
|
228
|
+
OpenAIChatPassthroughPorts(
|
|
229
|
+
normalize_model=self.normalize_model,
|
|
230
|
+
normalize_request=self.normalize_request,
|
|
231
|
+
upstream_base=self.provider_transport.upstream_base,
|
|
232
|
+
join_url=self.provider_transport.join_url,
|
|
233
|
+
headers=self.provider_projection.chat_headers,
|
|
234
|
+
urlopen=self.provider_transport.urlopen,
|
|
235
|
+
timeout_seconds=self.provider_transport.timeout_seconds,
|
|
236
|
+
copy_response_headers=self.provider_transport.copy_response_headers,
|
|
237
|
+
)
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
def forward_provider_chat(self, *args: Any, **kwargs: Any) -> None:
|
|
241
|
+
self.chat_passthrough().forward(*args, **kwargs)
|
|
242
|
+
|
|
243
|
+
def responses_passthrough(self) -> ProviderResponsesPassthrough:
|
|
244
|
+
return ProviderResponsesPassthrough(
|
|
245
|
+
ProviderResponsesPassthroughPorts(
|
|
246
|
+
project_channel_context=self.project_channel_context,
|
|
247
|
+
begin_channel_delivery=self.channel.begin_delivery,
|
|
248
|
+
normalize_model=self.normalize_model,
|
|
249
|
+
normalize_request=self.normalize_request,
|
|
250
|
+
upstream_base=self.provider_transport.upstream_base,
|
|
251
|
+
join_url=self.provider_transport.join_url,
|
|
252
|
+
headers=self.provider_projection.responses_headers,
|
|
253
|
+
urlopen=self.provider_transport.urlopen,
|
|
254
|
+
timeout_seconds=self.provider_transport.timeout_seconds,
|
|
255
|
+
copy_response_headers=self.provider_transport.copy_response_headers,
|
|
256
|
+
record_usage=lambda provider, model, usage: self.provider_transport.write_activity(
|
|
257
|
+
"success",
|
|
258
|
+
provider,
|
|
259
|
+
model,
|
|
260
|
+
protocol="openai_responses",
|
|
261
|
+
**usage,
|
|
262
|
+
),
|
|
263
|
+
log=self.transport.log,
|
|
264
|
+
)
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def forward_provider_responses(self, *args: Any, **kwargs: Any) -> Any:
|
|
268
|
+
return self.responses_passthrough().forward(*args, **kwargs)
|
|
269
|
+
|
|
270
|
+
def upstream_url(self, request_path: str, query: str = "") -> str:
|
|
271
|
+
return self.backend_adapter().upstream_url(request_path, query)
|
|
272
|
+
|
|
273
|
+
def forward_json(self, *args: Any, **kwargs: Any) -> Any:
|
|
274
|
+
return self.backend_adapter().forward_json(*args, **kwargs)
|
|
275
|
+
|
|
276
|
+
def forward_get(self, *args: Any, **kwargs: Any) -> None:
|
|
277
|
+
self.backend_adapter().forward_get(*args, **kwargs)
|
|
278
|
+
|
|
279
|
+
def forward_responses(
|
|
280
|
+
self,
|
|
281
|
+
handler: BaseHTTPRequestHandler,
|
|
282
|
+
provider: str,
|
|
283
|
+
pcfg: dict[str, Any],
|
|
284
|
+
body: dict[str, Any],
|
|
285
|
+
) -> None:
|
|
286
|
+
delivery_body = self.forward_json(
|
|
287
|
+
handler,
|
|
288
|
+
provider,
|
|
289
|
+
pcfg,
|
|
290
|
+
body,
|
|
291
|
+
mutate_responses=True,
|
|
292
|
+
)
|
|
293
|
+
if delivery_body is None:
|
|
294
|
+
return
|
|
295
|
+
self.channel.mark_delivery_success(handler, "codex_responses_proxy")
|
|
296
|
+
self.channel.commit_delivery(delivery_body, handler)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
@dataclass(frozen=True, slots=True)
|
|
300
|
+
class CodexBackendCompatibilityApi:
|
|
301
|
+
context: Callable[[], CodexBackendContext]
|
|
302
|
+
|
|
303
|
+
def routed_headers(self, *args: Any, **kwargs: Any) -> Any:
|
|
304
|
+
return self.context().routed_headers(*args, **kwargs)
|
|
305
|
+
|
|
306
|
+
def routed_auth_error_message(self, message: str) -> str:
|
|
307
|
+
return self.context().routed_auth_error_message(message)
|
|
308
|
+
|
|
309
|
+
def project_channel_context(self, *args: Any, **kwargs: Any) -> Any:
|
|
310
|
+
return self.context().project_channel_context(*args, **kwargs)
|
|
311
|
+
|
|
312
|
+
def responses_input_as_list(self, value: Any) -> list[Any]:
|
|
313
|
+
return self.context().responses_input_as_list(value)
|
|
314
|
+
|
|
315
|
+
def copy_response_headers(self, *args: Any, **kwargs: Any) -> None:
|
|
316
|
+
self.context().copy_response_headers(*args, **kwargs)
|
|
317
|
+
|
|
318
|
+
def backend_adapter(self) -> CodexBackendHttpAdapter:
|
|
319
|
+
return self.context().backend_adapter()
|
|
320
|
+
|
|
321
|
+
def provider_responses_headers(self, *args: Any, **kwargs: Any) -> Any:
|
|
322
|
+
return self.context().provider_responses_headers(*args, **kwargs)
|
|
323
|
+
|
|
324
|
+
def provider_chat_headers(self, *args: Any, **kwargs: Any) -> Any:
|
|
325
|
+
return self.context().provider_chat_headers(*args, **kwargs)
|
|
326
|
+
|
|
327
|
+
def chat_passthrough(self) -> OpenAIChatPassthrough:
|
|
328
|
+
return self.context().chat_passthrough()
|
|
329
|
+
|
|
330
|
+
def forward_provider_chat(self, *args: Any, **kwargs: Any) -> None:
|
|
331
|
+
self.context().forward_provider_chat(*args, **kwargs)
|
|
332
|
+
|
|
333
|
+
def responses_passthrough(self) -> ProviderResponsesPassthrough:
|
|
334
|
+
return self.context().responses_passthrough()
|
|
335
|
+
|
|
336
|
+
def forward_provider_responses(self, *args: Any, **kwargs: Any) -> Any:
|
|
337
|
+
return self.context().forward_provider_responses(*args, **kwargs)
|
|
338
|
+
|
|
339
|
+
def upstream_url(self, *args: Any, **kwargs: Any) -> str:
|
|
340
|
+
return self.context().upstream_url(*args, **kwargs)
|
|
341
|
+
|
|
342
|
+
def forward_json(self, *args: Any, **kwargs: Any) -> Any:
|
|
343
|
+
return self.context().forward_json(*args, **kwargs)
|
|
344
|
+
|
|
345
|
+
def capacity_retry_limit(self) -> int:
|
|
346
|
+
return self.context().capacity_retry_limit()
|
|
347
|
+
|
|
348
|
+
def forward_get(self, *args: Any, **kwargs: Any) -> None:
|
|
349
|
+
self.context().forward_get(*args, **kwargs)
|
|
350
|
+
|
|
351
|
+
def forward_responses(self, *args: Any, **kwargs: Any) -> None:
|
|
352
|
+
self.context().forward_responses(*args, **kwargs)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
__all__ = [
|
|
356
|
+
"CodexBackendChannelPorts",
|
|
357
|
+
"CodexBackendCompatibilityApi",
|
|
358
|
+
"CodexBackendContext",
|
|
359
|
+
"CodexBackendReplayPorts",
|
|
360
|
+
"CodexBackendTransportPorts",
|
|
361
|
+
"ProviderPassthroughProjectionPorts",
|
|
362
|
+
"ProviderPassthroughTransportPorts",
|
|
363
|
+
]
|
|
@@ -146,8 +146,20 @@ def normalize_codex_mcp_server(
|
|
|
146
146
|
out: dict[str, Any] = {"type": server_type, "url": url}
|
|
147
147
|
if explicit_type:
|
|
148
148
|
out["_ciel_runtime_explicit_type"] = True
|
|
149
|
+
headers: dict[str, Any] = {}
|
|
150
|
+
raw_headers = raw_server.get("headers")
|
|
151
|
+
if isinstance(raw_headers, dict):
|
|
152
|
+
headers.update(raw_headers)
|
|
153
|
+
codex_http_headers = raw_server.get("http_headers")
|
|
154
|
+
if isinstance(codex_http_headers, dict):
|
|
155
|
+
headers.update(codex_http_headers)
|
|
156
|
+
if headers:
|
|
157
|
+
# Codex names literal HTTP headers ``http_headers`` while the shared
|
|
158
|
+
# MCP discovery/transport contract uses ``headers``. Normalize the
|
|
159
|
+
# spelling here so the independently owned notification worker keeps
|
|
160
|
+
# the same authentication as Codex's native MCP client.
|
|
161
|
+
out["headers"] = headers
|
|
149
162
|
for key in (
|
|
150
|
-
"headers",
|
|
151
163
|
"env_http_headers",
|
|
152
164
|
"bearer_token_env_var",
|
|
153
165
|
"token_env_var",
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"""Assemble Codex CLI and app-server launch services from shared typed ports."""
|
|
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 CodexLaunchSharedConfigPorts:
|
|
16
|
+
apply_endpoint_policy: Callback
|
|
17
|
+
current_alias: Callback
|
|
18
|
+
current_launch_cwd_key: Callback
|
|
19
|
+
ensure_model_cache: Callback
|
|
20
|
+
get_current_provider: Callback
|
|
21
|
+
load_config: Callback
|
|
22
|
+
provider_mode_label: Callback
|
|
23
|
+
record_launch_state: Callback
|
|
24
|
+
model_catalog_args: Callback
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class CodexLaunchSharedInstallationPorts:
|
|
29
|
+
find_executable: Callback
|
|
30
|
+
install_codex: Callback
|
|
31
|
+
warn_multiple_installs: Callback
|
|
32
|
+
disable_prompts: Callback
|
|
33
|
+
has_passthrough_option: Callback
|
|
34
|
+
install_prompts: Callback
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class CodexLaunchSharedDispatchPorts:
|
|
39
|
+
launch_agy: Callback
|
|
40
|
+
launch_claude: Callback
|
|
41
|
+
launch_codex: Callback
|
|
42
|
+
launch_app_server: Callback
|
|
43
|
+
materialize_command: Callback
|
|
44
|
+
run_runtime_update: Callback
|
|
45
|
+
run_codex_update: Callback
|
|
46
|
+
run_prelaunch_menu: Callback
|
|
47
|
+
log_passthrough_mapping: Callback
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass(frozen=True, slots=True)
|
|
51
|
+
class CodexLaunchSharedRoutingPorts:
|
|
52
|
+
cleanup_services: Callback
|
|
53
|
+
routed_enabled: Callback
|
|
54
|
+
direct_native_enabled: Callback
|
|
55
|
+
readiness_errors: Callback
|
|
56
|
+
native_enabled: Callback
|
|
57
|
+
launch_enabled: Callback
|
|
58
|
+
run_with_router_lifetime: Callback
|
|
59
|
+
start_router: Callback
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class CodexLaunchSharedChannelPorts:
|
|
64
|
+
auto_import_channels: Callback
|
|
65
|
+
delivery_mode: Callback
|
|
66
|
+
capable_server_names: Callback
|
|
67
|
+
native_http_args: Callback
|
|
68
|
+
split_proxy_enabled: Callback
|
|
69
|
+
restore_config: Callback
|
|
70
|
+
start_sse: Callback
|
|
71
|
+
write_discovery_config: Callback
|
|
72
|
+
select_resume_session: Callback
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True)
|
|
76
|
+
class CodexCliLaunchPorts:
|
|
77
|
+
process: runtime_launch.CodexLaunchProcess
|
|
78
|
+
policy: runtime_launch.CodexLaunchCliPolicy
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@dataclass(frozen=True, slots=True)
|
|
82
|
+
class CodexAppServerLaunchPorts:
|
|
83
|
+
process: runtime_launch.CodexAppServerProcess
|
|
84
|
+
policy: runtime_launch.CodexAppServerCliPolicy
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@dataclass(frozen=True, slots=True)
|
|
88
|
+
class CodexLaunchAssembly:
|
|
89
|
+
config: CodexLaunchSharedConfigPorts
|
|
90
|
+
installation: CodexLaunchSharedInstallationPorts
|
|
91
|
+
dispatch: CodexLaunchSharedDispatchPorts
|
|
92
|
+
routing: CodexLaunchSharedRoutingPorts
|
|
93
|
+
channel: CodexLaunchSharedChannelPorts
|
|
94
|
+
cli: CodexCliLaunchPorts
|
|
95
|
+
app_server: CodexAppServerLaunchPorts
|
|
96
|
+
|
|
97
|
+
def cli_services(self) -> runtime_launch.CodexLaunchServices:
|
|
98
|
+
return runtime_launch.CodexLaunchServices(
|
|
99
|
+
constants=runtime_launch.build_default_codex_launch_constants(),
|
|
100
|
+
process=self.cli.process,
|
|
101
|
+
cli_policy=self.cli.policy,
|
|
102
|
+
config=runtime_launch.CodexLaunchConfig(
|
|
103
|
+
apply_launch_endpoint_policy=self.config.apply_endpoint_policy,
|
|
104
|
+
current_alias=self.config.current_alias,
|
|
105
|
+
current_launch_cwd_key=self.config.current_launch_cwd_key,
|
|
106
|
+
ensure_model_cache_for_launch=self.config.ensure_model_cache,
|
|
107
|
+
get_current_provider=self.config.get_current_provider,
|
|
108
|
+
load_config=self.config.load_config,
|
|
109
|
+
provider_mode_label=self.config.provider_mode_label,
|
|
110
|
+
record_launch_state_for_cwd=self.config.record_launch_state,
|
|
111
|
+
codex_runtime_model_catalog_args=self.config.model_catalog_args,
|
|
112
|
+
),
|
|
113
|
+
installation=runtime_launch.CodexLaunchInstallation(
|
|
114
|
+
disable_ciel_runtime_codex_prompts_for_native=self.installation.disable_prompts,
|
|
115
|
+
find_executable=self.installation.find_executable,
|
|
116
|
+
has_passthrough_option=self.installation.has_passthrough_option,
|
|
117
|
+
install_ciel_runtime_codex_prompts=self.installation.install_prompts,
|
|
118
|
+
install_codex_if_missing=self.installation.install_codex,
|
|
119
|
+
warn_if_multiple_ciel_runtime_installs=self.installation.warn_multiple_installs,
|
|
120
|
+
),
|
|
121
|
+
dispatch=runtime_launch.CodexLaunchDispatch(
|
|
122
|
+
launch_agy=self.dispatch.launch_agy,
|
|
123
|
+
launch_claude=self.dispatch.launch_claude,
|
|
124
|
+
launch_codex_app_server=self.dispatch.launch_app_server,
|
|
125
|
+
log_codex_passthrough_mapping=self.dispatch.log_passthrough_mapping,
|
|
126
|
+
materialize_runtime_command=self.dispatch.materialize_command,
|
|
127
|
+
run_ciel_runtime_update_check=self.dispatch.run_runtime_update,
|
|
128
|
+
run_codex_update_check=self.dispatch.run_codex_update,
|
|
129
|
+
run_prelaunch_menu=self.dispatch.run_prelaunch_menu,
|
|
130
|
+
),
|
|
131
|
+
routing=runtime_launch.CodexLaunchRouting(
|
|
132
|
+
cleanup_managed_services_for_provider=self.routing.cleanup_services,
|
|
133
|
+
codex_routed_enabled=self.routing.routed_enabled,
|
|
134
|
+
direct_native_codex_enabled=self.routing.direct_native_enabled,
|
|
135
|
+
launch_readiness_errors=self.routing.readiness_errors,
|
|
136
|
+
native_codex_enabled=self.routing.native_enabled,
|
|
137
|
+
run_with_router_lifetime=self.routing.run_with_router_lifetime,
|
|
138
|
+
start_router_if_needed=self.routing.start_router,
|
|
139
|
+
),
|
|
140
|
+
channel=runtime_launch.CodexLaunchChannel(
|
|
141
|
+
auto_import_passthrough_channels=self.channel.auto_import_channels,
|
|
142
|
+
channel_delivery_mode=self.channel.delivery_mode,
|
|
143
|
+
codex_channel_capable_mcp_server_names=self.channel.capable_server_names,
|
|
144
|
+
codex_mcp_native_http_compat_args=self.channel.native_http_args,
|
|
145
|
+
codex_mcp_split_proxy_enabled=self.channel.split_proxy_enabled,
|
|
146
|
+
restore_codex_mcp_config_from_managed=self.channel.restore_config,
|
|
147
|
+
select_codex_resume_session=self.channel.select_resume_session,
|
|
148
|
+
start_codex_mcp_channel_sse_for_launch=self.channel.start_sse,
|
|
149
|
+
write_codex_mcp_config_for_channel_discovery=self.channel.write_discovery_config,
|
|
150
|
+
),
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
def app_server_services(self) -> runtime_launch.CodexAppServerLaunchServices:
|
|
154
|
+
return runtime_launch.CodexAppServerLaunchServices(
|
|
155
|
+
constants=runtime_launch.build_default_codex_launch_constants(),
|
|
156
|
+
process=self.app_server.process,
|
|
157
|
+
config=runtime_launch.CodexAppServerConfig(
|
|
158
|
+
apply_launch_endpoint_policy=self.config.apply_endpoint_policy,
|
|
159
|
+
current_alias=self.config.current_alias,
|
|
160
|
+
current_launch_cwd_key=self.config.current_launch_cwd_key,
|
|
161
|
+
ensure_model_cache_for_launch=self.config.ensure_model_cache,
|
|
162
|
+
get_current_provider=self.config.get_current_provider,
|
|
163
|
+
load_config=self.config.load_config,
|
|
164
|
+
provider_mode_label=self.config.provider_mode_label,
|
|
165
|
+
record_launch_state_for_cwd=self.config.record_launch_state,
|
|
166
|
+
),
|
|
167
|
+
cli_policy=self.app_server.policy,
|
|
168
|
+
installation=runtime_launch.CodexAppServerInstallation(
|
|
169
|
+
find_executable=self.installation.find_executable,
|
|
170
|
+
install_codex_if_missing=self.installation.install_codex,
|
|
171
|
+
warn_if_multiple_ciel_runtime_installs=self.installation.warn_multiple_installs,
|
|
172
|
+
),
|
|
173
|
+
dispatch=runtime_launch.CodexAppServerDispatch(
|
|
174
|
+
launch_agy=self.dispatch.launch_agy,
|
|
175
|
+
launch_claude=self.dispatch.launch_claude,
|
|
176
|
+
launch_codex=self.dispatch.launch_codex,
|
|
177
|
+
run_ciel_runtime_update_check=self.dispatch.run_runtime_update,
|
|
178
|
+
run_codex_update_check=self.dispatch.run_codex_update,
|
|
179
|
+
run_prelaunch_menu=self.dispatch.run_prelaunch_menu,
|
|
180
|
+
),
|
|
181
|
+
routing=runtime_launch.CodexAppServerRouting(
|
|
182
|
+
cleanup_managed_services_for_provider=self.routing.cleanup_services,
|
|
183
|
+
codex_launch_enabled_for_provider=self.routing.launch_enabled,
|
|
184
|
+
codex_routed_enabled=self.routing.routed_enabled,
|
|
185
|
+
direct_native_codex_enabled=self.routing.direct_native_enabled,
|
|
186
|
+
launch_readiness_errors=self.routing.readiness_errors,
|
|
187
|
+
native_codex_enabled=self.routing.native_enabled,
|
|
188
|
+
run_with_router_lifetime=self.routing.run_with_router_lifetime,
|
|
189
|
+
start_router_if_needed=self.routing.start_router,
|
|
190
|
+
),
|
|
191
|
+
channel=runtime_launch.CodexAppServerChannel(
|
|
192
|
+
auto_import_passthrough_channels=self.channel.auto_import_channels,
|
|
193
|
+
channel_delivery_mode=self.channel.delivery_mode,
|
|
194
|
+
codex_channel_capable_mcp_server_names=self.channel.capable_server_names,
|
|
195
|
+
codex_mcp_native_http_compat_args=self.channel.native_http_args,
|
|
196
|
+
codex_mcp_split_proxy_enabled=self.channel.split_proxy_enabled,
|
|
197
|
+
restore_codex_mcp_config_from_managed=self.channel.restore_config,
|
|
198
|
+
start_codex_mcp_channel_sse_for_launch=self.channel.start_sse,
|
|
199
|
+
write_codex_mcp_config_for_channel_discovery=self.channel.write_discovery_config,
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
__all__ = [
|
|
205
|
+
"CodexAppServerLaunchPorts",
|
|
206
|
+
"CodexCliLaunchPorts",
|
|
207
|
+
"CodexLaunchAssembly",
|
|
208
|
+
"CodexLaunchSharedChannelPorts",
|
|
209
|
+
"CodexLaunchSharedConfigPorts",
|
|
210
|
+
"CodexLaunchSharedDispatchPorts",
|
|
211
|
+
"CodexLaunchSharedInstallationPorts",
|
|
212
|
+
"CodexLaunchSharedRoutingPorts",
|
|
213
|
+
]
|
|
@@ -178,6 +178,9 @@ class CodexLaunchConfigurationService:
|
|
|
178
178
|
auto_compact_token_limit = None
|
|
179
179
|
if auto_compact_token_limit is not None and auto_compact_token_limit <= 0:
|
|
180
180
|
auto_compact_token_limit = None
|
|
181
|
+
metadata = provider_config.get("codex_model_catalog")
|
|
182
|
+
if not isinstance(metadata, Mapping):
|
|
183
|
+
metadata = None
|
|
181
184
|
return self.catalog.write(
|
|
182
185
|
codex,
|
|
183
186
|
CodexModelCatalogSpec(
|
|
@@ -186,16 +189,42 @@ class CodexLaunchConfigurationService:
|
|
|
186
189
|
context_window=context_window,
|
|
187
190
|
effort=str(provider_config.get("effort_level") or "").strip().lower(),
|
|
188
191
|
auto_compact_token_limit=auto_compact_token_limit,
|
|
192
|
+
metadata=metadata,
|
|
189
193
|
),
|
|
190
194
|
catalog_env,
|
|
191
195
|
)
|
|
192
196
|
|
|
197
|
+
def auto_compact_config_args(self, cfg: dict[str, Any]) -> list[str]:
|
|
198
|
+
"""Move Codex's own compaction trigger to the operator's threshold.
|
|
199
|
+
|
|
200
|
+
A session that crosses providers carries history built under whatever
|
|
201
|
+
window was in force at the time. Codex compacts on its own, but only
|
|
202
|
+
once its configured limit is reached — and by then a history grown
|
|
203
|
+
under a larger window no longer fits the smaller one, so the compaction
|
|
204
|
+
request itself is refused and the turn cannot recover.
|
|
205
|
+
|
|
206
|
+
Passing the threshold keeps that trigger where the operator wants it
|
|
207
|
+
rather than claiming a window size of our own: it only decides when
|
|
208
|
+
Codex compacts, never how much context it believes it has.
|
|
209
|
+
"""
|
|
210
|
+
|
|
211
|
+
_provider, provider_config = self.model.current_provider(cfg)
|
|
212
|
+
try:
|
|
213
|
+
limit = int(provider_config.get("codex_auto_compact_window"))
|
|
214
|
+
except (TypeError, ValueError):
|
|
215
|
+
return []
|
|
216
|
+
if limit <= 0:
|
|
217
|
+
return []
|
|
218
|
+
return ["-c", f"model_auto_compact_token_limit={limit}"]
|
|
219
|
+
|
|
193
220
|
def runtime_model_catalog_args(
|
|
194
221
|
self, codex: str, cfg: dict[str, Any]
|
|
195
222
|
) -> list[str]:
|
|
196
223
|
path = self.write_runtime_model_catalog(codex, cfg)
|
|
197
224
|
if path is None:
|
|
198
|
-
|
|
225
|
+
# A native provider keeps its own bundled catalog; only the
|
|
226
|
+
# compaction threshold is ours to set.
|
|
227
|
+
return self.auto_compact_config_args(cfg)
|
|
199
228
|
value = self.policy.toml_string(str(path.resolve()))
|
|
200
229
|
return ["-c", f"model_catalog_json={value}"]
|
|
201
230
|
|