@oneciel-ai/ciel-runtime 0.2.37 → 0.2.38
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/CHANGELOG.md +78 -0
- package/README.md +5 -2
- package/ciel_runtime.py +94 -96
- package/ciel_runtime_support/anthropic_model_policy.py +20 -3
- package/ciel_runtime_support/architecture.py +5 -0
- package/ciel_runtime_support/channel_inflight.py +4 -4
- package/ciel_runtime_support/channel_injection.py +56 -1
- package/ciel_runtime_support/channel_mcp_tools.py +34 -0
- package/ciel_runtime_support/channel_message_policy.py +9 -1
- package/ciel_runtime_support/channel_message_prompt.py +23 -0
- package/ciel_runtime_support/channel_pending_injection.py +86 -32
- package/ciel_runtime_support/channel_pending_poll.py +6 -2
- package/ciel_runtime_support/channel_runtime_environment.py +0 -11
- package/ciel_runtime_support/channel_terminal_context.py +0 -6
- package/ciel_runtime_support/channel_terminal_proxy.py +3 -42
- package/ciel_runtime_support/channel_transcript_repository.py +16 -1
- package/ciel_runtime_support/channel_wake_claim_repository.py +0 -19
- package/ciel_runtime_support/channel_wake_context.py +6 -13
- package/ciel_runtime_support/channel_wake_delivery_repository.py +50 -0
- package/ciel_runtime_support/chat_http_controller.py +88 -5
- package/ciel_runtime_support/claude_environment.py +100 -3
- package/ciel_runtime_support/claude_launch_assembly.py +4 -0
- package/ciel_runtime_support/claude_session_socket.py +181 -0
- package/ciel_runtime_support/cli_application_context.py +18 -10
- package/ciel_runtime_support/cli_dispatch.py +19 -3
- package/ciel_runtime_support/cli_parser.py +2 -0
- package/ciel_runtime_support/cli_usage.py +3 -2
- package/ciel_runtime_support/codex_backend_context.py +8 -3
- package/ciel_runtime_support/codex_completion_gate.py +198 -0
- package/ciel_runtime_support/codex_turn_recovery.py +240 -83
- package/ciel_runtime_support/compatibility_protocol.py +5 -2
- package/ciel_runtime_support/config_migrations.py +79 -0
- package/ciel_runtime_support/config_repository.py +7 -0
- package/ciel_runtime_support/context_summary_policy.py +24 -4
- package/ciel_runtime_support/external_event_menu.py +107 -0
- package/ciel_runtime_support/external_event_receiver.py +19 -1
- package/ciel_runtime_support/launch_state.py +2 -0
- package/ciel_runtime_support/managed_tool_injection.py +33 -0
- package/ciel_runtime_support/muse_runtime_context.py +291 -0
- package/ciel_runtime_support/ollama_thinking.py +12 -26
- package/ciel_runtime_support/prelaunch.py +13 -0
- package/ciel_runtime_support/prelaunch_launch_panel.py +2 -1
- package/ciel_runtime_support/prompt_compaction.py +39 -0
- package/ciel_runtime_support/provider_files_proxy.py +248 -0
- package/ciel_runtime_support/provider_model_identity.py +10 -2
- package/ciel_runtime_support/provider_option_cli.py +1 -1
- package/ciel_runtime_support/provider_request_access.py +54 -1
- package/ciel_runtime_support/provider_responses_passthrough.py +152 -15
- package/ciel_runtime_support/providers/alibaba.py +34 -1
- package/ciel_runtime_support/providers/anthropic.py +18 -1
- package/ciel_runtime_support/providers/meta.py +252 -21
- package/ciel_runtime_support/pseudo_tool_parser.py +17 -1
- package/ciel_runtime_support/remote_bridge.py +4 -0
- package/ciel_runtime_support/remote_instructions.py +5 -0
- package/ciel_runtime_support/remote_memory.py +1 -1
- package/ciel_runtime_support/responses_cache_diagnostics.py +82 -0
- package/ciel_runtime_support/responses_custom_tool_bridge.py +282 -0
- package/ciel_runtime_support/responses_input_compatibility.py +19 -3
- package/ciel_runtime_support/router_http.py +301 -4
- package/ciel_runtime_support/router_observability_context.py +26 -0
- package/ciel_runtime_support/router_server_context.py +1 -0
- package/ciel_runtime_support/runtime_adapters.py +31 -0
- package/ciel_runtime_support/runtime_constants.py +23 -1
- package/ciel_runtime_support/runtime_input_gateway.py +85 -5
- package/ciel_runtime_support/runtime_input_status.py +150 -0
- package/ciel_runtime_support/runtime_launch.py +27 -4
- package/ciel_runtime_support/runtime_paths.py +1 -0
- package/ciel_runtime_support/speech_http_controller.py +3 -2
- package/ciel_runtime_support/streaming_anthropic.py +3 -0
- package/ciel_runtime_support/tool_call_events.py +98 -0
- package/ciel_runtime_support/transcript_delta_delivery.py +140 -1
- package/ciel_runtime_support/ui_text.py +1 -0
- package/ciel_runtime_support/web_search_result_events.py +130 -0
- package/ciel_runtime_support/web_ui.py +1 -0
- package/ciel_runtime_support/windows_conpty.py +92 -8
- package/ciel_runtime_support/workspace_mcp.py +4 -1
- package/ciel_runtime_support/workspace_state.py +1 -0
- package/docs/Configuration.md +20 -4
- package/docs/MCP-Channels.md +79 -6
- package/docs/Managed-Tool-Injection.md +25 -0
- package/docs/Module-Map.md +4 -0
- package/docs/Muse-Code.md +73 -0
- package/docs/Observability.md +39 -0
- package/docs/Providers.md +29 -2
- package/docs/Remote-Bridge.md +12 -0
- package/docs/Router.md +1 -0
- package/docs/journal/2026/09/01/diagnostics/anthropic/router/fable51-usage-credits.okf +70 -0
- package/docs/journal/2026/09/01/implementation/claude/session-socket/all-input-paths/default-delivery.okf +133 -0
- package/docs/journal/2026/09/01/implementation/claude/session-socket/windows-direct-input.okf +112 -0
- package/docs/journal/2026/09/02/diagnostics/cache/alibaba/qwen38-post-restart-hit-rate.okf +107 -0
- package/docs/journal/2026/09/02/diagnostics/cache/ollama-cloud/kimi-k3-codex-hit-rate.okf +91 -0
- package/docs/journal/2026/09/02/diagnostics/codex/tui/statusline-cache-metrics.okf +44 -0
- package/docs/journal/2026/09/02/diagnostics/input/transport/fallback-behavior.okf +31 -0
- package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-agent-turn-recovery.okf +125 -0
- package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-parameters.okf +128 -0
- package/docs/journal/2026/09/02/diagnostics/remote/mia/socket-tui-visibility.okf +47 -0
- package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-latency.okf +108 -0
- package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-vs-gpt56-latency.okf +92 -0
- package/docs/journal/2026/09/02/implementation/anthropic/context/one-million-defaults.okf +85 -0
- package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.okf +104 -0
- package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.png +0 -0
- package/docs/journal/2026/09/02/implementation/cache/alibaba/responses-session-cache.okf +97 -0
- package/docs/journal/2026/09/02/implementation/providers/alibaba/qwen38/0902-parameter-alignment.okf +97 -0
- package/docs/journal/2026/09/02/implementation/providers/meta/multimodal-tools/contributor-protocol-adoption.okf +140 -0
- package/docs/journal/2026/09/02/implementation/providers/meta/muse-spark-1.3-support.okf +53 -0
- package/docs/journal/2026/09/02/monitoring/cache/alibaba/cielarvis-qwen38-live-hit-rate.okf +858 -0
- package/docs/journal/2026/09/02/operations/release/main-merge-local-deploy.okf +36 -0
- package/docs/journal/2026/09/02/operations/release/nightly/local-cache-deployment.okf +54 -0
- package/docs/journal/2026/09/02/operations/release/nightly/ollama-kimi-recovery-model-audit-deployment.okf +58 -0
- package/docs/journal/2026/09/02/operations/release/nightly/qwen38-cache-improvement-deployment.okf +72 -0
- package/docs/journal/2026/09/02/research/cache/alibaba/qwen38-hit-rate-improvement.okf +233 -0
- package/docs/journal/2026/09/02/research/cache/codex/provider-scope.okf +77 -0
- package/docs/journal/2026/09/02/research/providers/ollama-cloud/desktop-model-exhaustive-audit.okf +159 -0
- package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-cache-hit.okf +148 -0
- package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-required-schema.okf +127 -0
- package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-resumed-session-stall.okf +160 -0
- package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-substantive-dangling-action.okf +94 -0
- package/docs/journal/2026/09/03/diagnostics/remote/kevin/codex-gpt-early-turn-completion.okf +162 -0
- package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/default-yolo.okf +40 -0
- package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/native-subscription-router.okf +86 -0
- package/docs/journal/2026/09/03/operations/deployment/local/session-socket-default-fallback.okf +41 -0
- package/docs/journal/2026/09/03/research/deployment/colab/tailscale/credential-storage.okf +41 -0
- package/docs/journal/2026/09/03/research/providers/meta/muse-code/subscription-billing-boundary.okf +64 -0
- package/docs/journal/2026/09/03/research/web-chat/voice/instruction-format.okf +135 -0
- package/docs/journal/2026/09/04/diagnostics/claude/context/early-auto-compaction.okf +75 -0
- package/docs/journal/2026/09/04/diagnostics/claude/context/fable-51-status-200k.okf +54 -0
- package/docs/journal/2026/09/04/diagnostics/runtime/codex/responses/replayed-item-id-validation.okf +74 -0
- package/docs/journal/2026/09/04/diagnostics/workspaces/onecieldmsui/codex/replay-stall.okf +100 -0
- package/docs/journal/2026/09/04/implementation/observability/tool-calls/websocket-stream.okf +49 -0
- package/docs/journal/2026/09/04/implementation/web-chat/input/raw-injection.okf +42 -0
- package/docs/journal/2026/09/05/diagnostics/tty/claude-raw-idle-render.okf +25 -0
- package/docs/journal/2026/09/05/implementation/events/search-response-urls.okf +23 -0
- package/docs/journal/2026/09/05/implementation/tools/native-injection.okf +19 -0
- package/docs/journal/2026/09/05/implementation/windows/conpty/prompt-delivery-lifecycle.okf +43 -0
- package/docs/journal/2026/09/05/release/nightly/conpty-native-tools.okf +13 -0
- package/docs/journal/2026/09/05/release/nightly/search-result-urls.okf +14 -0
- package/docs/journal/2026/09/06/implementation/codex/inherited-web-mcp.okf +22 -0
- package/docs/journal/2026/09/07/integration/main/nightly-merge.okf +13 -0
- package/docs/journal/2026/09/07/release/stable/0.2.38.okf +10 -0
- package/package.json +1 -1
|
@@ -17,6 +17,42 @@ from typing import Any, Callable
|
|
|
17
17
|
class RuntimeInputGateway:
|
|
18
18
|
append: Callable[[dict[str, Any]], dict[str, Any]]
|
|
19
19
|
project_attachment: Callable[[dict[str, Any]], dict[str, Any]] | None = None
|
|
20
|
+
default_input_transport: Callable[[], str] = lambda: "session_socket"
|
|
21
|
+
status: Any = None
|
|
22
|
+
|
|
23
|
+
def _append(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
24
|
+
message = self.append(payload)
|
|
25
|
+
request_id = int(message.get("id") or 0)
|
|
26
|
+
if (
|
|
27
|
+
request_id > 0
|
|
28
|
+
and not message.get("_ciel_runtime_duplicate")
|
|
29
|
+
and self.status is not None
|
|
30
|
+
):
|
|
31
|
+
self.status.transition(
|
|
32
|
+
request_id,
|
|
33
|
+
"queued",
|
|
34
|
+
data={
|
|
35
|
+
"channel": str(message.get("channel") or "default"),
|
|
36
|
+
"input_transport": str(
|
|
37
|
+
(message.get("meta") or {}).get("input_transport") or ""
|
|
38
|
+
),
|
|
39
|
+
},
|
|
40
|
+
)
|
|
41
|
+
return message
|
|
42
|
+
|
|
43
|
+
def _transport(self, value: Any = None) -> str:
|
|
44
|
+
transport = str(value or self.default_input_transport() or "session_socket").strip().lower().replace("-", "_")
|
|
45
|
+
aliases = {
|
|
46
|
+
"socket": "session_socket",
|
|
47
|
+
"claude_socket": "session_socket",
|
|
48
|
+
"messaging_socket": "session_socket",
|
|
49
|
+
"llm": "router",
|
|
50
|
+
"context": "router",
|
|
51
|
+
"terminal": "tty",
|
|
52
|
+
"stdin": "tty",
|
|
53
|
+
}
|
|
54
|
+
transport = aliases.get(transport, transport)
|
|
55
|
+
return transport if transport in {"session_socket", "router", "tty"} else "tty"
|
|
20
56
|
|
|
21
57
|
def submit_web_chat(
|
|
22
58
|
self,
|
|
@@ -30,6 +66,10 @@ class RuntimeInputGateway:
|
|
|
30
66
|
meta.setdefault("source", "ciel-runtime-web-chat")
|
|
31
67
|
meta.setdefault("source_kind", "web_chat")
|
|
32
68
|
meta.setdefault("injection_mode", "structured")
|
|
69
|
+
meta.setdefault(
|
|
70
|
+
"input_transport",
|
|
71
|
+
self._transport(inbound.get("input_transport") or meta.get("input_transport")),
|
|
72
|
+
)
|
|
33
73
|
meta.setdefault("response_mode", "web_chat")
|
|
34
74
|
if meta["response_mode"] == "web_chat":
|
|
35
75
|
meta["reply_parent_id"] = public_message.get("id")
|
|
@@ -58,7 +98,7 @@ class RuntimeInputGateway:
|
|
|
58
98
|
"delivery": ["llm"],
|
|
59
99
|
"visibility": "private_runtime",
|
|
60
100
|
}
|
|
61
|
-
return self.
|
|
101
|
+
return self._append(payload)
|
|
62
102
|
|
|
63
103
|
def submit_tty(
|
|
64
104
|
self,
|
|
@@ -94,6 +134,9 @@ class RuntimeInputGateway:
|
|
|
94
134
|
meta["source"] = "ciel-runtime-api-tty"
|
|
95
135
|
meta["source_kind"] = "tty_input"
|
|
96
136
|
meta["injection_mode"] = "tty"
|
|
137
|
+
meta["input_transport"] = self._transport(
|
|
138
|
+
meta.get("input_transport") or body.get("input_transport")
|
|
139
|
+
)
|
|
97
140
|
meta["response_mode"] = response_mode
|
|
98
141
|
|
|
99
142
|
content: Any = params.get("content")
|
|
@@ -103,7 +146,7 @@ class RuntimeInputGateway:
|
|
|
103
146
|
content = json.dumps(content, ensure_ascii=False, separators=(",", ":"))
|
|
104
147
|
meta["content_type"] = "application/json"
|
|
105
148
|
|
|
106
|
-
return self.
|
|
149
|
+
return self._append(
|
|
107
150
|
{
|
|
108
151
|
"channel": body.get("channel") or meta.get("channel") or "default",
|
|
109
152
|
"sender_id": body.get("sender_id") or body.get("sender") or "api-user",
|
|
@@ -126,10 +169,14 @@ class RuntimeInputGateway:
|
|
|
126
169
|
meta = dict(meta_value) if isinstance(meta_value, dict) else {}
|
|
127
170
|
meta.setdefault("source", "ciel-runtime-notify")
|
|
128
171
|
meta.setdefault("source_kind", "notification")
|
|
172
|
+
meta.setdefault(
|
|
173
|
+
"input_transport",
|
|
174
|
+
self._transport(body.get("input_transport") or meta.get("input_transport")),
|
|
175
|
+
)
|
|
129
176
|
content = params.get("content")
|
|
130
177
|
if content is None:
|
|
131
178
|
content = body.get("content", body.get("message", body.get("text", "")))
|
|
132
|
-
return self.
|
|
179
|
+
return self._append(
|
|
133
180
|
{
|
|
134
181
|
"channel": body.get("channel") or meta.get("channel") or "default",
|
|
135
182
|
"sender_id": body.get("sender_id") or body.get("sender") or body.get("server") or meta.get("source") or "channel",
|
|
@@ -144,6 +191,36 @@ class RuntimeInputGateway:
|
|
|
144
191
|
}
|
|
145
192
|
)
|
|
146
193
|
|
|
194
|
+
def submit_stream_input(self, body: dict[str, Any]) -> dict[str, Any]:
|
|
195
|
+
"""Admit an external Streamable HTTP/MCP input into the runtime queue."""
|
|
196
|
+
|
|
197
|
+
meta = dict(body.get("meta") or {}) if isinstance(body.get("meta"), dict) else {}
|
|
198
|
+
meta.setdefault("source", "ciel-runtime-mcp-streamable")
|
|
199
|
+
meta.setdefault("source_kind", "mcp_streamable_input")
|
|
200
|
+
meta.setdefault("injection_mode", "structured")
|
|
201
|
+
meta["input_transport"] = self._transport(
|
|
202
|
+
body.get("input_transport") or meta.get("input_transport")
|
|
203
|
+
)
|
|
204
|
+
meta.setdefault("response_mode", str(body.get("response_mode") or "tty"))
|
|
205
|
+
content = body.get("message", body.get("content", body.get("text", "")))
|
|
206
|
+
if isinstance(content, (dict, list)):
|
|
207
|
+
content = json.dumps(content, ensure_ascii=False, separators=(",", ":"))
|
|
208
|
+
meta["content_type"] = "application/json"
|
|
209
|
+
return self._append(
|
|
210
|
+
{
|
|
211
|
+
"channel": body.get("channel") or "mcp-streamable",
|
|
212
|
+
"sender_id": body.get("sender_id") or body.get("sender") or "mcp-client",
|
|
213
|
+
"recipients": body.get("recipients", "all"),
|
|
214
|
+
"thread_id": body.get("thread_id"),
|
|
215
|
+
"parent_id": body.get("parent_id"),
|
|
216
|
+
"kind": body.get("kind") or "mcp_streamable_input",
|
|
217
|
+
"message": content if content is not None else "",
|
|
218
|
+
"meta": meta,
|
|
219
|
+
"delivery": ["llm"],
|
|
220
|
+
"visibility": "private_runtime",
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
|
|
147
224
|
def submit_external_event(
|
|
148
225
|
self,
|
|
149
226
|
raw_event: str,
|
|
@@ -154,10 +231,11 @@ class RuntimeInputGateway:
|
|
|
154
231
|
event_type: str,
|
|
155
232
|
event_source: str,
|
|
156
233
|
transport_event_id: str = "",
|
|
234
|
+
input_transport: str = "",
|
|
157
235
|
) -> dict[str, Any]:
|
|
158
236
|
"""Admit exact decoded event text; never normalize or re-serialize it."""
|
|
159
237
|
|
|
160
|
-
return self.
|
|
238
|
+
return self._append(
|
|
161
239
|
{
|
|
162
240
|
"channel": f"external:{receiver_id}",
|
|
163
241
|
"sender_id": event_source or receiver_id,
|
|
@@ -174,6 +252,7 @@ class RuntimeInputGateway:
|
|
|
174
252
|
"event_type": event_type,
|
|
175
253
|
"event_source": event_source,
|
|
176
254
|
"transport_event_id": transport_event_id,
|
|
255
|
+
"input_transport": self._transport(input_transport),
|
|
177
256
|
"raw_preserved": True,
|
|
178
257
|
},
|
|
179
258
|
"delivery": ["llm"],
|
|
@@ -216,7 +295,7 @@ class RuntimeInputGateway:
|
|
|
216
295
|
"to read only the needed range. This is input-only telemetry; no acknowledgement "
|
|
217
296
|
f"or response is required. Stored ranges: {locations}"
|
|
218
297
|
)
|
|
219
|
-
return self.
|
|
298
|
+
return self._append(
|
|
220
299
|
{
|
|
221
300
|
"channel": "telemetry",
|
|
222
301
|
"sender_id": "otlp",
|
|
@@ -231,6 +310,7 @@ class RuntimeInputGateway:
|
|
|
231
310
|
"ack_required": False,
|
|
232
311
|
"response_expected": False,
|
|
233
312
|
"logs_embedded": False,
|
|
313
|
+
"input_transport": self._transport(),
|
|
234
314
|
},
|
|
235
315
|
"delivery": ["llm"],
|
|
236
316
|
"visibility": "private_runtime",
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Durable lifecycle state for private runtime input requests."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
import json
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
|
+
from typing import Any, Callable
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
RUNTIME_INPUT_STATES = frozenset({"queued", "submitted", "replied", "failed"})
|
|
15
|
+
_TERMINAL_STATES = frozenset({"replied", "failed"})
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass(slots=True)
|
|
19
|
+
class RuntimeInputStatusRepository:
|
|
20
|
+
path: Path
|
|
21
|
+
publish_event: Callable[..., Any]
|
|
22
|
+
log: Callable[[str, str], None]
|
|
23
|
+
lock: threading.Lock | threading.RLock
|
|
24
|
+
|
|
25
|
+
def transition(
|
|
26
|
+
self,
|
|
27
|
+
request_id: int,
|
|
28
|
+
status: str,
|
|
29
|
+
*,
|
|
30
|
+
reason: str = "",
|
|
31
|
+
data: dict[str, Any] | None = None,
|
|
32
|
+
) -> dict[str, Any]:
|
|
33
|
+
request_id = int(request_id)
|
|
34
|
+
status = str(status or "").strip().lower()
|
|
35
|
+
if request_id <= 0:
|
|
36
|
+
raise ValueError("request_id must be positive")
|
|
37
|
+
if status not in RUNTIME_INPUT_STATES:
|
|
38
|
+
raise ValueError(f"invalid runtime input status: {status}")
|
|
39
|
+
with self.lock:
|
|
40
|
+
current = self.get(request_id, _locked=True)
|
|
41
|
+
current_status = str((current or {}).get("status") or "")
|
|
42
|
+
if current_status in _TERMINAL_STATES or current_status == status:
|
|
43
|
+
return current or self._record(request_id, status, reason, data)
|
|
44
|
+
allowed = {
|
|
45
|
+
"": {"queued"},
|
|
46
|
+
"queued": {"submitted", "failed"},
|
|
47
|
+
"submitted": {"replied", "failed"},
|
|
48
|
+
}
|
|
49
|
+
if status not in allowed.get(current_status, set()):
|
|
50
|
+
self.log(
|
|
51
|
+
"WARN",
|
|
52
|
+
"runtime_input_status_transition_rejected "
|
|
53
|
+
f"request_id={request_id} from={current_status or '-'} to={status}",
|
|
54
|
+
)
|
|
55
|
+
return current or {}
|
|
56
|
+
record = self._record(request_id, status, reason, data)
|
|
57
|
+
self.publish_event(
|
|
58
|
+
level="error" if status == "failed" else "info",
|
|
59
|
+
category="runtime_input.status",
|
|
60
|
+
message=f"runtime input {status}",
|
|
61
|
+
source="runtime-input",
|
|
62
|
+
request_id=str(request_id),
|
|
63
|
+
data=record,
|
|
64
|
+
)
|
|
65
|
+
return record
|
|
66
|
+
|
|
67
|
+
def _record(
|
|
68
|
+
self,
|
|
69
|
+
request_id: int,
|
|
70
|
+
status: str,
|
|
71
|
+
reason: str,
|
|
72
|
+
data: dict[str, Any] | None,
|
|
73
|
+
) -> dict[str, Any]:
|
|
74
|
+
now = time.time()
|
|
75
|
+
record: dict[str, Any] = {
|
|
76
|
+
"request_id": request_id,
|
|
77
|
+
"status": status,
|
|
78
|
+
"updated_at": datetime.now(timezone.utc).astimezone().isoformat(timespec="seconds"),
|
|
79
|
+
"updated_at_epoch": now,
|
|
80
|
+
}
|
|
81
|
+
if reason:
|
|
82
|
+
record["reason"] = str(reason)
|
|
83
|
+
if data:
|
|
84
|
+
record["data"] = dict(data)
|
|
85
|
+
self.path.parent.mkdir(parents=True, exist_ok=True)
|
|
86
|
+
with self.path.open("a", encoding="utf-8") as stream:
|
|
87
|
+
stream.write(json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n")
|
|
88
|
+
stream.flush()
|
|
89
|
+
return record
|
|
90
|
+
|
|
91
|
+
def get(self, request_id: int, *, _locked: bool = False) -> dict[str, Any] | None:
|
|
92
|
+
def read() -> dict[str, Any] | None:
|
|
93
|
+
latest = None
|
|
94
|
+
for item in self._items():
|
|
95
|
+
try:
|
|
96
|
+
if int(item.get("request_id") or 0) == int(request_id):
|
|
97
|
+
latest = item
|
|
98
|
+
except (TypeError, ValueError):
|
|
99
|
+
continue
|
|
100
|
+
return latest
|
|
101
|
+
|
|
102
|
+
if _locked:
|
|
103
|
+
return read()
|
|
104
|
+
with self.lock:
|
|
105
|
+
return read()
|
|
106
|
+
|
|
107
|
+
def list_latest(
|
|
108
|
+
self,
|
|
109
|
+
*,
|
|
110
|
+
after_request_id: int = 0,
|
|
111
|
+
status: str = "",
|
|
112
|
+
limit: int = 100,
|
|
113
|
+
) -> list[dict[str, Any]]:
|
|
114
|
+
with self.lock:
|
|
115
|
+
latest: dict[int, dict[str, Any]] = {}
|
|
116
|
+
for item in self._items():
|
|
117
|
+
try:
|
|
118
|
+
request_id = int(item.get("request_id") or 0)
|
|
119
|
+
except (TypeError, ValueError):
|
|
120
|
+
continue
|
|
121
|
+
if request_id > after_request_id:
|
|
122
|
+
latest[request_id] = item
|
|
123
|
+
expected = str(status or "").strip().lower()
|
|
124
|
+
rows = [latest[key] for key in sorted(latest)]
|
|
125
|
+
if expected:
|
|
126
|
+
rows = [row for row in rows if row.get("status") == expected]
|
|
127
|
+
return rows[: max(1, min(500, int(limit)))]
|
|
128
|
+
|
|
129
|
+
def _items(self) -> list[dict[str, Any]]:
|
|
130
|
+
if not self.path.exists():
|
|
131
|
+
return []
|
|
132
|
+
items: list[dict[str, Any]] = []
|
|
133
|
+
try:
|
|
134
|
+
with self.path.open("r", encoding="utf-8") as stream:
|
|
135
|
+
for line in stream:
|
|
136
|
+
try:
|
|
137
|
+
item = json.loads(line)
|
|
138
|
+
except (json.JSONDecodeError, TypeError):
|
|
139
|
+
continue
|
|
140
|
+
if isinstance(item, dict):
|
|
141
|
+
items.append(item)
|
|
142
|
+
except OSError as exc:
|
|
143
|
+
self.log(
|
|
144
|
+
"WARN",
|
|
145
|
+
f"runtime_input_status_read_failed error={type(exc).__name__}: {exc}",
|
|
146
|
+
)
|
|
147
|
+
return items
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
__all__ = ["RUNTIME_INPUT_STATES", "RuntimeInputStatusRepository"]
|
|
@@ -8,6 +8,7 @@ from typing import Any, Callable
|
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
|
|
10
10
|
from ciel_runtime_support.claude_environment import CLAUDE_PROJECTED_ENV_KEYS
|
|
11
|
+
from ciel_runtime_support.managed_tool_injection import should_inject_tool, codex_native_web_tool_overrides
|
|
11
12
|
from ciel_runtime_support.runtime_constants import (
|
|
12
13
|
CLAUDE_SERVER_SIDE_WEB_TOOLS,
|
|
13
14
|
CODEX_RUNTIME_API_KEY_ENV,
|
|
@@ -151,6 +152,8 @@ class ClaudeLaunchChannelDelivery:
|
|
|
151
152
|
channel_wake_submit_delay_seconds: Callable[..., Any]
|
|
152
153
|
channel_wake_submit_retries: Callable[..., Any]
|
|
153
154
|
set_channel_transcript_scope: Callable[..., Any]
|
|
155
|
+
prepare_session_socket: Callable[..., str]
|
|
156
|
+
configure_session_socket: Callable[[str | None], None]
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
@dataclass(frozen=True, slots=True)
|
|
@@ -245,6 +248,8 @@ def run_claude(
|
|
|
245
248
|
channel_wake_submit_delay_seconds = services.channel_delivery.channel_wake_submit_delay_seconds
|
|
246
249
|
channel_wake_submit_retries = services.channel_delivery.channel_wake_submit_retries
|
|
247
250
|
set_channel_transcript_scope = services.channel_delivery.set_channel_transcript_scope
|
|
251
|
+
prepare_session_socket = services.channel_delivery.prepare_session_socket
|
|
252
|
+
configure_session_socket = services.channel_delivery.configure_session_socket
|
|
248
253
|
start_router_if_needed = services.routing.start_router_if_needed
|
|
249
254
|
subprocess_call_with_channel_wake_proxy = services.process.subprocess_call_with_channel_wake_proxy
|
|
250
255
|
subprocess_call_with_child_pid_record = services.process.subprocess_call_with_child_pid_record
|
|
@@ -330,6 +335,11 @@ def run_claude(
|
|
|
330
335
|
if use_native_anthropic and web_backend_start_requested(cfg):
|
|
331
336
|
llm_channel_delivery = True
|
|
332
337
|
stdin_channel_proxy = not has_noninteractive_claude_args(launch_passthrough)
|
|
338
|
+
session_socket_target = (
|
|
339
|
+
prepare_session_socket(cfg, launch_passthrough)
|
|
340
|
+
if stdin_channel_proxy
|
|
341
|
+
else ""
|
|
342
|
+
)
|
|
333
343
|
manage_router_lifetime = False
|
|
334
344
|
if use_router_mode or llm_channel_delivery:
|
|
335
345
|
manage_router_lifetime = bool(start_router_if_needed())
|
|
@@ -454,7 +464,7 @@ def run_claude(
|
|
|
454
464
|
zai_mcp_config = write_zai_mcp_config(provider, pcfg)
|
|
455
465
|
if zai_mcp_config:
|
|
456
466
|
mcp_config_paths.append(str(zai_mcp_config))
|
|
457
|
-
if should_attach_web_search(provider, cfg, web_search_override):
|
|
467
|
+
if should_inject_tool(native=provider == "anthropic", mode="non_native") and should_attach_web_search(provider, cfg, web_search_override):
|
|
458
468
|
mcp_config_paths.append(str(write_duckduckgo_mcp_config(cfg)))
|
|
459
469
|
workspace_mcp_launch = (
|
|
460
470
|
workspace_mcp.prepare(
|
|
@@ -463,6 +473,7 @@ def run_claude(
|
|
|
463
473
|
"transport": "streamable_http", "url": f"{ROUTER_BASE}/ca/mcp",
|
|
464
474
|
"runtimes": ["claude"],
|
|
465
475
|
}} if llm_channel_delivery and web_backend_start_requested(cfg) else None,
|
|
476
|
+
native=provider == "anthropic",
|
|
466
477
|
) if workspace_mcp is not None else None
|
|
467
478
|
)
|
|
468
479
|
if workspace_mcp_launch is not None:
|
|
@@ -472,7 +483,15 @@ def run_claude(
|
|
|
472
483
|
extra_args.extend(["--mcp-config", *mcp_config_paths])
|
|
473
484
|
if should_append_compat_prompt(provider, pcfg, cfg) and not has_passthrough_option(launch_passthrough, "--system-prompt"):
|
|
474
485
|
extra_args.extend(["--append-system-prompt", ROUTED_COMPAT_PROMPT])
|
|
475
|
-
|
|
486
|
+
if session_socket_target and not has_passthrough_option(
|
|
487
|
+
launch_passthrough, "--messaging-socket-path"
|
|
488
|
+
):
|
|
489
|
+
extra_args.extend(["--messaging-socket-path", session_socket_target])
|
|
490
|
+
runtime_settings_config = dict(pcfg)
|
|
491
|
+
runtime_settings_config["_ciel_session_socket_enabled"] = bool(session_socket_target)
|
|
492
|
+
append_claude_code_runtime_settings_args(
|
|
493
|
+
extra_args, launch_passthrough, provider, runtime_settings_config
|
|
494
|
+
)
|
|
476
495
|
if fork_native_session:
|
|
477
496
|
session_id = str(uuid.uuid4())
|
|
478
497
|
extra_args.extend(["--session-id", session_id])
|
|
@@ -538,6 +557,7 @@ def run_claude(
|
|
|
538
557
|
def run_claude_process() -> int:
|
|
539
558
|
rc = 1
|
|
540
559
|
try:
|
|
560
|
+
configure_session_socket(session_socket_target or None)
|
|
541
561
|
set_channel_transcript_scope(
|
|
542
562
|
"claude",
|
|
543
563
|
cwd=Path.cwd(),
|
|
@@ -573,6 +593,7 @@ def run_claude(
|
|
|
573
593
|
rc = subprocess.call(cmd, env=env)
|
|
574
594
|
return rc
|
|
575
595
|
finally:
|
|
596
|
+
configure_session_socket(None)
|
|
576
597
|
if use_router_mode:
|
|
577
598
|
print_routed_claude_exit_diagnostics(rc, provider, pcfg, log_offset=launch_log_offset)
|
|
578
599
|
|
|
@@ -902,10 +923,11 @@ def run_codex(
|
|
|
902
923
|
),
|
|
903
924
|
)
|
|
904
925
|
workspace_mcp_launch = (
|
|
905
|
-
workspace_mcp.prepare("codex", cfg, env) if workspace_mcp is not None else None
|
|
926
|
+
workspace_mcp.prepare("codex", cfg, env, native=native_codex_enabled(provider)) if workspace_mcp is not None else None
|
|
906
927
|
)
|
|
907
928
|
if workspace_mcp_launch is not None:
|
|
908
929
|
codex_mcp_compat_args.extend(workspace_mcp_launch.codex_args)
|
|
930
|
+
codex_mcp_compat_args.extend(codex_native_web_tool_overrides(native=native_codex_enabled(provider)))
|
|
909
931
|
codex_yolo_args = codex_yolo_launch_args(codex_passthrough)
|
|
910
932
|
if not use_native_codex and not use_codex_routed:
|
|
911
933
|
env[CODEX_RUNTIME_API_KEY_ENV] = env.get(CODEX_RUNTIME_API_KEY_ENV) or "ciel-runtime-router-local-key"
|
|
@@ -1232,12 +1254,13 @@ def run_codex_app_server(
|
|
|
1232
1254
|
),
|
|
1233
1255
|
)
|
|
1234
1256
|
workspace_mcp_launch = (
|
|
1235
|
-
workspace_mcp.prepare("codex-app-server", cfg, env)
|
|
1257
|
+
workspace_mcp.prepare("codex-app-server", cfg, env, native=native_codex_enabled(provider))
|
|
1236
1258
|
if workspace_mcp is not None else None
|
|
1237
1259
|
)
|
|
1238
1260
|
try:
|
|
1239
1261
|
if workspace_mcp_launch is not None:
|
|
1240
1262
|
codex_mcp_compat_args.extend(workspace_mcp_launch.codex_args)
|
|
1263
|
+
codex_mcp_compat_args.extend(codex_native_web_tool_overrides(native=native_codex_enabled(provider)))
|
|
1241
1264
|
config_args = [*config_args, *codex_mcp_compat_args]
|
|
1242
1265
|
listen_url = codex_app_server_default_listen_url()
|
|
1243
1266
|
app_server_args = codex_app_server_launch_args(
|
|
@@ -167,6 +167,7 @@ CONTEXT_USAGE_PATH = ROUTER_INSTANCE_DIR / "context-usage.json"
|
|
|
167
167
|
OLLAMA_MODEL_CATALOG_PATH = CONFIG_DIR / "ollama-model-catalog.json"
|
|
168
168
|
CHAT_MESSAGES_PATH = WORKSPACE_STATE_DIR / "chat-messages.jsonl"
|
|
169
169
|
RUNTIME_INPUTS_PATH = WORKSPACE_STATE_DIR / "runtime-inputs.jsonl"
|
|
170
|
+
RUNTIME_INPUT_STATUS_PATH = WORKSPACE_STATE_DIR / "runtime-input-status.jsonl"
|
|
170
171
|
CHAT_FILES_DIR = WORKSPACE_STATE_DIR / "chat-files"
|
|
171
172
|
MENU_KEY_DEBUG_PATH = ROUTER_INSTANCE_DIR / "ca-key-debug.log"
|
|
172
173
|
PLAN_ARTIFACTS_DIR = WORKSPACE_STATE_DIR / "plan-artifacts"
|
|
@@ -151,8 +151,9 @@ class SpeechHttpController:
|
|
|
151
151
|
"chat_message_processing": {
|
|
152
152
|
"input_mode": {"default": "structured", "allowed": ["structured", "tty"]},
|
|
153
153
|
"input_transport": {
|
|
154
|
-
"default": "tty",
|
|
155
|
-
"allowed": ["tty", "router"],
|
|
154
|
+
"default": "session_socket; tty fallback when the active runtime has no usable session socket",
|
|
155
|
+
"allowed": ["session_socket", "tty", "router"],
|
|
156
|
+
"session_socket_requires": "an active Claude session launched with its Ciel-owned messaging socket",
|
|
156
157
|
"router_requires": "an active model session routed through Ciel Router",
|
|
157
158
|
},
|
|
158
159
|
"response_mode": {"default": "web_chat", "allowed": ["web_chat", "tty", "mcp"]},
|
|
@@ -716,6 +716,7 @@ def rebatch_anthropic_sse_text(
|
|
|
716
716
|
"anthropic_stream_tool_call",
|
|
717
717
|
{
|
|
718
718
|
"model": model,
|
|
719
|
+
"tool_id": tool_id,
|
|
719
720
|
"raw_name": raw_name,
|
|
720
721
|
"matched_name": matched_name,
|
|
721
722
|
"raw_arguments": raw_args,
|
|
@@ -1656,6 +1657,7 @@ def ollama_stream_to_anthropic_sse(
|
|
|
1656
1657
|
"ollama_stream_tool_call",
|
|
1657
1658
|
{
|
|
1658
1659
|
"model": model,
|
|
1660
|
+
"tool_id": tool_id,
|
|
1659
1661
|
"raw_name": raw_name,
|
|
1660
1662
|
"matched_name": matched_name,
|
|
1661
1663
|
"raw_arguments": raw_args,
|
|
@@ -2434,6 +2436,7 @@ def forward_openai_chat_to_anthropic_sse(
|
|
|
2434
2436
|
"openai_stream_tool_call",
|
|
2435
2437
|
{
|
|
2436
2438
|
"model": model,
|
|
2439
|
+
"tool_id": tool_id,
|
|
2437
2440
|
"raw_name": raw_name,
|
|
2438
2441
|
"matched_name": matched_name,
|
|
2439
2442
|
"raw_arguments": fragment.get("arguments"),
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""Normalize tool-call records emitted by supported CLI transcript formats."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
_CODEX_CALL_TYPES = {"function_call", "custom_tool_call", "local_shell_call"}
|
|
10
|
+
_CODEX_BEGIN_TYPES = {"mcp_tool_call_begin", "tool_call_begin"}
|
|
11
|
+
_CLAUDE_CALL_TYPES = {"tool_use", "server_tool_use"}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _object(value: Any) -> dict[str, Any]:
|
|
15
|
+
return value if isinstance(value, dict) else {}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _call_id(value: dict[str, Any]) -> str:
|
|
19
|
+
for key in ("call_id", "id", "tool_call_id"):
|
|
20
|
+
text = str(value.get(key) or "").strip()
|
|
21
|
+
if text:
|
|
22
|
+
return text
|
|
23
|
+
return ""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _arguments(value: Any) -> Any:
|
|
27
|
+
if not isinstance(value, str):
|
|
28
|
+
return value
|
|
29
|
+
text = value.strip()
|
|
30
|
+
if not text:
|
|
31
|
+
return ""
|
|
32
|
+
try:
|
|
33
|
+
return json.loads(text)
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _project(value: dict[str, Any], *, call_type: str) -> dict[str, Any]:
|
|
39
|
+
name = str(value.get("name") or value.get("tool_name") or "").strip()
|
|
40
|
+
arguments = value.get("arguments")
|
|
41
|
+
if arguments is None:
|
|
42
|
+
arguments = value.get("input")
|
|
43
|
+
if arguments is None:
|
|
44
|
+
arguments = value.get("action")
|
|
45
|
+
if arguments is None:
|
|
46
|
+
arguments = value.get("args")
|
|
47
|
+
return {
|
|
48
|
+
"call_id": _call_id(value),
|
|
49
|
+
"name": name or call_type,
|
|
50
|
+
"call_type": call_type,
|
|
51
|
+
"arguments": _arguments(arguments),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def project_transcript_tool_calls(
|
|
56
|
+
record: dict[str, Any], runtime: str
|
|
57
|
+
) -> list[dict[str, Any]]:
|
|
58
|
+
"""Project one Claude/Codex JSONL record into normalized tool-call starts."""
|
|
59
|
+
|
|
60
|
+
record_type = str(record.get("type") or "").strip()
|
|
61
|
+
payload = _object(record.get("payload"))
|
|
62
|
+
payload_type = str(payload.get("type") or "").strip()
|
|
63
|
+
calls: list[dict[str, Any]] = []
|
|
64
|
+
if record_type == "response_item" and payload_type in _CODEX_CALL_TYPES:
|
|
65
|
+
calls.append(_project(payload, call_type=payload_type))
|
|
66
|
+
elif record_type == "event_msg" and payload_type in _CODEX_BEGIN_TYPES:
|
|
67
|
+
invocation = _object(payload.get("invocation")) or _object(payload.get("tool_call"))
|
|
68
|
+
calls.append(_project({**payload, **invocation}, call_type=payload_type))
|
|
69
|
+
|
|
70
|
+
muse_payload_type = str(record.get("payload_type") or "").strip()
|
|
71
|
+
if str(record.get("record_type") or "").strip() == "event" and muse_payload_type == "runtime.session":
|
|
72
|
+
muse_event = _object(payload.get("event"))
|
|
73
|
+
muse_calls = muse_event.get("tool_calls")
|
|
74
|
+
if isinstance(muse_calls, list):
|
|
75
|
+
for call in muse_calls:
|
|
76
|
+
if isinstance(call, dict):
|
|
77
|
+
calls.append(_project(call, call_type="muse_tool_call"))
|
|
78
|
+
|
|
79
|
+
message = _object(record.get("message"))
|
|
80
|
+
message_role = str(message.get("role") or "").strip()
|
|
81
|
+
if record_type == "assistant" or message_role == "assistant":
|
|
82
|
+
content = message.get("content")
|
|
83
|
+
blocks = [content] if isinstance(content, dict) else content if isinstance(content, list) else []
|
|
84
|
+
for block in blocks:
|
|
85
|
+
block_obj = _object(block)
|
|
86
|
+
block_type = str(block_obj.get("type") or "").strip()
|
|
87
|
+
if block_type in _CLAUDE_CALL_TYPES:
|
|
88
|
+
calls.append(_project(block_obj, call_type=block_type))
|
|
89
|
+
|
|
90
|
+
model = str(record.get("model") or message.get("model") or "").strip()
|
|
91
|
+
for call in calls:
|
|
92
|
+
call["runtime"] = str(runtime or "runtime")
|
|
93
|
+
if model:
|
|
94
|
+
call["model"] = model
|
|
95
|
+
return calls
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
__all__ = ["project_transcript_tool_calls"]
|