@oneciel-ai/ciel-runtime 0.2.22 → 0.2.23
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 +108 -0
- package/README.md +150 -57
- package/ciel-runtime-stop.cmd +1 -0
- package/ciel-runtime.cmd +1 -0
- package/ciel-runtimectl.cmd +1 -0
- package/ciel_runtime.py +56 -53
- package/ciel_runtime_support/anthropic_tool_turns.py +81 -0
- package/ciel_runtime_support/architecture.py +17 -0
- package/ciel_runtime_support/channel_injection.py +113 -3
- package/ciel_runtime_support/channel_message_prompt.py +15 -0
- package/ciel_runtime_support/channel_pending_injection.py +28 -4
- package/ciel_runtime_support/channel_pending_poll.py +2 -0
- package/ciel_runtime_support/channel_terminal_context.py +6 -0
- package/ciel_runtime_support/channel_terminal_dispatch.py +2 -0
- package/ciel_runtime_support/channel_terminal_proxy.py +8 -2
- package/ciel_runtime_support/channel_wake_claim_repository.py +7 -0
- package/ciel_runtime_support/channel_wake_context.py +7 -3
- package/ciel_runtime_support/cli_parser.py +6 -0
- package/ciel_runtime_support/codex_backend_context.py +4 -0
- package/ciel_runtime_support/codex_turn_recovery.py +75 -12
- package/ciel_runtime_support/config_migrations.py +102 -10
- package/ciel_runtime_support/config_repository.py +9 -0
- package/ciel_runtime_support/event_settings_cli.py +284 -2
- package/ciel_runtime_support/openai_chat_passthrough.py +2 -0
- package/ciel_runtime_support/protocols/openai_responses.py +22 -18
- package/ciel_runtime_support/provider_adapters.py +7 -0
- package/ciel_runtime_support/provider_policy.py +10 -1
- package/ciel_runtime_support/provider_request_builder.py +9 -2
- package/ciel_runtime_support/provider_responses_passthrough.py +4 -0
- package/ciel_runtime_support/providers/__init__.py +2 -0
- package/ciel_runtime_support/providers/alibaba.py +33 -10
- package/ciel_runtime_support/providers/constants.py +1 -0
- package/ciel_runtime_support/providers/opencode.py +25 -5
- package/ciel_runtime_support/providers/opencode_go.py +3 -3
- package/ciel_runtime_support/providers/openrouter.py +76 -3
- package/ciel_runtime_support/providers/tabitoken.py +130 -0
- package/ciel_runtime_support/remote_instructions.py +20 -6
- package/ciel_runtime_support/remote_memory.py +515 -55
- package/ciel_runtime_support/responses_input_compatibility.py +14 -0
- package/ciel_runtime_support/router_http.py +5 -0
- package/ciel_runtime_support/runtime_constants.py +1 -1
- package/ciel_runtime_support/runtime_launch.py +9 -2
- package/ciel_runtime_support/runtime_paths.py +2 -0
- package/ciel_runtime_support/tool_side_effect_dedupe.py +7 -4
- package/ciel_runtime_support/transcript_delta_delivery.py +333 -0
- package/ciel_runtime_support/usage_events.py +45 -3
- package/ciel_runtime_support/usage_service.py +871 -0
- package/ciel_runtime_support/windows_conpty.py +108 -2
- package/ciel_runtime_support/workspace_mcp.py +4 -0
- package/docs/CLI-Reference.md +11 -0
- package/docs/Configuration.md +7 -2
- package/docs/MCP-Channels.md +7 -3
- package/docs/Module-Map.md +4 -0
- package/docs/Providers.md +32 -0
- package/docs/Remote-Memory.md +29 -10
- package/docs/Test-Suite.md +1 -0
- package/docs/journal/2026/08/20/kevin-wake-orphan-tool-repair.md +161 -0
- package/docs/journal/2026/08/20/nightly-push-local-deployment.md +59 -2
- package/docs/journal/2026/08/20/wing-remote-memory-replication-verification.md +107 -0
- package/docs/journal/2026/08/21/alibaba-singapore-qwen38-official-alignment.md +64 -0
- package/docs/journal/2026/08/21/codex-visible-sse-wake-submit-regression.md +85 -0
- package/docs/journal/2026/08/21/external-sse-wake-only-change-analysis.md +131 -0
- package/docs/journal/2026/08/21/hyundai-invalid-replayed-tool-name-repair.okf +88 -0
- package/docs/journal/2026/08/21/local-stale-temporary-runtime-pin-repair.okf +118 -0
- package/docs/journal/2026/08/21/transcript-compaction-external-events.okf +131 -0
- package/docs/journal/2026/08/21/wing-ai-net-mcp-boundary-diagnosis.okf +129 -0
- package/docs/journal/2026/08/21/wing-cold-start-wake-submit-repair.okf +161 -0
- package/docs/journal/2026/08/21/wing-powershell-memory-runaway.okf +101 -0
- package/docs/journal/2026/08/21/wing-repeated-reasoning-tool-turn-repair.okf +112 -0
- package/docs/journal/2026/08/21/wing-repeated-tool-guard-finalization.okf +81 -0
- package/docs/journal/2026/08/21/wing-working-directory-memory-deployment.md +110 -0
- package/docs/journal/2026/08/21/wing-workspace-state-memory-prompt-injection.md +88 -0
- package/docs/journal/2026/08/22/alibaba-singapore-dual-endpoint-wire-alignment.okf +142 -0
- package/docs/journal/2026/08/22/delluhiold-claude-compaction-empty-tool-name.okf +168 -0
- package/docs/journal/2026/08/22/remote-memory-relative-prompt-paths.okf +79 -0
- package/docs/journal/2026/08/22/remote-memory-root-system-prompt.okf +121 -0
- package/docs/journal/2026/08/23/claude-web-chat-stateless-mcp-repair.okf +97 -0
- package/docs/journal/2026/08/23/cross-runtime-token-usage-interfaces-research.okf +378 -0
- package/docs/journal/2026/08/23/main-0.2.23-readme-release.okf +81 -0
- package/docs/journal/2026/08/23/mia-openrouter-ox-alpha-latency-diagnosis.okf +146 -0
- package/docs/journal/2026/08/23/openrouter-opencode-ox-alpha-support.okf +114 -0
- package/docs/journal/2026/08/23/tabitoken-provider-model-catalog.okf +97 -0
- package/docs/journal/2026/08/23/usage-stream-snapshot-implementation.okf +134 -0
- package/docs/usage-observability.md +141 -0
- package/install.ps1 +24 -3
- package/package.json +2 -1
|
@@ -22,6 +22,10 @@ from .base import (
|
|
|
22
22
|
from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
|
|
23
23
|
|
|
24
24
|
|
|
25
|
+
OPENCODE_ZEN_OX_ALPHA_FREE_MODEL = "x-preview-f-free"
|
|
26
|
+
OPENCODE_GO_OX_ALPHA_FREE_MODEL = "ox-alpha-free"
|
|
27
|
+
|
|
28
|
+
|
|
25
29
|
@dataclass(frozen=True)
|
|
26
30
|
class OpenCodeProviderAdapter(HttpBearerProviderAdapter):
|
|
27
31
|
name: str = "opencode"
|
|
@@ -29,7 +33,10 @@ class OpenCodeProviderAdapter(HttpBearerProviderAdapter):
|
|
|
29
33
|
configuration_defaults_value: dict = field(
|
|
30
34
|
default_factory=lambda: provider_configuration(
|
|
31
35
|
"claude-sonnet-4-6",
|
|
32
|
-
custom_models=(
|
|
36
|
+
custom_models=(
|
|
37
|
+
"claude-sonnet-4-6",
|
|
38
|
+
OPENCODE_ZEN_OX_ALPHA_FREE_MODEL,
|
|
39
|
+
),
|
|
33
40
|
native_compat=True,
|
|
34
41
|
context_window=200000,
|
|
35
42
|
max_output_tokens=8192,
|
|
@@ -40,7 +47,7 @@ class OpenCodeProviderAdapter(HttpBearerProviderAdapter):
|
|
|
40
47
|
ip_family="ipv6-preferred",
|
|
41
48
|
haiku_model="claude-haiku-4-5",
|
|
42
49
|
subagent_model="claude-sonnet-4-6",
|
|
43
|
-
model_endpoints={},
|
|
50
|
+
model_endpoints={OPENCODE_ZEN_OX_ALPHA_FREE_MODEL: "openai-chat"},
|
|
44
51
|
)
|
|
45
52
|
)
|
|
46
53
|
send_placeholder_key: bool = True
|
|
@@ -132,21 +139,30 @@ class OpenCodeProviderAdapter(HttpBearerProviderAdapter):
|
|
|
132
139
|
normalized = normalized[len(prefix) :]
|
|
133
140
|
break
|
|
134
141
|
if self.name == "opencode-go":
|
|
142
|
+
if normalized.startswith(("gpt-", "grok-", "muse-spark-")):
|
|
143
|
+
return "openai_responses"
|
|
144
|
+
if normalized == OPENCODE_GO_OX_ALPHA_FREE_MODEL:
|
|
145
|
+
return "openai_chat"
|
|
146
|
+
if normalized == "hy3":
|
|
147
|
+
return "openai_chat"
|
|
135
148
|
if normalized.startswith(("glm-", "kimi-", "deepseek-", "mimo-", "hy3-")):
|
|
136
149
|
return "openai_chat"
|
|
137
150
|
return "anthropic_messages"
|
|
138
|
-
if normalized.startswith("gpt-"):
|
|
151
|
+
if normalized.startswith(("gpt-", "grok-", "muse-spark-")):
|
|
139
152
|
return "openai_responses"
|
|
140
153
|
if normalized.startswith("gemini-"):
|
|
141
154
|
return "google_generative"
|
|
155
|
+
if normalized == OPENCODE_ZEN_OX_ALPHA_FREE_MODEL:
|
|
156
|
+
return "openai_chat"
|
|
142
157
|
if normalized.startswith(
|
|
143
158
|
(
|
|
144
159
|
"minimax-",
|
|
145
160
|
"glm-",
|
|
146
161
|
"kimi-",
|
|
147
|
-
"grok-",
|
|
148
162
|
"big-pickle",
|
|
149
163
|
"deepseek-",
|
|
164
|
+
"hy3-",
|
|
165
|
+
"laguna-",
|
|
150
166
|
"mimo-",
|
|
151
167
|
"nemotron-",
|
|
152
168
|
"north-",
|
|
@@ -217,4 +233,8 @@ class OpenCodeProviderAdapter(HttpBearerProviderAdapter):
|
|
|
217
233
|
return configuration_policy(supports_model_endpoint_overrides=True)
|
|
218
234
|
|
|
219
235
|
|
|
220
|
-
__all__ = [
|
|
236
|
+
__all__ = [
|
|
237
|
+
"OPENCODE_GO_OX_ALPHA_FREE_MODEL",
|
|
238
|
+
"OPENCODE_ZEN_OX_ALPHA_FREE_MODEL",
|
|
239
|
+
"OpenCodeProviderAdapter",
|
|
240
|
+
]
|
|
@@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
|
|
4
4
|
|
|
5
5
|
from .base import provider_configuration
|
|
6
6
|
from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
|
|
7
|
-
from .opencode import OpenCodeProviderAdapter
|
|
7
|
+
from .opencode import OPENCODE_GO_OX_ALPHA_FREE_MODEL, OpenCodeProviderAdapter
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
@dataclass(frozen=True)
|
|
@@ -14,7 +14,7 @@ class OpenCodeGoProviderAdapter(OpenCodeProviderAdapter):
|
|
|
14
14
|
configuration_defaults_value: dict = field(
|
|
15
15
|
default_factory=lambda: provider_configuration(
|
|
16
16
|
"qwen3.6-plus",
|
|
17
|
-
custom_models=("qwen3.6-plus",),
|
|
17
|
+
custom_models=("qwen3.6-plus", OPENCODE_GO_OX_ALPHA_FREE_MODEL),
|
|
18
18
|
native_compat=True,
|
|
19
19
|
context_window=1048576,
|
|
20
20
|
max_output_tokens=8192,
|
|
@@ -25,7 +25,7 @@ class OpenCodeGoProviderAdapter(OpenCodeProviderAdapter):
|
|
|
25
25
|
ip_family="ipv6-preferred",
|
|
26
26
|
haiku_model="qwen3.5-plus",
|
|
27
27
|
subagent_model="qwen3.6-plus",
|
|
28
|
-
model_endpoints={},
|
|
28
|
+
model_endpoints={OPENCODE_GO_OX_ALPHA_FREE_MODEL: "openai-chat"},
|
|
29
29
|
)
|
|
30
30
|
)
|
|
31
31
|
api_key_display_name_value: str = "OpenCode Go"
|
|
@@ -1,12 +1,23 @@
|
|
|
1
1
|
"""OpenRouter provider adapter."""
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Mapping
|
|
4
5
|
|
|
5
|
-
from ..architecture import
|
|
6
|
+
from ..architecture import (
|
|
7
|
+
MessageProtocol,
|
|
8
|
+
ProviderCapabilities,
|
|
9
|
+
ProviderConfig,
|
|
10
|
+
ProviderContextPolicy,
|
|
11
|
+
)
|
|
6
12
|
from .base import OpenAICompatibleProviderAdapter, provider_configuration
|
|
7
13
|
from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
|
|
8
14
|
|
|
9
15
|
|
|
16
|
+
OPENROUTER_OX_ALPHA_MODEL = "stealth/ox-alpha"
|
|
17
|
+
OPENROUTER_OX_ALPHA_CONTEXT_WINDOW = 1_048_576
|
|
18
|
+
OPENROUTER_OX_ALPHA_MAX_OUTPUT_TOKENS = 131_072
|
|
19
|
+
|
|
20
|
+
|
|
10
21
|
@dataclass(frozen=True)
|
|
11
22
|
class OpenRouterProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
12
23
|
name: str = "OpenRouter"
|
|
@@ -14,6 +25,7 @@ class OpenRouterProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
14
25
|
configuration_defaults_value: dict = field(
|
|
15
26
|
default_factory=lambda: provider_configuration(
|
|
16
27
|
"nvidia/nemotron-3-ultra-550b-a55b:free",
|
|
28
|
+
custom_models=(OPENROUTER_OX_ALPHA_MODEL,),
|
|
17
29
|
native_compat=False,
|
|
18
30
|
rate_limit_rpm=0,
|
|
19
31
|
rate_limit_status=False,
|
|
@@ -50,8 +62,69 @@ class OpenRouterProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
|
50
62
|
def router_native_anthropic_enabled(
|
|
51
63
|
self, config: ProviderConfig, model: str | None = None
|
|
52
64
|
) -> bool:
|
|
65
|
+
return self.select_protocol("anthropic_messages", config, model) == "anthropic_messages"
|
|
66
|
+
|
|
67
|
+
def supported_protocols(
|
|
68
|
+
self, config: ProviderConfig, model: str | None = None
|
|
69
|
+
) -> frozenset[MessageProtocol]:
|
|
70
|
+
protocols: set[MessageProtocol] = {"openai_chat"}
|
|
71
|
+
selected = self.normalize_model_id(str(model or config.model or ""))
|
|
72
|
+
native = config.options.get("native_compat")
|
|
73
|
+
if selected == OPENROUTER_OX_ALPHA_MODEL or native is True or str(
|
|
74
|
+
native
|
|
75
|
+
).strip().lower() in {"1", "true", "yes", "on"}:
|
|
76
|
+
protocols.add("anthropic_messages")
|
|
77
|
+
return frozenset(protocols)
|
|
78
|
+
|
|
79
|
+
def select_protocol(
|
|
80
|
+
self,
|
|
81
|
+
operation: MessageProtocol,
|
|
82
|
+
config: ProviderConfig,
|
|
83
|
+
model: str | None = None,
|
|
84
|
+
) -> MessageProtocol:
|
|
85
|
+
if operation == "anthropic_messages" and "anthropic_messages" in self.supported_protocols(
|
|
86
|
+
config, model
|
|
87
|
+
):
|
|
88
|
+
return "anthropic_messages"
|
|
89
|
+
return "openai_chat"
|
|
90
|
+
|
|
91
|
+
def model_configuration_profile(
|
|
92
|
+
self, config: ProviderConfig
|
|
93
|
+
) -> tuple[Mapping[str, Any], str | None]:
|
|
94
|
+
if self.normalize_model_id(config.model) != OPENROUTER_OX_ALPHA_MODEL:
|
|
95
|
+
return {}, None
|
|
96
|
+
return (
|
|
97
|
+
{
|
|
98
|
+
"context_window": OPENROUTER_OX_ALPHA_CONTEXT_WINDOW,
|
|
99
|
+
"max_model_len": OPENROUTER_OX_ALPHA_CONTEXT_WINDOW,
|
|
100
|
+
"max_output_tokens": OPENROUTER_OX_ALPHA_MAX_OUTPUT_TOKENS,
|
|
101
|
+
"model_profile": "openrouter-ox-alpha-1m",
|
|
102
|
+
"supports_tool_choice": True,
|
|
103
|
+
"supports_vision": True,
|
|
104
|
+
},
|
|
105
|
+
"OpenRouter Ox Alpha profile applied: 1,048,576-token context and 131,072-token maximum output.",
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def openai_reasoning_effort(
|
|
109
|
+
self,
|
|
110
|
+
config: ProviderConfig,
|
|
111
|
+
model: str,
|
|
112
|
+
request: Mapping[str, Any],
|
|
113
|
+
) -> str | None:
|
|
53
114
|
del config, model
|
|
54
|
-
|
|
115
|
+
metadata = request.get("metadata")
|
|
116
|
+
hinted = (
|
|
117
|
+
metadata.get("ciel_runtime_reasoning_effort")
|
|
118
|
+
if isinstance(metadata, Mapping)
|
|
119
|
+
else None
|
|
120
|
+
)
|
|
121
|
+
value = str(request.get("reasoning_effort") or hinted or "").strip().casefold()
|
|
122
|
+
return value or None
|
|
55
123
|
|
|
56
124
|
|
|
57
|
-
__all__ = [
|
|
125
|
+
__all__ = [
|
|
126
|
+
"OPENROUTER_OX_ALPHA_CONTEXT_WINDOW",
|
|
127
|
+
"OPENROUTER_OX_ALPHA_MAX_OUTPUT_TOKENS",
|
|
128
|
+
"OPENROUTER_OX_ALPHA_MODEL",
|
|
129
|
+
"OpenRouterProviderAdapter",
|
|
130
|
+
]
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""TaBiAI (tabitoken.com) provider adapter."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Mapping
|
|
5
|
+
|
|
6
|
+
from ..architecture import (
|
|
7
|
+
MessageProtocol,
|
|
8
|
+
ProviderCapabilities,
|
|
9
|
+
ProviderConfig,
|
|
10
|
+
ProviderContextPolicy,
|
|
11
|
+
ProviderModelCatalogPolicy,
|
|
12
|
+
ProviderRequestPolicy,
|
|
13
|
+
)
|
|
14
|
+
from .base import OpenAICompatibleProviderAdapter, provider_configuration
|
|
15
|
+
from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
TABITOKEN_MODELS: tuple[str, ...] = (
|
|
19
|
+
"claude-opus-4-8",
|
|
20
|
+
"claude-opus-4-8-thinking",
|
|
21
|
+
"claude-opus-5",
|
|
22
|
+
"claude-opus-5-thinking",
|
|
23
|
+
)
|
|
24
|
+
TABITOKEN_THINKING_MODELS = frozenset(
|
|
25
|
+
model for model in TABITOKEN_MODELS if model.endswith("-thinking")
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(frozen=True)
|
|
30
|
+
class TabitokenProviderAdapter(OpenAICompatibleProviderAdapter):
|
|
31
|
+
"""Dual OpenAI Chat and Anthropic Messages transport for TaBiAI."""
|
|
32
|
+
|
|
33
|
+
name: str = "tabitoken"
|
|
34
|
+
base_url: str = PROVIDER_DEFAULT_BASE_URLS["tabitoken"]
|
|
35
|
+
configuration_defaults_value: dict = field(
|
|
36
|
+
default_factory=lambda: provider_configuration(
|
|
37
|
+
TABITOKEN_MODELS[0],
|
|
38
|
+
custom_models=TABITOKEN_MODELS,
|
|
39
|
+
native_compat=True,
|
|
40
|
+
context_window=1_000_000,
|
|
41
|
+
max_output_tokens=8192,
|
|
42
|
+
context_reserve_tokens=8192,
|
|
43
|
+
request_timeout_ms=DEFAULT_REQUEST_TIMEOUT_MS,
|
|
44
|
+
stream_enabled=True,
|
|
45
|
+
stream_word_chunking=False,
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
authorization_header: str = "Authorization"
|
|
49
|
+
include_x_api_key: bool = False
|
|
50
|
+
require_api_key: bool = True
|
|
51
|
+
api_key_display_name_value: str = "TaBiAI (Tabitoken.com)"
|
|
52
|
+
api_key_launch_error_value: str = (
|
|
53
|
+
"Launch blocked: TaBiAI (Tabitoken.com) requires an API key."
|
|
54
|
+
)
|
|
55
|
+
capabilities_value: ProviderCapabilities = field(
|
|
56
|
+
default_factory=lambda: ProviderCapabilities(
|
|
57
|
+
upstream_protocol="openai_chat",
|
|
58
|
+
requires_api_key=True,
|
|
59
|
+
supports_thinking=True,
|
|
60
|
+
preserves_anthropic_thinking=True,
|
|
61
|
+
)
|
|
62
|
+
)
|
|
63
|
+
request_policy_value: ProviderRequestPolicy = field(
|
|
64
|
+
default_factory=lambda: ProviderRequestPolicy(
|
|
65
|
+
chat_path="/v1/chat/completions",
|
|
66
|
+
models_path="/v1/models",
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
model_catalog_policy_value: ProviderModelCatalogPolicy = field(
|
|
70
|
+
default_factory=lambda: ProviderModelCatalogPolicy(
|
|
71
|
+
kind="openai",
|
|
72
|
+
fallback_models=TABITOKEN_MODELS,
|
|
73
|
+
allow_configured_fallback=True,
|
|
74
|
+
)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def context_policy(self, config: ProviderConfig) -> ProviderContextPolicy:
|
|
78
|
+
del config
|
|
79
|
+
return ProviderContextPolicy(
|
|
80
|
+
capacity_strategy="configured_first",
|
|
81
|
+
settings_strategy="standard",
|
|
82
|
+
hosted_timeout=True,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def anthropic_base_url(self, config: ProviderConfig) -> str:
|
|
86
|
+
return str(config.base_url or self.default_base_url()).rstrip("/")
|
|
87
|
+
|
|
88
|
+
def supported_protocols(
|
|
89
|
+
self,
|
|
90
|
+
config: ProviderConfig,
|
|
91
|
+
model: str | None = None,
|
|
92
|
+
) -> frozenset[MessageProtocol]:
|
|
93
|
+
del config, model
|
|
94
|
+
return frozenset({"anthropic_messages", "openai_chat"})
|
|
95
|
+
|
|
96
|
+
def select_protocol(
|
|
97
|
+
self,
|
|
98
|
+
operation: MessageProtocol,
|
|
99
|
+
config: ProviderConfig,
|
|
100
|
+
model: str | None = None,
|
|
101
|
+
) -> MessageProtocol:
|
|
102
|
+
del config, model
|
|
103
|
+
if operation == "anthropic_messages":
|
|
104
|
+
return "anthropic_messages"
|
|
105
|
+
return "openai_chat"
|
|
106
|
+
|
|
107
|
+
def openai_reasoning_effort(
|
|
108
|
+
self,
|
|
109
|
+
config: ProviderConfig,
|
|
110
|
+
model: str,
|
|
111
|
+
request: Mapping[str, Any],
|
|
112
|
+
) -> str | None:
|
|
113
|
+
del config
|
|
114
|
+
if self.normalize_model_id(model) not in TABITOKEN_THINKING_MODELS:
|
|
115
|
+
return None
|
|
116
|
+
metadata = request.get("metadata")
|
|
117
|
+
hinted = (
|
|
118
|
+
metadata.get("ciel_runtime_reasoning_effort")
|
|
119
|
+
if isinstance(metadata, Mapping)
|
|
120
|
+
else None
|
|
121
|
+
)
|
|
122
|
+
value = str(request.get("reasoning_effort") or hinted or "").strip().casefold()
|
|
123
|
+
return value or None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
__all__ = [
|
|
127
|
+
"TABITOKEN_MODELS",
|
|
128
|
+
"TABITOKEN_THINKING_MODELS",
|
|
129
|
+
"TabitokenProviderAdapter",
|
|
130
|
+
]
|
|
@@ -65,6 +65,13 @@ def target_file(workspace: Path, runtime: str) -> Path:
|
|
|
65
65
|
return workspace.resolve() / filename
|
|
66
66
|
|
|
67
67
|
|
|
68
|
+
def normalized_instruction_sha256(value: str) -> str:
|
|
69
|
+
"""Hash managed instruction text independently of platform line endings."""
|
|
70
|
+
|
|
71
|
+
normalized = value.replace("\r\n", "\n").replace("\r", "\n").strip()
|
|
72
|
+
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
|
73
|
+
|
|
74
|
+
|
|
68
75
|
@dataclass(frozen=True, slots=True)
|
|
69
76
|
class RemoteInstructionResult:
|
|
70
77
|
runtime: str
|
|
@@ -104,10 +111,14 @@ class RemoteInstructionSynchronizer:
|
|
|
104
111
|
return self._failed(runtime, url, f"missing authorization environment variable: {', '.join(sorted(set(missing)))}")
|
|
105
112
|
if authorization.strip():
|
|
106
113
|
headers["Authorization"] = authorization.strip()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
# States written before normalized content verification cannot safely
|
|
115
|
+
# authorize pointer projection after platform newline conversion.
|
|
116
|
+
# Refresh them without validators once so the next state is verifiable.
|
|
117
|
+
if state.get("normalized_sha256"):
|
|
118
|
+
if state.get("etag"):
|
|
119
|
+
headers["If-None-Match"] = str(state["etag"])
|
|
120
|
+
if state.get("last_modified"):
|
|
121
|
+
headers["If-Modified-Since"] = str(state["last_modified"])
|
|
111
122
|
request = urllib.request.Request(url, headers=headers, method="GET")
|
|
112
123
|
try:
|
|
113
124
|
with self.urlopen(request, timeout=timeout) as response:
|
|
@@ -126,6 +137,7 @@ class RemoteInstructionSynchronizer:
|
|
|
126
137
|
"url": url,
|
|
127
138
|
"target": path.name,
|
|
128
139
|
"sha256": digest,
|
|
140
|
+
"normalized_sha256": normalized_instruction_sha256(text),
|
|
129
141
|
"etag": str(response.headers.get("etag") or ""),
|
|
130
142
|
"last_modified": str(response.headers.get("last-modified") or ""),
|
|
131
143
|
"updated_at": self.now(),
|
|
@@ -162,14 +174,15 @@ class RemoteInstructionSynchronizer:
|
|
|
162
174
|
|
|
163
175
|
@staticmethod
|
|
164
176
|
def _write_if_changed(path: Path, text: str) -> bool:
|
|
177
|
+
raw = text.encode("utf-8")
|
|
165
178
|
try:
|
|
166
|
-
if path.
|
|
179
|
+
if path.read_bytes() == raw:
|
|
167
180
|
return False
|
|
168
181
|
except FileNotFoundError:
|
|
169
182
|
pass
|
|
170
183
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
171
184
|
temporary = path.with_name(path.name + ".ciel-runtime.tmp")
|
|
172
|
-
temporary.
|
|
185
|
+
temporary.write_bytes(raw)
|
|
173
186
|
os.replace(temporary, path)
|
|
174
187
|
return True
|
|
175
188
|
|
|
@@ -237,6 +250,7 @@ __all__ = [
|
|
|
237
250
|
"SynchronizedLaunch",
|
|
238
251
|
"configured_url",
|
|
239
252
|
"expand_environment_references",
|
|
253
|
+
"normalized_instruction_sha256",
|
|
240
254
|
"panel_rows",
|
|
241
255
|
"settings",
|
|
242
256
|
"target_file",
|