@oneciel-ai/ciel-runtime 0.1.0 → 0.1.1
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-menu.py +204 -22
- package/ciel-runtime-tool-guard.py +29 -24
- package/ciel_runtime.py +6023 -2515
- package/ciel_runtime_support/agent_router.py +97 -0
- package/ciel_runtime_support/agy_cli.py +248 -0
- package/ciel_runtime_support/claude_router.py +479 -0
- package/ciel_runtime_support/codex_app_server.py +430 -0
- package/ciel_runtime_support/codex_cli.py +303 -0
- package/ciel_runtime_support/codex_router.py +80 -0
- package/docs/AGY-CLI-Research.md +62 -0
- package/docs/Architecture.md +23 -0
- package/docs/CLI-Reference.md +1 -1
- package/docs/Home.md +1 -1
- package/docs/Installation.md +9 -5
- package/docs/MCP-Channels.md +2 -4
- package/docs/Module-Map.md +29 -1
- package/docs/Providers.md +3 -2
- package/docs/Router.md +1 -1
- package/docs/Test-Suite.md +1 -0
- package/package.json +2 -3
- package/npm-bin/postinstall.js +0 -46
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
CODEX_COMMAND_NAMES = {
|
|
5
|
+
"app",
|
|
6
|
+
"app-server",
|
|
7
|
+
"apply",
|
|
8
|
+
"archive",
|
|
9
|
+
"cloud",
|
|
10
|
+
"completion",
|
|
11
|
+
"debug",
|
|
12
|
+
"delete",
|
|
13
|
+
"doctor",
|
|
14
|
+
"exec",
|
|
15
|
+
"exec-server",
|
|
16
|
+
"features",
|
|
17
|
+
"fork",
|
|
18
|
+
"help",
|
|
19
|
+
"login",
|
|
20
|
+
"logout",
|
|
21
|
+
"mcp",
|
|
22
|
+
"mcp-server",
|
|
23
|
+
"plugin",
|
|
24
|
+
"remote-control",
|
|
25
|
+
"resume",
|
|
26
|
+
"review",
|
|
27
|
+
"sandbox",
|
|
28
|
+
"unarchive",
|
|
29
|
+
"update",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
CODEX_OPTIONS_WITH_VALUE = {
|
|
34
|
+
"-a",
|
|
35
|
+
"--add-dir",
|
|
36
|
+
"--ask-for-approval",
|
|
37
|
+
"-C",
|
|
38
|
+
"--cd",
|
|
39
|
+
"-c",
|
|
40
|
+
"--color",
|
|
41
|
+
"--config",
|
|
42
|
+
"--disable",
|
|
43
|
+
"--enable",
|
|
44
|
+
"-i",
|
|
45
|
+
"--image",
|
|
46
|
+
"--local-provider",
|
|
47
|
+
"-m",
|
|
48
|
+
"--model",
|
|
49
|
+
"-o",
|
|
50
|
+
"--output-last-message",
|
|
51
|
+
"--output-schema",
|
|
52
|
+
"-p",
|
|
53
|
+
"--profile",
|
|
54
|
+
"--remote",
|
|
55
|
+
"--remote-auth-token-env",
|
|
56
|
+
"-s",
|
|
57
|
+
"--sandbox",
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
CODEX_CLAUDE_ONLY_VALUE_FLAGS = {
|
|
62
|
+
"--allowedTools",
|
|
63
|
+
"--append-system-prompt",
|
|
64
|
+
"--disallowedTools",
|
|
65
|
+
"--fallback-model",
|
|
66
|
+
"--input-format",
|
|
67
|
+
"--output-format",
|
|
68
|
+
"--permission-prompt-tool",
|
|
69
|
+
"--settings",
|
|
70
|
+
"--system-prompt",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def codex_passthrough_first_non_option_arg(passthrough: list[str]) -> str:
|
|
75
|
+
i = 0
|
|
76
|
+
while i < len(passthrough):
|
|
77
|
+
arg = str(passthrough[i])
|
|
78
|
+
if arg == "--":
|
|
79
|
+
return str(passthrough[i + 1]) if i + 1 < len(passthrough) else ""
|
|
80
|
+
if arg.startswith("--") and "=" in arg:
|
|
81
|
+
i += 1
|
|
82
|
+
continue
|
|
83
|
+
if arg in CODEX_OPTIONS_WITH_VALUE:
|
|
84
|
+
i += 2 if i + 1 < len(passthrough) else 1
|
|
85
|
+
continue
|
|
86
|
+
if arg.startswith("-") and arg != "-":
|
|
87
|
+
i += 1
|
|
88
|
+
continue
|
|
89
|
+
return arg
|
|
90
|
+
return ""
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def codex_passthrough_has_command(passthrough: list[str]) -> bool:
|
|
94
|
+
return codex_passthrough_first_non_option_arg(passthrough) in CODEX_COMMAND_NAMES
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _codex_consume_optional_value(passthrough: list[str], index: int) -> tuple[str, int]:
|
|
98
|
+
if index + 1 < len(passthrough):
|
|
99
|
+
value = str(passthrough[index + 1])
|
|
100
|
+
if value != "--" and not value.startswith("-"):
|
|
101
|
+
return value, index + 2
|
|
102
|
+
return "", index + 1
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _is_channel_spec_tagged(spec: str) -> bool:
|
|
106
|
+
return spec.startswith("plugin:") or spec.startswith("server:")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _codex_drop_passthrough_channel_args(passthrough: list[str], index: int) -> int:
|
|
110
|
+
arg = str(passthrough[index])
|
|
111
|
+
if arg.startswith("--channels=") or arg.startswith("--dangerously-load-development-channels="):
|
|
112
|
+
return index + 1
|
|
113
|
+
i = index + 1
|
|
114
|
+
while i < len(passthrough) and _is_channel_spec_tagged(str(passthrough[i])):
|
|
115
|
+
i += 1
|
|
116
|
+
return i
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _codex_drop_greedy_passthrough_values(passthrough: list[str], index: int) -> int:
|
|
120
|
+
i = index + 1
|
|
121
|
+
while i < len(passthrough) and not str(passthrough[i]).startswith("-"):
|
|
122
|
+
i += 1
|
|
123
|
+
return i
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _codex_session_id_after_index(passthrough: list[str], index: int) -> str:
|
|
127
|
+
i = index + 1
|
|
128
|
+
while i < len(passthrough):
|
|
129
|
+
arg = str(passthrough[i])
|
|
130
|
+
if arg.startswith("--session-id="):
|
|
131
|
+
return arg.split("=", 1)[1]
|
|
132
|
+
if arg == "--session-id" and i + 1 < len(passthrough):
|
|
133
|
+
value = str(passthrough[i + 1])
|
|
134
|
+
return value if value != "--" and not value.startswith("-") else ""
|
|
135
|
+
i += 1
|
|
136
|
+
return ""
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def codex_passthrough_args_for_launch(passthrough: list[str]) -> tuple[list[str], list[str]]:
|
|
140
|
+
"""Translate Claude-oriented passthrough flags before launching Codex.
|
|
141
|
+
|
|
142
|
+
The prelaunch menu is shared with Claude Code, so users can arrive here
|
|
143
|
+
with Claude-only session flags such as --continue. Codex should receive
|
|
144
|
+
native Codex commands where the intent is clear, and should not receive
|
|
145
|
+
flags it cannot parse.
|
|
146
|
+
"""
|
|
147
|
+
out: list[str] = []
|
|
148
|
+
notes: list[str] = []
|
|
149
|
+
existing_codex_command = codex_passthrough_has_command(passthrough)
|
|
150
|
+
mapped_command: list[str] = []
|
|
151
|
+
mapped_permission_bypass = False
|
|
152
|
+
i = 0
|
|
153
|
+
while i < len(passthrough):
|
|
154
|
+
arg = str(passthrough[i])
|
|
155
|
+
|
|
156
|
+
if arg == "--continue":
|
|
157
|
+
if not existing_codex_command and not mapped_command:
|
|
158
|
+
mapped_command = ["resume", "--last"]
|
|
159
|
+
notes.append("--continue -> resume --last")
|
|
160
|
+
i += 1
|
|
161
|
+
continue
|
|
162
|
+
if arg.startswith("--continue="):
|
|
163
|
+
value = arg.split("=", 1)[1]
|
|
164
|
+
if not existing_codex_command and not mapped_command:
|
|
165
|
+
mapped_command = ["resume", "--last"]
|
|
166
|
+
if value:
|
|
167
|
+
out.append(value)
|
|
168
|
+
notes.append("--continue -> resume --last")
|
|
169
|
+
i += 1
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
if arg == "-c":
|
|
173
|
+
next_value = str(passthrough[i + 1]) if i + 1 < len(passthrough) else ""
|
|
174
|
+
if next_value and "=" in next_value:
|
|
175
|
+
out.extend([arg, next_value])
|
|
176
|
+
i += 2
|
|
177
|
+
continue
|
|
178
|
+
if not existing_codex_command and not mapped_command:
|
|
179
|
+
mapped_command = ["resume", "--last"]
|
|
180
|
+
notes.append("-c -> resume --last")
|
|
181
|
+
i += 1
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
if arg in ("--resume", "-r"):
|
|
185
|
+
session_id, i = _codex_consume_optional_value(passthrough, i)
|
|
186
|
+
if not existing_codex_command and not mapped_command:
|
|
187
|
+
mapped_command = ["resume"]
|
|
188
|
+
if session_id:
|
|
189
|
+
mapped_command.append(session_id)
|
|
190
|
+
notes.append(f"{arg} <session> -> resume <session>")
|
|
191
|
+
else:
|
|
192
|
+
notes.append(f"{arg} -> resume")
|
|
193
|
+
continue
|
|
194
|
+
if arg.startswith("--resume="):
|
|
195
|
+
session_id = arg.split("=", 1)[1]
|
|
196
|
+
if not existing_codex_command and not mapped_command:
|
|
197
|
+
mapped_command = ["resume"]
|
|
198
|
+
if session_id:
|
|
199
|
+
mapped_command.append(session_id)
|
|
200
|
+
notes.append("--resume=<session> -> resume <session>")
|
|
201
|
+
i += 1
|
|
202
|
+
continue
|
|
203
|
+
|
|
204
|
+
if arg == "--session-id":
|
|
205
|
+
session_id, i = _codex_consume_optional_value(passthrough, i)
|
|
206
|
+
if session_id and not existing_codex_command and not mapped_command:
|
|
207
|
+
mapped_command = ["resume", session_id]
|
|
208
|
+
notes.append("--session-id <session> -> resume <session>")
|
|
209
|
+
continue
|
|
210
|
+
if arg.startswith("--session-id="):
|
|
211
|
+
session_id = arg.split("=", 1)[1]
|
|
212
|
+
if session_id and not existing_codex_command and not mapped_command:
|
|
213
|
+
mapped_command = ["resume", session_id]
|
|
214
|
+
notes.append("--session-id=<session> -> resume <session>")
|
|
215
|
+
i += 1
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
if arg == "--fork-session":
|
|
219
|
+
session_id = _codex_session_id_after_index(passthrough, i)
|
|
220
|
+
if not existing_codex_command and not mapped_command:
|
|
221
|
+
mapped_command = ["fork"]
|
|
222
|
+
if session_id:
|
|
223
|
+
mapped_command.append(session_id)
|
|
224
|
+
notes.append("--fork-session --session-id=<session> -> fork <session>")
|
|
225
|
+
else:
|
|
226
|
+
mapped_command.append("--last")
|
|
227
|
+
notes.append("--fork-session -> fork --last")
|
|
228
|
+
i += 1
|
|
229
|
+
continue
|
|
230
|
+
|
|
231
|
+
if arg == "--print":
|
|
232
|
+
if not existing_codex_command and not mapped_command:
|
|
233
|
+
mapped_command = ["exec"]
|
|
234
|
+
notes.append("--print -> exec")
|
|
235
|
+
i += 1
|
|
236
|
+
continue
|
|
237
|
+
if arg.startswith("--print="):
|
|
238
|
+
prompt = arg.split("=", 1)[1]
|
|
239
|
+
if not existing_codex_command and not mapped_command:
|
|
240
|
+
mapped_command = ["exec"]
|
|
241
|
+
if prompt:
|
|
242
|
+
out.append(prompt)
|
|
243
|
+
notes.append("--print -> exec")
|
|
244
|
+
i += 1
|
|
245
|
+
continue
|
|
246
|
+
|
|
247
|
+
if arg == "--dangerously-skip-permissions":
|
|
248
|
+
if not mapped_permission_bypass:
|
|
249
|
+
out.append("--dangerously-bypass-approvals-and-sandbox")
|
|
250
|
+
mapped_permission_bypass = True
|
|
251
|
+
notes.append("--dangerously-skip-permissions -> --dangerously-bypass-approvals-and-sandbox")
|
|
252
|
+
i += 1
|
|
253
|
+
continue
|
|
254
|
+
|
|
255
|
+
if arg == "--permission-mode" or arg.startswith("--permission-mode="):
|
|
256
|
+
if arg == "--permission-mode":
|
|
257
|
+
value, i = _codex_consume_optional_value(passthrough, i)
|
|
258
|
+
else:
|
|
259
|
+
value = arg.split("=", 1)[1]
|
|
260
|
+
i += 1
|
|
261
|
+
if value == "bypassPermissions" and not mapped_permission_bypass:
|
|
262
|
+
out.append("--dangerously-bypass-approvals-and-sandbox")
|
|
263
|
+
mapped_permission_bypass = True
|
|
264
|
+
notes.append("--permission-mode bypassPermissions -> --dangerously-bypass-approvals-and-sandbox")
|
|
265
|
+
else:
|
|
266
|
+
notes.append("--permission-mode ignored for Codex")
|
|
267
|
+
continue
|
|
268
|
+
|
|
269
|
+
if arg in ("--channels", "--dangerously-load-development-channels") or arg.startswith(
|
|
270
|
+
("--channels=", "--dangerously-load-development-channels=")
|
|
271
|
+
):
|
|
272
|
+
i = _codex_drop_passthrough_channel_args(passthrough, i)
|
|
273
|
+
notes.append(f"{arg.split('=', 1)[0]} ignored for Codex launch")
|
|
274
|
+
continue
|
|
275
|
+
|
|
276
|
+
if arg in ("--mcp-config",):
|
|
277
|
+
i = _codex_drop_greedy_passthrough_values(passthrough, i)
|
|
278
|
+
notes.append("--mcp-config ignored for Codex launch")
|
|
279
|
+
continue
|
|
280
|
+
|
|
281
|
+
if arg in CODEX_CLAUDE_ONLY_VALUE_FLAGS:
|
|
282
|
+
_, i = _codex_consume_optional_value(passthrough, i)
|
|
283
|
+
notes.append(f"{arg} ignored for Codex launch")
|
|
284
|
+
continue
|
|
285
|
+
if any(arg.startswith(flag + "=") for flag in CODEX_CLAUDE_ONLY_VALUE_FLAGS):
|
|
286
|
+
notes.append(f"{arg.split('=', 1)[0]} ignored for Codex launch")
|
|
287
|
+
i += 1
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
if arg == "--from-pr" or arg.startswith("--from-pr="):
|
|
291
|
+
if arg == "--from-pr":
|
|
292
|
+
_, i = _codex_consume_optional_value(passthrough, i)
|
|
293
|
+
else:
|
|
294
|
+
i += 1
|
|
295
|
+
notes.append("--from-pr ignored for Codex launch")
|
|
296
|
+
continue
|
|
297
|
+
|
|
298
|
+
out.append(arg)
|
|
299
|
+
i += 1
|
|
300
|
+
|
|
301
|
+
if mapped_command and not existing_codex_command:
|
|
302
|
+
return [*mapped_command, *out], notes
|
|
303
|
+
return out, notes
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Codex runtime HTTP router."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Callable
|
|
6
|
+
|
|
7
|
+
from .agent_router import COMMON_RUNTIME_ROUTER_CAPABILITIES, RouterCapability
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class CodexRouter:
|
|
11
|
+
name = "codex"
|
|
12
|
+
runtime = "codex"
|
|
13
|
+
protocol = "openai_responses"
|
|
14
|
+
request_paths = ("/backend-api/codex/*", "/backend-api/codex/responses", "/v1/responses")
|
|
15
|
+
capabilities = tuple(
|
|
16
|
+
RouterCapability(name, description)
|
|
17
|
+
for name, description in (
|
|
18
|
+
("auth_forwarding", "Native Codex auth headers are forwarded to the ChatGPT Codex backend."),
|
|
19
|
+
("sse_stream_proxy", "Responses API SSE streams are proxied without buffering the full response."),
|
|
20
|
+
("channel_context_injection", "Pending external channel messages are injected into Responses input."),
|
|
21
|
+
("pending_delivery_ack", "Injected channel cursors are committed after successful delivery."),
|
|
22
|
+
("request_observability", "Responses requests are traced and published to the runtime event bus."),
|
|
23
|
+
("upstream_error_mapping", "Upstream HTTP and client disconnect errors are mapped for Codex."),
|
|
24
|
+
("backend_passthrough", "Non-responses Codex backend endpoints are passed through."),
|
|
25
|
+
("legacy_responses", "The legacy /v1/responses path remains supported."),
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
routed_enabled: Callable[[str, dict[str, Any]], bool],
|
|
33
|
+
handle_responses_post: Callable[[Any, dict[str, Any], str, dict[str, Any], dict[str, Any]], None],
|
|
34
|
+
handle_backend_passthrough_post: Callable[[Any, str, dict[str, Any], dict[str, Any]], None],
|
|
35
|
+
handle_backend_passthrough_get: Callable[[Any, str, dict[str, Any]], None],
|
|
36
|
+
) -> None:
|
|
37
|
+
self._routed_enabled = routed_enabled
|
|
38
|
+
self._handle_responses_post = handle_responses_post
|
|
39
|
+
self._handle_backend_passthrough_post = handle_backend_passthrough_post
|
|
40
|
+
self._handle_backend_passthrough_get = handle_backend_passthrough_get
|
|
41
|
+
|
|
42
|
+
def can_handle_get(self, path: str, provider: str, pcfg: dict[str, Any]) -> bool:
|
|
43
|
+
return self._routed_enabled(provider, pcfg) and path.startswith("/backend-api/codex/")
|
|
44
|
+
|
|
45
|
+
def handle_get(self, handler: Any, path: str, provider: str, pcfg: dict[str, Any]) -> bool:
|
|
46
|
+
if not self.can_handle_get(path, provider, pcfg):
|
|
47
|
+
return False
|
|
48
|
+
self._handle_backend_passthrough_get(handler, provider, pcfg)
|
|
49
|
+
return True
|
|
50
|
+
|
|
51
|
+
def can_handle_post(self, path: str, provider: str, pcfg: dict[str, Any]) -> bool:
|
|
52
|
+
if path == "/v1/responses":
|
|
53
|
+
return True
|
|
54
|
+
return self._routed_enabled(provider, pcfg) and path.startswith("/backend-api/codex/")
|
|
55
|
+
|
|
56
|
+
def handle_post(
|
|
57
|
+
self,
|
|
58
|
+
handler: Any,
|
|
59
|
+
cfg: dict[str, Any],
|
|
60
|
+
provider: str,
|
|
61
|
+
pcfg: dict[str, Any],
|
|
62
|
+
path: str,
|
|
63
|
+
body: dict[str, Any],
|
|
64
|
+
) -> bool:
|
|
65
|
+
if path == "/v1/responses":
|
|
66
|
+
self._handle_responses_post(handler, cfg, provider, pcfg, body)
|
|
67
|
+
return True
|
|
68
|
+
if path == "/backend-api/codex/responses" and self._routed_enabled(provider, pcfg):
|
|
69
|
+
self._handle_responses_post(handler, cfg, provider, pcfg, body)
|
|
70
|
+
return True
|
|
71
|
+
if self._routed_enabled(provider, pcfg) and path.startswith("/backend-api/codex/"):
|
|
72
|
+
self._handle_backend_passthrough_post(handler, provider, pcfg, body)
|
|
73
|
+
return True
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
assert all(any(capability.name == required for capability in CodexRouter.capabilities) for required in COMMON_RUNTIME_ROUTER_CAPABILITIES)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
__all__ = ["CodexRouter"]
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# AGY CLI Support Research
|
|
2
|
+
|
|
3
|
+
Date: 2026-06-28
|
|
4
|
+
|
|
5
|
+
## Official Sources Checked
|
|
6
|
+
|
|
7
|
+
- Google Antigravity CLI repo: https://github.com/google-antigravity/antigravity-cli
|
|
8
|
+
- Google Antigravity SDK Python repo: https://github.com/google-antigravity/antigravity-sdk-python
|
|
9
|
+
- AGY install scripts: https://antigravity.google/cli/install.sh and https://antigravity.google/cli/install.ps1
|
|
10
|
+
- AGY updater manifests: `https://antigravity-cli-auto-updater-974169037036.us-central1.run.app/manifests/<platform>_<arch>.json`
|
|
11
|
+
- Google Agent2Agent/A2A protocol: https://github.com/a2aproject/A2A
|
|
12
|
+
|
|
13
|
+
## CLI Surface
|
|
14
|
+
|
|
15
|
+
The public AGY CLI command is `agy`. The inspected `agy --help` surface includes:
|
|
16
|
+
|
|
17
|
+
- `--continue`
|
|
18
|
+
- `--conversation`
|
|
19
|
+
- `--dangerously-skip-permissions`
|
|
20
|
+
- `--model`
|
|
21
|
+
- `--print`
|
|
22
|
+
- `--project`
|
|
23
|
+
- `--new-project`
|
|
24
|
+
- `--sandbox`
|
|
25
|
+
- subcommands including `install`, `models`, `plugin`, and `update`
|
|
26
|
+
|
|
27
|
+
AGY uses native Google Antigravity sign-in and local settings. The official docs and CLI help did not expose a supported model upstream base URL override equivalent to Codex `model_providers` or Claude `ANTHROPIC_BASE_URL`.
|
|
28
|
+
|
|
29
|
+
## Install And Update
|
|
30
|
+
|
|
31
|
+
AGY is not an npm package. The official installer downloads a platform manifest, verifies SHA512, installs the native binary, then runs `agy install`.
|
|
32
|
+
|
|
33
|
+
Ciel Runtime follows that model:
|
|
34
|
+
|
|
35
|
+
- Windows: `%LOCALAPPDATA%\agy\bin\agy.exe`
|
|
36
|
+
- POSIX: `~/.local/bin/agy`
|
|
37
|
+
- Missing binary: download official manifest, verify SHA512, install, run `agy install`
|
|
38
|
+
- Existing binary: run `agy update` with forced `y\n`/CI yes environment; if native update fails and a newer manifest exists, reinstall from the manifest
|
|
39
|
+
|
|
40
|
+
## Routed Mode Scope
|
|
41
|
+
|
|
42
|
+
`AGY` means native Antigravity CLI launch with Ciel Runtime disabled except for prelaunch/update behavior.
|
|
43
|
+
|
|
44
|
+
`AGY Routed` currently means native Antigravity CLI launch plus Ciel Runtime channel/PTY wake support. It does not claim to proxy AGY model upstream traffic, because no official CLI/API source showed a supported AGY model endpoint override.
|
|
45
|
+
|
|
46
|
+
## Message Injection Options
|
|
47
|
+
|
|
48
|
+
Short term:
|
|
49
|
+
|
|
50
|
+
- Use the existing Ciel Runtime PTY wake proxy.
|
|
51
|
+
- Submit external channel messages with bracketed paste and submit confirmation, matching the safer Codex path.
|
|
52
|
+
|
|
53
|
+
Medium term:
|
|
54
|
+
|
|
55
|
+
- Use AGY's MCP support where a channel backend can be exposed as MCP.
|
|
56
|
+
- Use AGY hooks/statusline only for observation, not for instructing the model.
|
|
57
|
+
|
|
58
|
+
Long term:
|
|
59
|
+
|
|
60
|
+
- Evaluate the Antigravity SDK `Conversation.send()` and trigger APIs for a direct bridge.
|
|
61
|
+
- A2A is a candidate for agent-to-agent bridge work, but it is not a drop-in TUI input injection mechanism.
|
|
62
|
+
|
package/docs/Architecture.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Architecture — 시스템 아키텍처
|
|
2
2
|
|
|
3
3
|
> 소스: `ciel_runtime_support/architecture.py`
|
|
4
|
+
> 라우터 소스: `ciel_runtime_support/agent_router.py`, `claude_router.py`, `codex_router.py`
|
|
4
5
|
|
|
5
6
|
---
|
|
6
7
|
|
|
@@ -12,6 +13,7 @@
|
|
|
12
13
|
| **Provider Adapter** | 업스트림 LLM API, 키, 모델, 헤더, 제한 관리 | 런타임 실행 플래그 금지 |
|
|
13
14
|
| **Protocol Adapter** | 요청/응답 Wire 형식 변환 | — |
|
|
14
15
|
| **Tool Dialect** | 런타임별 툴 이름 및 수정 | — |
|
|
16
|
+
| **Runtime Router** | 런타임별 HTTP 경로와 라우터 기능 소유 | 다른 런타임의 경로 직접 처리 금지 |
|
|
15
17
|
|
|
16
18
|
---
|
|
17
19
|
|
|
@@ -115,6 +117,27 @@ class RateLimitState:
|
|
|
115
117
|
|
|
116
118
|
---
|
|
117
119
|
|
|
120
|
+
## Runtime Router 분리
|
|
121
|
+
|
|
122
|
+
`RouterHandler`는 공통 HTTP 진입점만 담당하고, 런타임별 경로 소유권은 별도 라우터가 가진다.
|
|
123
|
+
|
|
124
|
+
| 라우터 | 파일 | 경로 | 프로토콜 |
|
|
125
|
+
|--------|------|------|----------|
|
|
126
|
+
| Claude Router | `ciel_runtime_support/claude_router.py` | `/v1/messages`, `/v1/messages/count_tokens` | Anthropic Messages |
|
|
127
|
+
| Codex Router | `ciel_runtime_support/codex_router.py` | `/backend-api/codex/*`, `/backend-api/codex/responses`, `/v1/responses` | OpenAI Responses / Codex backend |
|
|
128
|
+
|
|
129
|
+
공통 기능은 `COMMON_RUNTIME_ROUTER_CAPABILITIES`로 검증한다:
|
|
130
|
+
- auth forwarding
|
|
131
|
+
- SSE stream proxy
|
|
132
|
+
- channel context injection
|
|
133
|
+
- pending delivery ack
|
|
134
|
+
- request observability
|
|
135
|
+
- upstream error mapping
|
|
136
|
+
|
|
137
|
+
`tests/test_runtime_routers.py`가 Claude/Codex 라우터가 공통 기능을 모두 제공하는지와 경로 소유권이 섞이지 않는지 확인한다.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
118
141
|
## LaunchMode 흐름
|
|
119
142
|
|
|
120
143
|
```
|
package/docs/CLI-Reference.md
CHANGED
package/docs/Home.md
CHANGED
package/docs/Installation.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Installation — 설치 방법
|
|
2
2
|
|
|
3
|
-
> 소스: `install.sh`, `install.ps1`, `
|
|
3
|
+
> 소스: `install.sh`, `install.ps1`, `package.json`
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -22,11 +22,15 @@ npm install -g @oneciel-ai/ciel-runtime
|
|
|
22
22
|
- Node.js ≥ 18
|
|
23
23
|
- Python 3 (`python3` 또는 `python`)
|
|
24
24
|
|
|
25
|
-
###
|
|
25
|
+
### npm install script
|
|
26
26
|
|
|
27
|
-
`
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
npm 패키지는 `postinstall` 같은 install script를 사용하지 않는다. 따라서 `allow-scripts` 설정이 엄격한 환경에서도 추가 승인 없이 설치된다.
|
|
28
|
+
|
|
29
|
+
업그레이드 후 이미 실행 중인 세션이 새 코드를 쓰게 하려면 명시적으로 재시작한다:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
ciel-runtime-stop
|
|
33
|
+
ciel-runtime --continue
|
|
30
34
|
```
|
|
31
35
|
|
|
32
36
|
---
|
package/docs/MCP-Channels.md
CHANGED
|
@@ -113,12 +113,11 @@ Claude Code 세션 (프롬프트로 전달)
|
|
|
113
113
|
|
|
114
114
|
---
|
|
115
115
|
|
|
116
|
-
## 채널 LLM
|
|
116
|
+
## 채널 LLM 전달
|
|
117
117
|
|
|
118
|
-
채널
|
|
118
|
+
채널 메시지를 실행 중인 에이전트 입력으로 전달하기 위한 커서:
|
|
119
119
|
|
|
120
120
|
- `CHANNEL_LLM_CURSOR_PATH` — LLM 처리 커서
|
|
121
|
-
- `CHANNEL_LLM_SUMMARY_QUEUE_PATH` — 요약 큐
|
|
122
121
|
- `CHANNEL_LLM_LAUNCH_GUARD_PATH` — 중복 실행 방지
|
|
123
122
|
- `CHANNEL_LLM_LAUNCH_RECENT_SECONDS_DEFAULT` = 600초
|
|
124
123
|
|
|
@@ -154,7 +153,6 @@ Router가 관리하는 SSE 연결 상태:
|
|
|
154
153
|
| `channel-llm-cursor.json` | LLM 커서 상태 |
|
|
155
154
|
| `channel-llm-clear-floor.json` | LLM 플로어 초기화 |
|
|
156
155
|
| `channel-llm-launch-guard.json` | LLM 실행 가드 |
|
|
157
|
-
| `channel-llm-summary-queue.jsonl` | LLM 요약 큐 |
|
|
158
156
|
|
|
159
157
|
---
|
|
160
158
|
|
package/docs/Module-Map.md
CHANGED
|
@@ -51,6 +51,35 @@
|
|
|
51
51
|
|
|
52
52
|
→ [[Architecture]]
|
|
53
53
|
|
|
54
|
+
### `ciel_runtime_support/agent_router.py`
|
|
55
|
+
|
|
56
|
+
런타임 HTTP 라우터 공통 계약:
|
|
57
|
+
- `RuntimeRouter` 프로토콜
|
|
58
|
+
- `RouterCapability`
|
|
59
|
+
- `COMMON_RUNTIME_ROUTER_CAPABILITIES`
|
|
60
|
+
- 라우터 capability matrix / gap 검사
|
|
61
|
+
|
|
62
|
+
→ [[Architecture]]
|
|
63
|
+
|
|
64
|
+
### `ciel_runtime_support/claude_router.py`
|
|
65
|
+
|
|
66
|
+
Claude Code용 HTTP 라우터:
|
|
67
|
+
- `/v1/messages`
|
|
68
|
+
- `/v1/messages/count_tokens`
|
|
69
|
+
- Anthropic Messages 기반 SSE, 채널 주입, 토큰 카운트 경로 소유
|
|
70
|
+
|
|
71
|
+
→ [[Architecture]]
|
|
72
|
+
|
|
73
|
+
### `ciel_runtime_support/codex_router.py`
|
|
74
|
+
|
|
75
|
+
Codex용 HTTP 라우터:
|
|
76
|
+
- `/backend-api/codex/*`
|
|
77
|
+
- `/backend-api/codex/responses`
|
|
78
|
+
- `/v1/responses`
|
|
79
|
+
- native Codex auth passthrough, Responses SSE proxy, 채널 주입 경로 소유
|
|
80
|
+
|
|
81
|
+
→ [[Architecture]]
|
|
82
|
+
|
|
54
83
|
### `ciel_runtime_support/observability.py`
|
|
55
84
|
|
|
56
85
|
이벤트 버스 및 HTML 렌더러:
|
|
@@ -103,7 +132,6 @@ Claude Code 트랜스크립트 이벤트 필터:
|
|
|
103
132
|
| `npm-bin/ciel-runtimectl.js` | npm 글로벌 바이너리 (`ciel-runtimectl`) |
|
|
104
133
|
| `npm-bin/ciel-runtime-stop.js` | npm 글로벌 바이너리 (`ciel-runtime-stop`) |
|
|
105
134
|
| `npm-bin/run-ciel-runtime.js` | Python 프로세스 실행 헬퍼 |
|
|
106
|
-
| `npm-bin/postinstall.js` | npm install 후 훅 (기존 Router 중지) |
|
|
107
135
|
|
|
108
136
|
---
|
|
109
137
|
|
package/docs/Providers.md
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
| `deepseek` | DeepSeek.com | OpenAI Chat | `https://api.deepseek.com` |
|
|
15
15
|
| `opencode` | OpenCode Zen | Anthropic Messages / OpenAI Chat | `https://opencode.ai/zen` |
|
|
16
16
|
| `opencode-go` | OpenCode Go | Anthropic Messages / OpenAI Chat | `https://opencode.ai/zen/go` |
|
|
17
|
-
| `kimi` | Kimi.com | OpenAI Chat | `https://api.kimi.com/coding` |
|
|
17
|
+
| `kimi` | Kimi.com | Anthropic Messages / OpenAI Chat | `https://api.kimi.com/coding` |
|
|
18
18
|
| `zai` | Z.AI GLM | Anthropic Messages | `https://api.z.ai/api/anthropic` |
|
|
19
19
|
| `vllm` | vLLM | OpenAI Chat | 로컬/원격 vLLM |
|
|
20
20
|
| `lm-studio` | LM Studio | OpenAI Chat | 로컬 LM Studio |
|
|
@@ -84,7 +84,8 @@
|
|
|
84
84
|
## Kimi (Moonshot)
|
|
85
85
|
|
|
86
86
|
- 기본 모델: `kimi-for-coding`
|
|
87
|
-
-
|
|
87
|
+
- Claude Code 경로는 Kimi 공식 Claude Code 설정과 맞춰 Anthropic Messages 호환 엔드포인트를 우선 사용한다.
|
|
88
|
+
- Codex/Codex App 경로는 Codex의 OpenAI Responses 입력을 OpenAI Chat 호환 요청으로 변환해 `https://api.kimi.com/coding/v1/chat/completions`로 보낸다.
|
|
88
89
|
|
|
89
90
|
---
|
|
90
91
|
|
package/docs/Router.md
CHANGED
|
@@ -130,7 +130,7 @@ Codex 경로는 `write_openai_responses_response()`가 Responses SSE를 생성
|
|
|
130
130
|
|
|
131
131
|
```
|
|
132
132
|
GET /ca/health
|
|
133
|
-
→ {"status": "ok", "version": "0.1.
|
|
133
|
+
→ {"status": "ok", "version": "0.1.1", "provider": "...", "model": "...", ...}
|
|
134
134
|
```
|
|
135
135
|
|
|
136
136
|
- `router_health()` — 라우터 상태 JSON 반환
|
package/docs/Test-Suite.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneciel-ai/ciel-runtime",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Universal AI coding-agent runtime and model-routing layer for Claude, Codex, AGY, and compatible runtimes.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "One Ciel LLC",
|
|
@@ -42,8 +42,7 @@
|
|
|
42
42
|
"docs/"
|
|
43
43
|
],
|
|
44
44
|
"scripts": {
|
|
45
|
-
"
|
|
46
|
-
"test": "python -m py_compile ciel_runtime.py ciel_runtime_support/__init__.py ciel_runtime_support/architecture.py ciel_runtime_support/observability.py ciel_runtime_support/transcript_filter.py ciel-runtime-menu.py ciel-runtime-tool-guard.py scripts/make_demo_assets.py && python -m unittest discover -s tests -p \"test_*.py\"",
|
|
45
|
+
"test": "python -m py_compile ciel_runtime.py ciel_runtime_support/__init__.py ciel_runtime_support/architecture.py ciel_runtime_support/agent_router.py ciel_runtime_support/claude_router.py ciel_runtime_support/codex_app_server.py ciel_runtime_support/codex_cli.py ciel_runtime_support/codex_router.py ciel_runtime_support/observability.py ciel_runtime_support/transcript_filter.py ciel-runtime-menu.py ciel-runtime-tool-guard.py scripts/make_demo_assets.py && python -m unittest discover -s tests -p \"test_*.py\"",
|
|
47
46
|
"lint": "python -m ruff check .",
|
|
48
47
|
"demo:assets": "python scripts/make_demo_assets.py"
|
|
49
48
|
},
|