@oneciel-ai/ciel-runtime 0.2.48 → 0.2.49

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.
@@ -24,7 +24,10 @@ from .responses_cache_diagnostics import (
24
24
  request_cache_profile,
25
25
  usage_with_cache_profile,
26
26
  )
27
- from .responses_input_compatibility import repair_replayed_response_items
27
+ from .responses_input_compatibility import (
28
+ hoist_additional_tools,
29
+ repair_replayed_response_items,
30
+ )
28
31
  from .responses_custom_tool_bridge import (
29
32
  ResponsesCustomToolStreamProjector,
30
33
  project_response_payload,
@@ -523,8 +526,13 @@ class ProviderResponsesPassthrough:
523
526
  body: dict[str, Any],
524
527
  ) -> dict[str, Any]:
525
528
  remote_bridge = is_remote_bridge_request(handler)
529
+ # Codex carries its tool catalogue in an `additional_tools` input item;
530
+ # only the OpenAI backend accepts that item, so lift it into `tools`
531
+ # before the provider's own normalisation runs.
526
532
  upstream_body = dict(
527
- body if remote_bridge else repair_replayed_response_items(body)
533
+ body
534
+ if remote_bridge
535
+ else hoist_additional_tools(repair_replayed_response_items(body))
528
536
  )
529
537
  response_tools = (
530
538
  tool_definitions(upstream_body)
@@ -147,6 +147,18 @@ class OpenCodeProviderAdapter(HttpBearerProviderAdapter):
147
147
  "user-agent": f"opencode/{OPENCODE_CLIENT_VERSION}",
148
148
  }
149
149
 
150
+ def compatibility_headers(self, config: ProviderConfig) -> Mapping[str, str]:
151
+ # A compatibility probe is its own short conversation, so it gets a
152
+ # fresh session rather than the router's; Zen and Go both route through
153
+ # the same gateway and expect the same client identity.
154
+ del config
155
+ return {
156
+ "x-opencode-session": new_opencode_session_id(),
157
+ "x-opencode-request": new_opencode_message_id(),
158
+ "x-opencode-client": "cli",
159
+ "user-agent": f"opencode/{OPENCODE_CLIENT_VERSION}",
160
+ }
161
+
150
162
  def router_native_anthropic_enabled(
151
163
  self, config: ProviderConfig, model: str | None = None
152
164
  ) -> bool:
@@ -3,17 +3,11 @@
3
3
  from dataclasses import dataclass, field
4
4
  from typing import Mapping
5
5
 
6
- from ..architecture import MessageProtocol, ProviderConfig
6
+ from ..architecture import MessageProtocol
7
7
 
8
8
  from .base import provider_configuration
9
9
  from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
10
- from .opencode import (
11
- OPENCODE_CLIENT_VERSION,
12
- OPENCODE_GO_OX_ALPHA_FREE_MODEL,
13
- OpenCodeProviderAdapter,
14
- new_opencode_message_id,
15
- new_opencode_session_id,
16
- )
10
+ from .opencode import OPENCODE_GO_OX_ALPHA_FREE_MODEL, OpenCodeProviderAdapter
17
11
  from .opencode_catalog import OPENCODE_GO_MODEL_PROTOCOLS
18
12
 
19
13
 
@@ -44,18 +38,6 @@ class OpenCodeGoProviderAdapter(OpenCodeProviderAdapter):
44
38
  def documented_model_protocols(self) -> Mapping[str, MessageProtocol]:
45
39
  return OPENCODE_GO_MODEL_PROTOCOLS
46
40
 
47
- def compatibility_headers(self, config: ProviderConfig) -> Mapping[str, str]:
48
- # Each compatibility probe is one fresh OpenCode conversation; the
49
- # identity headers mirror the OpenCode CLI (see OpenCodeProviderAdapter
50
- # .session_headers).
51
- del config
52
- return {
53
- "x-opencode-session": new_opencode_session_id(),
54
- "x-opencode-request": new_opencode_message_id(),
55
- "x-opencode-client": "cli",
56
- "user-agent": f"opencode/{OPENCODE_CLIENT_VERSION}",
57
- }
58
-
59
41
  api_key_launch_error_value: str = (
60
42
  "Launch blocked: OpenCode Go requires a OpenCode Go API key."
61
43
  )
@@ -237,3 +237,68 @@ __all__ = [
237
237
  "repair_replayed_response_items",
238
238
  "router_synthesized_item_id",
239
239
  ]
240
+
241
+
242
+ ADDITIONAL_TOOLS_ITEM_TYPE = "additional_tools"
243
+
244
+
245
+ def _flatten_tool_namespace(entry: Any) -> list[dict[str, Any]]:
246
+ """Return the concrete tool definitions inside one namespace entry."""
247
+
248
+ if not isinstance(entry, dict):
249
+ return []
250
+ if str(entry.get("type") or "") == "namespace":
251
+ flattened: list[dict[str, Any]] = []
252
+ for nested in entry.get("tools") or []:
253
+ flattened.extend(_flatten_tool_namespace(nested))
254
+ return flattened
255
+ return [dict(entry)] if entry.get("name") else []
256
+
257
+
258
+ def hoist_additional_tools(body: dict[str, Any]) -> dict[str, Any]:
259
+ """Move Codex ``additional_tools`` input items into the request's tools.
260
+
261
+ Codex sends its tool catalogue as an ``additional_tools`` input item that
262
+ carries namespaces of tool definitions, leaving the top-level ``tools``
263
+ array empty. Only the OpenAI backend accepts that item: the Meta Responses
264
+ API answers ``400 `input[0]` did not match any supported type`` and other
265
+ providers drop it, which silently strips every tool from the turn.
266
+
267
+ Lift the definitions into ``tools`` so each provider's own tool
268
+ normalisation sees them, and drop the item. Names are kept as the client
269
+ sent them so tool calls still map back; a duplicate name keeps its first
270
+ definition.
271
+ """
272
+
273
+ items = body.get("input")
274
+ if not isinstance(items, list):
275
+ return body
276
+ hoisted: list[dict[str, Any]] = []
277
+ kept: list[Any] = []
278
+ for item in items:
279
+ if (
280
+ isinstance(item, dict)
281
+ and str(item.get("type") or "") == ADDITIONAL_TOOLS_ITEM_TYPE
282
+ ):
283
+ for entry in item.get("tools") or []:
284
+ hoisted.extend(_flatten_tool_namespace(entry))
285
+ continue
286
+ kept.append(item)
287
+ if not hoisted:
288
+ return body
289
+ existing = list(body.get("tools") or [])
290
+ seen = {
291
+ str(tool.get("name"))
292
+ for tool in existing
293
+ if isinstance(tool, dict) and tool.get("name")
294
+ }
295
+ for tool in hoisted:
296
+ name = str(tool.get("name") or "")
297
+ if not name or name in seen:
298
+ continue
299
+ seen.add(name)
300
+ existing.append(tool)
301
+ projected = dict(body)
302
+ projected["input"] = kept
303
+ projected["tools"] = existing
304
+ return projected
@@ -97,7 +97,7 @@ OPENCODE_ENDPOINT_ALIASES = {
97
97
  }
98
98
 
99
99
  APP_NAME = "Ciel Runtime"
100
- VERSION = "0.2.48"
100
+ VERSION = "0.2.49"
101
101
  CREDITS = "Credits: One Ciel LLC"
102
102
  PRELAUNCH_CANCEL = 10
103
103
  PRELAUNCH_LAUNCH_CODEX = 11
@@ -0,0 +1,25 @@
1
+ okf_version: "1.0"
2
+ task: "User: muse-spark does not work on 10.0.0.238:9683; suspects the last git patch; then 'most models do not work'."
3
+ remote_access:
4
+ reachability: "10.0.0.238 pings (3 ms); TCP 9683/445/3389/80 open, 22 closed. GET /health and POST /v1/messages both answer 401 'ciel-runtime router external authentication is required' (administrative external auth enabled, no token on this computer; same state as docs/journal/2026/09/07/diagnostics/windows/remote-backend-log-access.okf)."
5
+ consequence: "Remote version, provider, model and router.log could not be read. No SSH path (port 22 closed; the aap_* keys are for other hosts)."
6
+ suspect_patch_review:
7
+ commits_since_main: "923989f..bd13b0c: 173864b/1c0bb27 opencode catalog+session, 6fee3e8/7c48409 codex completion gate (adds a gate to the provider Responses passthrough for every provider on the Responses route; Meta tool_choice required/named -> auto), bff7596 opencode identity + openrouter, 3d132dc/0fde800 console guard, bf9d480 openrouter pareto, bd13b0c 0.2.48."
8
+ cross_provider_code_paths_changed: "provider_request_access adapter_headers now passes router_originated (default session_headers is {} for every non-opencode adapter); provider_responses_passthrough completion gate (7c48409, already published 16:03 KST and installed here since then)."
9
+ direct_meta_probe:
10
+ text: "POST https://api.meta.ai/v1/responses muse-spark-1.3: non-stream 200 completed (reasoning+message); stream 200 completed."
11
+ tools: "tools + tool_choice auto: max_output_tokens 128 -> response.incomplete (reason max_output_tokens, 128 output tokens = reasoning ate the budget); 4096 or unset -> completed with reasoning+message; 'run echo hi' -> completed with function_call shell. muse-spark-1.3-contributor same."
12
+ tool_choice: "required / named -> HTTP 400 'only \"auto\" is supported for tool_choice' — exactly the case 7c48409's meta.py projection to auto handles; through the router this never reaches Meta."
13
+ end_to_end_on_this_computer:
14
+ harness: "scratchpad e2e_provider.py: isolated config dir + own port, router = chosen build, real `claude -p` (2.1.275, Bash tool) and `codex exec` (responses wire), prompt asks for a shell tool call and the output. Keys copied from the live config into the throw-away config only."
15
+ bd13b0c_0_2_48: "meta/muse-spark-1.3: Claude exit 0 'hello-from-tool' (2 turns, 8 s), Codex exit 0 'hello-from-tool' (20 s), router log provider_responses_completion_gate_kept confirmed=True. meta/muse-spark-1.3-contributor Codex exit 0 (33 s, gate kept). deepseek/deepseek-v4-pro Claude+Codex exit 0. ollama-cloud/glm-5.1 Claude+Codex exit 0. openrouter/unbiased-pareto Claude+Codex exit 0. nvidia-hosted/qwen3-coder-480b: Claude exit 1 + Codex exit 1, both HTTP 410."
16
+ ab_7c48409: "Same Codex run on the previous build (0.2.47-nightly.20260917-160314.7c48409, backup dir): meta/muse-spark-1.3 exit 0 'hello-from-tool' (42 s, gate kept); nvidia-hosted same 410 on both builds."
17
+ nvidia_410: "Direct upstream POST with the live key, no router: 410 Gone 'The model qwen/qwen3-coder-480b-a35b-instruct has reached its end of life on 2026-06-11 and is no longer available' — upstream EOL, not the patch (the model is still this computer's nvidia-hosted current_model)."
18
+ conclusion:
19
+ patch_not_reproduced: "On this computer the last patches (7c48409 through bd13b0c) do not break muse-spark or the other keyed providers; every route that fails does so identically on the previous build and directly upstream."
20
+ remote_unknown: "Whether 10.0.0.238 runs the same build, which provider/model its router selects, and what its router.log says remain unverified because of the auth gate. 'Most models do not work' there is therefore not attributable yet."
21
+ next_requirement: "From the user or that machine: the administrative external access token for 10.0.0.238:9683 (or its router.log / the exact error text and `ciel-runtimectl version` output there)."
22
+ side_findings:
23
+ - "nvidia-hosted default/current model qwen/qwen3-coder-480b-a35b-instruct is EOL upstream since 2026-06-11 (410); catalog needs a replacement — not changed today."
24
+ - "ollama-cloud glm-5.3-flash: router log repeats WARN ollama_stream_trace_finalize_failed TypeError ResponseTraceController.write() got an unexpected keyword argument 'text_so_far' (live router 9611, 21:17-21:18 KST) — not investigated today."
25
+ - "The pre-gate 6fee3e8 backup lives under ciel-runtime-local-before-general-gate-7c48409-111434/share/ciel-runtime (bin/share layout); the first A/B attempt pointed at the parent dir and failed to import."
@@ -0,0 +1,32 @@
1
+ okf_version: "1.0"
2
+ task: "User: muse-spark stops working, most models fail, 'it was fine before this patch'. Find the cause instead of dismissing the report."
3
+ symptom: "Codex turns end immediately with `event: error {\"type\":\"invalid_request_error\",\"message\":\"invalid_request_error: `input[0]` did not match any supported type\"}`. Observed in G:\ciel-bridge-service (02:15:33Z, 02:59:52Z) and C:\Users\djlov\t (03:24:45Z), all on ciel-runtime-meta-muse-spark-1.3-contributor."
4
+ investigation:
5
+ error_origin: "Reproduced the exact wording by POSTing to https://api.meta.ai/v1/responses: a string, an unknown item type, or an empty object at input[0] each answer `input[0]` did not match any supported type. Orphan tool calls, reasoning items and developer messages produce different messages, so the union-mismatch wording pins the shape."
6
+ false_starts: "Replaying the failing sessions (text and the exact pasted image) through the current build succeeded, and the persisted rollout items all answer 200 when sent directly, so reconstruction from the rollout could not reproduce it. The Responses passthrough logs no upstream line at INFO, so the live router.log holds only `POST /v1/responses 400` and the real body was unrecoverable."
7
+ capture: "Ran the Codex TUI through ciel-runtime's own ConPTY launcher in C:\Users\djlov\t against an isolated router with CIEL_RUNTIME_DUMP_UPSTREAM set. The dumped body reproduced the 400."
8
+ root_cause: "Codex now sends its tool catalogue as an `additional_tools` input item ({type: additional_tools, role: developer, tools: [{type: namespace, name: functions|clock|collaboration|mcp__cua_repl, tools: [...]}]}) and leaves top-level `tools` empty. Only the OpenAI backend accepts that item type; Meta Responses rejects it outright. The router forwarded it unchanged. `codex exec` still uses the old shape, which is why it kept working while the TUI failed in the same folder, on the same build, one minute apart."
9
+ evidence:
10
+ captured_as_is: "HTTP 400 `input[0]` did not match any supported type."
11
+ item_removed: "HTTP 200."
12
+ side_effect_elsewhere: "On the translated (non-Responses) route the item is dropped instead, so the model loses every tool; the ollama turn at 03:11 answered with a raw <tool_call> text blob for that reason."
13
+ fix:
14
+ - "responses_input_compatibility.py: hoist_additional_tools() lifts the namespaced definitions into top-level `tools`, keeping client names so tool calls still map back, first definition wins on a duplicate name, and drops the item."
15
+ - "provider_responses_passthrough.py: forward() applies it after repair_replayed_response_items and before provider normalisation, so every provider on this route benefits and each provider's own tool projection still runs."
16
+ verification:
17
+ live_tui: "Codex TUI in C:\Users\djlov\t through the working tree: before the fix POST /v1/responses 400 twice; after it 7 requests all 200, input carries no additional_tools item, 13 tools hoisted, and the model really called `exec` (function_call replayed on later requests)."
18
+ tests: "test_responses_input_compatibility 23 OK (3 new), test_provider_responses_completion_gate 8 OK; sweep of 28 files touching opencode or the changed modules: 799 tests, 2 pre-existing environment failures only (see below). ruff clean."
19
+ opencode_scope_audit:
20
+ question: "User: the previous patch was meant to reproduce the OpenCode UA and session headers only for the opencode provider — re-check it was not implemented wrongly."
21
+ method: "Computed provider_headers for all 67 default providers on the pre-patch build (0.2.47-nightly.20260917-050538.6fee3e8) and the working tree, for router-originated and client-forwarded requests, and diffed."
22
+ result: "Only opencode and opencode-go differ, and only on the router-originated side: +x-opencode-session, +x-opencode-request, +x-opencode-client and user-agent claude-cli -> opencode/1.18.31. Client-forwarded headers are byte-identical for every provider. No leak."
23
+ gap_found_and_fixed: "compatibility_headers carried the identity on OpenCodeGoProviderAdapter only, so OpenCode Zen probes sent {}. Moved the override to the shared OpenCodeProviderAdapter: Zen and Go now both mint a fresh ses_/msg_ pair with x-opencode-client cli and the opencode user agent per probe. Live `ciel_runtime.py test` on opencode-go still reports Compatibility: OK."
24
+ unrelated_same_day_failures:
25
+ - "OpenRouter stealth/union-alpha retired upstream (404 pointing at unbiased/pareto); catalog swapped in bf9d480."
26
+ - "OpenCode union-alpha unavailable upstream (Go 403 API access paused, Zen 400 Model is unavailable) for every identity, while other models on the same key answer 200."
27
+ - "nvidia-hosted qwen/qwen3-coder-480b-a35b-instruct reached end of life 2026-06-11 and answers 410 directly upstream; it is still that provider's configured model."
28
+ pre_existing_test_failures:
29
+ - "test_statusline.py: 6 failures. They reproduce on origin/main (923989f) and pass in CI, and every case reads the same stale numbers (401,121 tok / compact 1 chunks), so the statusline state lookup is not fully redirected by CIEL_RUNTIME_CONFIG_DIR on a machine with live routers."
30
+ - "test_claude_native_provider.test_start_router_replaces_matching_router_by_default: stash-proven pre-existing; the test patches subprocess.Popen globally while an un-mocked prelaunch taskkill path runs."
31
+ measured_cost_note: "The completion gate from 7c48409 applies to every provider on the Responses route, not just opencode. Measured on captured dumps: 2 of 4 requests in a two-turn run were gate follow-ups carrying full context (72,808 + 73,588 bytes; 48,706 + 49,524 bytes), so a normal text answer costs roughly one extra full-context call. Not changed today; flagged for a decision."
32
+ state: "Working tree; not committed or deployed at the time of writing."
@@ -25,4 +25,5 @@ nightly_push_followup:
25
25
  fix_2: "0fde800 makes guard start best-effort (except OSError -> _console_guard = None) and mocks start_console_guard in the parent-console test with call-argument assertions. Local: conpty file 33/33 OK, ruff clean."
26
26
  green: "CI 35292698229 success, Publish 35292698231 success; npm dist-tag nightly = 0.2.47-nightly.20260918-005041.0fde800."
27
27
  local_resync: "Reinstalled in place from a locally packed 0fde800 tree as 0.2.47-nightly.20260918-010149.0fde800 (backup ciel-local-backup-before-20260918-010149.0fde800); installed windows_conpty.py/windows_console_guard.py SHA-match the working tree; launcher version verified."
28
+ npm_nightly_install: "User asked for the npm nightly install: snapshot pin from the published package (npm pack @oneciel-ai/ciel-runtime@nightly = 0.2.48-nightly.20260918-013037.bd13b0c) into CIEL_RUNTIME_INSTALL_HOME=~/\\.local/share/ciel-runtime-bd13b0c2c9876b915bf8977a5f2de1bb8664aa24, PREFIX=~/.local. User env CIEL_RUNTIME_HOME now pinned to that snapshot dir. Verified: launcher version matches; six key modules SHA-match the bd13b0c tree; import check passes (opencode identity, pareto in openrouter defaults)."
28
29
  union_alpha_upstream: "Meanwhile union-alpha remains unavailable upstream and it is identity-independent: OpenCode CLI itself (v1.18.31) hit the same error in the user's screenshot (OCR: invalid_request_error, api_error 'Error from provider (Console): Upstream request failed: Model is unavailable'); fresh probes show Zen 400 'Model is unavailable' and Go 403 'API access paused for this organization' for both identities, while the same machine's OpenCode session succeeded at 05:55 KST — upstream flapping, gateway /v1/models still lists union-alpha."
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/ciel-runtime",
3
- "version": "0.2.48",
3
+ "version": "0.2.49",
4
4
  "description": "Universal AI coding-agent runtime and model-routing layer for Claude, Codex, AGY, ZCode, and compatible runtimes.",
5
5
  "license": "MIT",
6
6
  "author": "One Ciel LLC",