@oneciel-ai/ciel-runtime 0.2.2 → 0.2.4
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/ciel_runtime.py +2555 -9635
- package/ciel_runtime_support/advisor_request_builder.py +8 -21
- package/ciel_runtime_support/anthropic_tool_turns.py +13 -8
- package/ciel_runtime_support/architecture.py +68 -0
- package/ciel_runtime_support/architecture_budget.py +1 -1
- package/ciel_runtime_support/channel_connection_context.py +233 -0
- package/ciel_runtime_support/channel_delivery_context.py +332 -0
- package/ciel_runtime_support/channel_mcp_context.py +313 -0
- package/ciel_runtime_support/channel_mcp_discovery.py +47 -0
- package/ciel_runtime_support/channel_mcp_transport.py +5 -1
- package/ciel_runtime_support/channel_message_context.py +212 -0
- package/ciel_runtime_support/channel_message_repository.py +14 -3
- package/ciel_runtime_support/channel_pending_injection.py +9 -0
- package/ciel_runtime_support/channel_probe_launch_context.py +213 -0
- package/ciel_runtime_support/channel_replay_policy.py +38 -0
- package/ciel_runtime_support/channel_runtime_environment.py +8 -0
- package/ciel_runtime_support/channel_session_context.py +236 -0
- package/ciel_runtime_support/channel_terminal_context.py +350 -0
- package/ciel_runtime_support/channel_wake_context.py +532 -0
- package/ciel_runtime_support/claude_environment.py +60 -0
- package/ciel_runtime_support/claude_launch_assembly.py +249 -0
- package/ciel_runtime_support/claude_router.py +62 -12
- package/ciel_runtime_support/cli_application_context.py +132 -0
- package/ciel_runtime_support/cli_assembly.py +50 -0
- package/ciel_runtime_support/codex_backend_context.py +363 -0
- package/ciel_runtime_support/codex_config.py +13 -1
- package/ciel_runtime_support/codex_launch_assembly.py +213 -0
- package/ciel_runtime_support/codex_launch_configuration.py +30 -1
- package/ciel_runtime_support/codex_mcp_integration.py +90 -8
- package/ciel_runtime_support/codex_model_catalog.py +4 -1
- package/ciel_runtime_support/codex_reasoning_rejects.py +225 -0
- package/ciel_runtime_support/codex_router.py +38 -8
- package/ciel_runtime_support/codex_turn_recovery.py +154 -0
- package/ciel_runtime_support/config_migrations.py +103 -0
- package/ciel_runtime_support/config_repository.py +30 -0
- package/ciel_runtime_support/configuration_cli.py +38 -0
- package/ciel_runtime_support/context_compaction.py +9 -4
- package/ciel_runtime_support/credential_management.py +12 -0
- package/ciel_runtime_support/credentials.py +12 -0
- package/ciel_runtime_support/github_copilot_oauth.py +2 -2
- package/ciel_runtime_support/hosted_formula_tools.py +216 -0
- package/ciel_runtime_support/kimi_runtime_context.py +208 -0
- package/ciel_runtime_support/llm_preset_context.py +338 -0
- package/ciel_runtime_support/managed_mcp_config.py +8 -4
- package/ciel_runtime_support/mcp_configuration_context.py +291 -0
- package/ciel_runtime_support/mcp_http_proxy.py +14 -8
- package/ciel_runtime_support/mcp_probe_transport.py +47 -15
- package/ciel_runtime_support/mcp_transport.py +14 -1
- package/ciel_runtime_support/native_context_recovery.py +72 -0
- package/ciel_runtime_support/ollama_catalog_context.py +213 -0
- package/ciel_runtime_support/ollama_stream_collection.py +103 -0
- package/ciel_runtime_support/ollama_thinking.py +6 -1
- package/ciel_runtime_support/ollama_wire_projection.py +157 -0
- package/ciel_runtime_support/openai_forwarding.py +32 -10
- package/ciel_runtime_support/openai_responses_router.py +12 -0
- package/ciel_runtime_support/package_lifecycle.py +39 -0
- package/ciel_runtime_support/prelaunch_assembly.py +37 -0
- package/ciel_runtime_support/prelaunch_panel_context.py +418 -0
- package/ciel_runtime_support/prelaunch_shell_context.py +394 -0
- package/ciel_runtime_support/prompt_compaction.py +144 -0
- package/ciel_runtime_support/prompt_injection.py +45 -0
- package/ciel_runtime_support/protocols/anthropic_thinking_policy.py +1 -1
- package/ciel_runtime_support/protocols/chat_projection.py +85 -5
- package/ciel_runtime_support/protocols/conversation_turn_policy.py +43 -0
- package/ciel_runtime_support/protocols/ollama_chat.py +31 -0
- package/ciel_runtime_support/protocols/ollama_response.py +57 -5
- package/ciel_runtime_support/protocols/openai_reasoning.py +5 -2
- package/ciel_runtime_support/protocols/openai_responses.py +61 -15
- package/ciel_runtime_support/provider_adapters.py +26 -0
- package/ciel_runtime_support/provider_administration_context.py +207 -0
- package/ciel_runtime_support/provider_config_mutations.py +3 -0
- package/ciel_runtime_support/provider_model_catalog_context.py +137 -0
- package/ciel_runtime_support/provider_model_context.py +107 -0
- package/ciel_runtime_support/provider_model_metadata_context.py +197 -0
- package/ciel_runtime_support/provider_model_selection.py +10 -3
- package/ciel_runtime_support/provider_models.py +45 -2
- package/ciel_runtime_support/provider_option_cli.py +19 -0
- package/ciel_runtime_support/provider_policy.py +1 -1
- package/ciel_runtime_support/provider_readiness_context.py +189 -0
- package/ciel_runtime_support/provider_request_builder.py +64 -28
- package/ciel_runtime_support/provider_responses_passthrough.py +21 -2
- package/ciel_runtime_support/provider_timeout_policy.py +54 -0
- package/ciel_runtime_support/provider_tool_policy.py +9 -1
- package/ciel_runtime_support/providers/__init__.py +6 -0
- package/ciel_runtime_support/providers/alibaba.py +634 -0
- package/ciel_runtime_support/providers/catalog.py +24 -16
- package/ciel_runtime_support/providers/deepseek.py +73 -0
- package/ciel_runtime_support/providers/github_copilot_oauth.py +22 -1
- package/ciel_runtime_support/providers/kimi.py +69 -9
- package/ciel_runtime_support/providers/ollama.py +8 -0
- package/ciel_runtime_support/providers/ollama_context.py +21 -2
- package/ciel_runtime_support/providers/vllm.py +7 -1
- package/ciel_runtime_support/response_collection.py +68 -18
- package/ciel_runtime_support/response_collection_context.py +391 -0
- package/ciel_runtime_support/response_stream_context.py +555 -0
- package/ciel_runtime_support/responses_input_compatibility.py +121 -0
- package/ciel_runtime_support/responses_usage_observer.py +83 -0
- package/ciel_runtime_support/router_client_lifecycle.py +1 -0
- package/ciel_runtime_support/router_http.py +245 -3
- package/ciel_runtime_support/router_observability_context.py +251 -0
- package/ciel_runtime_support/router_process_context.py +200 -0
- package/ciel_runtime_support/router_process_lifecycle.py +2 -0
- package/ciel_runtime_support/router_request_assembly.py +399 -0
- package/ciel_runtime_support/router_request_context.py +215 -0
- package/ciel_runtime_support/router_server_context.py +84 -0
- package/ciel_runtime_support/runaway_output_guard.py +488 -0
- package/ciel_runtime_support/runtime_asset_assembly.py +147 -0
- package/ciel_runtime_support/runtime_asset_context.py +297 -0
- package/ciel_runtime_support/runtime_constants.py +16 -1
- package/ciel_runtime_support/runtime_launch.py +9 -5
- package/ciel_runtime_support/runtime_launch_context.py +130 -0
- package/ciel_runtime_support/runtime_maintenance_assembly.py +60 -0
- package/ciel_runtime_support/runtime_maintenance_context.py +309 -0
- package/ciel_runtime_support/runtime_maintenance_services.py +265 -0
- package/ciel_runtime_support/runtime_paths.py +60 -40
- package/ciel_runtime_support/runtime_primitives.py +78 -0
- package/ciel_runtime_support/speech_http_controller.py +335 -0
- package/ciel_runtime_support/sse_stream_collection.py +236 -0
- package/ciel_runtime_support/statusline_script.py +57 -8
- package/ciel_runtime_support/streaming_anthropic.py +361 -24
- package/ciel_runtime_support/tool_schema.py +40 -2
- package/ciel_runtime_support/tool_side_effect_dedupe.py +117 -12
- package/ciel_runtime_support/upstream_dump.py +68 -0
- package/ciel_runtime_support/upstream_retry_context.py +259 -0
- package/ciel_runtime_support/web_ui.py +248 -1
- package/ciel_runtime_support/workspace_router_selection.py +86 -0
- package/docs/COLAB_SPEECH.md +32 -0
- package/docs/Configuration.md +50 -0
- package/docs/Test-Suite.md +1 -0
- package/package.json +4 -1
- package/scripts/colab/__pycache__/bootstrap_moss_tts.cpython-311.pyc +0 -0
- package/scripts/colab/__pycache__/bootstrap_qwen_asr.cpython-311.pyc +0 -0
- package/scripts/colab/bootstrap_moss_tts.py +106 -0
- package/scripts/colab/bootstrap_qwen_asr.py +106 -0
- package/scripts/configure_speech_workers.py +37 -0
- package/scripts/deploy_colab_speech.ps1 +47 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""Provider response normalization and streaming bridge bounded context."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from http.server import BaseHTTPRequestHandler
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
from . import streaming_anthropic
|
|
10
|
+
from .openai_responses_stream import OpenAIResponsesStreamServices
|
|
11
|
+
from .protocols.ollama_response import (
|
|
12
|
+
OllamaResponseOutput,
|
|
13
|
+
OllamaResponseRecovery,
|
|
14
|
+
OllamaResponseServices,
|
|
15
|
+
OllamaResponseText,
|
|
16
|
+
OllamaResponseTools,
|
|
17
|
+
)
|
|
18
|
+
from .pseudo_tool_parser import PseudoToolParserServices
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class ResponseStreamAlgorithms:
|
|
23
|
+
normalize_tool_arguments: Callable[..., dict[str, Any]]
|
|
24
|
+
infer_tool_name: Callable[[dict[str, Any]], str]
|
|
25
|
+
parse_pseudo_tool_calls: Callable[..., tuple[str, list[dict[str, Any]]]]
|
|
26
|
+
project_ollama_response: Callable[..., dict[str, Any]]
|
|
27
|
+
project_openai_chat_response: Callable[..., dict[str, Any]]
|
|
28
|
+
split_word_buffer: Callable[..., tuple[str, str]]
|
|
29
|
+
write_openai_response: Callable[..., Any]
|
|
30
|
+
write_openai_error: Callable[..., Any]
|
|
31
|
+
protocol_adapter: Callable[..., Any]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass(frozen=True, slots=True)
|
|
35
|
+
class ResponseStreamTextPorts:
|
|
36
|
+
decode_ollama: Callable[..., Any]
|
|
37
|
+
ollama_thinking_to_block: Callable[..., Any]
|
|
38
|
+
ollama_reasoning_only_notice: Callable[..., str]
|
|
39
|
+
strip_thinking: Callable[..., str]
|
|
40
|
+
parse_xml_tools: Callable[..., Any]
|
|
41
|
+
find_pseudo_xml_start: Callable[..., Any]
|
|
42
|
+
fuzzy_tool_name: Callable[..., Any]
|
|
43
|
+
reasoning_to_thinking: Callable[..., Any]
|
|
44
|
+
anthropic_content_to_text: Callable[..., str]
|
|
45
|
+
positive_int: Callable[..., int | None]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@dataclass(frozen=True, slots=True)
|
|
49
|
+
class ResponseStreamToolPorts:
|
|
50
|
+
resolve_name: Callable[..., str]
|
|
51
|
+
validate_input: Callable[..., Any]
|
|
52
|
+
plan_mode_name: Callable[..., Any]
|
|
53
|
+
cap_notification_wait: Callable[..., dict[str, Any]]
|
|
54
|
+
should_drop: Callable[..., bool]
|
|
55
|
+
should_drop_duplicate: Callable[..., bool]
|
|
56
|
+
append_log: Callable[..., Any]
|
|
57
|
+
remember_tool_use: Callable[..., Any]
|
|
58
|
+
repair_passthrough_input: Callable[..., bool]
|
|
59
|
+
is_mcp_notification_wait: Callable[..., bool]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class ResponseStreamRecoveryPorts:
|
|
64
|
+
auto_enter_plan: Callable[..., bool]
|
|
65
|
+
auto_exit_plan: Callable[..., bool]
|
|
66
|
+
recover_empty: Callable[..., bool]
|
|
67
|
+
keep_alive: Callable[..., bool]
|
|
68
|
+
auto_continue_choice: Callable[..., bool]
|
|
69
|
+
empty_notice: Callable[..., str]
|
|
70
|
+
latest_tool_results: Callable[..., list[str]]
|
|
71
|
+
synthetic_tool_response: Callable[..., dict[str, Any]]
|
|
72
|
+
synthesize_tasklist: Callable[..., bool]
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@dataclass(frozen=True, slots=True)
|
|
76
|
+
class ResponseStreamConversationPorts:
|
|
77
|
+
backfill_exit_plan: Callable[..., Any]
|
|
78
|
+
ultracode_enabled: Callable[..., bool]
|
|
79
|
+
has_tool: Callable[..., bool]
|
|
80
|
+
latest_intent_index: Callable[..., int]
|
|
81
|
+
suggestion_mode: Callable[..., bool]
|
|
82
|
+
recent_tasklist_count: Callable[..., int]
|
|
83
|
+
remember_suppressed_thinking: Callable[..., Any]
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass(frozen=True, slots=True)
|
|
87
|
+
class ResponseStreamIoPorts:
|
|
88
|
+
encode_message: Callable[..., dict[str, Any]]
|
|
89
|
+
estimate_tokens: Callable[..., int]
|
|
90
|
+
log: Callable[..., Any]
|
|
91
|
+
mark_delivery_failed: Callable[..., Any]
|
|
92
|
+
mark_delivery_success: Callable[..., Any]
|
|
93
|
+
client_connection_closed: Callable[..., bool]
|
|
94
|
+
iter_upstream_lines: Callable[..., Any]
|
|
95
|
+
write_activity: Callable[..., Any]
|
|
96
|
+
write_json: Callable[..., Any]
|
|
97
|
+
write_open_stream_stop: Callable[..., Any]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@dataclass(frozen=True, slots=True)
|
|
101
|
+
class ResponseStreamTracePorts:
|
|
102
|
+
dump_response: Callable[..., Any]
|
|
103
|
+
finish_sse: Callable[..., Any]
|
|
104
|
+
make_sse: Callable[..., Any]
|
|
105
|
+
record_sse: Callable[..., Any]
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass(frozen=True, slots=True)
|
|
109
|
+
class ResponseStreamRuntimePorts:
|
|
110
|
+
timestamp_ms: Callable[[], int]
|
|
111
|
+
process_id: Callable[[], int]
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
@dataclass(frozen=True, slots=True)
|
|
115
|
+
class ResponseStreamTypes:
|
|
116
|
+
thinking_block_types: Any
|
|
117
|
+
visible_tool_filter: Any
|
|
118
|
+
visible_thinking_filter: Any
|
|
119
|
+
client_disconnected_error: Any
|
|
120
|
+
pseudo_tool_start: str
|
|
121
|
+
pseudo_tool_end: str
|
|
122
|
+
word_chunk_max_buffer: int
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@dataclass(frozen=True, slots=True)
|
|
126
|
+
class ResponseStreamContext:
|
|
127
|
+
algorithms: ResponseStreamAlgorithms
|
|
128
|
+
text: ResponseStreamTextPorts
|
|
129
|
+
tools: ResponseStreamToolPorts
|
|
130
|
+
recovery: ResponseStreamRecoveryPorts
|
|
131
|
+
conversation: ResponseStreamConversationPorts
|
|
132
|
+
io: ResponseStreamIoPorts
|
|
133
|
+
trace: ResponseStreamTracePorts
|
|
134
|
+
runtime: ResponseStreamRuntimePorts
|
|
135
|
+
types: ResponseStreamTypes
|
|
136
|
+
|
|
137
|
+
def normalize_tool_arguments(
|
|
138
|
+
self, tool_name: str, args: Any
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
return self.algorithms.normalize_tool_arguments(tool_name, args)
|
|
141
|
+
|
|
142
|
+
def infer_tool_name(self, args: dict[str, Any]) -> str:
|
|
143
|
+
return self.algorithms.infer_tool_name(args)
|
|
144
|
+
|
|
145
|
+
def parse_pseudo_tool_calls(
|
|
146
|
+
self,
|
|
147
|
+
text: str,
|
|
148
|
+
source_body: dict[str, Any] | None = None,
|
|
149
|
+
) -> tuple[str, list[dict[str, Any]]]:
|
|
150
|
+
return self.algorithms.parse_pseudo_tool_calls(
|
|
151
|
+
text,
|
|
152
|
+
source_body,
|
|
153
|
+
PseudoToolParserServices(
|
|
154
|
+
parse_xml=self.text.parse_xml_tools,
|
|
155
|
+
fuzzy_tool_name=self.text.fuzzy_tool_name,
|
|
156
|
+
),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
def ollama_response_services(self) -> OllamaResponseServices:
|
|
160
|
+
return OllamaResponseServices(
|
|
161
|
+
text=OllamaResponseText(
|
|
162
|
+
decode=self.text.decode_ollama,
|
|
163
|
+
thinking_to_block=self.text.ollama_thinking_to_block,
|
|
164
|
+
reasoning_only_notice=self.text.ollama_reasoning_only_notice,
|
|
165
|
+
strip_thinking=self.text.strip_thinking,
|
|
166
|
+
parse_pseudo_tools=self.parse_pseudo_tool_calls,
|
|
167
|
+
log=self.io.log,
|
|
168
|
+
),
|
|
169
|
+
tools=OllamaResponseTools(
|
|
170
|
+
resolve_name=self.tools.resolve_name,
|
|
171
|
+
normalize_arguments=self.normalize_tool_arguments,
|
|
172
|
+
validate_input=self.tools.validate_input,
|
|
173
|
+
plan_mode_name=self.tools.plan_mode_name,
|
|
174
|
+
cap_notification_wait=self.tools.cap_notification_wait,
|
|
175
|
+
should_drop=self.tools.should_drop,
|
|
176
|
+
should_drop_duplicate=self.tools.should_drop_duplicate,
|
|
177
|
+
append_log=self.tools.append_log,
|
|
178
|
+
),
|
|
179
|
+
recovery=OllamaResponseRecovery(
|
|
180
|
+
auto_enter_plan=self.recovery.auto_enter_plan,
|
|
181
|
+
recover_empty_with_tasklist=self.recovery.recover_empty,
|
|
182
|
+
keep_alive_with_tasklist=self.recovery.keep_alive,
|
|
183
|
+
auto_continue_choice=self.recovery.auto_continue_choice,
|
|
184
|
+
empty_notice=self.recovery.empty_notice,
|
|
185
|
+
latest_tool_result_names=self.recovery.latest_tool_results,
|
|
186
|
+
synthetic_tool_response=self.recovery.synthetic_tool_response,
|
|
187
|
+
),
|
|
188
|
+
output=OllamaResponseOutput(
|
|
189
|
+
encode_message=self.io.encode_message,
|
|
190
|
+
estimate_tokens=self.io.estimate_tokens,
|
|
191
|
+
timestamp_ms=self.runtime.timestamp_ms,
|
|
192
|
+
process_id=self.runtime.process_id,
|
|
193
|
+
),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def ollama_chat_to_anthropic(
|
|
197
|
+
self,
|
|
198
|
+
data: dict[str, Any],
|
|
199
|
+
model: str,
|
|
200
|
+
source_body: dict[str, Any] | None = None,
|
|
201
|
+
) -> dict[str, Any]:
|
|
202
|
+
return self.algorithms.project_ollama_response(
|
|
203
|
+
data,
|
|
204
|
+
model,
|
|
205
|
+
source_body,
|
|
206
|
+
self.ollama_response_services(),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
def split_word_buffer(
|
|
210
|
+
self,
|
|
211
|
+
buf: str,
|
|
212
|
+
force: bool = False,
|
|
213
|
+
max_buffer: int | None = None,
|
|
214
|
+
) -> tuple[str, str]:
|
|
215
|
+
return self.algorithms.split_word_buffer(
|
|
216
|
+
buf,
|
|
217
|
+
force=force,
|
|
218
|
+
max_buffer=(
|
|
219
|
+
self.types.word_chunk_max_buffer
|
|
220
|
+
if max_buffer is None
|
|
221
|
+
else max_buffer
|
|
222
|
+
),
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
def anthropic_stream_services(self) -> streaming_anthropic.AnthropicStreamServices:
|
|
226
|
+
return streaming_anthropic.AnthropicStreamServices(
|
|
227
|
+
io=streaming_anthropic.AnthropicStreamIO(
|
|
228
|
+
ANTHROPIC_THINKING_BLOCK_TYPES=self.types.thinking_block_types,
|
|
229
|
+
VisibleToolCallArtifactFilter=self.types.visible_tool_filter,
|
|
230
|
+
_find_pseudo_xml_tool_start=self.text.find_pseudo_xml_start,
|
|
231
|
+
_split_word_buffer=self.split_word_buffer,
|
|
232
|
+
mark_pending_channel_delivery_failed=self.io.mark_delivery_failed,
|
|
233
|
+
mark_pending_channel_delivery_success=self.io.mark_delivery_success,
|
|
234
|
+
remember_suppressed_thinking_passback=(
|
|
235
|
+
self.conversation.remember_suppressed_thinking
|
|
236
|
+
),
|
|
237
|
+
router_client_connection_closed=self.io.client_connection_closed,
|
|
238
|
+
router_log=self.io.log,
|
|
239
|
+
),
|
|
240
|
+
tool_projection=streaming_anthropic.AnthropicToolProjection(
|
|
241
|
+
_is_mcp_notification_wait_tool=self.tools.is_mcp_notification_wait,
|
|
242
|
+
_remember_channel_injected_tool_use=self.tools.remember_tool_use,
|
|
243
|
+
_validate_and_fix_tool_input=self.tools.validate_input,
|
|
244
|
+
append_tool_call_log=self.tools.append_log,
|
|
245
|
+
cap_mcp_notification_wait_tool_input=self.tools.cap_notification_wait,
|
|
246
|
+
infer_tool_name_from_args=self.infer_tool_name,
|
|
247
|
+
normalize_tool_arguments=self.normalize_tool_arguments,
|
|
248
|
+
parse_pseudo_tool_calls=self.parse_pseudo_tool_calls,
|
|
249
|
+
plan_mode_tool_name_for_emit=self.tools.plan_mode_name,
|
|
250
|
+
resolve_emitted_tool_name=self.tools.resolve_name,
|
|
251
|
+
),
|
|
252
|
+
tool_policy=streaming_anthropic.AnthropicToolPolicy(
|
|
253
|
+
should_drop_duplicate_side_effect_tool_call=self.tools.should_drop_duplicate,
|
|
254
|
+
should_drop_emitted_tool_call=self.tools.should_drop,
|
|
255
|
+
should_repair_anthropic_passthrough_tool_input=self.tools.repair_passthrough_input,
|
|
256
|
+
),
|
|
257
|
+
conversation=streaming_anthropic.AnthropicConversationContext(
|
|
258
|
+
backfill_exit_plan_mode_allowed_prompts=self.conversation.backfill_exit_plan,
|
|
259
|
+
body_ultracode_runtime_enabled=self.conversation.ultracode_enabled,
|
|
260
|
+
empty_end_turn_notice_for_body=self.recovery.empty_notice,
|
|
261
|
+
has_tool=self.conversation.has_tool,
|
|
262
|
+
latest_user_intent_message_index=self.conversation.latest_intent_index,
|
|
263
|
+
latest_user_is_claude_code_suggestion_mode=self.conversation.suggestion_mode,
|
|
264
|
+
latest_user_tool_result_names=self.recovery.latest_tool_results,
|
|
265
|
+
recent_synthetic_tasklist_count=self.conversation.recent_tasklist_count,
|
|
266
|
+
),
|
|
267
|
+
continuation=streaming_anthropic.AnthropicContinuationPolicy(
|
|
268
|
+
should_auto_continue_choice_question_with_tasklist=self.recovery.auto_continue_choice,
|
|
269
|
+
should_auto_exit_plan_mode=self.recovery.auto_exit_plan,
|
|
270
|
+
should_keep_work_alive_with_tasklist=self.recovery.keep_alive,
|
|
271
|
+
should_recover_empty_end_turn_with_tasklist=self.recovery.recover_empty,
|
|
272
|
+
should_synthesize_tasklist_for_provider=self.recovery.synthesize_tasklist,
|
|
273
|
+
),
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def rebatch_anthropic_sse_text(
|
|
277
|
+
self,
|
|
278
|
+
handler: BaseHTTPRequestHandler,
|
|
279
|
+
resp: Any,
|
|
280
|
+
model: str = "ciel-runtime-upstream",
|
|
281
|
+
word_chunking: bool = True,
|
|
282
|
+
source_body: dict[str, Any] | None = None,
|
|
283
|
+
preserve_thinking: bool = True,
|
|
284
|
+
normalize_tool_use: bool = False,
|
|
285
|
+
provider: str = "",
|
|
286
|
+
) -> None:
|
|
287
|
+
return streaming_anthropic.rebatch_anthropic_sse_text(
|
|
288
|
+
handler,
|
|
289
|
+
resp,
|
|
290
|
+
model=model,
|
|
291
|
+
word_chunking=word_chunking,
|
|
292
|
+
source_body=source_body,
|
|
293
|
+
preserve_thinking=preserve_thinking,
|
|
294
|
+
normalize_tool_use=normalize_tool_use,
|
|
295
|
+
provider=provider,
|
|
296
|
+
services=self.anthropic_stream_services(),
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def ollama_stream_services(self) -> streaming_anthropic.OllamaStreamServices:
|
|
300
|
+
return streaming_anthropic.OllamaStreamServices(
|
|
301
|
+
io=streaming_anthropic.OllamaStreamIO(
|
|
302
|
+
UpstreamClientDisconnected=self.types.client_disconnected_error,
|
|
303
|
+
VisibleThinkingMarkupFilter=self.types.visible_thinking_filter,
|
|
304
|
+
_split_word_buffer=self.split_word_buffer,
|
|
305
|
+
estimate_tokens=self.io.estimate_tokens,
|
|
306
|
+
iter_upstream_lines_until_client_disconnect=self.io.iter_upstream_lines,
|
|
307
|
+
mark_pending_channel_delivery_failed=self.io.mark_delivery_failed,
|
|
308
|
+
mark_pending_channel_delivery_success=self.io.mark_delivery_success,
|
|
309
|
+
router_log=self.io.log,
|
|
310
|
+
write_router_activity=self.io.write_activity,
|
|
311
|
+
),
|
|
312
|
+
trace=streaming_anthropic.OllamaStreamTrace(
|
|
313
|
+
dump_response_for_trace=self.trace.dump_response,
|
|
314
|
+
finish_outgoing_sse_trace=self.trace.finish_sse,
|
|
315
|
+
make_outgoing_sse_trace=self.trace.make_sse,
|
|
316
|
+
record_outgoing_sse_event=self.trace.record_sse,
|
|
317
|
+
),
|
|
318
|
+
tool_projection=streaming_anthropic.OllamaToolProjection(
|
|
319
|
+
_remember_channel_injected_tool_use=self.tools.remember_tool_use,
|
|
320
|
+
_validate_and_fix_tool_input=self.tools.validate_input,
|
|
321
|
+
append_tool_call_log=self.tools.append_log,
|
|
322
|
+
cap_mcp_notification_wait_tool_input=self.tools.cap_notification_wait,
|
|
323
|
+
normalize_tool_arguments=self.normalize_tool_arguments,
|
|
324
|
+
plan_mode_tool_name_for_emit=self.tools.plan_mode_name,
|
|
325
|
+
resolve_emitted_tool_name=self.tools.resolve_name,
|
|
326
|
+
should_drop_duplicate_side_effect_tool_call=self.tools.should_drop_duplicate,
|
|
327
|
+
should_drop_emitted_tool_call=self.tools.should_drop,
|
|
328
|
+
),
|
|
329
|
+
continuation=streaming_anthropic.OllamaContinuationPolicy(
|
|
330
|
+
empty_end_turn_notice_for_body=self.recovery.empty_notice,
|
|
331
|
+
reasoning_only_notice=self.text.ollama_reasoning_only_notice,
|
|
332
|
+
should_auto_continue_choice_question_with_tasklist=self.recovery.auto_continue_choice,
|
|
333
|
+
should_auto_enter_plan_mode=self.recovery.auto_enter_plan,
|
|
334
|
+
should_keep_work_alive_with_tasklist=self.recovery.keep_alive,
|
|
335
|
+
should_recover_empty_end_turn_with_tasklist=self.recovery.recover_empty,
|
|
336
|
+
),
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
def ollama_stream_to_anthropic_sse(
|
|
340
|
+
self,
|
|
341
|
+
handler: BaseHTTPRequestHandler,
|
|
342
|
+
resp: Any,
|
|
343
|
+
model: str,
|
|
344
|
+
word_chunking: bool = False,
|
|
345
|
+
provider: str = "ollama",
|
|
346
|
+
source_body: dict[str, Any] | None = None,
|
|
347
|
+
idle_timeout: float = 30.0,
|
|
348
|
+
) -> None:
|
|
349
|
+
return streaming_anthropic.ollama_stream_to_anthropic_sse(
|
|
350
|
+
handler,
|
|
351
|
+
resp,
|
|
352
|
+
model,
|
|
353
|
+
word_chunking=word_chunking,
|
|
354
|
+
provider=provider,
|
|
355
|
+
source_body=source_body,
|
|
356
|
+
idle_timeout=idle_timeout,
|
|
357
|
+
services=self.ollama_stream_services(),
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
def openai_chat_to_anthropic(
|
|
361
|
+
self,
|
|
362
|
+
data: dict[str, Any],
|
|
363
|
+
model: str,
|
|
364
|
+
source_body: dict[str, Any] | None = None,
|
|
365
|
+
) -> dict[str, Any]:
|
|
366
|
+
return self.algorithms.project_openai_chat_response(
|
|
367
|
+
data,
|
|
368
|
+
model,
|
|
369
|
+
source_body,
|
|
370
|
+
services=self.ollama_response_services(),
|
|
371
|
+
positive_int=self.text.positive_int,
|
|
372
|
+
reasoning_to_block=self.text.reasoning_to_thinking,
|
|
373
|
+
content_to_text=self.text.anthropic_content_to_text,
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
def openai_responses_to_anthropic_messages(
|
|
377
|
+
self, body: dict[str, Any], fallback_model: str
|
|
378
|
+
) -> dict[str, Any]:
|
|
379
|
+
adapter = self.algorithms.protocol_adapter(
|
|
380
|
+
"openai_responses", fallback_model=fallback_model
|
|
381
|
+
)
|
|
382
|
+
return dict(adapter.normalize_request(body))
|
|
383
|
+
|
|
384
|
+
def anthropic_message_to_openai_response(
|
|
385
|
+
self,
|
|
386
|
+
message: dict[str, Any],
|
|
387
|
+
source_body: dict[str, Any] | None = None,
|
|
388
|
+
) -> dict[str, Any]:
|
|
389
|
+
adapter = self.algorithms.protocol_adapter(
|
|
390
|
+
"openai_responses", source_body=source_body
|
|
391
|
+
)
|
|
392
|
+
return dict(adapter.normalize_response(message))
|
|
393
|
+
|
|
394
|
+
def openai_responses_stream_services(self) -> OpenAIResponsesStreamServices:
|
|
395
|
+
return OpenAIResponsesStreamServices(
|
|
396
|
+
to_response=self.anthropic_message_to_openai_response,
|
|
397
|
+
write_json=self.io.write_json,
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
def write_openai_responses_response(
|
|
401
|
+
self,
|
|
402
|
+
handler: BaseHTTPRequestHandler,
|
|
403
|
+
message: dict[str, Any],
|
|
404
|
+
source_body: dict[str, Any] | None = None,
|
|
405
|
+
*,
|
|
406
|
+
stream: bool = True,
|
|
407
|
+
) -> None:
|
|
408
|
+
self.algorithms.write_openai_response(
|
|
409
|
+
handler,
|
|
410
|
+
message,
|
|
411
|
+
source_body,
|
|
412
|
+
stream=stream,
|
|
413
|
+
services=self.openai_responses_stream_services(),
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
def write_openai_responses_error(
|
|
417
|
+
self,
|
|
418
|
+
handler: BaseHTTPRequestHandler,
|
|
419
|
+
message: str,
|
|
420
|
+
*,
|
|
421
|
+
stream: bool = True,
|
|
422
|
+
status: int = 500,
|
|
423
|
+
) -> None:
|
|
424
|
+
self.algorithms.write_openai_error(
|
|
425
|
+
handler,
|
|
426
|
+
message,
|
|
427
|
+
stream=stream,
|
|
428
|
+
status=status,
|
|
429
|
+
services=self.openai_responses_stream_services(),
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
def openai_chat_stream_services(
|
|
433
|
+
self,
|
|
434
|
+
) -> streaming_anthropic.OpenAIChatStreamServices:
|
|
435
|
+
return streaming_anthropic.OpenAIChatStreamServices(
|
|
436
|
+
io=streaming_anthropic.OpenAIChatStreamIO(
|
|
437
|
+
PSEUDO_TOOL_END=self.types.pseudo_tool_end,
|
|
438
|
+
PSEUDO_TOOL_START=self.types.pseudo_tool_start,
|
|
439
|
+
_split_word_buffer=self.split_word_buffer,
|
|
440
|
+
positive_int=self.text.positive_int,
|
|
441
|
+
router_log=self.io.log,
|
|
442
|
+
write_anthropic_open_stream_stop=self.io.write_open_stream_stop,
|
|
443
|
+
write_router_activity=self.io.write_activity,
|
|
444
|
+
),
|
|
445
|
+
tool_projection=streaming_anthropic.OpenAIChatToolProjection(
|
|
446
|
+
_remember_channel_injected_tool_use=self.tools.remember_tool_use,
|
|
447
|
+
_validate_and_fix_tool_input=self.tools.validate_input,
|
|
448
|
+
append_tool_call_log=self.tools.append_log,
|
|
449
|
+
cap_mcp_notification_wait_tool_input=self.tools.cap_notification_wait,
|
|
450
|
+
normalize_tool_arguments=self.normalize_tool_arguments,
|
|
451
|
+
parse_pseudo_tool_calls=self.parse_pseudo_tool_calls,
|
|
452
|
+
plan_mode_tool_name_for_emit=self.tools.plan_mode_name,
|
|
453
|
+
resolve_emitted_tool_name=self.tools.resolve_name,
|
|
454
|
+
should_drop_duplicate_side_effect_tool_call=self.tools.should_drop_duplicate,
|
|
455
|
+
should_drop_emitted_tool_call=self.tools.should_drop,
|
|
456
|
+
),
|
|
457
|
+
continuation=streaming_anthropic.OpenAIChatContinuationPolicy(
|
|
458
|
+
empty_end_turn_notice_for_body=self.recovery.empty_notice,
|
|
459
|
+
latest_user_tool_result_names=self.recovery.latest_tool_results,
|
|
460
|
+
should_auto_continue_choice_question_with_tasklist=self.recovery.auto_continue_choice,
|
|
461
|
+
should_auto_enter_plan_mode=self.recovery.auto_enter_plan,
|
|
462
|
+
should_keep_work_alive_with_tasklist=self.recovery.keep_alive,
|
|
463
|
+
should_recover_empty_end_turn_with_tasklist=self.recovery.recover_empty,
|
|
464
|
+
),
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
def stream_openai_chat_to_anthropic_sse(
|
|
468
|
+
self,
|
|
469
|
+
handler: BaseHTTPRequestHandler,
|
|
470
|
+
resp: Any,
|
|
471
|
+
model: str,
|
|
472
|
+
provider: str,
|
|
473
|
+
source_body: dict[str, Any] | None = None,
|
|
474
|
+
start_index: int = 0,
|
|
475
|
+
word_chunking: bool = False,
|
|
476
|
+
input_tokens: int | None = None,
|
|
477
|
+
input_bytes: int | None = None,
|
|
478
|
+
) -> bool:
|
|
479
|
+
return streaming_anthropic.forward_openai_chat_to_anthropic_sse(
|
|
480
|
+
handler,
|
|
481
|
+
resp,
|
|
482
|
+
model,
|
|
483
|
+
provider,
|
|
484
|
+
source_body=source_body,
|
|
485
|
+
start_index=start_index,
|
|
486
|
+
word_chunking=word_chunking,
|
|
487
|
+
input_tokens=input_tokens,
|
|
488
|
+
input_bytes=input_bytes,
|
|
489
|
+
services=self.openai_chat_stream_services(),
|
|
490
|
+
)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@dataclass(frozen=True, slots=True)
|
|
494
|
+
class ResponseStreamCompatibilityApi:
|
|
495
|
+
context: Callable[[], ResponseStreamContext]
|
|
496
|
+
|
|
497
|
+
def normalize_tool_arguments(self, *args: Any, **kwargs: Any) -> Any:
|
|
498
|
+
return self.context().normalize_tool_arguments(*args, **kwargs)
|
|
499
|
+
|
|
500
|
+
def infer_tool_name(self, *args: Any, **kwargs: Any) -> Any:
|
|
501
|
+
return self.context().infer_tool_name(*args, **kwargs)
|
|
502
|
+
|
|
503
|
+
def parse_pseudo_tool_calls(self, *args: Any, **kwargs: Any) -> Any:
|
|
504
|
+
return self.context().parse_pseudo_tool_calls(*args, **kwargs)
|
|
505
|
+
|
|
506
|
+
def ollama_response_services(self) -> OllamaResponseServices:
|
|
507
|
+
return self.context().ollama_response_services()
|
|
508
|
+
|
|
509
|
+
def ollama_chat_to_anthropic(self, *args: Any, **kwargs: Any) -> Any:
|
|
510
|
+
return self.context().ollama_chat_to_anthropic(*args, **kwargs)
|
|
511
|
+
|
|
512
|
+
def split_word_buffer(self, *args: Any, **kwargs: Any) -> Any:
|
|
513
|
+
return self.context().split_word_buffer(*args, **kwargs)
|
|
514
|
+
|
|
515
|
+
def rebatch_anthropic_sse_text(self, *args: Any, **kwargs: Any) -> Any:
|
|
516
|
+
return self.context().rebatch_anthropic_sse_text(*args, **kwargs)
|
|
517
|
+
|
|
518
|
+
def ollama_stream_to_anthropic_sse(self, *args: Any, **kwargs: Any) -> Any:
|
|
519
|
+
return self.context().ollama_stream_to_anthropic_sse(*args, **kwargs)
|
|
520
|
+
|
|
521
|
+
def openai_chat_to_anthropic(self, *args: Any, **kwargs: Any) -> Any:
|
|
522
|
+
return self.context().openai_chat_to_anthropic(*args, **kwargs)
|
|
523
|
+
|
|
524
|
+
def openai_responses_to_anthropic_messages(self, *args: Any, **kwargs: Any) -> Any:
|
|
525
|
+
return self.context().openai_responses_to_anthropic_messages(*args, **kwargs)
|
|
526
|
+
|
|
527
|
+
def anthropic_message_to_openai_response(self, *args: Any, **kwargs: Any) -> Any:
|
|
528
|
+
return self.context().anthropic_message_to_openai_response(*args, **kwargs)
|
|
529
|
+
|
|
530
|
+
def openai_responses_stream_services(self) -> OpenAIResponsesStreamServices:
|
|
531
|
+
return self.context().openai_responses_stream_services()
|
|
532
|
+
|
|
533
|
+
def write_openai_responses_response(self, *args: Any, **kwargs: Any) -> Any:
|
|
534
|
+
return self.context().write_openai_responses_response(*args, **kwargs)
|
|
535
|
+
|
|
536
|
+
def write_openai_responses_error(self, *args: Any, **kwargs: Any) -> Any:
|
|
537
|
+
return self.context().write_openai_responses_error(*args, **kwargs)
|
|
538
|
+
|
|
539
|
+
def stream_openai_chat_to_anthropic_sse(self, *args: Any, **kwargs: Any) -> Any:
|
|
540
|
+
return self.context().stream_openai_chat_to_anthropic_sse(*args, **kwargs)
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
__all__ = [
|
|
544
|
+
"ResponseStreamAlgorithms",
|
|
545
|
+
"ResponseStreamCompatibilityApi",
|
|
546
|
+
"ResponseStreamContext",
|
|
547
|
+
"ResponseStreamConversationPorts",
|
|
548
|
+
"ResponseStreamIoPorts",
|
|
549
|
+
"ResponseStreamRecoveryPorts",
|
|
550
|
+
"ResponseStreamRuntimePorts",
|
|
551
|
+
"ResponseStreamTextPorts",
|
|
552
|
+
"ResponseStreamToolPorts",
|
|
553
|
+
"ResponseStreamTracePorts",
|
|
554
|
+
"ResponseStreamTypes",
|
|
555
|
+
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Compatibility repairs for replayed OpenAI Responses input items."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
OPENAI_RESPONSES_ITEM_ID_PREFIXES = {
|
|
10
|
+
"message": "msg_",
|
|
11
|
+
"reasoning": "rs_",
|
|
12
|
+
"function_call": "fc_",
|
|
13
|
+
"function_call_output": "fco_",
|
|
14
|
+
"custom_tool_call": "ctc_",
|
|
15
|
+
"custom_tool_call_output": "ctco_",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
# Item IDs the router mints itself when it projects a foreign provider's answer
|
|
19
|
+
# into a Responses payload: a type prefix, eight hex digits taken from the
|
|
20
|
+
# locally generated response ID, and the block's index. No upstream issues IDs
|
|
21
|
+
# in this shape -- OpenAI's own are a prefix plus a long unbroken hex run
|
|
22
|
+
# (``rs_0b9eabb999...``) and Codex's client-side IDs are UUIDs
|
|
23
|
+
# (``fco_019fd660-738b-...``) -- so the shape identifies the router's own.
|
|
24
|
+
_ROUTER_SYNTHESIZED_ITEM_ID = re.compile(r"^[a-z]+_[0-9a-f]{8}_\d+$")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def router_synthesized_item_id(prefix: str, response_id: str, index: int) -> str:
|
|
28
|
+
"""Mint the item ID for one projected block of a foreign response."""
|
|
29
|
+
|
|
30
|
+
return f"{prefix}_{response_id[5:13]}_{index}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def is_router_synthesized_item_id(item_id: Any) -> bool:
|
|
34
|
+
"""Report whether an ID was minted locally rather than by an upstream."""
|
|
35
|
+
|
|
36
|
+
return bool(_ROUTER_SYNTHESIZED_ITEM_ID.match(str(item_id or "")))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def repair_replayed_response_items(body: dict[str, Any]) -> dict[str, Any]:
|
|
40
|
+
"""Repair provider response records that cannot be replayed by OpenAI.
|
|
41
|
+
|
|
42
|
+
Some Responses-compatible providers emit reasoning items with message IDs
|
|
43
|
+
(``msg_...``). Codex persists those output items and later replays them as
|
|
44
|
+
input. The OpenAI Codex backend requires reasoning IDs to start with
|
|
45
|
+
``rs_`` and rejects the whole turn otherwise.
|
|
46
|
+
|
|
47
|
+
A foreign summary-only reasoning item is not authoritative conversation
|
|
48
|
+
content, so it is dropped. If encrypted reasoning content is present, the
|
|
49
|
+
item is retained but its invalid local/provider ID is omitted.
|
|
50
|
+
|
|
51
|
+
Some providers also emit tool items with message IDs (``msg_``) while
|
|
52
|
+
preserving the independent ``call_id`` used to pair calls with outputs.
|
|
53
|
+
Validate IDs by Responses item type and remove only a mismatched item ID;
|
|
54
|
+
keep ``call_id``, the tool name, arguments, output, and message content so
|
|
55
|
+
the transcript remains usable.
|
|
56
|
+
|
|
57
|
+
IDs the router minted itself are treated the same way. They carry the right
|
|
58
|
+
prefix, so the type check alone accepts them, but no backend ever stored
|
|
59
|
+
them: OpenAI answers a replayed one with ``Item with id 'rs_..._0' not
|
|
60
|
+
found. Items are not persisted when 'store' is set to false``, which kills
|
|
61
|
+
the turn. Dropping the ID keeps the content and lets the item be replayed
|
|
62
|
+
inline.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
value = body.get("input")
|
|
66
|
+
if not isinstance(value, list):
|
|
67
|
+
return body
|
|
68
|
+
|
|
69
|
+
changed = False
|
|
70
|
+
repaired: list[Any] = []
|
|
71
|
+
for value_item in value:
|
|
72
|
+
if not isinstance(value_item, dict):
|
|
73
|
+
repaired.append(value_item)
|
|
74
|
+
continue
|
|
75
|
+
item = value_item
|
|
76
|
+
item_type = item.get("type")
|
|
77
|
+
item_id = str(item.get("id") or "")
|
|
78
|
+
expected_prefix = OPENAI_RESPONSES_ITEM_ID_PREFIXES.get(str(item_type or ""))
|
|
79
|
+
invalid_item_id = bool(item_id) and (
|
|
80
|
+
bool(expected_prefix and not item_id.startswith(expected_prefix))
|
|
81
|
+
or is_router_synthesized_item_id(item_id)
|
|
82
|
+
)
|
|
83
|
+
if item_type != "reasoning" and invalid_item_id:
|
|
84
|
+
retained = dict(item)
|
|
85
|
+
retained.pop("id", None)
|
|
86
|
+
repaired.append(retained)
|
|
87
|
+
changed = True
|
|
88
|
+
continue
|
|
89
|
+
if item_type != "reasoning":
|
|
90
|
+
repaired.append(item)
|
|
91
|
+
continue
|
|
92
|
+
if not invalid_item_id:
|
|
93
|
+
repaired.append(item)
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
changed = True
|
|
97
|
+
if not str(item.get("encrypted_content") or "").strip():
|
|
98
|
+
# A summary generated by another provider is neither signed nor
|
|
99
|
+
# required to preserve the visible/tool conversation history.
|
|
100
|
+
continue
|
|
101
|
+
retained = dict(item)
|
|
102
|
+
retained.pop("id", None)
|
|
103
|
+
repaired.append(retained)
|
|
104
|
+
|
|
105
|
+
if not changed:
|
|
106
|
+
return body
|
|
107
|
+
projected = dict(body)
|
|
108
|
+
projected["input"] = repaired
|
|
109
|
+
return projected
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
repair_replayed_reasoning_items = repair_replayed_response_items
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
__all__ = [
|
|
116
|
+
"OPENAI_RESPONSES_ITEM_ID_PREFIXES",
|
|
117
|
+
"is_router_synthesized_item_id",
|
|
118
|
+
"repair_replayed_reasoning_items",
|
|
119
|
+
"repair_replayed_response_items",
|
|
120
|
+
"router_synthesized_item_id",
|
|
121
|
+
]
|