@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.
@@ -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
+
@@ -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
  ```
@@ -271,7 +271,7 @@ ciel-runtimectl install-diag
271
271
  #### `version`
272
272
  ```bash
273
273
  ciel-runtimectl version
274
- # → 0.1.0
274
+ # → 0.1.1
275
275
  ```
276
276
 
277
277
  ---
package/docs/Home.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # ciel-runtime (ciel-runtime) — Wiki 홈
2
2
 
3
3
  > **패키지명**: `@oneciel-ai/ciel-runtime`
4
- > **버전**: 0.1.0
4
+ > **버전**: 0.1.1
5
5
  > **제작**: One Ciel LLC
6
6
  > **라이선스**: MIT
7
7
 
@@ -1,6 +1,6 @@
1
1
  # Installation — 설치 방법
2
2
 
3
- > 소스: `install.sh`, `install.ps1`, `npm-bin/postinstall.js`, `package.json`
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
- `npm-bin/postinstall.js` 설치 기존 Router 프로세스 자동 중지:
28
- ```
29
- python3 ciel_runtime.py cli stop
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
  ---
@@ -113,12 +113,11 @@ Claude Code 세션 (프롬프트로 전달)
113
113
 
114
114
  ---
115
115
 
116
- ## 채널 LLM 연동
116
+ ## 채널 LLM 전달
117
117
 
118
- 채널 메시지에 LLM 응답을 자동으로 생성하는 기능:
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
 
@@ -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
- - OpenAI Chat 호환.
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.0", "provider": "...", "model": "...", ...}
133
+ → {"status": "ok", "version": "0.1.1", "provider": "...", "model": "...", ...}
134
134
  ```
135
135
 
136
136
  - `router_health()` — 라우터 상태 JSON 반환
@@ -13,6 +13,7 @@
13
13
  | 파일 | 테스트 대상 |
14
14
  |------|-----------|
15
15
  | `test_architecture_contracts.py` | `architecture.py` 추상 클래스 구현 계약 |
16
+ | `test_runtime_routers.py` | Claude/Codex 라우터 경로 소유권 및 공통 capability parity |
16
17
 
17
18
  ---
18
19
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/ciel-runtime",
3
- "version": "0.1.0",
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
- "postinstall": "node npm-bin/postinstall.js",
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
  },