@oneciel-ai/ciel-runtime 0.2.37 → 0.2.39

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.
Files changed (142) hide show
  1. package/CHANGELOG.md +85 -0
  2. package/README.md +5 -2
  3. package/ciel_runtime.py +94 -96
  4. package/ciel_runtime_support/anthropic_model_policy.py +20 -3
  5. package/ciel_runtime_support/architecture.py +5 -0
  6. package/ciel_runtime_support/channel_inflight.py +4 -4
  7. package/ciel_runtime_support/channel_injection.py +56 -1
  8. package/ciel_runtime_support/channel_mcp_tools.py +34 -0
  9. package/ciel_runtime_support/channel_message_policy.py +9 -1
  10. package/ciel_runtime_support/channel_message_prompt.py +23 -0
  11. package/ciel_runtime_support/channel_pending_injection.py +86 -32
  12. package/ciel_runtime_support/channel_pending_poll.py +6 -2
  13. package/ciel_runtime_support/channel_runtime_environment.py +0 -11
  14. package/ciel_runtime_support/channel_terminal_context.py +0 -6
  15. package/ciel_runtime_support/channel_terminal_proxy.py +3 -42
  16. package/ciel_runtime_support/channel_transcript_repository.py +16 -1
  17. package/ciel_runtime_support/channel_wake_claim_repository.py +0 -19
  18. package/ciel_runtime_support/channel_wake_context.py +6 -13
  19. package/ciel_runtime_support/channel_wake_delivery_repository.py +50 -0
  20. package/ciel_runtime_support/chat_http_controller.py +88 -5
  21. package/ciel_runtime_support/claude_environment.py +100 -3
  22. package/ciel_runtime_support/claude_launch_assembly.py +4 -0
  23. package/ciel_runtime_support/claude_session_socket.py +181 -0
  24. package/ciel_runtime_support/cli_application_context.py +18 -10
  25. package/ciel_runtime_support/cli_dispatch.py +19 -3
  26. package/ciel_runtime_support/cli_parser.py +2 -0
  27. package/ciel_runtime_support/cli_usage.py +3 -2
  28. package/ciel_runtime_support/codex_backend_context.py +8 -3
  29. package/ciel_runtime_support/codex_completion_gate.py +198 -0
  30. package/ciel_runtime_support/codex_turn_recovery.py +240 -83
  31. package/ciel_runtime_support/compatibility_protocol.py +5 -2
  32. package/ciel_runtime_support/config_migrations.py +79 -0
  33. package/ciel_runtime_support/config_repository.py +7 -0
  34. package/ciel_runtime_support/context_summary_policy.py +24 -4
  35. package/ciel_runtime_support/external_event_menu.py +107 -0
  36. package/ciel_runtime_support/external_event_receiver.py +19 -1
  37. package/ciel_runtime_support/launch_state.py +2 -0
  38. package/ciel_runtime_support/managed_tool_injection.py +81 -0
  39. package/ciel_runtime_support/muse_runtime_context.py +291 -0
  40. package/ciel_runtime_support/ollama_thinking.py +12 -26
  41. package/ciel_runtime_support/prelaunch.py +13 -0
  42. package/ciel_runtime_support/prelaunch_launch_panel.py +2 -1
  43. package/ciel_runtime_support/prompt_compaction.py +39 -0
  44. package/ciel_runtime_support/provider_files_proxy.py +248 -0
  45. package/ciel_runtime_support/provider_model_identity.py +10 -2
  46. package/ciel_runtime_support/provider_option_cli.py +1 -1
  47. package/ciel_runtime_support/provider_request_access.py +54 -1
  48. package/ciel_runtime_support/provider_responses_passthrough.py +152 -15
  49. package/ciel_runtime_support/providers/alibaba.py +34 -1
  50. package/ciel_runtime_support/providers/anthropic.py +18 -1
  51. package/ciel_runtime_support/providers/meta.py +252 -21
  52. package/ciel_runtime_support/pseudo_tool_parser.py +17 -1
  53. package/ciel_runtime_support/remote_bridge.py +4 -0
  54. package/ciel_runtime_support/remote_instructions.py +5 -0
  55. package/ciel_runtime_support/remote_memory.py +1 -1
  56. package/ciel_runtime_support/responses_cache_diagnostics.py +82 -0
  57. package/ciel_runtime_support/responses_custom_tool_bridge.py +282 -0
  58. package/ciel_runtime_support/responses_input_compatibility.py +19 -3
  59. package/ciel_runtime_support/router_http.py +301 -4
  60. package/ciel_runtime_support/router_observability_context.py +26 -0
  61. package/ciel_runtime_support/router_server_context.py +1 -0
  62. package/ciel_runtime_support/runtime_adapters.py +31 -0
  63. package/ciel_runtime_support/runtime_constants.py +23 -1
  64. package/ciel_runtime_support/runtime_input_gateway.py +85 -5
  65. package/ciel_runtime_support/runtime_input_status.py +150 -0
  66. package/ciel_runtime_support/runtime_launch.py +35 -4
  67. package/ciel_runtime_support/runtime_paths.py +1 -0
  68. package/ciel_runtime_support/speech_http_controller.py +3 -2
  69. package/ciel_runtime_support/streaming_anthropic.py +3 -0
  70. package/ciel_runtime_support/tool_call_events.py +98 -0
  71. package/ciel_runtime_support/transcript_delta_delivery.py +140 -1
  72. package/ciel_runtime_support/ui_text.py +1 -0
  73. package/ciel_runtime_support/web_search_result_events.py +130 -0
  74. package/ciel_runtime_support/web_ui.py +1 -0
  75. package/ciel_runtime_support/windows_conpty.py +92 -8
  76. package/ciel_runtime_support/workspace_mcp.py +4 -1
  77. package/ciel_runtime_support/workspace_state.py +1 -0
  78. package/docs/Configuration.md +20 -4
  79. package/docs/MCP-Channels.md +79 -6
  80. package/docs/Managed-Tool-Injection.md +25 -0
  81. package/docs/Module-Map.md +4 -0
  82. package/docs/Muse-Code.md +73 -0
  83. package/docs/Observability.md +39 -0
  84. package/docs/Providers.md +29 -2
  85. package/docs/Remote-Bridge.md +12 -0
  86. package/docs/Router.md +1 -0
  87. package/docs/journal/2026/09/01/diagnostics/anthropic/router/fable51-usage-credits.okf +70 -0
  88. package/docs/journal/2026/09/01/implementation/claude/session-socket/all-input-paths/default-delivery.okf +133 -0
  89. package/docs/journal/2026/09/01/implementation/claude/session-socket/windows-direct-input.okf +112 -0
  90. package/docs/journal/2026/09/02/diagnostics/cache/alibaba/qwen38-post-restart-hit-rate.okf +107 -0
  91. package/docs/journal/2026/09/02/diagnostics/cache/ollama-cloud/kimi-k3-codex-hit-rate.okf +91 -0
  92. package/docs/journal/2026/09/02/diagnostics/codex/tui/statusline-cache-metrics.okf +44 -0
  93. package/docs/journal/2026/09/02/diagnostics/input/transport/fallback-behavior.okf +31 -0
  94. package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-agent-turn-recovery.okf +125 -0
  95. package/docs/journal/2026/09/02/diagnostics/providers/ollama-cloud/kimi-k3-parameters.okf +128 -0
  96. package/docs/journal/2026/09/02/diagnostics/remote/mia/socket-tui-visibility.okf +47 -0
  97. package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-latency.okf +108 -0
  98. package/docs/journal/2026/09/02/diagnostics/runtime/alibaba/token-plan/qwen38-vs-gpt56-latency.okf +92 -0
  99. package/docs/journal/2026/09/02/implementation/anthropic/context/one-million-defaults.okf +85 -0
  100. package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.okf +104 -0
  101. package/docs/journal/2026/09/02/implementation/cache/alibaba/qwen38-cache-hit-improvement.png +0 -0
  102. package/docs/journal/2026/09/02/implementation/cache/alibaba/responses-session-cache.okf +97 -0
  103. package/docs/journal/2026/09/02/implementation/providers/alibaba/qwen38/0902-parameter-alignment.okf +97 -0
  104. package/docs/journal/2026/09/02/implementation/providers/meta/multimodal-tools/contributor-protocol-adoption.okf +140 -0
  105. package/docs/journal/2026/09/02/implementation/providers/meta/muse-spark-1.3-support.okf +53 -0
  106. package/docs/journal/2026/09/02/monitoring/cache/alibaba/cielarvis-qwen38-live-hit-rate.okf +858 -0
  107. package/docs/journal/2026/09/02/operations/release/main-merge-local-deploy.okf +36 -0
  108. package/docs/journal/2026/09/02/operations/release/nightly/local-cache-deployment.okf +54 -0
  109. package/docs/journal/2026/09/02/operations/release/nightly/ollama-kimi-recovery-model-audit-deployment.okf +58 -0
  110. package/docs/journal/2026/09/02/operations/release/nightly/qwen38-cache-improvement-deployment.okf +72 -0
  111. package/docs/journal/2026/09/02/research/cache/alibaba/qwen38-hit-rate-improvement.okf +233 -0
  112. package/docs/journal/2026/09/02/research/cache/codex/provider-scope.okf +77 -0
  113. package/docs/journal/2026/09/02/research/providers/ollama-cloud/desktop-model-exhaustive-audit.okf +159 -0
  114. package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-cache-hit.okf +148 -0
  115. package/docs/journal/2026/09/03/diagnostics/providers/meta/muse-spark-contributor-required-schema.okf +127 -0
  116. package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-resumed-session-stall.okf +160 -0
  117. package/docs/journal/2026/09/03/diagnostics/providers/ollama-cloud/kimi-k3-substantive-dangling-action.okf +94 -0
  118. package/docs/journal/2026/09/03/diagnostics/remote/kevin/codex-gpt-early-turn-completion.okf +162 -0
  119. package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/default-yolo.okf +40 -0
  120. package/docs/journal/2026/09/03/implementation/runtimes/meta/muse-code/native-subscription-router.okf +86 -0
  121. package/docs/journal/2026/09/03/operations/deployment/local/session-socket-default-fallback.okf +41 -0
  122. package/docs/journal/2026/09/03/research/deployment/colab/tailscale/credential-storage.okf +41 -0
  123. package/docs/journal/2026/09/03/research/providers/meta/muse-code/subscription-billing-boundary.okf +64 -0
  124. package/docs/journal/2026/09/03/research/web-chat/voice/instruction-format.okf +135 -0
  125. package/docs/journal/2026/09/04/diagnostics/claude/context/early-auto-compaction.okf +75 -0
  126. package/docs/journal/2026/09/04/diagnostics/claude/context/fable-51-status-200k.okf +54 -0
  127. package/docs/journal/2026/09/04/diagnostics/runtime/codex/responses/replayed-item-id-validation.okf +74 -0
  128. package/docs/journal/2026/09/04/diagnostics/workspaces/onecieldmsui/codex/replay-stall.okf +100 -0
  129. package/docs/journal/2026/09/04/implementation/observability/tool-calls/websocket-stream.okf +49 -0
  130. package/docs/journal/2026/09/04/implementation/web-chat/input/raw-injection.okf +42 -0
  131. package/docs/journal/2026/09/05/diagnostics/tty/claude-raw-idle-render.okf +25 -0
  132. package/docs/journal/2026/09/05/implementation/events/search-response-urls.okf +23 -0
  133. package/docs/journal/2026/09/05/implementation/tools/native-injection.okf +19 -0
  134. package/docs/journal/2026/09/05/implementation/windows/conpty/prompt-delivery-lifecycle.okf +43 -0
  135. package/docs/journal/2026/09/05/release/nightly/conpty-native-tools.okf +13 -0
  136. package/docs/journal/2026/09/05/release/nightly/search-result-urls.okf +14 -0
  137. package/docs/journal/2026/09/06/implementation/codex/inherited-web-mcp.okf +22 -0
  138. package/docs/journal/2026/09/07/fixes/codex/native-web-transport.okf +39 -0
  139. package/docs/journal/2026/09/07/integration/main/nightly-merge.okf +13 -0
  140. package/docs/journal/2026/09/07/release/stable/0.2.38.okf +10 -0
  141. package/docs/journal/2026/09/07/release/stable/0.2.39.okf +10 -0
  142. package/package.json +1 -1
@@ -228,12 +228,42 @@ def responses_tail_is_safe(items: list[dict[str, Any]], start: int) -> bool:
228
228
  )
229
229
 
230
230
 
231
+ def responses_stable_checkpoint_start(
232
+ items: list[dict[str, Any]], start: int, checkpoint_items: int
233
+ ) -> int:
234
+ """Move an omitted-prefix boundary onto a stable, pair-safe checkpoint.
235
+
236
+ A Responses cache reuses an exact prefix. Advancing the ordinary sliding
237
+ boundary by a few items on every turn regenerates the summary at item zero
238
+ and invalidates that prefix. Rounding the boundary forward means appended
239
+ turns reuse the same summary until they cross the next checkpoint.
240
+
241
+ Moving forward is conservative for the wire budget: it summarizes more old
242
+ input and retains less verbatim history. The final item is always retained,
243
+ and a checkpoint never leaves a tool output without its matching call.
244
+ """
245
+
246
+ try:
247
+ width = max(0, int(checkpoint_items or 0))
248
+ except (TypeError, ValueError):
249
+ width = 0
250
+ if width <= 1 or start <= 0 or start >= len(items):
251
+ return start
252
+ checkpoint = ((start + width - 1) // width) * width
253
+ if checkpoint >= len(items):
254
+ return start
255
+ while checkpoint < len(items) and not responses_tail_is_safe(items, checkpoint):
256
+ checkpoint += 1
257
+ return checkpoint if checkpoint < len(items) else start
258
+
259
+
231
260
  def compact_responses_input_for_budget(
232
261
  body: dict[str, Any],
233
262
  budget_tokens: int,
234
263
  *,
235
264
  provider: str = "",
236
265
  model: str = "",
266
+ stable_prefix_checkpoint_items: int = 0,
237
267
  services: PromptCompactionServices,
238
268
  ) -> dict[str, Any]:
239
269
  """Fit a Responses ``input`` array inside the target model's budget.
@@ -278,6 +308,9 @@ def compact_responses_input_for_budget(
278
308
  tail_start = len(typed) - 1
279
309
  while tail_start > 0 and not responses_tail_is_safe(typed, tail_start):
280
310
  tail_start -= 1
311
+ tail_start = responses_stable_checkpoint_start(
312
+ typed, tail_start, stable_prefix_checkpoint_items
313
+ )
281
314
 
282
315
  def projected(omitted: list[dict[str, Any]]) -> dict[str, Any]:
283
316
  summary = text.build_summary(
@@ -299,6 +332,12 @@ def compact_responses_input_for_budget(
299
332
  if not tail:
300
333
  tail = [typed[-1]]
301
334
  break
335
+ next_start = responses_stable_checkpoint_start(
336
+ typed,
337
+ len(typed) - len(tail),
338
+ stable_prefix_checkpoint_items,
339
+ )
340
+ tail = typed[next_start:]
302
341
  out["input"] = [projected(typed[: len(typed) - len(tail)]), *tail]
303
342
  final_tokens = runtime.estimate_tokens(out)
304
343
  runtime.log(
@@ -0,0 +1,248 @@
1
+ """Streaming passthrough for provider-native OpenAI Files endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import urllib.error
6
+ import urllib.parse
7
+ import urllib.request
8
+ from dataclasses import dataclass
9
+ from typing import Any, Callable
10
+
11
+
12
+ PROVIDER_FILES_PATH = "/v1/files"
13
+ META_FILE_UPLOAD_MAX_BYTES = 1_073_741_824
14
+ # Multipart boundaries and form fields are transport overhead rather than file
15
+ # bytes. Leave one MiB for that envelope while Meta enforces the exact 1 GiB
16
+ # file limit upstream.
17
+ META_FILE_UPLOAD_WIRE_MAX_BYTES = META_FILE_UPLOAD_MAX_BYTES + 1_048_576
18
+ FILES_API_PROVIDERS = frozenset({"meta"})
19
+
20
+
21
+ def is_provider_files_path(path: str) -> bool:
22
+ normalized = urllib.parse.urlparse(str(path or "")).path
23
+ return normalized == PROVIDER_FILES_PATH or normalized.startswith(
24
+ PROVIDER_FILES_PATH + "/"
25
+ )
26
+
27
+
28
+ class _LengthLimitedReader:
29
+ """Expose exactly one HTTP request body without waiting for socket EOF."""
30
+
31
+ def __init__(self, stream: Any, length: int) -> None:
32
+ self._stream = stream
33
+ self._remaining = length
34
+
35
+ def read(self, size: int = -1) -> bytes:
36
+ if self._remaining <= 0:
37
+ return b""
38
+ requested = self._remaining if size is None or size < 0 else min(size, self._remaining)
39
+ data = self._stream.read(requested)
40
+ if not data and self._remaining:
41
+ raise EOFError(
42
+ f"multipart request ended with {self._remaining} bytes unread"
43
+ )
44
+ self._remaining -= len(data)
45
+ return data
46
+
47
+
48
+ @dataclass(frozen=True, slots=True)
49
+ class ProviderFilesProxyPorts:
50
+ current_provider: Callable[[dict[str, Any]], tuple[str, dict[str, Any]]]
51
+ bridge_enabled: Callable[[dict[str, Any]], bool]
52
+ bridge_is_request: Callable[[Any, dict[str, Any]], bool]
53
+ bridge_resolve: Callable[..., Any]
54
+ upstream_base: Callable[[str, dict[str, Any]], str]
55
+ join_url: Callable[[str, str], str]
56
+ headers: Callable[..., dict[str, str]]
57
+ urlopen: Callable[..., Any]
58
+ timeout_seconds: Callable[[dict[str, Any]], float]
59
+ copy_response_headers: Callable[[Any, Any], None]
60
+ write_json: Callable[..., Any]
61
+ log: Callable[[str, str], Any] = lambda _level, _message: None
62
+
63
+
64
+ class ProviderFilesProxy:
65
+ """Proxy Meta's Files API without buffering uploads in router memory."""
66
+
67
+ def __init__(self, ports: ProviderFilesProxyPorts) -> None:
68
+ self._ports = ports
69
+
70
+ def _route(
71
+ self, handler: Any, config: dict[str, Any], path: str
72
+ ) -> tuple[str, dict[str, Any]]:
73
+ if self._ports.bridge_enabled(config) and self._ports.bridge_is_request(
74
+ handler, config
75
+ ):
76
+ route = self._ports.bridge_resolve(config, handler.headers, {}, path)
77
+ return route.provider, route.provider_config
78
+ return self._ports.current_provider(config)
79
+
80
+ def _reject(
81
+ self, handler: Any, status: int, error_type: str, message: str
82
+ ) -> bool:
83
+ handler.close_connection = True
84
+ self._ports.write_json(
85
+ handler,
86
+ {
87
+ "error": {
88
+ "message": message,
89
+ "type": error_type,
90
+ "param": None,
91
+ "code": error_type,
92
+ }
93
+ },
94
+ status,
95
+ )
96
+ return True
97
+
98
+ def _target(
99
+ self, handler: Any, config: dict[str, Any], path: str
100
+ ) -> tuple[str, dict[str, Any], str] | None:
101
+ if not is_provider_files_path(path):
102
+ return None
103
+ try:
104
+ provider, provider_config = self._route(handler, config, path)
105
+ except ValueError as exc:
106
+ self._reject(handler, 400, "invalid_request_error", str(exc))
107
+ return None
108
+ if provider not in FILES_API_PROVIDERS:
109
+ self._reject(
110
+ handler,
111
+ 404,
112
+ "not_found_error",
113
+ f"Provider does not expose a native Files API through Ciel Runtime: {provider}",
114
+ )
115
+ return None
116
+ parsed = urllib.parse.urlparse(handler.path)
117
+ url = self._ports.join_url(
118
+ self._ports.upstream_base(provider, provider_config), parsed.path
119
+ )
120
+ if parsed.query:
121
+ url += "?" + parsed.query
122
+ return provider, provider_config, url
123
+
124
+ def _relay(
125
+ self,
126
+ handler: Any,
127
+ provider: str,
128
+ provider_config: dict[str, Any],
129
+ request: urllib.request.Request,
130
+ ) -> bool:
131
+ try:
132
+ response = self._ports.urlopen(
133
+ request,
134
+ timeout=self._ports.timeout_seconds(provider_config),
135
+ provider=provider,
136
+ pcfg=provider_config,
137
+ )
138
+ except urllib.error.HTTPError as exc:
139
+ response = exc
140
+ with response:
141
+ status = getattr(response, "status", None) or getattr(
142
+ response, "code", 200
143
+ )
144
+ handler.send_response(status)
145
+ self._ports.copy_response_headers(handler, response.headers)
146
+ handler.end_headers()
147
+ while chunk := response.read(65_536):
148
+ handler.wfile.write(chunk)
149
+ handler.wfile.flush()
150
+ return True
151
+
152
+ def get(self, handler: Any, path: str, config: dict[str, Any]) -> bool:
153
+ target = self._target(handler, config, path)
154
+ if target is None:
155
+ return is_provider_files_path(path)
156
+ provider, provider_config, url = target
157
+ request = urllib.request.Request(
158
+ url,
159
+ headers=self._ports.headers(
160
+ provider, provider_config, handler.headers, "openai_responses"
161
+ ),
162
+ method="GET",
163
+ )
164
+ return self._relay(handler, provider, provider_config, request)
165
+
166
+ def post(
167
+ self,
168
+ handler: Any,
169
+ path: str,
170
+ content_length: int,
171
+ content_type: str,
172
+ config: dict[str, Any],
173
+ ) -> bool:
174
+ if not is_provider_files_path(path):
175
+ return False
176
+ if path != PROVIDER_FILES_PATH:
177
+ return self._reject(
178
+ handler, 404, "not_found_error", f"Unsupported Files API path: {path}"
179
+ )
180
+ if "multipart/form-data" not in content_type.casefold():
181
+ return self._reject(
182
+ handler,
183
+ 415,
184
+ "invalid_request_error",
185
+ "POST /v1/files requires multipart/form-data",
186
+ )
187
+ if content_length <= 0 or content_length > META_FILE_UPLOAD_WIRE_MAX_BYTES:
188
+ return self._reject(
189
+ handler,
190
+ 413,
191
+ "request_too_large",
192
+ "Meta Files API multipart upload exceeds the 1 GiB file limit",
193
+ )
194
+ target = self._target(handler, config, path)
195
+ if target is None:
196
+ return True
197
+ provider, provider_config, url = target
198
+ headers = self._ports.headers(
199
+ provider, provider_config, handler.headers, "openai_responses"
200
+ )
201
+ headers["content-length"] = str(content_length)
202
+ request = urllib.request.Request(
203
+ url,
204
+ data=_LengthLimitedReader(handler.rfile, content_length),
205
+ headers=headers,
206
+ method="POST",
207
+ )
208
+ # An upstream can reject before consuming the whole body. Closing the
209
+ # client connection prevents unread multipart bytes from becoming a
210
+ # second HTTP request on this socket.
211
+ handler.close_connection = True
212
+ self._ports.log(
213
+ "INFO",
214
+ f"provider_files_upload provider={provider} bytes={content_length}",
215
+ )
216
+ return self._relay(handler, provider, provider_config, request)
217
+
218
+ def delete(self, handler: Any, path: str, config: dict[str, Any]) -> bool:
219
+ target = self._target(handler, config, path)
220
+ if target is None:
221
+ return is_provider_files_path(path)
222
+ if path == PROVIDER_FILES_PATH:
223
+ return self._reject(
224
+ handler,
225
+ 405,
226
+ "invalid_request_error",
227
+ "DELETE /v1/files requires a file id",
228
+ )
229
+ provider, provider_config, url = target
230
+ request = urllib.request.Request(
231
+ url,
232
+ headers=self._ports.headers(
233
+ provider, provider_config, handler.headers, "openai_responses"
234
+ ),
235
+ method="DELETE",
236
+ )
237
+ return self._relay(handler, provider, provider_config, request)
238
+
239
+
240
+ __all__ = [
241
+ "FILES_API_PROVIDERS",
242
+ "META_FILE_UPLOAD_MAX_BYTES",
243
+ "META_FILE_UPLOAD_WIRE_MAX_BYTES",
244
+ "PROVIDER_FILES_PATH",
245
+ "ProviderFilesProxy",
246
+ "ProviderFilesProxyPorts",
247
+ "is_provider_files_path",
248
+ ]
@@ -64,9 +64,17 @@ class ProviderModelIdentityService:
64
64
  return self.adapters.create(provider).upstream_api_model_id(str(model_id or ""))
65
65
 
66
66
  def alias_for(self, provider: str, model_id: str) -> str:
67
- if self.adapters.create(provider).preserves_claude_model_alias(model_id):
67
+ adapter = self.adapters.create(provider)
68
+ if adapter.preserves_claude_model_alias(model_id):
68
69
  return model_id
69
- return f"ciel-runtime-{provider}-{self.slug(model_id)}"
70
+ one_million = adapter.preserves_claude_context_suffix_in_alias() and bool(
71
+ re.search(r"\[1m\]\s*$", str(model_id or ""), re.IGNORECASE)
72
+ )
73
+ base_model_id = (
74
+ self.strip_claude_context_suffix(model_id) if one_million else model_id
75
+ )
76
+ alias = f"ciel-runtime-{provider}-{self.slug(base_model_id)}"
77
+ return f"{alias}[1m]" if one_million else alias
70
78
 
71
79
  def unslug_alias(
72
80
  self,
@@ -94,7 +94,7 @@ DEFAULT_PROVIDER_NOTES: Mapping[str, Sequence[str]] = {
94
94
  }
95
95
  DEFAULT_UNSUPPORTED_MESSAGE = (
96
96
  "Provider options are available for anthropic, ollama, ollama-cloud, "
97
- "deepseek, opencode, opencode-go, kimi, z.ai, fireworks, vllm, "
97
+ "deepseek, opencode, opencode-go, kimi, meta, z.ai, fireworks, vllm, "
98
98
  "lm-studio, nvidia-hosted, self-hosted-nim, and openrouter."
99
99
  )
100
100
 
@@ -7,7 +7,11 @@ from dataclasses import dataclass
7
7
  from typing import Any
8
8
 
9
9
  from ciel_runtime_support.architecture import MessageProtocol, ProviderRequestPolicy
10
- from ciel_runtime_support.header_forwarding import project_end_to_end_request_headers
10
+ from ciel_runtime_support.header_forwarding import (
11
+ CONFIGURED_PROVIDER_CREDENTIAL_HEADERS,
12
+ HOP_BY_HOP_REQUEST_HEADERS,
13
+ project_end_to_end_request_headers,
14
+ )
11
15
  from ciel_runtime_support.remote_bridge import (
12
16
  REMOTE_BRIDGE_CONFIG_MARKER,
13
17
  REQUEST_API_KEY_MARKER,
@@ -17,6 +21,9 @@ from ciel_runtime_support.remote_bridge import (
17
21
  _HOST_CREDENTIAL_SCOPE_HEADERS = frozenset(
18
22
  {"openai-organization", "openai-project"}
19
23
  )
24
+ _CONFIGURED_PROTOCOL_HEADER_EXCLUSIONS = (
25
+ CONFIGURED_PROVIDER_CREDENTIAL_HEADERS | HOP_BY_HOP_REQUEST_HEADERS
26
+ )
20
27
 
21
28
 
22
29
  @dataclass(frozen=True, slots=True)
@@ -46,6 +53,39 @@ class ProviderRequestAccessService:
46
53
  ports: ProviderRequestAccessPorts
47
54
  effects: ProviderRequestAccessEffects
48
55
 
56
+ @staticmethod
57
+ def _configured_protocol_headers(
58
+ config: dict[str, Any], protocol: MessageProtocol | None
59
+ ) -> dict[str, str]:
60
+ """Project safe, protocol-specific provider headers from configuration.
61
+
62
+ Credentials and connection-owned headers stay under the existing
63
+ adapter/transport policies. This hook is for provider protocol
64
+ switches such as Alibaba's Responses session cache header.
65
+ """
66
+
67
+ if protocol is None:
68
+ return {}
69
+ configured = config.get("protocol_headers")
70
+ if not isinstance(configured, Mapping):
71
+ return {}
72
+ selected = configured.get(protocol)
73
+ if not isinstance(selected, Mapping):
74
+ return {}
75
+ projected: dict[str, str] = {}
76
+ for raw_name, raw_value in selected.items():
77
+ name = str(raw_name).strip()
78
+ folded = name.casefold()
79
+ if (
80
+ not name
81
+ or raw_value is None
82
+ or folded in _CONFIGURED_PROTOCOL_HEADER_EXCLUSIONS
83
+ or folded.startswith("x-ciel-runtime-")
84
+ ):
85
+ continue
86
+ projected[name] = str(raw_value)
87
+ return projected
88
+
49
89
  def upstream_model(
50
90
  self, provider: str, config: dict[str, Any], model: str
51
91
  ) -> str:
@@ -137,6 +177,19 @@ class ProviderRequestAccessService:
137
177
  provider, config, meaningful
138
178
  )
139
179
  )
180
+ configured_protocol_headers = self._configured_protocol_headers(
181
+ config, protocol
182
+ )
183
+ if configured_protocol_headers:
184
+ configured_names = {
185
+ name.casefold() for name in configured_protocol_headers
186
+ }
187
+ headers = {
188
+ name: value
189
+ for name, value in headers.items()
190
+ if name.casefold() not in configured_names
191
+ }
192
+ headers.update(configured_protocol_headers)
140
193
  if protocol == "anthropic_messages":
141
194
  normalized_names = {
142
195
  str(name).casefold() for name in headers