@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,394 @@
|
|
|
1
|
+
"""Prelaunch terminal shell and provider launch-preference 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, Mapping
|
|
8
|
+
|
|
9
|
+
from .architecture import ProviderAdapter, ProviderConfig, ProviderUiPolicy
|
|
10
|
+
from .prelaunch_terminal import (
|
|
11
|
+
PrelaunchInputStyle,
|
|
12
|
+
PrelaunchRenderServices,
|
|
13
|
+
TerminalSelectionServices,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass(frozen=True, slots=True)
|
|
18
|
+
class PrelaunchVisualPorts:
|
|
19
|
+
ansi: Callable[[str, str], str]
|
|
20
|
+
fit_cells: Callable[[Any, int], str]
|
|
21
|
+
stdout_isatty: Callable[[], bool]
|
|
22
|
+
render_intro: Callable[[int, str, str], list[str]]
|
|
23
|
+
app_name: str
|
|
24
|
+
credits: str
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True, slots=True)
|
|
28
|
+
class PrelaunchInputPorts:
|
|
29
|
+
enable_ansi: Callable[[], None]
|
|
30
|
+
status_lines: Callable[[], list[str]]
|
|
31
|
+
read_terminal_key: Callable[..., str]
|
|
32
|
+
write_debug: Callable[[Path, str], None]
|
|
33
|
+
debug_path: Path
|
|
34
|
+
run_select: Callable[..., int | None]
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass(frozen=True, slots=True)
|
|
38
|
+
class PrelaunchProviderPorts:
|
|
39
|
+
configured_adapter: Callable[[str, dict[str, Any]], ProviderAdapter]
|
|
40
|
+
contract_config: Callable[[str, dict[str, Any]], ProviderConfig]
|
|
41
|
+
labels: Mapping[str, str]
|
|
42
|
+
supports_runtime: Callable[[str, str], bool]
|
|
43
|
+
load_config: Callable[[], dict[str, Any]]
|
|
44
|
+
preferred_action: Callable[
|
|
45
|
+
[
|
|
46
|
+
dict[str, Any],
|
|
47
|
+
str,
|
|
48
|
+
Callable[[str], bool],
|
|
49
|
+
Callable[[str], bool],
|
|
50
|
+
Callable[[str], bool],
|
|
51
|
+
],
|
|
52
|
+
str,
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class PrelaunchPromptPorts:
|
|
58
|
+
render_services: PrelaunchRenderServices
|
|
59
|
+
input_style: PrelaunchInputStyle
|
|
60
|
+
render_screen: Callable[..., bool]
|
|
61
|
+
read_value_raw: Callable[..., str | None]
|
|
62
|
+
read_value: Callable[..., str]
|
|
63
|
+
read_multiline_raw: Callable[..., str | None]
|
|
64
|
+
read_multiline: Callable[..., str]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True, slots=True)
|
|
68
|
+
class PrelaunchShellContext:
|
|
69
|
+
visual: PrelaunchVisualPorts
|
|
70
|
+
input: PrelaunchInputPorts
|
|
71
|
+
provider: PrelaunchProviderPorts
|
|
72
|
+
prompt: PrelaunchPromptPorts
|
|
73
|
+
main_menu_actions: tuple[str, ...]
|
|
74
|
+
|
|
75
|
+
def color_line(self, text: str, code: str, width: int) -> str:
|
|
76
|
+
return self.visual.ansi(self.visual.fit_cells(text, width), code)
|
|
77
|
+
|
|
78
|
+
def clean_render_lines(self, lines: list[str], width: int) -> list[str]:
|
|
79
|
+
return [self.visual.fit_cells(line, width) for line in lines]
|
|
80
|
+
|
|
81
|
+
def clear_screen(self) -> None:
|
|
82
|
+
if self.visual.stdout_isatty():
|
|
83
|
+
print("\033[2J\033[H", end="")
|
|
84
|
+
|
|
85
|
+
def intro_panel_lines(self, width: int) -> list[str]:
|
|
86
|
+
return self.visual.render_intro(
|
|
87
|
+
width, self.visual.app_name, self.visual.credits
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
def print_intro_panel(self, width: int) -> None:
|
|
91
|
+
print("\n".join(self.intro_panel_lines(width)))
|
|
92
|
+
|
|
93
|
+
def append_menu_key_debug_log(self, line: str) -> None:
|
|
94
|
+
self.input.write_debug(self.input.debug_path, line)
|
|
95
|
+
|
|
96
|
+
def read_menu_key(self, fd: int | None = None) -> str:
|
|
97
|
+
return self.input.read_terminal_key(
|
|
98
|
+
fd, debug_log=self.append_menu_key_debug_log
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
def portable_select(
|
|
102
|
+
self,
|
|
103
|
+
title: str,
|
|
104
|
+
rows: list[str],
|
|
105
|
+
current: int = 0,
|
|
106
|
+
footer: str = "",
|
|
107
|
+
info_lines: list[str] | None = None,
|
|
108
|
+
show_intro: bool = False,
|
|
109
|
+
) -> int | None:
|
|
110
|
+
return self.input.run_select(
|
|
111
|
+
title,
|
|
112
|
+
rows,
|
|
113
|
+
current,
|
|
114
|
+
footer,
|
|
115
|
+
info_lines,
|
|
116
|
+
show_intro,
|
|
117
|
+
services=TerminalSelectionServices(
|
|
118
|
+
enable_ansi=self.input.enable_ansi,
|
|
119
|
+
ansi=self.visual.ansi,
|
|
120
|
+
intro_panel_lines=self.intro_panel_lines,
|
|
121
|
+
status_lines=self.input.status_lines,
|
|
122
|
+
read_key=self.read_menu_key,
|
|
123
|
+
),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
def compact_text(self, value: Any, width: int = 72) -> str:
|
|
127
|
+
return self.visual.fit_cells(value, width)
|
|
128
|
+
|
|
129
|
+
def provider_ui_policy(
|
|
130
|
+
self, provider: str, config: dict[str, Any]
|
|
131
|
+
) -> ProviderUiPolicy:
|
|
132
|
+
adapter = self.provider.configured_adapter(provider, config)
|
|
133
|
+
return adapter.ui_policy(self.provider.contract_config(provider, config))
|
|
134
|
+
|
|
135
|
+
def provider_menu_label(self, provider: str, config: dict[str, Any]) -> str:
|
|
136
|
+
policy = self.provider_ui_policy(provider, config)
|
|
137
|
+
if config.get("route_through_router") and policy.routed_menu_label:
|
|
138
|
+
return policy.routed_menu_label
|
|
139
|
+
return policy.menu_label or self.provider.labels.get(provider, provider)
|
|
140
|
+
|
|
141
|
+
def current_provider_panel_choice(
|
|
142
|
+
self, provider: str, config: dict[str, Any]
|
|
143
|
+
) -> str:
|
|
144
|
+
policy = self.provider_ui_policy(provider, config)
|
|
145
|
+
if config.get("route_through_router") and policy.routed_choice:
|
|
146
|
+
return policy.routed_choice
|
|
147
|
+
return policy.native_choice or provider
|
|
148
|
+
|
|
149
|
+
def launch_enabled(self, runtime: str, provider: str) -> bool:
|
|
150
|
+
return self.provider.supports_runtime(runtime, provider)
|
|
151
|
+
|
|
152
|
+
def default_prelaunch_action(self, provider: str) -> str:
|
|
153
|
+
config = self.provider.load_config()
|
|
154
|
+
if provider == "kimi":
|
|
155
|
+
remembered = str(config.get("last_launch_action") or "").strip()
|
|
156
|
+
if remembered == "launch":
|
|
157
|
+
return remembered
|
|
158
|
+
if remembered in {"launch-codex", "launch-codex-app-server"}:
|
|
159
|
+
return remembered
|
|
160
|
+
return "launch-kimi"
|
|
161
|
+
return self.provider.preferred_action(
|
|
162
|
+
config,
|
|
163
|
+
provider,
|
|
164
|
+
lambda name: self.launch_enabled("agy", name),
|
|
165
|
+
lambda name: self.launch_enabled("claude", name),
|
|
166
|
+
lambda name: self.launch_enabled("codex", name),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
def prelaunch_action_index(self, action: str) -> int:
|
|
170
|
+
try:
|
|
171
|
+
return self.main_menu_actions.index(action)
|
|
172
|
+
except ValueError:
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
def prelaunch_render_services(self) -> PrelaunchRenderServices:
|
|
176
|
+
return self.prompt.render_services
|
|
177
|
+
|
|
178
|
+
def prelaunch_input_style(self) -> PrelaunchInputStyle:
|
|
179
|
+
return self.prompt.input_style
|
|
180
|
+
|
|
181
|
+
def render_prelaunch_screen(
|
|
182
|
+
self,
|
|
183
|
+
main_index: int,
|
|
184
|
+
panel: str | None,
|
|
185
|
+
panel_index: int,
|
|
186
|
+
panel_rows: list[str],
|
|
187
|
+
checks: list[str],
|
|
188
|
+
messages: list[str],
|
|
189
|
+
first_render: bool,
|
|
190
|
+
) -> bool:
|
|
191
|
+
return self.prompt.render_screen(
|
|
192
|
+
main_index,
|
|
193
|
+
panel,
|
|
194
|
+
panel_index,
|
|
195
|
+
panel_rows,
|
|
196
|
+
checks,
|
|
197
|
+
messages,
|
|
198
|
+
first_render,
|
|
199
|
+
services=self.prompt.render_services,
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
def prompt_menu_value_raw(
|
|
203
|
+
self, label: str, default: str = "", secret: bool = False
|
|
204
|
+
) -> str | None:
|
|
205
|
+
return self.prompt.read_value_raw(
|
|
206
|
+
label, default, secret, style=self.prompt.input_style
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def prompt_menu_value(
|
|
210
|
+
self,
|
|
211
|
+
prompt: str,
|
|
212
|
+
default: str = "",
|
|
213
|
+
secret: bool = False,
|
|
214
|
+
restore_tty: Callable[[], None] | None = None,
|
|
215
|
+
raw_tty: Callable[[], None] | None = None,
|
|
216
|
+
) -> str:
|
|
217
|
+
return self.prompt.read_value(
|
|
218
|
+
prompt,
|
|
219
|
+
default,
|
|
220
|
+
secret,
|
|
221
|
+
restore_tty,
|
|
222
|
+
raw_tty,
|
|
223
|
+
style=self.prompt.input_style,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
def prompt_menu_multiline_value_raw(
|
|
227
|
+
self, label: str, secret: bool = False
|
|
228
|
+
) -> str | None:
|
|
229
|
+
return self.prompt.read_multiline_raw(
|
|
230
|
+
label, secret, style=self.prompt.input_style
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def prompt_menu_multiline_value(
|
|
234
|
+
self,
|
|
235
|
+
prompt: str,
|
|
236
|
+
restore_tty: Callable[[], None] | None = None,
|
|
237
|
+
raw_tty: Callable[[], None] | None = None,
|
|
238
|
+
secret: bool = True,
|
|
239
|
+
) -> str:
|
|
240
|
+
return self.prompt.read_multiline(
|
|
241
|
+
prompt,
|
|
242
|
+
restore_tty,
|
|
243
|
+
raw_tty,
|
|
244
|
+
secret,
|
|
245
|
+
style=self.prompt.input_style,
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@dataclass(frozen=True, slots=True)
|
|
250
|
+
class PrelaunchShellCompatibilityApi:
|
|
251
|
+
context: Callable[[], PrelaunchShellContext]
|
|
252
|
+
|
|
253
|
+
def color_line(self, text: str, code: str, width: int) -> str:
|
|
254
|
+
return self.context().color_line(text, code, width)
|
|
255
|
+
|
|
256
|
+
def clean_render_lines(self, lines: list[str], width: int) -> list[str]:
|
|
257
|
+
return self.context().clean_render_lines(lines, width)
|
|
258
|
+
|
|
259
|
+
def clear_screen(self) -> None:
|
|
260
|
+
self.context().clear_screen()
|
|
261
|
+
|
|
262
|
+
def intro_panel_lines(self, width: int) -> list[str]:
|
|
263
|
+
return self.context().intro_panel_lines(width)
|
|
264
|
+
|
|
265
|
+
def print_intro_panel(self, width: int) -> None:
|
|
266
|
+
self.context().print_intro_panel(width)
|
|
267
|
+
|
|
268
|
+
def append_menu_key_debug_log(self, line: str) -> None:
|
|
269
|
+
self.context().append_menu_key_debug_log(line)
|
|
270
|
+
|
|
271
|
+
def read_menu_key(self, fd: int | None = None) -> str:
|
|
272
|
+
return self.context().read_menu_key(fd)
|
|
273
|
+
|
|
274
|
+
def portable_select(
|
|
275
|
+
self,
|
|
276
|
+
title: str,
|
|
277
|
+
rows: list[str],
|
|
278
|
+
current: int = 0,
|
|
279
|
+
footer: str = "",
|
|
280
|
+
info_lines: list[str] | None = None,
|
|
281
|
+
show_intro: bool = False,
|
|
282
|
+
) -> int | None:
|
|
283
|
+
return self.context().portable_select(
|
|
284
|
+
title, rows, current, footer, info_lines, show_intro
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
def compact_text(self, value: Any, width: int = 72) -> str:
|
|
288
|
+
return self.context().compact_text(value, width)
|
|
289
|
+
|
|
290
|
+
def provider_ui_policy(
|
|
291
|
+
self, provider: str, config: dict[str, Any]
|
|
292
|
+
) -> ProviderUiPolicy:
|
|
293
|
+
return self.context().provider_ui_policy(provider, config)
|
|
294
|
+
|
|
295
|
+
def provider_menu_label(self, provider: str, config: dict[str, Any]) -> str:
|
|
296
|
+
return self.context().provider_menu_label(provider, config)
|
|
297
|
+
|
|
298
|
+
def current_provider_panel_choice(
|
|
299
|
+
self, provider: str, config: dict[str, Any]
|
|
300
|
+
) -> str:
|
|
301
|
+
return self.context().current_provider_panel_choice(provider, config)
|
|
302
|
+
|
|
303
|
+
def claude_launch_enabled(
|
|
304
|
+
self, provider: str, config: dict[str, Any] | None = None
|
|
305
|
+
) -> bool:
|
|
306
|
+
del config
|
|
307
|
+
return self.context().launch_enabled("claude", provider)
|
|
308
|
+
|
|
309
|
+
def agy_launch_enabled(
|
|
310
|
+
self, provider: str, config: dict[str, Any] | None = None
|
|
311
|
+
) -> bool:
|
|
312
|
+
del config
|
|
313
|
+
return self.context().launch_enabled("agy", provider)
|
|
314
|
+
|
|
315
|
+
def codex_launch_enabled(
|
|
316
|
+
self, provider: str, config: dict[str, Any] | None = None
|
|
317
|
+
) -> bool:
|
|
318
|
+
del config
|
|
319
|
+
return self.context().launch_enabled("codex", provider)
|
|
320
|
+
|
|
321
|
+
def default_prelaunch_action(self, provider: str) -> str:
|
|
322
|
+
return self.context().default_prelaunch_action(provider)
|
|
323
|
+
|
|
324
|
+
def prelaunch_action_index(self, action: str) -> int:
|
|
325
|
+
return self.context().prelaunch_action_index(action)
|
|
326
|
+
|
|
327
|
+
def prelaunch_render_services(self) -> PrelaunchRenderServices:
|
|
328
|
+
return self.context().prelaunch_render_services()
|
|
329
|
+
|
|
330
|
+
def prelaunch_input_style(self) -> PrelaunchInputStyle:
|
|
331
|
+
return self.context().prelaunch_input_style()
|
|
332
|
+
|
|
333
|
+
def render_prelaunch_screen(
|
|
334
|
+
self,
|
|
335
|
+
main_index: int,
|
|
336
|
+
panel: str | None,
|
|
337
|
+
panel_index: int,
|
|
338
|
+
panel_rows: list[str],
|
|
339
|
+
checks: list[str],
|
|
340
|
+
messages: list[str],
|
|
341
|
+
first_render: bool,
|
|
342
|
+
) -> bool:
|
|
343
|
+
return self.context().render_prelaunch_screen(
|
|
344
|
+
main_index,
|
|
345
|
+
panel,
|
|
346
|
+
panel_index,
|
|
347
|
+
panel_rows,
|
|
348
|
+
checks,
|
|
349
|
+
messages,
|
|
350
|
+
first_render,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def prompt_menu_value_raw(
|
|
354
|
+
self, label: str, default: str = "", secret: bool = False
|
|
355
|
+
) -> str | None:
|
|
356
|
+
return self.context().prompt_menu_value_raw(label, default, secret)
|
|
357
|
+
|
|
358
|
+
def prompt_menu_value(
|
|
359
|
+
self,
|
|
360
|
+
prompt: str,
|
|
361
|
+
default: str = "",
|
|
362
|
+
secret: bool = False,
|
|
363
|
+
restore_tty: Callable[[], None] | None = None,
|
|
364
|
+
raw_tty: Callable[[], None] | None = None,
|
|
365
|
+
) -> str:
|
|
366
|
+
return self.context().prompt_menu_value(
|
|
367
|
+
prompt, default, secret, restore_tty, raw_tty
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
def prompt_menu_multiline_value_raw(
|
|
371
|
+
self, label: str, secret: bool = False
|
|
372
|
+
) -> str | None:
|
|
373
|
+
return self.context().prompt_menu_multiline_value_raw(label, secret)
|
|
374
|
+
|
|
375
|
+
def prompt_menu_multiline_value(
|
|
376
|
+
self,
|
|
377
|
+
prompt: str,
|
|
378
|
+
restore_tty: Callable[[], None] | None = None,
|
|
379
|
+
raw_tty: Callable[[], None] | None = None,
|
|
380
|
+
secret: bool = True,
|
|
381
|
+
) -> str:
|
|
382
|
+
return self.context().prompt_menu_multiline_value(
|
|
383
|
+
prompt, restore_tty, raw_tty, secret
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
__all__ = [
|
|
388
|
+
"PrelaunchInputPorts",
|
|
389
|
+
"PrelaunchProviderPorts",
|
|
390
|
+
"PrelaunchPromptPorts",
|
|
391
|
+
"PrelaunchShellCompatibilityApi",
|
|
392
|
+
"PrelaunchShellContext",
|
|
393
|
+
"PrelaunchVisualPorts",
|
|
394
|
+
]
|
|
@@ -181,6 +181,150 @@ def compact_chat_messages_for_budget(
|
|
|
181
181
|
return compacted
|
|
182
182
|
|
|
183
183
|
|
|
184
|
+
RESPONSES_CALL_TYPES = ("function_call", "custom_tool_call")
|
|
185
|
+
RESPONSES_OUTPUT_TYPES = ("function_call_output", "custom_tool_call_output")
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def responses_item_as_message(item: dict[str, Any]) -> dict[str, Any]:
|
|
189
|
+
"""Project one Responses input item into the shape the summary builder reads.
|
|
190
|
+
|
|
191
|
+
The chunked context-guard summary is written against ``role``/``content``
|
|
192
|
+
messages. Responses carries the same conversation as typed items, so the
|
|
193
|
+
projection exists only so one summariser serves both wires.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
item_type = str(item.get("type") or "")
|
|
197
|
+
if item_type == "message":
|
|
198
|
+
return {"role": str(item.get("role") or "user"), "content": item.get("content")}
|
|
199
|
+
if item_type in RESPONSES_CALL_TYPES:
|
|
200
|
+
arguments = item.get("arguments")
|
|
201
|
+
if arguments is None:
|
|
202
|
+
arguments = item.get("input")
|
|
203
|
+
return {
|
|
204
|
+
"role": "assistant",
|
|
205
|
+
"content": f"{item.get('name') or item_type}({arguments if isinstance(arguments, str) else ''})",
|
|
206
|
+
}
|
|
207
|
+
if item_type in RESPONSES_OUTPUT_TYPES:
|
|
208
|
+
return {"role": "tool", "content": item.get("output")}
|
|
209
|
+
return {"role": "assistant", "content": item_type}
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def responses_tail_is_safe(items: list[dict[str, Any]], start: int) -> bool:
|
|
213
|
+
"""Report whether cutting before ``start`` leaves every kept pair intact.
|
|
214
|
+
|
|
215
|
+
A tool output replayed without the call it answers is not valid Responses
|
|
216
|
+
input, so a tail may not begin inside a call/output pair.
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
retained_calls = {
|
|
220
|
+
str(item.get("call_id") or "")
|
|
221
|
+
for item in items[start:]
|
|
222
|
+
if str(item.get("type") or "") in RESPONSES_CALL_TYPES
|
|
223
|
+
}
|
|
224
|
+
return not any(
|
|
225
|
+
str(item.get("type") or "") in RESPONSES_OUTPUT_TYPES
|
|
226
|
+
and str(item.get("call_id") or "") not in retained_calls
|
|
227
|
+
for item in items[start:]
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def compact_responses_input_for_budget(
|
|
232
|
+
body: dict[str, Any],
|
|
233
|
+
budget_tokens: int,
|
|
234
|
+
*,
|
|
235
|
+
provider: str = "",
|
|
236
|
+
model: str = "",
|
|
237
|
+
services: PromptCompactionServices,
|
|
238
|
+
) -> dict[str, Any]:
|
|
239
|
+
"""Fit a Responses ``input`` array inside the target model's budget.
|
|
240
|
+
|
|
241
|
+
The Anthropic and chat wires have had this since the context guard was
|
|
242
|
+
written; Responses did not, so a session carried across a model change kept
|
|
243
|
+
a history the new window cannot hold. The upstream then rejects every turn
|
|
244
|
+
with ``context_length_exceeded``, and the client's own recovery drops one
|
|
245
|
+
history item per round trip, which does not converge on a transcript whose
|
|
246
|
+
bulk is a handful of very large tool outputs.
|
|
247
|
+
|
|
248
|
+
Same policy as the other wires: keep the newest items that fit, replace the
|
|
249
|
+
rest with the deterministic chunked summary, and never split a call from
|
|
250
|
+
its output.
|
|
251
|
+
"""
|
|
252
|
+
|
|
253
|
+
items = body.get("input")
|
|
254
|
+
if not isinstance(items, list) or not items:
|
|
255
|
+
return body
|
|
256
|
+
typed = [item for item in items if isinstance(item, dict)]
|
|
257
|
+
if len(typed) != len(items):
|
|
258
|
+
return body
|
|
259
|
+
text = services.text
|
|
260
|
+
runtime = services.runtime
|
|
261
|
+
budget_tokens = max(8192, budget_tokens)
|
|
262
|
+
initial_tokens = runtime.estimate_tokens(body)
|
|
263
|
+
if initial_tokens <= budget_tokens:
|
|
264
|
+
return body
|
|
265
|
+
|
|
266
|
+
summary_budget = max(1024, min(24576, budget_tokens // 10))
|
|
267
|
+
tail_budget = max(8192, budget_tokens - summary_budget)
|
|
268
|
+
tail_start = len(typed)
|
|
269
|
+
for index in range(len(typed) - 1, -1, -1):
|
|
270
|
+
candidate = dict(body)
|
|
271
|
+
candidate["input"] = typed[index:]
|
|
272
|
+
if runtime.estimate_tokens(candidate) > tail_budget:
|
|
273
|
+
break
|
|
274
|
+
tail_start = index
|
|
275
|
+
while tail_start < len(typed) and not responses_tail_is_safe(typed, tail_start):
|
|
276
|
+
tail_start += 1
|
|
277
|
+
if tail_start >= len(typed):
|
|
278
|
+
tail_start = len(typed) - 1
|
|
279
|
+
while tail_start > 0 and not responses_tail_is_safe(typed, tail_start):
|
|
280
|
+
tail_start -= 1
|
|
281
|
+
|
|
282
|
+
def projected(omitted: list[dict[str, Any]]) -> dict[str, Any]:
|
|
283
|
+
summary = text.build_summary(
|
|
284
|
+
[responses_item_as_message(item) for item in omitted], budget_tokens
|
|
285
|
+
)
|
|
286
|
+
return {
|
|
287
|
+
"type": "message",
|
|
288
|
+
"role": "user",
|
|
289
|
+
"content": [{"type": "input_text", "text": summary}],
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
tail = typed[tail_start:]
|
|
293
|
+
out = dict(body)
|
|
294
|
+
out["input"] = [projected(typed[:tail_start]), *tail]
|
|
295
|
+
while runtime.estimate_tokens(out) > budget_tokens and len(tail) > 1:
|
|
296
|
+
tail = tail[1:]
|
|
297
|
+
while tail and not responses_tail_is_safe(tail, 0):
|
|
298
|
+
tail = tail[1:]
|
|
299
|
+
if not tail:
|
|
300
|
+
tail = [typed[-1]]
|
|
301
|
+
break
|
|
302
|
+
out["input"] = [projected(typed[: len(typed) - len(tail)]), *tail]
|
|
303
|
+
final_tokens = runtime.estimate_tokens(out)
|
|
304
|
+
runtime.log(
|
|
305
|
+
"WARN",
|
|
306
|
+
f"compacted responses payload provider={provider} model={model} "
|
|
307
|
+
f"items {len(typed)}->{len(out['input'])} tokens {initial_tokens}->{final_tokens} budget={budget_tokens}",
|
|
308
|
+
)
|
|
309
|
+
omitted = typed[: len(typed) - len(tail)]
|
|
310
|
+
chunk_count = text.chunk_count(
|
|
311
|
+
[responses_item_as_message(item) for item in omitted], budget_tokens
|
|
312
|
+
)
|
|
313
|
+
if chunk_count and (provider or model):
|
|
314
|
+
runtime.write_activity(
|
|
315
|
+
provider or "provider",
|
|
316
|
+
model,
|
|
317
|
+
chunks=chunk_count,
|
|
318
|
+
parallel_sessions=1,
|
|
319
|
+
tokens=initial_tokens,
|
|
320
|
+
final_tokens=final_tokens,
|
|
321
|
+
budget=budget_tokens,
|
|
322
|
+
omitted_messages=len(omitted),
|
|
323
|
+
retained_messages=len(out["input"]),
|
|
324
|
+
)
|
|
325
|
+
return out
|
|
326
|
+
|
|
327
|
+
|
|
184
328
|
def anthropic_message_has_tool_result(message: dict[str, Any]) -> bool:
|
|
185
329
|
content = message.get("content")
|
|
186
330
|
return isinstance(content, list) and any(
|
|
@@ -174,3 +174,48 @@ def normalize_anthropic_system_role_messages(
|
|
|
174
174
|
out["messages"] = next_messages
|
|
175
175
|
out["system"] = append_anthropic_system_texts(body.get("system"), system_texts)
|
|
176
176
|
return out
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def inline_anthropic_system_role_messages(
|
|
180
|
+
body: Mapping[str, Any],
|
|
181
|
+
content_to_text: Callable[[Any], str],
|
|
182
|
+
) -> JsonObject:
|
|
183
|
+
"""Make non-standard system history wire-valid without moving its position.
|
|
184
|
+
|
|
185
|
+
Anthropic Messages only permits user/assistant roles in ``messages``. A
|
|
186
|
+
late runtime reminder must therefore be represented as user context, but
|
|
187
|
+
moving it into the leading ``system`` field would rewrite the entire cache
|
|
188
|
+
prefix on every turn.
|
|
189
|
+
"""
|
|
190
|
+
|
|
191
|
+
messages = body.get("messages")
|
|
192
|
+
if not isinstance(messages, list):
|
|
193
|
+
return dict(body)
|
|
194
|
+
changed = False
|
|
195
|
+
projected: list[Any] = []
|
|
196
|
+
for message in messages:
|
|
197
|
+
if not isinstance(message, dict) or str(message.get("role") or "").strip() != "system":
|
|
198
|
+
projected.append(message)
|
|
199
|
+
continue
|
|
200
|
+
changed = True
|
|
201
|
+
text = content_to_text(message.get("content")).strip()
|
|
202
|
+
projected.append(
|
|
203
|
+
{
|
|
204
|
+
**message,
|
|
205
|
+
"role": "user",
|
|
206
|
+
"content": f"[Runtime system context]\n{text}" if text else "[Runtime system context]",
|
|
207
|
+
}
|
|
208
|
+
)
|
|
209
|
+
if not changed:
|
|
210
|
+
return dict(body)
|
|
211
|
+
return {**body, "messages": projected}
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def normalize_anthropic_system_role_messages_by_strategy(
|
|
215
|
+
body: Mapping[str, Any],
|
|
216
|
+
strategy: str,
|
|
217
|
+
content_to_text: Callable[[Any], str],
|
|
218
|
+
) -> JsonObject:
|
|
219
|
+
if strategy == "hoist_top_level":
|
|
220
|
+
return normalize_anthropic_system_role_messages(body, content_to_text)
|
|
221
|
+
return inline_anthropic_system_role_messages(body, content_to_text)
|
|
@@ -210,7 +210,7 @@ class AnthropicThinkingPolicy:
|
|
|
210
210
|
self._ports.log(
|
|
211
211
|
"INFO",
|
|
212
212
|
"removed top-level Anthropic thinking request but preserved thinking blocks "
|
|
213
|
-
f"for
|
|
213
|
+
f"for provider reasoning passback provider={provider} "
|
|
214
214
|
f"thinking_blocks={block_count}",
|
|
215
215
|
)
|
|
216
216
|
return projected
|