@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
@@ -2,6 +2,8 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import json
6
+ from copy import deepcopy
5
7
  from dataclasses import dataclass, field
6
8
  from typing import Any, Mapping
7
9
 
@@ -19,9 +21,34 @@ from .base import HttpBearerProviderAdapter, provider_configuration
19
21
  from .constants import DEFAULT_REQUEST_TIMEOUT_MS, PROVIDER_DEFAULT_BASE_URLS
20
22
 
21
23
 
22
- MUSE_SPARK_MODEL = "muse-spark-1.1"
24
+ MUSE_SPARK_MODEL = "muse-spark-1.3"
25
+ MUSE_SPARK_MODELS = (
26
+ MUSE_SPARK_MODEL,
27
+ "muse-spark-1.3-contributor",
28
+ "muse-spark-1.2",
29
+ "muse-spark-1.2-contributor",
30
+ "muse-spark-1.1",
31
+ )
23
32
  MUSE_SPARK_CONTEXT_WINDOW = 1_048_576
24
33
  MUSE_SPARK_AUTO_COMPACT_LIMIT = 900_000
34
+ MUSE_SPARK_CODEX_CATALOG = {
35
+ "context_window": MUSE_SPARK_CONTEXT_WINDOW,
36
+ "max_context_window": MUSE_SPARK_CONTEXT_WINDOW,
37
+ "input_modalities": ["text", "image"],
38
+ "support_verbosity": False,
39
+ "supports_reasoning_summaries": True,
40
+ "supported_reasoning_levels": [
41
+ {"effort": "minimal", "description": "Shortest reasoning pass"},
42
+ {"effort": "low", "description": "Light reasoning"},
43
+ {"effort": "medium", "description": "Moderate reasoning depth"},
44
+ {"effort": "high", "description": "Deep reasoning"},
45
+ {
46
+ "effort": "xhigh",
47
+ "description": "Accepted alias; currently the same strength as high",
48
+ },
49
+ ],
50
+ "default_reasoning_level": "high",
51
+ }
25
52
 
26
53
 
27
54
  @dataclass(frozen=True)
@@ -33,21 +60,29 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
33
60
  configuration_defaults_value: dict = field(
34
61
  default_factory=lambda: provider_configuration(
35
62
  MUSE_SPARK_MODEL,
36
- custom_models=(MUSE_SPARK_MODEL,),
63
+ custom_models=MUSE_SPARK_MODELS,
37
64
  native_compat=True,
38
65
  preserve_anthropic_thinking=True,
39
66
  normalize_anthropic_tool_use=True,
40
67
  supports_tool_choice=True,
41
- claude_code_supported_capabilities=["effort", "thinking"],
68
+ claude_code_supported_capabilities=[
69
+ "effort",
70
+ "xhigh_effort",
71
+ "thinking",
72
+ "adaptive_thinking",
73
+ ],
42
74
  context_window=MUSE_SPARK_CONTEXT_WINDOW,
43
75
  max_model_len=MUSE_SPARK_CONTEXT_WINDOW,
44
76
  auto_compact_window=MUSE_SPARK_AUTO_COMPACT_LIMIT,
45
77
  codex_auto_compact_window=MUSE_SPARK_AUTO_COMPACT_LIMIT,
78
+ codex_model_catalog=deepcopy(MUSE_SPARK_CODEX_CATALOG),
46
79
  request_timeout_ms=DEFAULT_REQUEST_TIMEOUT_MS,
47
80
  stream_enabled=True,
48
81
  stream_word_chunking=False,
49
82
  effort_level="high",
50
83
  enable_tool_search=True,
84
+ responses_custom_tools_as_functions=True,
85
+ prompt_cache_retention="24h",
51
86
  haiku_model=MUSE_SPARK_MODEL,
52
87
  opus_model=MUSE_SPARK_MODEL,
53
88
  sonnet_model=MUSE_SPARK_MODEL,
@@ -78,7 +113,7 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
78
113
  model_catalog_policy_value: ProviderModelCatalogPolicy = field(
79
114
  default_factory=lambda: ProviderModelCatalogPolicy(
80
115
  kind="openai",
81
- fallback_models=(MUSE_SPARK_MODEL,),
116
+ fallback_models=MUSE_SPARK_MODELS,
82
117
  allow_configured_fallback=True,
83
118
  )
84
119
  )
@@ -97,11 +132,17 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
97
132
  del model
98
133
  return bool(config.options.get("native_compat", True))
99
134
 
135
+ def supports_server_web_tools(self, config: ProviderConfig) -> bool:
136
+ del config
137
+ return True
138
+
100
139
  def supported_protocols(
101
140
  self, config: ProviderConfig, model: str | None = None
102
141
  ) -> frozenset[MessageProtocol]:
103
142
  del config, model
104
- return frozenset({"anthropic_messages", "openai_responses"})
143
+ return frozenset(
144
+ {"anthropic_messages", "openai_chat", "openai_responses"}
145
+ )
105
146
 
106
147
  def select_protocol(
107
148
  self,
@@ -109,29 +150,33 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
109
150
  config: ProviderConfig,
110
151
  model: str | None = None,
111
152
  ) -> MessageProtocol:
112
- del config, model
113
- return (
114
- "openai_responses"
115
- if operation == "openai_responses"
116
- else "anthropic_messages"
117
- )
153
+ if operation in self.supported_protocols(config, model):
154
+ return operation
155
+ return "anthropic_messages"
118
156
 
119
157
  def model_configuration_profile(
120
158
  self, config: ProviderConfig
121
159
  ) -> tuple[Mapping[str, Any], str | None]:
122
- if self.normalize_model_id(config.model) != MUSE_SPARK_MODEL:
160
+ model = self.normalize_model_id(config.model)
161
+ if model not in MUSE_SPARK_MODELS:
123
162
  return {}, None
163
+ contributor_notice = (
164
+ " Contributor tier permits Meta to train on prompts and completions."
165
+ if model.endswith("-contributor")
166
+ else ""
167
+ )
124
168
  return (
125
169
  {
126
170
  "context_window": MUSE_SPARK_CONTEXT_WINDOW,
127
171
  "max_model_len": MUSE_SPARK_CONTEXT_WINDOW,
128
172
  "auto_compact_window": MUSE_SPARK_AUTO_COMPACT_LIMIT,
129
173
  "effort_level": "high",
130
- "model_profile": "muse-spark-1.1-1m",
174
+ "codex_model_catalog": deepcopy(MUSE_SPARK_CODEX_CATALOG),
175
+ "model_profile": f"{model}-1m",
131
176
  },
132
- "Muse Spark 1.1 profile applied: 1M context, high reasoning effort, "
177
+ f"{model} profile applied: 1M context, high reasoning effort, "
133
178
  "and 900K automatic compaction. Start a new session after changing "
134
- "model, context, or reasoning effort.",
179
+ f"model, context, or reasoning effort.{contributor_notice}",
135
180
  )
136
181
 
137
182
  def model_selection_config_updates(
@@ -169,10 +214,28 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
169
214
  def normalize_request_options(
170
215
  self, config: ProviderConfig, request: Mapping[str, Any]
171
216
  ) -> Mapping[str, Any]:
172
- del config
173
217
  normalized = dict(request)
174
218
  if "input" in normalized and "messages" not in normalized:
175
- self._normalize_responses_request(normalized)
219
+ self._normalize_responses_request(normalized, config)
220
+ else:
221
+ self._normalize_messages_request(normalized)
222
+ return normalized
223
+
224
+ def normalize_request_options_for_protocol(
225
+ self,
226
+ config: ProviderConfig,
227
+ request: Mapping[str, Any],
228
+ protocol: MessageProtocol | None,
229
+ ) -> Mapping[str, Any]:
230
+ normalized = dict(request)
231
+ if protocol == "openai_responses":
232
+ self._normalize_responses_request(normalized, config)
233
+ elif protocol == "openai_chat":
234
+ self._normalize_chat_request(normalized)
235
+ elif protocol == "anthropic_messages":
236
+ self._normalize_messages_request(normalized)
237
+ elif "input" in normalized and "messages" not in normalized:
238
+ self._normalize_responses_request(normalized, config)
176
239
  else:
177
240
  self._normalize_messages_request(normalized)
178
241
  return normalized
@@ -192,7 +255,15 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
192
255
  return False
193
256
 
194
257
  @classmethod
195
- def _normalize_responses_request(cls, request: dict[str, Any]) -> None:
258
+ def _normalize_responses_request(
259
+ cls, request: dict[str, Any], config: ProviderConfig
260
+ ) -> None:
261
+ if "prompt_cache_retention" not in request and request.get("prompt_cache_key"):
262
+ retention = str(
263
+ config.options.get("prompt_cache_retention") or ""
264
+ ).strip()
265
+ if retention in {"in_memory", "24h"}:
266
+ request["prompt_cache_retention"] = retention
196
267
  include = request.get("include")
197
268
  if request.get("previous_response_id"):
198
269
  if isinstance(include, list):
@@ -216,8 +287,150 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
216
287
  reasoning = request.get("reasoning")
217
288
  if isinstance(reasoning, Mapping):
218
289
  projected = dict(reasoning)
219
- projected["effort"] = cls._effort(projected.get("effort"))
290
+ projected["effort"] = cls._responses_effort(projected.get("effort"))
291
+ # Match the official OpenCode Meta path: keep a concise visible
292
+ # reasoning summary while encrypted_content carries replay state.
293
+ projected.setdefault("summary", "auto")
220
294
  request["reasoning"] = projected
295
+ tools = request.get("tools")
296
+ if isinstance(tools, list):
297
+ request["tools"] = [cls._normalize_responses_tool(tool) for tool in tools]
298
+ raw_input = request.get("input")
299
+ if isinstance(raw_input, list):
300
+ request["input"] = [cls._normalize_responses_input(item) for item in raw_input]
301
+
302
+ @classmethod
303
+ def _normalize_responses_tool(cls, tool: Any) -> Any:
304
+ """Project tool parameters to Meta's strict Responses schema contract."""
305
+
306
+ if not isinstance(tool, Mapping):
307
+ return deepcopy(tool)
308
+ projected = deepcopy(dict(tool))
309
+ if str(projected.get("type") or "") == "custom":
310
+ description = str(projected.get("description") or "").strip()
311
+ format_value = projected.get("format")
312
+ if isinstance(format_value, Mapping):
313
+ definition = str(format_value.get("definition") or "").strip()
314
+ if definition:
315
+ description = "\n\n".join(
316
+ part
317
+ for part in (
318
+ description,
319
+ "Raw input must satisfy this grammar:\n" + definition,
320
+ )
321
+ if part
322
+ )
323
+ projected = {
324
+ "type": "function",
325
+ "name": str(projected.get("name") or ""),
326
+ "description": description,
327
+ "parameters": {
328
+ "type": "object",
329
+ "properties": {"input": {"type": "string"}},
330
+ "required": ["input"],
331
+ "additionalProperties": False,
332
+ },
333
+ "strict": True,
334
+ }
335
+ parameters = projected.get("parameters")
336
+ if isinstance(parameters, Mapping):
337
+ projected["parameters"] = cls._strict_responses_schema(parameters)
338
+ if (
339
+ str(tool.get("type") or "") == "function"
340
+ and isinstance(parameters, Mapping)
341
+ ):
342
+ projected["strict"] = True
343
+ return projected
344
+
345
+ @staticmethod
346
+ def _normalize_responses_input(item: Any) -> Any:
347
+ if not isinstance(item, Mapping):
348
+ return deepcopy(item)
349
+ projected = deepcopy(dict(item))
350
+ item_type = str(projected.get("type") or "")
351
+ if item_type == "custom_tool_call":
352
+ projected["type"] = "function_call"
353
+ projected["arguments"] = json.dumps(
354
+ {"input": str(projected.pop("input", ""))},
355
+ ensure_ascii=False,
356
+ separators=(",", ":"),
357
+ )
358
+ elif item_type == "custom_tool_call_output":
359
+ projected["type"] = "function_call_output"
360
+ return projected
361
+
362
+ @classmethod
363
+ def _strict_responses_schema(cls, schema: Mapping[str, Any]) -> dict[str, Any]:
364
+ """Require every object property while keeping former optionals nullable."""
365
+
366
+ projected = deepcopy(dict(schema))
367
+ properties = projected.get("properties")
368
+ if isinstance(properties, Mapping):
369
+ originally_required = {
370
+ str(name)
371
+ for name in projected.get("required") or []
372
+ if isinstance(name, str)
373
+ }
374
+ strict_properties: dict[str, Any] = {}
375
+ for raw_name, raw_property in properties.items():
376
+ name = str(raw_name)
377
+ if isinstance(raw_property, Mapping):
378
+ normalized_property = cls._strict_responses_schema(raw_property)
379
+ if name not in originally_required:
380
+ normalized_property = cls._nullable_schema(normalized_property)
381
+ strict_properties[name] = normalized_property
382
+ else:
383
+ strict_properties[name] = deepcopy(raw_property)
384
+ projected["properties"] = strict_properties
385
+ projected["required"] = list(strict_properties)
386
+ projected["additionalProperties"] = False
387
+
388
+ items = projected.get("items")
389
+ if isinstance(items, Mapping):
390
+ projected["items"] = cls._strict_responses_schema(items)
391
+ for keyword in ("anyOf", "oneOf", "allOf"):
392
+ variants = projected.get(keyword)
393
+ if isinstance(variants, list):
394
+ projected[keyword] = [
395
+ cls._strict_responses_schema(item)
396
+ if isinstance(item, Mapping)
397
+ else deepcopy(item)
398
+ for item in variants
399
+ ]
400
+ definitions = projected.get("$defs")
401
+ if isinstance(definitions, Mapping):
402
+ projected["$defs"] = {
403
+ str(name): cls._strict_responses_schema(value)
404
+ if isinstance(value, Mapping)
405
+ else deepcopy(value)
406
+ for name, value in definitions.items()
407
+ }
408
+ return projected
409
+
410
+ @staticmethod
411
+ def _nullable_schema(schema: Mapping[str, Any]) -> dict[str, Any]:
412
+ projected = deepcopy(dict(schema))
413
+ raw_type = projected.get("type")
414
+ if isinstance(raw_type, str):
415
+ if raw_type != "null":
416
+ projected["type"] = [raw_type, "null"]
417
+ elif isinstance(raw_type, list):
418
+ if "null" not in raw_type:
419
+ projected["type"] = [*raw_type, "null"]
420
+ elif isinstance(projected.get("anyOf"), list):
421
+ variants = list(projected["anyOf"])
422
+ if not any(
423
+ isinstance(item, Mapping) and item.get("type") == "null"
424
+ for item in variants
425
+ ):
426
+ variants.append({"type": "null"})
427
+ projected["anyOf"] = variants
428
+ else:
429
+ projected = {"anyOf": [projected, {"type": "null"}]}
430
+ enum = projected.get("enum")
431
+ if isinstance(enum, list) and None not in enum:
432
+ projected["enum"] = [*enum, None]
433
+ return projected
221
434
 
222
435
  @classmethod
223
436
  def _normalize_messages_request(cls, request: dict[str, Any]) -> None:
@@ -234,11 +447,22 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
234
447
  output_config = request.get("output_config")
235
448
  if isinstance(output_config, Mapping) and output_config.get("effort") is not None:
236
449
  projected_output = dict(output_config)
237
- projected_output["effort"] = cls._effort(projected_output.get("effort"))
450
+ projected_output["effort"] = cls._messages_effort(
451
+ projected_output.get("effort")
452
+ )
238
453
  request["output_config"] = projected_output
239
454
 
240
455
  @staticmethod
241
- def _effort(value: Any) -> str:
456
+ def _normalize_chat_request(request: dict[str, Any]) -> None:
457
+ # Meta Chat Completions documents only the automatic tool-selection
458
+ # mode. OpenAI clients can send a named function choice object; retain
459
+ # the tools but project that unsupported selector to Meta's auto mode.
460
+ tool_choice = request.get("tool_choice")
461
+ if isinstance(tool_choice, Mapping):
462
+ request["tool_choice"] = "auto"
463
+
464
+ @staticmethod
465
+ def _responses_effort(value: Any) -> str:
242
466
  effort = str(value or "high").strip().lower()
243
467
  if effort in {"none", "minimal"}:
244
468
  return "minimal"
@@ -248,10 +472,17 @@ class MetaModelProviderAdapter(HttpBearerProviderAdapter):
248
472
  return "xhigh"
249
473
  return "high"
250
474
 
475
+ @classmethod
476
+ def _messages_effort(cls, value: Any) -> str:
477
+ effort = cls._responses_effort(value)
478
+ return "low" if effort == "minimal" else effort
479
+
251
480
 
252
481
  __all__ = [
253
482
  "MUSE_SPARK_AUTO_COMPACT_LIMIT",
483
+ "MUSE_SPARK_CODEX_CATALOG",
254
484
  "MUSE_SPARK_CONTEXT_WINDOW",
255
485
  "MUSE_SPARK_MODEL",
486
+ "MUSE_SPARK_MODELS",
256
487
  "MetaModelProviderAdapter",
257
488
  ]
@@ -18,7 +18,23 @@ PSEUDO_CALL_END = "<|tool_call_end|>"
18
18
 
19
19
  def normalize_tool_arguments(tool_name: str, arguments: Any) -> dict[str, Any]:
20
20
  if isinstance(arguments, dict):
21
- return arguments
21
+ normalized = dict(arguments)
22
+ # Kimi occasionally abbreviates Codex's ``exec_command`` tool to
23
+ # ``exec`` and emits the shell payload under a generic ``input`` (or
24
+ # OpenAI-style ``command``/``script``) key. Name canonicalization has
25
+ # already resolved the tool before this function runs, so repair only
26
+ # non-empty string aliases whose semantics are still a shell command.
27
+ # A ``js`` field is deliberately not repaired: JavaScript intended for
28
+ # a Node REPL is not a valid PowerShell command.
29
+ if str(tool_name or "").casefold() == "exec_command":
30
+ cmd = normalized.get("cmd")
31
+ if not isinstance(cmd, str) or not cmd.strip():
32
+ for alias in ("command", "script", "input"):
33
+ value = normalized.get(alias)
34
+ if isinstance(value, str) and value.strip():
35
+ normalized["cmd"] = value
36
+ break
37
+ return normalized
22
38
  if isinstance(arguments, str):
23
39
  text = arguments.strip()
24
40
  if not text:
@@ -20,6 +20,7 @@ REMOTE_LLM_PATHS = frozenset(
20
20
  REMOTE_GENERATION_PATHS = frozenset(
21
21
  {"/v1/chat/completions", "/v1/messages", "/v1/responses"}
22
22
  )
23
+ REMOTE_FILES_PATH = "/v1/files"
23
24
 
24
25
  PROVIDER_HEADER = "x-ciel-runtime-provider"
25
26
  MODEL_HEADER = "x-ciel-runtime-model"
@@ -200,6 +201,8 @@ def remote_bridge_path_allowed(path: str) -> bool:
200
201
  normalized in REMOTE_LLM_PATHS
201
202
  or normalized in {"/ca/bridge", "/v1/models"}
202
203
  or normalized.startswith("/v1/models/")
204
+ or normalized == REMOTE_FILES_PATH
205
+ or normalized.startswith(REMOTE_FILES_PATH + "/")
203
206
  )
204
207
 
205
208
 
@@ -218,6 +221,7 @@ __all__ = [
218
221
  "REQUEST_API_KEY_MARKER",
219
222
  "REMOTE_BRIDGE_INCOMPATIBLE_PROVIDERS",
220
223
  "REMOTE_GENERATION_PATHS",
224
+ "REMOTE_FILES_PATH",
221
225
  "REMOTE_LLM_PATHS",
222
226
  "ROUTER_MANAGED_CREDENTIAL_PROVIDERS",
223
227
  "RemoteBridgeRoute",
@@ -25,6 +25,8 @@ RUNTIME_FILES = {
25
25
  "grok": "AGENTS.md",
26
26
  # ZCode discovers workspace instructions from AGENTS.md.
27
27
  "zcode": "AGENTS.md",
28
+ # Muse Code loads AGENTS.md before other project instruction files.
29
+ "muse": "AGENTS.md",
28
30
  }
29
31
  URL_KEYS = {
30
32
  "claude": "claude_url",
@@ -33,6 +35,7 @@ URL_KEYS = {
33
35
  "agy": "agy_url",
34
36
  "kimi": "kimi_url",
35
37
  "grok": "grok_url",
38
+ "muse": "muse_url",
36
39
  }
37
40
 
38
41
  _ENV_REFERENCE = re.compile(r"%([A-Za-z_][A-Za-z0-9_]*)%|\$\{([A-Za-z_][A-Za-z0-9_]*)\}|\{([A-Za-z_][A-Za-z0-9_]*)\}")
@@ -229,6 +232,7 @@ def panel_rows(config: dict[str, Any]) -> tuple[list[str], list[str]]:
229
232
  f"AGY URL → GEMINI.md [{compact(current.get('agy_url') or 'unset')}]",
230
233
  f"Kimi URL → AGENTS.md [{compact(current.get('kimi_url') or 'unset')}]",
231
234
  f"Grok URL → AGENTS.md [{compact(current.get('grok_url') or 'unset')}]",
235
+ f"Muse URL → AGENTS.md [{compact(current.get('muse_url') or 'unset')}]",
232
236
  f"Authorization header [{'configured' if current.get('authorization') else 'unset'}]",
233
237
  f"HTTP timeout seconds [{current.get('timeout_seconds') or 5}]",
234
238
  "Sync configured instruction files now",
@@ -241,6 +245,7 @@ def panel_rows(config: dict[str, Any]) -> tuple[list[str], list[str]]:
241
245
  "agy_url",
242
246
  "kimi_url",
243
247
  "grok_url",
248
+ "muse_url",
244
249
  "authorization",
245
250
  "timeout_seconds",
246
251
  "sync",
@@ -993,7 +993,7 @@ def sync_all_memory_pointers(
993
993
  """Download once and project the verified pointer for every runtime."""
994
994
 
995
995
  result = synchronizer.sync("codex", reason="manual")
996
- for runtime in ("codex-app-server", "claude", "agy", "kimi", "grok"):
996
+ for runtime in ("codex-app-server", "claude", "agy", "kimi", "grok", "muse"):
997
997
  try:
998
998
  synchronizer.project_current_pointer(runtime)
999
999
  except (OSError, UnicodeError, ValueError) as exc:
@@ -0,0 +1,82 @@
1
+ """Privacy-safe request fingerprints for native Responses cache diagnosis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import hashlib
6
+ import json
7
+ from typing import Any, Mapping
8
+
9
+
10
+ def _fingerprint(value: Any) -> str:
11
+ encoded = json.dumps(
12
+ value,
13
+ ensure_ascii=False,
14
+ sort_keys=True,
15
+ separators=(",", ":"),
16
+ ).encode("utf-8")
17
+ return hashlib.sha256(encoded).hexdigest()[:16]
18
+
19
+
20
+ def request_cache_profile(
21
+ body: Mapping[str, Any], request_bytes: int
22
+ ) -> dict[str, Any]:
23
+ raw_input = body.get("input")
24
+ input_items = raw_input if isinstance(raw_input, list) else [raw_input]
25
+ input_head = input_items[:8]
26
+ cache_key = str(body.get("prompt_cache_key") or "")
27
+ return {
28
+ "cache_hit_percent": 0.0,
29
+ "prompt_cache_key_fingerprint": (
30
+ hashlib.sha256(cache_key.encode("utf-8")).hexdigest()[:16]
31
+ if cache_key
32
+ else ""
33
+ ),
34
+ "prompt_cache_retention": str(body.get("prompt_cache_retention") or ""),
35
+ "request_instructions_fingerprint": _fingerprint(body.get("instructions")),
36
+ "request_tools_fingerprint": _fingerprint(body.get("tools")),
37
+ "request_input_head_fingerprint": _fingerprint(input_head),
38
+ "request_input_items": len(input_items) if raw_input is not None else 0,
39
+ "request_tools": len(body.get("tools") or []),
40
+ "request_bytes": max(0, int(request_bytes)),
41
+ "request_uses_previous_response_id": bool(body.get("previous_response_id")),
42
+ }
43
+
44
+
45
+ def usage_with_cache_profile(
46
+ usage: Mapping[str, Any], profile: Mapping[str, Any]
47
+ ) -> dict[str, Any]:
48
+ observed = dict(usage)
49
+ input_tokens = max(0, int(observed.get("input_tokens") or 0))
50
+ cache_read = max(0, int(observed.get("cache_read_tokens") or 0))
51
+ observed.update(profile)
52
+ observed["cache_hit_percent"] = (
53
+ round(cache_read * 100.0 / input_tokens, 2) if input_tokens else 0.0
54
+ )
55
+ return observed
56
+
57
+
58
+ def cache_trace(
59
+ provider: str, model: str, observation: Mapping[str, Any]
60
+ ) -> tuple[str, str]:
61
+ hit = float(observation.get("cache_hit_percent") or 0.0)
62
+ level = "WARN" if int(observation.get("input_tokens") or 0) and hit < 90.0 else "INFO"
63
+ fields = (
64
+ "input_tokens",
65
+ "cache_read_tokens",
66
+ "uncached_input_tokens",
67
+ "cache_hit_percent",
68
+ "prompt_cache_key_fingerprint",
69
+ "prompt_cache_retention",
70
+ "request_instructions_fingerprint",
71
+ "request_tools_fingerprint",
72
+ "request_input_head_fingerprint",
73
+ "request_input_items",
74
+ "request_tools",
75
+ "request_bytes",
76
+ "request_uses_previous_response_id",
77
+ )
78
+ detail = " ".join(f"{name}={observation.get(name)}" for name in fields)
79
+ return level, f"provider_responses_cache provider={provider} model={model} {detail}"
80
+
81
+
82
+ __all__ = ["cache_trace", "request_cache_profile", "usage_with_cache_profile"]