@oneciel-ai/ciel-runtime 0.2.31 → 0.2.32

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 CHANGED
@@ -3,6 +3,17 @@
3
3
  This file records stable Ciel Runtime releases. Changes are grouped by user-visible
4
4
  capability, followed by the complete commit ledger merged into each release.
5
5
 
6
+ ## 0.2.32 — 2026-08-24
7
+
8
+ - Keep Z.AI Start Plan wire identity aligned with the installed ZCode app:
9
+ `User-Agent: ZCode/3.8.1` and the OAuth JWT in `Authorization: Bearer`, without
10
+ adding `X-Api-Key`.
11
+ - Add opt-in remote CAPTCHA callback settings for browserless hosts: bind host,
12
+ fixed/dynamic port, public HTTP(S) origin, and timeout are configurable through
13
+ `ciel-runtimectl provider-options` or environment variables.
14
+ - Preserve loopback-only behavior by default and retain the state-bound,
15
+ one-request CAPTCHA result receiver when remote callback access is enabled.
16
+
6
17
  ## 0.2.31 — 2026-08-24
7
18
 
8
19
  - Force Z.AI Start Plan OAuth through the state-bound
@@ -5,6 +5,7 @@ from typing import Any, Mapping
5
5
 
6
6
  from ..architecture import (
7
7
  ProviderCapabilities,
8
+ ProviderConfigurationPolicy,
8
9
  ProviderConfig,
9
10
  ProviderContextPolicy,
10
11
  ProviderModelCatalogPolicy,
@@ -13,7 +14,7 @@ from ..architecture import (
13
14
  ProviderStatusPolicy,
14
15
  MessageProtocol,
15
16
  )
16
- from .base import HttpBearerProviderAdapter, provider_configuration
17
+ from .base import HttpBearerProviderAdapter, configuration_policy, provider_configuration
17
18
  from .constants import PROVIDER_DEFAULT_BASE_URLS, ZAI_MODEL_FALLBACK_IDS
18
19
 
19
20
 
@@ -354,6 +355,26 @@ class ZaiStartPlanProviderAdapter(ZaiCodingPlanProviderAdapter):
354
355
  ) -> Mapping[str, str]:
355
356
  return self.build_headers(config, api_key)
356
357
 
358
+ def configuration_policy(
359
+ self, config: ProviderConfig
360
+ ) -> ProviderConfigurationPolicy:
361
+ del config
362
+ return configuration_policy(
363
+ text_option_aliases={
364
+ "captcha_bind_host": "zai_captcha_bind_host",
365
+ "captcha_port": "zai_captcha_port",
366
+ "captcha_public_base_url": "zai_captcha_public_base_url",
367
+ "captcha_timeout_seconds": "zai_captcha_timeout_seconds",
368
+ "zai_captcha_bind_host": "zai_captcha_bind_host",
369
+ "zai_captcha_port": "zai_captcha_port",
370
+ "zai_captcha_public_base_url": "zai_captcha_public_base_url",
371
+ "zai_captcha_timeout_seconds": "zai_captcha_timeout_seconds",
372
+ },
373
+ strip_trailing_slash_fields=frozenset(
374
+ {"zai_captcha_public_base_url"}
375
+ ),
376
+ )
377
+
357
378
  def launch_api_key_error(self, config: ProviderConfig) -> str | None:
358
379
  if not config.api_keys:
359
380
  return self.api_key_launch_error_value
@@ -87,7 +87,7 @@ OPENCODE_ENDPOINT_ALIASES = {
87
87
  }
88
88
 
89
89
  APP_NAME = "Ciel Runtime"
90
- VERSION = "0.2.31"
90
+ VERSION = "0.2.32"
91
91
  CREDITS = "Credits: One Ciel LLC"
92
92
  PRELAUNCH_CANCEL = 10
93
93
  PRELAUNCH_LAUNCH_CODEX = 11
@@ -79,6 +79,7 @@ class _CaptchaResultReceiver:
79
79
  timeout_seconds: float
80
80
  host: str = "127.0.0.1"
81
81
  port: int = 0
82
+ public_base_url: str = ""
82
83
  _server: _LoopbackCaptchaServer | None = field(default=None, init=False)
83
84
  _thread: threading.Thread | None = field(default=None, init=False)
84
85
  _ready: threading.Event = field(default_factory=threading.Event, init=False)
@@ -90,7 +91,36 @@ class _CaptchaResultReceiver:
90
91
  if self._server is None:
91
92
  raise RuntimeError("Z.AI CAPTCHA receiver was not started.")
92
93
  query = urllib.parse.urlencode({"state": self.state})
93
- return f"http://localhost:{self._server.server_port}{_CAPTCHA_PATH}?{query}"
94
+ base = self._resolved_public_base_url(self._server.server_port)
95
+ return f"{base}{_CAPTCHA_PATH}?{query}"
96
+
97
+ def _resolved_public_base_url(self, server_port: int) -> str:
98
+ configured = str(self.public_base_url or "").strip().rstrip("/")
99
+ if not configured:
100
+ return f"http://localhost:{server_port}"
101
+ candidate = configured.replace("{port}", str(server_port))
102
+ parsed = urllib.parse.urlsplit(candidate)
103
+ if (
104
+ parsed.scheme not in {"http", "https"}
105
+ or not parsed.hostname
106
+ or parsed.username is not None
107
+ or parsed.password is not None
108
+ or parsed.query
109
+ or parsed.fragment
110
+ or parsed.path not in {"", "/"}
111
+ ):
112
+ raise RuntimeError(
113
+ "Z.AI CAPTCHA public base URL must be an HTTP(S) origin; "
114
+ "use {port} for the receiver port."
115
+ )
116
+ if "{port}" not in configured and parsed.port is None:
117
+ hostname = parsed.hostname
118
+ if ":" in hostname and not hostname.startswith("["):
119
+ hostname = f"[{hostname}]"
120
+ candidate = urllib.parse.urlunsplit(
121
+ (parsed.scheme, f"{hostname}:{server_port}", "", "", "")
122
+ )
123
+ return candidate.rstrip("/")
94
124
 
95
125
  def __enter__(self) -> "_CaptchaResultReceiver":
96
126
  receiver = self
@@ -314,13 +344,37 @@ class ZaiStartPlanCaptchaBroker:
314
344
  def headers(self, options: Mapping[str, Any]) -> dict[str, str]:
315
345
  app_version = str(options.get("zcode_app_version") or "3.8.1").strip()
316
346
  timeout = self._timeout(options)
347
+ bind_host = self._bind_host(options)
348
+ port = self._port(options)
349
+ public_base_url = self._public_base_url(options)
317
350
  with self._request_lock:
318
351
  config = self.fetch_config(app_version)
319
352
  state = self.random_state()
320
- with self.receiver_factory(config, state, timeout) as receiver:
353
+ with self.receiver_factory(
354
+ config,
355
+ state,
356
+ timeout,
357
+ host=bind_host,
358
+ port=port,
359
+ public_base_url=public_base_url,
360
+ ) as receiver:
321
361
  url = receiver.url
322
362
  self.log("INFO", f"zai_start_plan_captcha_waiting url={url}")
323
- if not self.open_url(url):
363
+ try:
364
+ opened = self.open_url(url)
365
+ except Exception as exc:
366
+ if not public_base_url:
367
+ raise RuntimeError(
368
+ "Could not open the Z.AI Start Plan verification page: "
369
+ + url
370
+ ) from exc
371
+ opened = False
372
+ self.log(
373
+ "WARN",
374
+ "zai_start_plan_captcha_browser_open_failed "
375
+ f"url={url} error={type(exc).__name__}",
376
+ )
377
+ if not opened and not public_base_url:
324
378
  raise RuntimeError(
325
379
  "Could not open the Z.AI Start Plan verification page: " + url
326
380
  )
@@ -345,6 +399,35 @@ class ZaiStartPlanCaptchaBroker:
345
399
  except (TypeError, ValueError):
346
400
  return 120.0
347
401
 
402
+ @staticmethod
403
+ def _bind_host(options: Mapping[str, Any]) -> str:
404
+ return str(
405
+ options.get("zai_captcha_bind_host")
406
+ or os.environ.get("CIEL_RUNTIME_ZAI_CAPTCHA_BIND_HOST")
407
+ or "127.0.0.1"
408
+ ).strip()
409
+
410
+ @staticmethod
411
+ def _port(options: Mapping[str, Any]) -> int:
412
+ raw = options.get("zai_captcha_port") or os.environ.get(
413
+ "CIEL_RUNTIME_ZAI_CAPTCHA_PORT", "0"
414
+ )
415
+ try:
416
+ port = int(raw)
417
+ except (TypeError, ValueError) as exc:
418
+ raise RuntimeError("Z.AI CAPTCHA port must be an integer.") from exc
419
+ if port < 0 or port > 65535:
420
+ raise RuntimeError("Z.AI CAPTCHA port must be between 0 and 65535.")
421
+ return port
422
+
423
+ @staticmethod
424
+ def _public_base_url(options: Mapping[str, Any]) -> str:
425
+ return str(
426
+ options.get("zai_captcha_public_base_url")
427
+ or os.environ.get("CIEL_RUNTIME_ZAI_CAPTCHA_PUBLIC_BASE_URL")
428
+ or ""
429
+ ).strip()
430
+
348
431
  @staticmethod
349
432
  def _platform_key() -> str:
350
433
  system = {"Windows": "win32", "Darwin": "darwin", "Linux": "linux"}.get(
@@ -108,6 +108,23 @@ ciel-runtimectl base-url [PROVIDER] [URL]
108
108
  ciel-runtimectl base-url ollama http://remote-server:11434
109
109
  ```
110
110
 
111
+ #### `provider-options` — Z.AI Start Plan remote CAPTCHA
112
+
113
+ 원격 Linux 호스트에서 Z.AI Start Plan의 사람 검증 페이지를 운영자 브라우저로
114
+ 열어야 할 때는 접근 가능한 인터페이스만 명시적으로 바인딩한다. 기본값은 계속
115
+ `127.0.0.1`이며 외부에 공개되지 않는다.
116
+
117
+ ```bash
118
+ ciel-runtimectl provider-options zai-start-plan \
119
+ captcha_bind_host=100.95.132.58 \
120
+ captcha_port=42119 \
121
+ 'captcha_public_base_url=http://100.95.132.58:{port}'
122
+ ```
123
+
124
+ `captcha_public_base_url`은 `http://` 또는 `https://` origin만 허용하며,
125
+ `{port}`는 실제 CAPTCHA 수신 포트로 치환된다. URL에는 요청마다 새로 생성되는
126
+ state 값이 추가된다.
127
+
111
128
  ---
112
129
 
113
130
  ### API 키 관리
@@ -408,6 +408,34 @@ ConPTY 생성이 불가능하거나 `CIEL_RUNTIME_WINDOWS_CONPTY=0`으로 명시
408
408
  ciel-runtimectl language ko
409
409
  ```
410
410
 
411
+ ### Z.AI Start Plan remote CAPTCHA callback
412
+
413
+ Z.AI Start Plan은 모델 요청 전에 공식 Aliyun CAPTCHA의 일회성 결과가 필요할 수
414
+ 있습니다. 브라우저가 없는 원격 호스트에서는 `provider-options`로 운영자
415
+ 브라우저가 접근할 callback origin을 설정할 수 있습니다.
416
+
417
+ ```bash
418
+ ciel-runtimectl provider-options zai-start-plan \
419
+ captcha_bind_host=100.95.132.58 \
420
+ captcha_port=42119 \
421
+ 'captcha_public_base_url=http://100.95.132.58:{port}' \
422
+ captcha_timeout_seconds=120
423
+ ```
424
+
425
+ - `captcha_bind_host`: 수신 서버가 바인딩할 로컬 인터페이스입니다. 기본값은
426
+ `127.0.0.1`입니다.
427
+ - `captcha_port`: 고정 포트이며 `0`이면 운영체제가 빈 포트를 선택합니다.
428
+ - `captcha_public_base_url`: 운영자 브라우저에 표시할 HTTP(S) origin입니다.
429
+ `{port}` 자리표시자를 사용할 수 있습니다.
430
+ - `captcha_timeout_seconds`: 결과 대기 시간이며 15~600초로 제한됩니다.
431
+
432
+ 환경 변수 `CIEL_RUNTIME_ZAI_CAPTCHA_BIND_HOST`,
433
+ `CIEL_RUNTIME_ZAI_CAPTCHA_PORT`,
434
+ `CIEL_RUNTIME_ZAI_CAPTCHA_PUBLIC_BASE_URL`,
435
+ `CIEL_RUNTIME_ZAI_CAPTCHA_TIMEOUT_SECONDS`도 같은 값을 제공합니다. 공개 callback은
436
+ state-bound 일회성 URL을 사용하지만, 신뢰하는 사설 인터페이스에만 바인딩해야
437
+ 합니다.
438
+
411
439
  ---
412
440
 
413
441
  ## 관련 문서
@@ -0,0 +1,75 @@
1
+ okf_version: "1.0"
2
+ knowledge:
3
+ task:
4
+ id: mia-zai-start-plan-auth-ua-wire-verification-20260824
5
+ date: "2026-08-24"
6
+ objective: >-
7
+ Verify from evidence whether Mia's Z.AI Start Plan routed requests carry
8
+ the OAuth bearer credential and the exact ZCode CLI user agent, and make
9
+ the required human CAPTCHA reachable from a remote operator browser.
10
+ status: in_progress
11
+
12
+ evidence:
13
+ official_zcode_runtime:
14
+ installed_cli_package: zcode-app-cli@3.8.1-15
15
+ app_version: 3.8.1
16
+ header_builder_observation: >-
17
+ The bundled ZCode runtime constructs User-Agent as ZCode/<appVersion>.
18
+ mia_installed_ciel_runtime:
19
+ version: 0.2.31-nightly.20260824-143754.e0bf793
20
+ provider: zai-start-plan
21
+ model: ciel-runtime-zai-start-plan-glm-5.3-1m
22
+ wire_capture:
23
+ user_agent: ZCode/3.8.1
24
+ authorization_present: true
25
+ authorization_scheme: Bearer
26
+ authorization_token_matches_configured_credential: true
27
+ x_api_key_present: false
28
+ secret_handling: >-
29
+ The credential value was not printed; verification used equality and
30
+ scheme checks only.
31
+ connection_failure:
32
+ reproduced_stage: pre-request CAPTCHA header acquisition
33
+ elapsed_seconds: 0.759
34
+ browser_probe: Python webbrowser.get() could not locate a runnable browser
35
+ request_reached_zai_gateway: false
36
+ conclusion: >-
37
+ The observed Mia failure occurs before the Z.AI model request, so the
38
+ verified Authorization and User-Agent headers are not its failing stage.
39
+ browser_runtime:
40
+ headless_shell_result: interactive CAPTCHA required and timed out
41
+ headed_chrome_xvfb_result: interactive CAPTCHA required and timed out
42
+ screenshot_observation: >-
43
+ The official Aliyun SDK displayed a human slider puzzle; changing UA or
44
+ running headed Chrome did not turn it into a traceless pass.
45
+
46
+ implementation:
47
+ auth_and_ua_behavior_change: none
48
+ remote_captcha_callback:
49
+ default_bind_host: 127.0.0.1
50
+ configurable_fields:
51
+ - zai_captcha_bind_host
52
+ - zai_captcha_port
53
+ - zai_captcha_public_base_url
54
+ - zai_captcha_timeout_seconds
55
+ security: >-
56
+ External binding remains opt-in. The public URL must be an HTTP(S)
57
+ origin, and each request retains its random state-bound result URL.
58
+ regression_coverage:
59
+ file: tests/test_zai_provider.py
60
+ assertion: >-
61
+ A real loopback HTTP request must retain User-Agent ZCode/3.8.1 and the
62
+ OAuth Bearer token and must not add X-Api-Key.
63
+
64
+ verification:
65
+ completed:
66
+ - Mia installed-package wire capture
67
+ - ZCode CLI version comparison
68
+ - focused Z.AI regression tests: 32 passed
69
+ - full regression suite: 2769 passed and 136 skipped
70
+ - remote callback GET 200 and state-bound result POST 204 from operator host
71
+ - operator Chrome traceless verification completed with a 280-byte result
72
+ - verification completion screenshot captured
73
+ pending:
74
+ - real Z.AI model response after CAPTCHA
75
+ - main, nightly, npm, and local deployment verification
@@ -57,4 +57,18 @@ knowledge:
57
57
  source_installed_sha256_match: true
58
58
  start_plan_credential_status: connected
59
59
  release:
60
- status: pending
60
+ status: passed
61
+ commit: 47605a5b71a2fc7dd7ead93269f5cf93c56cf66c
62
+ branches:
63
+ main: pushed
64
+ nightly: pushed
65
+ github_actions:
66
+ main_ci: success
67
+ main_npm_publish: success
68
+ nightly_ci: success
69
+ nightly_npm_publish: success
70
+ npm:
71
+ latest: 0.2.31
72
+ nightly: 0.2.31-nightly.20260824-142935.47605a5
73
+ latest_npx_version_verified: true
74
+ nightly_npx_version_verified: true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oneciel-ai/ciel-runtime",
3
- "version": "0.2.31",
3
+ "version": "0.2.32",
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",