@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.
- package/ciel_runtime.py +2553 -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/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 +239 -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 +82 -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/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/workspace_router_selection.py +86 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""Durable streamable-HTTP channel session bounded context."""
|
|
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
|
+
from .channel_session_lifecycle import (
|
|
10
|
+
ChannelSessionLifecycleServices,
|
|
11
|
+
cleanup_stale_channel_sessions,
|
|
12
|
+
delete_channel_session,
|
|
13
|
+
)
|
|
14
|
+
from .channel_session_repository import ChannelSessionRepository
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class ChannelSessionConfigPorts:
|
|
19
|
+
config_dir: Callable[[], Path]
|
|
20
|
+
protocol_version: str
|
|
21
|
+
log: Callable[[str, str], None]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(frozen=True, slots=True)
|
|
25
|
+
class ChannelSessionHttpPorts:
|
|
26
|
+
streamable_headers: Callable[..., dict[str, str]]
|
|
27
|
+
error_body: Callable[..., str]
|
|
28
|
+
session_not_found: Callable[..., bool]
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class ChannelSessionStatePorts:
|
|
33
|
+
connections: dict[str, dict[str, Any]]
|
|
34
|
+
lock: Any
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class ChannelSessionContext:
|
|
39
|
+
config: ChannelSessionConfigPorts
|
|
40
|
+
http: ChannelSessionHttpPorts
|
|
41
|
+
state: ChannelSessionStatePorts
|
|
42
|
+
|
|
43
|
+
def path(self) -> Path:
|
|
44
|
+
return self.config.config_dir() / "channel-streamable-sessions.json"
|
|
45
|
+
|
|
46
|
+
def repository(self) -> ChannelSessionRepository:
|
|
47
|
+
return ChannelSessionRepository(
|
|
48
|
+
path=self.path(),
|
|
49
|
+
default_protocol_version=self.config.protocol_version,
|
|
50
|
+
log=self.config.log,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
def services(self) -> ChannelSessionLifecycleServices:
|
|
54
|
+
return ChannelSessionLifecycleServices(
|
|
55
|
+
streamable_headers=self.http.streamable_headers,
|
|
56
|
+
http_error_body=self.http.error_body,
|
|
57
|
+
session_not_found=self.http.session_not_found,
|
|
58
|
+
records=self.records,
|
|
59
|
+
forget=self.forget,
|
|
60
|
+
log=self.config.log,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def records(self) -> list[dict[str, Any]]:
|
|
64
|
+
return self.repository().records()
|
|
65
|
+
|
|
66
|
+
def write(self, records: list[dict[str, Any]]) -> None:
|
|
67
|
+
self.repository().write(records)
|
|
68
|
+
|
|
69
|
+
def record(
|
|
70
|
+
self,
|
|
71
|
+
name: str,
|
|
72
|
+
url: str,
|
|
73
|
+
session_id: str | None,
|
|
74
|
+
protocol_version: str,
|
|
75
|
+
) -> None:
|
|
76
|
+
self.repository().record(name, url, session_id, protocol_version)
|
|
77
|
+
|
|
78
|
+
def forget(self, name: str, url: str, session_id: str | None) -> None:
|
|
79
|
+
del name, url
|
|
80
|
+
self.repository().forget(session_id)
|
|
81
|
+
|
|
82
|
+
def delete(
|
|
83
|
+
self,
|
|
84
|
+
name: str,
|
|
85
|
+
endpoint: str,
|
|
86
|
+
headers: dict[str, str],
|
|
87
|
+
protocol_version: str,
|
|
88
|
+
session_id: str | None,
|
|
89
|
+
reason: str,
|
|
90
|
+
*,
|
|
91
|
+
timeout: float = 5.0,
|
|
92
|
+
) -> bool:
|
|
93
|
+
return delete_channel_session(
|
|
94
|
+
name,
|
|
95
|
+
endpoint,
|
|
96
|
+
headers,
|
|
97
|
+
protocol_version,
|
|
98
|
+
session_id,
|
|
99
|
+
reason,
|
|
100
|
+
self.services(),
|
|
101
|
+
default_protocol_version=self.config.protocol_version,
|
|
102
|
+
timeout=timeout,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
def close_state(self, state: dict[str, Any], reason: str) -> bool:
|
|
106
|
+
if str(state.get("transport") or "").strip().lower() not in {
|
|
107
|
+
"http",
|
|
108
|
+
"streamable-http",
|
|
109
|
+
}:
|
|
110
|
+
return True
|
|
111
|
+
name = str(state.get("name") or "")
|
|
112
|
+
endpoint = str(state.get("mcp_endpoint") or state.get("url") or "")
|
|
113
|
+
session_id = str(state.get("mcp_session_id") or "").strip() or None
|
|
114
|
+
headers = dict(state.get("headers") or {})
|
|
115
|
+
protocol_version = str(
|
|
116
|
+
state.get("mcp_protocol_version") or self.config.protocol_version
|
|
117
|
+
)
|
|
118
|
+
ok = self.delete(
|
|
119
|
+
name,
|
|
120
|
+
endpoint,
|
|
121
|
+
headers,
|
|
122
|
+
protocol_version,
|
|
123
|
+
session_id,
|
|
124
|
+
reason,
|
|
125
|
+
)
|
|
126
|
+
if ok:
|
|
127
|
+
with self.state.lock:
|
|
128
|
+
current = self.state.connections.get(name)
|
|
129
|
+
same_session = current and str(
|
|
130
|
+
current.get("mcp_session_id") or ""
|
|
131
|
+
) == str(session_id or "")
|
|
132
|
+
if current is state or same_session:
|
|
133
|
+
current["mcp_session_id"] = None
|
|
134
|
+
current["mcp_initialized"] = False
|
|
135
|
+
return ok
|
|
136
|
+
|
|
137
|
+
def cleanup_stale(
|
|
138
|
+
self,
|
|
139
|
+
name: str,
|
|
140
|
+
url: str,
|
|
141
|
+
headers: dict[str, str],
|
|
142
|
+
protocol_version: str,
|
|
143
|
+
*,
|
|
144
|
+
keep_session_id: str | None = None,
|
|
145
|
+
) -> None:
|
|
146
|
+
cleanup_stale_channel_sessions(
|
|
147
|
+
name,
|
|
148
|
+
url,
|
|
149
|
+
headers,
|
|
150
|
+
protocol_version,
|
|
151
|
+
self.services(),
|
|
152
|
+
default_protocol_version=self.config.protocol_version,
|
|
153
|
+
keep_session_id=keep_session_id,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
@dataclass(frozen=True, slots=True)
|
|
158
|
+
class ChannelSessionCompatibilityApi:
|
|
159
|
+
context: Callable[[], ChannelSessionContext]
|
|
160
|
+
|
|
161
|
+
def path(self) -> Path:
|
|
162
|
+
return self.context().path()
|
|
163
|
+
|
|
164
|
+
def repository(self) -> ChannelSessionRepository:
|
|
165
|
+
return self.context().repository()
|
|
166
|
+
|
|
167
|
+
def services(self) -> ChannelSessionLifecycleServices:
|
|
168
|
+
return self.context().services()
|
|
169
|
+
|
|
170
|
+
def records(self) -> list[dict[str, Any]]:
|
|
171
|
+
return self.context().records()
|
|
172
|
+
|
|
173
|
+
def write(self, records: list[dict[str, Any]]) -> None:
|
|
174
|
+
self.context().write(records)
|
|
175
|
+
|
|
176
|
+
def record(
|
|
177
|
+
self,
|
|
178
|
+
name: str,
|
|
179
|
+
url: str,
|
|
180
|
+
session_id: str | None,
|
|
181
|
+
protocol_version: str,
|
|
182
|
+
) -> None:
|
|
183
|
+
self.context().record(name, url, session_id, protocol_version)
|
|
184
|
+
|
|
185
|
+
def forget(self, name: str, url: str, session_id: str | None) -> None:
|
|
186
|
+
self.context().forget(name, url, session_id)
|
|
187
|
+
|
|
188
|
+
def delete(
|
|
189
|
+
self,
|
|
190
|
+
name: str,
|
|
191
|
+
endpoint: str,
|
|
192
|
+
headers: dict[str, str],
|
|
193
|
+
protocol_version: str,
|
|
194
|
+
session_id: str | None,
|
|
195
|
+
reason: str,
|
|
196
|
+
*,
|
|
197
|
+
timeout: float = 5.0,
|
|
198
|
+
) -> bool:
|
|
199
|
+
return self.context().delete(
|
|
200
|
+
name,
|
|
201
|
+
endpoint,
|
|
202
|
+
headers,
|
|
203
|
+
protocol_version,
|
|
204
|
+
session_id,
|
|
205
|
+
reason,
|
|
206
|
+
timeout=timeout,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def close_state(self, state: dict[str, Any], reason: str) -> bool:
|
|
210
|
+
return self.context().close_state(state, reason)
|
|
211
|
+
|
|
212
|
+
def cleanup_stale(
|
|
213
|
+
self,
|
|
214
|
+
name: str,
|
|
215
|
+
url: str,
|
|
216
|
+
headers: dict[str, str],
|
|
217
|
+
protocol_version: str,
|
|
218
|
+
*,
|
|
219
|
+
keep_session_id: str | None = None,
|
|
220
|
+
) -> None:
|
|
221
|
+
self.context().cleanup_stale(
|
|
222
|
+
name,
|
|
223
|
+
url,
|
|
224
|
+
headers,
|
|
225
|
+
protocol_version,
|
|
226
|
+
keep_session_id=keep_session_id,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
__all__ = [
|
|
231
|
+
"ChannelSessionCompatibilityApi",
|
|
232
|
+
"ChannelSessionConfigPorts",
|
|
233
|
+
"ChannelSessionContext",
|
|
234
|
+
"ChannelSessionHttpPorts",
|
|
235
|
+
"ChannelSessionStatePorts",
|
|
236
|
+
]
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
"""Terminal channel process orchestration bounded context."""
|
|
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
|
+
from .channel_terminal_dispatch import (
|
|
10
|
+
ChannelDirectProcessPorts,
|
|
11
|
+
ChannelTerminalDispatchService,
|
|
12
|
+
ChannelTerminalDispatchSettings,
|
|
13
|
+
ChannelTerminalProxyPorts,
|
|
14
|
+
)
|
|
15
|
+
from .channel_terminal_proxy import (
|
|
16
|
+
ChannelTerminalIO,
|
|
17
|
+
ChannelTerminalPolicy,
|
|
18
|
+
ChannelTerminalPolling,
|
|
19
|
+
ChannelTerminalProcess,
|
|
20
|
+
ChannelTerminalServices,
|
|
21
|
+
ChannelWindowsConsole,
|
|
22
|
+
ChannelWindowsServices,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True, slots=True)
|
|
27
|
+
class ChannelTerminalProcessPorts:
|
|
28
|
+
popen: Callable[..., Any]
|
|
29
|
+
write_record: Callable[[Path | None, int, list[str]], None]
|
|
30
|
+
terminate: Callable[[Any, str], None]
|
|
31
|
+
release_record: Callable[[Path | None, int], None]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class ChannelTerminalPolicyPorts:
|
|
36
|
+
initial_cursor: Callable[[], int]
|
|
37
|
+
enter_bytes: Callable[[str | bytes | None], bytes]
|
|
38
|
+
enter_label: Callable[[bytes], str]
|
|
39
|
+
enter_is_fixed: Callable[[], bool]
|
|
40
|
+
unseen_retry_seconds: Callable[[], float]
|
|
41
|
+
inflight_is_stale: Callable[..., bool]
|
|
42
|
+
log: Callable[[str, str], None]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass(frozen=True, slots=True)
|
|
46
|
+
class ChannelTerminalPollingPorts:
|
|
47
|
+
inject_compact: Callable[..., Any]
|
|
48
|
+
file_marker: Callable[[], Any]
|
|
49
|
+
should_check: Callable[..., bool]
|
|
50
|
+
active_tool_call: Callable[..., bool]
|
|
51
|
+
inject_pending: Callable[..., Any]
|
|
52
|
+
wake_state: Callable[[int], Any]
|
|
53
|
+
inflight_effects: Callable[[], Any]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class ChannelTerminalIoPorts:
|
|
58
|
+
terminal_size: Callable[[int], tuple[int, int]]
|
|
59
|
+
apply_terminal_size: Callable[[int, int, int], bool]
|
|
60
|
+
write_all: Callable[[Any, bytes], None]
|
|
61
|
+
mouse_filter: Callable[[], Any]
|
|
62
|
+
observed_enter: Callable[..., bytes | None]
|
|
63
|
+
reset_input_mode: Callable[[], None]
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass(frozen=True, slots=True)
|
|
67
|
+
class ChannelTerminalWindowsPorts:
|
|
68
|
+
input_supported: Callable[[], bool]
|
|
69
|
+
run_proxy: Callable[..., int]
|
|
70
|
+
reset_input_mode: Callable[[], None]
|
|
71
|
+
mouse_guard: Callable[[], Any]
|
|
72
|
+
input_writer: Callable[[], Any]
|
|
73
|
+
startup_grace_seconds: Callable[[], float]
|
|
74
|
+
reset_interval_seconds: Callable[[float], float]
|
|
75
|
+
active_turn: Callable[[], bool]
|
|
76
|
+
retry_submit: Callable[..., tuple[int, float]]
|
|
77
|
+
sleep: Callable[[float], None]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass(frozen=True, slots=True)
|
|
81
|
+
class ChannelTerminalDispatchPorts:
|
|
82
|
+
platform_name: str
|
|
83
|
+
stdin_isatty: Callable[[], bool]
|
|
84
|
+
stdout_isatty: Callable[[], bool]
|
|
85
|
+
direct_call: Callable[..., int]
|
|
86
|
+
run_windows: Callable[..., int]
|
|
87
|
+
run_posix: Callable[..., int]
|
|
88
|
+
prepare_delivery: Callable[[], Any]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@dataclass(frozen=True, slots=True)
|
|
92
|
+
class ChannelTerminalContext:
|
|
93
|
+
process: ChannelTerminalProcessPorts
|
|
94
|
+
policy: ChannelTerminalPolicyPorts
|
|
95
|
+
polling: ChannelTerminalPollingPorts
|
|
96
|
+
io: ChannelTerminalIoPorts
|
|
97
|
+
windows: ChannelTerminalWindowsPorts
|
|
98
|
+
dispatch_ports: ChannelTerminalDispatchPorts
|
|
99
|
+
|
|
100
|
+
def process_services(self) -> ChannelTerminalProcess:
|
|
101
|
+
return ChannelTerminalProcess(
|
|
102
|
+
popen=self.process.popen,
|
|
103
|
+
write_child_record=self.process.write_record,
|
|
104
|
+
terminate_child=self.process.terminate,
|
|
105
|
+
release_child_record=self.process.release_record,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def policy_services(self) -> ChannelTerminalPolicy:
|
|
109
|
+
return ChannelTerminalPolicy(
|
|
110
|
+
initial_cursor=self.policy.initial_cursor,
|
|
111
|
+
enter_bytes=self.policy.enter_bytes,
|
|
112
|
+
enter_label=self.policy.enter_label,
|
|
113
|
+
enter_is_fixed=self.policy.enter_is_fixed,
|
|
114
|
+
unseen_retry_seconds=self.policy.unseen_retry_seconds,
|
|
115
|
+
inflight_is_stale=self.policy.inflight_is_stale,
|
|
116
|
+
log=self.policy.log,
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
def polling_services(self) -> ChannelTerminalPolling:
|
|
120
|
+
return ChannelTerminalPolling(
|
|
121
|
+
inject_compact=self.polling.inject_compact,
|
|
122
|
+
file_marker=self.polling.file_marker,
|
|
123
|
+
should_check=self.polling.should_check,
|
|
124
|
+
active_tool_call=self.polling.active_tool_call,
|
|
125
|
+
inject_pending=self.polling.inject_pending,
|
|
126
|
+
wake_state=self.polling.wake_state,
|
|
127
|
+
inflight_effects=self.polling.inflight_effects,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
def posix_services(self) -> ChannelTerminalServices:
|
|
131
|
+
return ChannelTerminalServices(
|
|
132
|
+
process=self.process_services(),
|
|
133
|
+
io=ChannelTerminalIO(
|
|
134
|
+
terminal_size=self.io.terminal_size,
|
|
135
|
+
apply_terminal_size=self.io.apply_terminal_size,
|
|
136
|
+
write_all=self.io.write_all,
|
|
137
|
+
mouse_filter=self.io.mouse_filter,
|
|
138
|
+
observed_enter=self.io.observed_enter,
|
|
139
|
+
reset_input_mode=self.io.reset_input_mode,
|
|
140
|
+
),
|
|
141
|
+
policy=self.policy_services(),
|
|
142
|
+
polling=self.polling_services(),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
def windows_services(self) -> ChannelWindowsServices:
|
|
146
|
+
return ChannelWindowsServices(
|
|
147
|
+
process=self.process_services(),
|
|
148
|
+
policy=self.policy_services(),
|
|
149
|
+
polling=self.polling_services(),
|
|
150
|
+
console=ChannelWindowsConsole(
|
|
151
|
+
reset_input_mode=self.windows.reset_input_mode,
|
|
152
|
+
mouse_guard=self.windows.mouse_guard,
|
|
153
|
+
input_writer=self.windows.input_writer,
|
|
154
|
+
startup_grace_seconds=self.windows.startup_grace_seconds,
|
|
155
|
+
reset_interval_seconds=self.windows.reset_interval_seconds,
|
|
156
|
+
active_turn=self.windows.active_turn,
|
|
157
|
+
retry_submit=self.windows.retry_submit,
|
|
158
|
+
sleep=self.windows.sleep,
|
|
159
|
+
),
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
def run_windows(
|
|
163
|
+
self,
|
|
164
|
+
cmd: list[str],
|
|
165
|
+
env: dict[str, str],
|
|
166
|
+
**options: Any,
|
|
167
|
+
) -> int:
|
|
168
|
+
options.pop("normalize_bare_cr_for_synthetic_enter", None)
|
|
169
|
+
options.pop("channel_wake_bracketed_paste", None)
|
|
170
|
+
return self.windows.run_proxy(
|
|
171
|
+
cmd,
|
|
172
|
+
env,
|
|
173
|
+
self.windows_services(),
|
|
174
|
+
**options,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def dispatch_service(self) -> ChannelTerminalDispatchService:
|
|
178
|
+
return ChannelTerminalDispatchService(
|
|
179
|
+
settings=ChannelTerminalDispatchSettings(
|
|
180
|
+
platform_name=self.dispatch_ports.platform_name,
|
|
181
|
+
stdin_isatty=self.dispatch_ports.stdin_isatty,
|
|
182
|
+
stdout_isatty=self.dispatch_ports.stdout_isatty,
|
|
183
|
+
),
|
|
184
|
+
proxy=ChannelTerminalProxyPorts(
|
|
185
|
+
windows_supported=self.windows.input_supported,
|
|
186
|
+
run_windows=self.dispatch_ports.run_windows,
|
|
187
|
+
run_posix=self.dispatch_ports.run_posix,
|
|
188
|
+
posix_services=self.posix_services,
|
|
189
|
+
),
|
|
190
|
+
direct=ChannelDirectProcessPorts(
|
|
191
|
+
call=self.dispatch_ports.direct_call,
|
|
192
|
+
popen=self.process.popen,
|
|
193
|
+
write_record=self.process.write_record,
|
|
194
|
+
terminate=self.process.terminate,
|
|
195
|
+
release_record=self.process.release_record,
|
|
196
|
+
),
|
|
197
|
+
log=self.policy.log,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def dispatch(
|
|
201
|
+
self,
|
|
202
|
+
cmd: list[str],
|
|
203
|
+
env: dict[str, str],
|
|
204
|
+
*,
|
|
205
|
+
inject_channel_messages: bool = True,
|
|
206
|
+
inject_web_chat_only: bool = False,
|
|
207
|
+
wake_for_llm_delivery: bool = False,
|
|
208
|
+
synthetic_enter_bytes: str | bytes | None = None,
|
|
209
|
+
normalize_bare_cr_for_synthetic_enter: bool = True,
|
|
210
|
+
channel_wake_submit_retries: int = 1,
|
|
211
|
+
channel_wake_confirm_submit: bool = False,
|
|
212
|
+
channel_wake_bracketed_paste: bool = False,
|
|
213
|
+
channel_wake_submit_delay_seconds: float | None = None,
|
|
214
|
+
tracked_child_pid_path: Path | None = None,
|
|
215
|
+
) -> int:
|
|
216
|
+
if inject_channel_messages:
|
|
217
|
+
self.dispatch_ports.prepare_delivery()
|
|
218
|
+
return self.dispatch_service().dispatch(
|
|
219
|
+
cmd,
|
|
220
|
+
env,
|
|
221
|
+
inject_channel_messages=inject_channel_messages,
|
|
222
|
+
inject_web_chat_only=inject_web_chat_only,
|
|
223
|
+
wake_for_llm_delivery=wake_for_llm_delivery,
|
|
224
|
+
synthetic_enter_bytes=synthetic_enter_bytes,
|
|
225
|
+
normalize_bare_cr_for_synthetic_enter=(
|
|
226
|
+
normalize_bare_cr_for_synthetic_enter
|
|
227
|
+
),
|
|
228
|
+
channel_wake_submit_retries=channel_wake_submit_retries,
|
|
229
|
+
channel_wake_confirm_submit=channel_wake_confirm_submit,
|
|
230
|
+
channel_wake_bracketed_paste=channel_wake_bracketed_paste,
|
|
231
|
+
channel_wake_submit_delay_seconds=channel_wake_submit_delay_seconds,
|
|
232
|
+
tracked_child_pid_path=tracked_child_pid_path,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
def call_direct(
|
|
236
|
+
self,
|
|
237
|
+
cmd: list[str],
|
|
238
|
+
env: dict[str, str],
|
|
239
|
+
pid_path: Path | None = None,
|
|
240
|
+
) -> int:
|
|
241
|
+
return self.dispatch_service().call_direct(cmd, env, pid_path)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
@dataclass(frozen=True, slots=True)
|
|
245
|
+
class ChannelTerminalCompatibilityApi:
|
|
246
|
+
context: Callable[[], ChannelTerminalContext]
|
|
247
|
+
|
|
248
|
+
def process_services(self) -> ChannelTerminalProcess:
|
|
249
|
+
return self.context().process_services()
|
|
250
|
+
|
|
251
|
+
def policy_services(self) -> ChannelTerminalPolicy:
|
|
252
|
+
return self.context().policy_services()
|
|
253
|
+
|
|
254
|
+
def polling_services(self) -> ChannelTerminalPolling:
|
|
255
|
+
return self.context().polling_services()
|
|
256
|
+
|
|
257
|
+
def posix_services(self) -> ChannelTerminalServices:
|
|
258
|
+
return self.context().posix_services()
|
|
259
|
+
|
|
260
|
+
def windows_services(self) -> ChannelWindowsServices:
|
|
261
|
+
return self.context().windows_services()
|
|
262
|
+
|
|
263
|
+
def dispatch_service(self) -> ChannelTerminalDispatchService:
|
|
264
|
+
return self.context().dispatch_service()
|
|
265
|
+
|
|
266
|
+
def run_windows(
|
|
267
|
+
self,
|
|
268
|
+
cmd: list[str],
|
|
269
|
+
env: dict[str, str],
|
|
270
|
+
*,
|
|
271
|
+
inject_channel_messages: bool = True,
|
|
272
|
+
inject_web_chat_only: bool = False,
|
|
273
|
+
wake_for_llm_delivery: bool = False,
|
|
274
|
+
synthetic_enter_bytes: str | bytes | None = None,
|
|
275
|
+
normalize_bare_cr_for_synthetic_enter: bool = True,
|
|
276
|
+
channel_wake_submit_retries: int = 1,
|
|
277
|
+
channel_wake_confirm_submit: bool = False,
|
|
278
|
+
channel_wake_bracketed_paste: bool = False,
|
|
279
|
+
channel_wake_submit_delay_seconds: float | None = None,
|
|
280
|
+
tracked_child_pid_path: Path | None = None,
|
|
281
|
+
) -> int:
|
|
282
|
+
return self.context().run_windows(
|
|
283
|
+
cmd,
|
|
284
|
+
env,
|
|
285
|
+
inject_channel_messages=inject_channel_messages,
|
|
286
|
+
inject_web_chat_only=inject_web_chat_only,
|
|
287
|
+
wake_for_llm_delivery=wake_for_llm_delivery,
|
|
288
|
+
synthetic_enter_bytes=synthetic_enter_bytes,
|
|
289
|
+
normalize_bare_cr_for_synthetic_enter=(
|
|
290
|
+
normalize_bare_cr_for_synthetic_enter
|
|
291
|
+
),
|
|
292
|
+
channel_wake_submit_retries=channel_wake_submit_retries,
|
|
293
|
+
channel_wake_confirm_submit=channel_wake_confirm_submit,
|
|
294
|
+
channel_wake_bracketed_paste=channel_wake_bracketed_paste,
|
|
295
|
+
channel_wake_submit_delay_seconds=channel_wake_submit_delay_seconds,
|
|
296
|
+
tracked_child_pid_path=tracked_child_pid_path,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def dispatch(
|
|
300
|
+
self,
|
|
301
|
+
cmd: list[str],
|
|
302
|
+
env: dict[str, str],
|
|
303
|
+
*,
|
|
304
|
+
inject_channel_messages: bool = True,
|
|
305
|
+
inject_web_chat_only: bool = False,
|
|
306
|
+
wake_for_llm_delivery: bool = False,
|
|
307
|
+
synthetic_enter_bytes: str | bytes | None = None,
|
|
308
|
+
normalize_bare_cr_for_synthetic_enter: bool = True,
|
|
309
|
+
channel_wake_submit_retries: int = 1,
|
|
310
|
+
channel_wake_confirm_submit: bool = False,
|
|
311
|
+
channel_wake_bracketed_paste: bool = False,
|
|
312
|
+
channel_wake_submit_delay_seconds: float | None = None,
|
|
313
|
+
tracked_child_pid_path: Path | None = None,
|
|
314
|
+
) -> int:
|
|
315
|
+
return self.context().dispatch(
|
|
316
|
+
cmd,
|
|
317
|
+
env,
|
|
318
|
+
inject_channel_messages=inject_channel_messages,
|
|
319
|
+
inject_web_chat_only=inject_web_chat_only,
|
|
320
|
+
wake_for_llm_delivery=wake_for_llm_delivery,
|
|
321
|
+
synthetic_enter_bytes=synthetic_enter_bytes,
|
|
322
|
+
normalize_bare_cr_for_synthetic_enter=(
|
|
323
|
+
normalize_bare_cr_for_synthetic_enter
|
|
324
|
+
),
|
|
325
|
+
channel_wake_submit_retries=channel_wake_submit_retries,
|
|
326
|
+
channel_wake_confirm_submit=channel_wake_confirm_submit,
|
|
327
|
+
channel_wake_bracketed_paste=channel_wake_bracketed_paste,
|
|
328
|
+
channel_wake_submit_delay_seconds=channel_wake_submit_delay_seconds,
|
|
329
|
+
tracked_child_pid_path=tracked_child_pid_path,
|
|
330
|
+
)
|
|
331
|
+
|
|
332
|
+
def call_direct(
|
|
333
|
+
self,
|
|
334
|
+
cmd: list[str],
|
|
335
|
+
env: dict[str, str],
|
|
336
|
+
pid_path: Path | None = None,
|
|
337
|
+
) -> int:
|
|
338
|
+
return self.context().call_direct(cmd, env, pid_path)
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
__all__ = [
|
|
342
|
+
"ChannelTerminalCompatibilityApi",
|
|
343
|
+
"ChannelTerminalContext",
|
|
344
|
+
"ChannelTerminalDispatchPorts",
|
|
345
|
+
"ChannelTerminalIoPorts",
|
|
346
|
+
"ChannelTerminalPolicyPorts",
|
|
347
|
+
"ChannelTerminalPollingPorts",
|
|
348
|
+
"ChannelTerminalProcessPorts",
|
|
349
|
+
"ChannelTerminalWindowsPorts",
|
|
350
|
+
]
|