devcopilot 0.2.0__py3-none-any.whl
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.
- api/__init__.py +17 -0
- api/admin_config.py +1303 -0
- api/admin_routes.py +287 -0
- api/admin_static/admin.css +459 -0
- api/admin_static/admin.js +497 -0
- api/admin_static/index.html +77 -0
- api/admin_urls.py +34 -0
- api/app.py +194 -0
- api/command_utils.py +164 -0
- api/dependencies.py +144 -0
- api/detection.py +152 -0
- api/gateway_model_ids.py +54 -0
- api/model_catalog.py +133 -0
- api/model_router.py +125 -0
- api/models/__init__.py +45 -0
- api/models/anthropic.py +234 -0
- api/models/openai_responses.py +28 -0
- api/models/responses.py +60 -0
- api/optimization_handlers.py +154 -0
- api/request_pipeline.py +424 -0
- api/routes.py +156 -0
- api/runtime.py +334 -0
- api/validation_log.py +48 -0
- api/web_server_tools.py +22 -0
- api/web_tools/__init__.py +17 -0
- api/web_tools/constants.py +15 -0
- api/web_tools/egress.py +99 -0
- api/web_tools/outbound.py +278 -0
- api/web_tools/parsers.py +104 -0
- api/web_tools/request.py +87 -0
- api/web_tools/streaming.py +206 -0
- cli/__init__.py +5 -0
- cli/claude_env.py +12 -0
- cli/entrypoints.py +166 -0
- cli/env.example +209 -0
- cli/launchers/__init__.py +1 -0
- cli/launchers/claude.py +84 -0
- cli/launchers/codex.py +204 -0
- cli/launchers/codex_model_catalog.py +186 -0
- cli/launchers/common.py +93 -0
- cli/managed/__init__.py +6 -0
- cli/managed/claude.py +215 -0
- cli/managed/manager.py +157 -0
- cli/managed/session.py +260 -0
- cli/process_registry.py +78 -0
- config/__init__.py +5 -0
- config/constants.py +13 -0
- config/logging_config.py +159 -0
- config/nim.py +118 -0
- config/paths.py +91 -0
- config/provider_catalog.py +259 -0
- config/provider_ids.py +7 -0
- config/settings.py +538 -0
- core/__init__.py +1 -0
- core/anthropic/__init__.py +46 -0
- core/anthropic/content.py +31 -0
- core/anthropic/conversion.py +587 -0
- core/anthropic/emitted_sse_tracker.py +346 -0
- core/anthropic/errors.py +70 -0
- core/anthropic/native_messages_request.py +280 -0
- core/anthropic/native_sse_block_policy.py +313 -0
- core/anthropic/provider_stream_error.py +34 -0
- core/anthropic/server_tool_sse.py +14 -0
- core/anthropic/sse.py +440 -0
- core/anthropic/stream_contracts.py +205 -0
- core/anthropic/stream_recovery.py +346 -0
- core/anthropic/stream_recovery_session.py +133 -0
- core/anthropic/thinking.py +140 -0
- core/anthropic/tokens.py +117 -0
- core/anthropic/tools.py +212 -0
- core/anthropic/utils.py +9 -0
- core/openai_responses/__init__.py +5 -0
- core/openai_responses/adapter.py +31 -0
- core/openai_responses/anthropic_sse.py +59 -0
- core/openai_responses/errors.py +22 -0
- core/openai_responses/events.py +19 -0
- core/openai_responses/ids.py +21 -0
- core/openai_responses/input.py +258 -0
- core/openai_responses/items.py +37 -0
- core/openai_responses/reasoning.py +52 -0
- core/openai_responses/stream.py +25 -0
- core/openai_responses/stream_state.py +654 -0
- core/openai_responses/tools.py +374 -0
- core/openai_responses/usage.py +37 -0
- core/rate_limit.py +60 -0
- core/trace.py +216 -0
- devcopilot-0.2.0.dist-info/METADATA +687 -0
- devcopilot-0.2.0.dist-info/RECORD +189 -0
- devcopilot-0.2.0.dist-info/WHEEL +4 -0
- devcopilot-0.2.0.dist-info/entry_points.txt +6 -0
- devcopilot-0.2.0.dist-info/licenses/LICENSE +21 -0
- messaging/__init__.py +26 -0
- messaging/cli_event_constants.py +67 -0
- messaging/command_context.py +66 -0
- messaging/command_dispatcher.py +37 -0
- messaging/commands.py +275 -0
- messaging/event_parser.py +181 -0
- messaging/limiter.py +300 -0
- messaging/models.py +36 -0
- messaging/node_event_pipeline.py +127 -0
- messaging/node_runner.py +342 -0
- messaging/platforms/__init__.py +15 -0
- messaging/platforms/base.py +228 -0
- messaging/platforms/discord.py +567 -0
- messaging/platforms/factory.py +103 -0
- messaging/platforms/outbox.py +144 -0
- messaging/platforms/telegram.py +688 -0
- messaging/platforms/voice_flow.py +295 -0
- messaging/rendering/__init__.py +3 -0
- messaging/rendering/discord_markdown.py +318 -0
- messaging/rendering/markdown_tables.py +49 -0
- messaging/rendering/profiles.py +55 -0
- messaging/rendering/telegram_markdown.py +327 -0
- messaging/safe_diagnostics.py +17 -0
- messaging/session.py +334 -0
- messaging/transcript.py +581 -0
- messaging/transcription.py +164 -0
- messaging/trees/__init__.py +15 -0
- messaging/trees/data.py +482 -0
- messaging/trees/manager.py +433 -0
- messaging/trees/processor.py +179 -0
- messaging/trees/repository.py +177 -0
- messaging/turn_intake.py +235 -0
- messaging/ui_updates.py +101 -0
- messaging/voice.py +76 -0
- messaging/workflow.py +200 -0
- providers/__init__.py +31 -0
- providers/base.py +152 -0
- providers/cerebras/__init__.py +7 -0
- providers/cerebras/client.py +31 -0
- providers/cerebras/request.py +55 -0
- providers/codestral/__init__.py +7 -0
- providers/codestral/client.py +34 -0
- providers/deepseek/__init__.py +11 -0
- providers/deepseek/client.py +51 -0
- providers/deepseek/request.py +475 -0
- providers/defaults.py +41 -0
- providers/error_mapping.py +309 -0
- providers/exceptions.py +113 -0
- providers/fireworks/__init__.py +5 -0
- providers/fireworks/client.py +45 -0
- providers/fireworks/request.py +48 -0
- providers/gemini/__init__.py +7 -0
- providers/gemini/client.py +49 -0
- providers/gemini/request.py +199 -0
- providers/groq/__init__.py +7 -0
- providers/groq/client.py +31 -0
- providers/groq/request.py +83 -0
- providers/kimi/__init__.py +10 -0
- providers/kimi/client.py +53 -0
- providers/kimi/request.py +42 -0
- providers/llamacpp/__init__.py +3 -0
- providers/llamacpp/client.py +16 -0
- providers/lmstudio/__init__.py +5 -0
- providers/lmstudio/client.py +16 -0
- providers/mistral/__init__.py +7 -0
- providers/mistral/client.py +31 -0
- providers/mistral/request.py +37 -0
- providers/model_listing.py +133 -0
- providers/nvidia_nim/__init__.py +7 -0
- providers/nvidia_nim/client.py +91 -0
- providers/nvidia_nim/request.py +430 -0
- providers/nvidia_nim/voice.py +95 -0
- providers/ollama/__init__.py +7 -0
- providers/ollama/client.py +39 -0
- providers/open_router/__init__.py +7 -0
- providers/open_router/client.py +124 -0
- providers/open_router/request.py +42 -0
- providers/opencode/__init__.py +11 -0
- providers/opencode/client.py +31 -0
- providers/opencode/request.py +35 -0
- providers/rate_limit.py +300 -0
- providers/registry.py +527 -0
- providers/transports/__init__.py +1 -0
- providers/transports/anthropic_messages/__init__.py +5 -0
- providers/transports/anthropic_messages/http.py +118 -0
- providers/transports/anthropic_messages/recovery.py +206 -0
- providers/transports/anthropic_messages/stream.py +295 -0
- providers/transports/anthropic_messages/transport.py +236 -0
- providers/transports/openai_chat/__init__.py +5 -0
- providers/transports/openai_chat/recovery.py +217 -0
- providers/transports/openai_chat/stream.py +384 -0
- providers/transports/openai_chat/tool_calls.py +293 -0
- providers/transports/openai_chat/transport.py +156 -0
- providers/wafer/__init__.py +10 -0
- providers/wafer/client.py +50 -0
- providers/zai/__init__.py +10 -0
- providers/zai/client.py +46 -0
- providers/zai/request.py +42 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Convert OpenAI Responses requests into Anthropic Messages payloads."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .errors import ResponsesConversionError
|
|
9
|
+
from .reasoning import (
|
|
10
|
+
combine_reasoning,
|
|
11
|
+
reasoning_text_from_item,
|
|
12
|
+
responses_reasoning_to_thinking,
|
|
13
|
+
)
|
|
14
|
+
from .tools import (
|
|
15
|
+
call_id_from_item,
|
|
16
|
+
convert_tool_choice,
|
|
17
|
+
convert_tools,
|
|
18
|
+
custom_tool_input_to_anthropic,
|
|
19
|
+
optional_str,
|
|
20
|
+
parse_arguments,
|
|
21
|
+
required_str,
|
|
22
|
+
responses_tool_name_to_anthropic_name,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def convert_request_to_anthropic_payload(
|
|
27
|
+
request: Mapping[str, Any],
|
|
28
|
+
) -> dict[str, Any]:
|
|
29
|
+
"""Convert an OpenAI Responses request into an Anthropic Messages payload."""
|
|
30
|
+
|
|
31
|
+
system_parts: list[str] = []
|
|
32
|
+
if instructions := optional_str(request.get("instructions")):
|
|
33
|
+
system_parts.append(instructions)
|
|
34
|
+
|
|
35
|
+
messages: list[dict[str, Any]] = []
|
|
36
|
+
pending_reasoning: str | None = None
|
|
37
|
+
for item in _iter_input_items(request.get("input")):
|
|
38
|
+
pending_reasoning = _append_input_item(
|
|
39
|
+
item,
|
|
40
|
+
messages=messages,
|
|
41
|
+
system_parts=system_parts,
|
|
42
|
+
pending_reasoning=pending_reasoning,
|
|
43
|
+
)
|
|
44
|
+
_append_pending_reasoning(messages, pending_reasoning)
|
|
45
|
+
|
|
46
|
+
if not messages:
|
|
47
|
+
raise ResponsesConversionError("Responses request input must contain a message")
|
|
48
|
+
|
|
49
|
+
payload: dict[str, Any] = {
|
|
50
|
+
"model": required_str(request.get("model"), "model"),
|
|
51
|
+
"messages": messages,
|
|
52
|
+
"stream": True,
|
|
53
|
+
}
|
|
54
|
+
if system_parts:
|
|
55
|
+
payload["system"] = "\n\n".join(system_parts)
|
|
56
|
+
_copy_if_present(request, payload, "temperature")
|
|
57
|
+
_copy_if_present(request, payload, "top_p")
|
|
58
|
+
if request.get("max_output_tokens") is not None:
|
|
59
|
+
payload["max_tokens"] = request["max_output_tokens"]
|
|
60
|
+
if isinstance(request.get("metadata"), dict):
|
|
61
|
+
payload["metadata"] = request["metadata"]
|
|
62
|
+
|
|
63
|
+
if thinking := responses_reasoning_to_thinking(request.get("reasoning")):
|
|
64
|
+
payload["thinking"] = thinking
|
|
65
|
+
|
|
66
|
+
raw_tool_choice = request.get("tool_choice")
|
|
67
|
+
tools = convert_tools(request.get("tools"))
|
|
68
|
+
if tools and raw_tool_choice != "none":
|
|
69
|
+
payload["tools"] = tools
|
|
70
|
+
tool_choice = convert_tool_choice(raw_tool_choice)
|
|
71
|
+
if tool_choice is not None:
|
|
72
|
+
payload["tool_choice"] = tool_choice
|
|
73
|
+
|
|
74
|
+
return payload
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _append_input_item(
|
|
78
|
+
item: Any,
|
|
79
|
+
*,
|
|
80
|
+
messages: list[dict[str, Any]],
|
|
81
|
+
system_parts: list[str],
|
|
82
|
+
pending_reasoning: str | None,
|
|
83
|
+
) -> str | None:
|
|
84
|
+
if isinstance(item, str):
|
|
85
|
+
_append_pending_reasoning(messages, pending_reasoning)
|
|
86
|
+
messages.append({"role": "user", "content": item})
|
|
87
|
+
return None
|
|
88
|
+
if not isinstance(item, dict):
|
|
89
|
+
raise ResponsesConversionError(
|
|
90
|
+
f"Unsupported Responses input item: {type(item).__name__}"
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
item_type = item.get("type")
|
|
94
|
+
if item_type in (None, "message") or "role" in item:
|
|
95
|
+
role = required_str(item.get("role", "user"), "input.role")
|
|
96
|
+
if role == "assistant":
|
|
97
|
+
_append_message_item(
|
|
98
|
+
role,
|
|
99
|
+
item.get("content", ""),
|
|
100
|
+
messages,
|
|
101
|
+
system_parts,
|
|
102
|
+
reasoning_content=pending_reasoning,
|
|
103
|
+
)
|
|
104
|
+
return None
|
|
105
|
+
_append_pending_reasoning(messages, pending_reasoning)
|
|
106
|
+
_append_message_item(role, item.get("content", ""), messages, system_parts)
|
|
107
|
+
return None
|
|
108
|
+
if item_type in {"function_call", "custom_tool_call"}:
|
|
109
|
+
namespace = optional_str(item.get("namespace"))
|
|
110
|
+
field_name = f"{item_type}.name"
|
|
111
|
+
name = required_str(item.get("name"), field_name)
|
|
112
|
+
if item_type == "custom_tool_call":
|
|
113
|
+
tool_input = custom_tool_input_to_anthropic(item.get("input"))
|
|
114
|
+
else:
|
|
115
|
+
tool_input = parse_arguments(item.get("arguments"))
|
|
116
|
+
message = {
|
|
117
|
+
"role": "assistant",
|
|
118
|
+
"content": [
|
|
119
|
+
{
|
|
120
|
+
"type": "tool_use",
|
|
121
|
+
"id": call_id_from_item(item),
|
|
122
|
+
"name": responses_tool_name_to_anthropic_name(
|
|
123
|
+
name, namespace=namespace
|
|
124
|
+
),
|
|
125
|
+
"input": tool_input,
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
}
|
|
129
|
+
if pending_reasoning:
|
|
130
|
+
message["reasoning_content"] = pending_reasoning
|
|
131
|
+
messages.append(message)
|
|
132
|
+
return None
|
|
133
|
+
if item_type in {"function_call_output", "custom_tool_call_output"}:
|
|
134
|
+
_append_pending_reasoning(messages, pending_reasoning)
|
|
135
|
+
messages.append(
|
|
136
|
+
{
|
|
137
|
+
"role": "user",
|
|
138
|
+
"content": [
|
|
139
|
+
{
|
|
140
|
+
"type": "tool_result",
|
|
141
|
+
"tool_use_id": call_id_from_item(item),
|
|
142
|
+
"content": item.get("output", ""),
|
|
143
|
+
}
|
|
144
|
+
],
|
|
145
|
+
}
|
|
146
|
+
)
|
|
147
|
+
return None
|
|
148
|
+
if item_type == "reasoning":
|
|
149
|
+
return combine_reasoning(pending_reasoning, reasoning_text_from_item(item))
|
|
150
|
+
if item_type in {"input_text", "output_text", "text"}:
|
|
151
|
+
_append_pending_reasoning(messages, pending_reasoning)
|
|
152
|
+
messages.append({"role": "user", "content": _text_from_part(item)})
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
raise ResponsesConversionError(
|
|
156
|
+
f"Unsupported Responses input item type: {item_type!r}"
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _append_message_item(
|
|
161
|
+
role: str,
|
|
162
|
+
content: Any,
|
|
163
|
+
messages: list[dict[str, Any]],
|
|
164
|
+
system_parts: list[str],
|
|
165
|
+
*,
|
|
166
|
+
reasoning_content: str | None = None,
|
|
167
|
+
) -> None:
|
|
168
|
+
normalized_role = "system" if role == "developer" else role
|
|
169
|
+
if normalized_role == "system":
|
|
170
|
+
text = _content_as_text(content)
|
|
171
|
+
if text:
|
|
172
|
+
system_parts.append(text)
|
|
173
|
+
return
|
|
174
|
+
if normalized_role not in {"user", "assistant"}:
|
|
175
|
+
raise ResponsesConversionError(f"Unsupported Responses message role: {role!r}")
|
|
176
|
+
message = {
|
|
177
|
+
"role": normalized_role,
|
|
178
|
+
"content": _convert_message_content(content),
|
|
179
|
+
}
|
|
180
|
+
if normalized_role == "assistant" and reasoning_content:
|
|
181
|
+
message["reasoning_content"] = reasoning_content
|
|
182
|
+
messages.append(message)
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def _append_pending_reasoning(
|
|
186
|
+
messages: list[dict[str, Any]], pending_reasoning: str | None
|
|
187
|
+
) -> None:
|
|
188
|
+
if pending_reasoning:
|
|
189
|
+
messages.append(
|
|
190
|
+
{
|
|
191
|
+
"role": "assistant",
|
|
192
|
+
"content": "",
|
|
193
|
+
"reasoning_content": pending_reasoning,
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def _iter_input_items(value: Any) -> list[Any]:
|
|
199
|
+
if value is None:
|
|
200
|
+
return []
|
|
201
|
+
if isinstance(value, list):
|
|
202
|
+
return value
|
|
203
|
+
return [value]
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def _convert_message_content(content: Any) -> str | list[dict[str, Any]]:
|
|
207
|
+
if isinstance(content, str):
|
|
208
|
+
return content
|
|
209
|
+
if isinstance(content, list):
|
|
210
|
+
blocks: list[dict[str, Any]] = []
|
|
211
|
+
for part in content:
|
|
212
|
+
if isinstance(part, str):
|
|
213
|
+
blocks.append({"type": "text", "text": part})
|
|
214
|
+
continue
|
|
215
|
+
if not isinstance(part, dict):
|
|
216
|
+
raise ResponsesConversionError(
|
|
217
|
+
f"Unsupported Responses content part: {type(part).__name__}"
|
|
218
|
+
)
|
|
219
|
+
part_type = part.get("type")
|
|
220
|
+
if part_type in {"input_text", "output_text", "text"} or "text" in part:
|
|
221
|
+
blocks.append({"type": "text", "text": _text_from_part(part)})
|
|
222
|
+
continue
|
|
223
|
+
if part_type == "refusal":
|
|
224
|
+
blocks.append({"type": "text", "text": str(part.get("refusal", ""))})
|
|
225
|
+
continue
|
|
226
|
+
raise ResponsesConversionError(
|
|
227
|
+
f"Unsupported Responses content part type: {part_type!r}"
|
|
228
|
+
)
|
|
229
|
+
return blocks
|
|
230
|
+
if isinstance(content, dict):
|
|
231
|
+
return [{"type": "text", "text": _text_from_part(content)}]
|
|
232
|
+
raise ResponsesConversionError(
|
|
233
|
+
f"Unsupported Responses message content: {type(content).__name__}"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _content_as_text(content: Any) -> str:
|
|
238
|
+
converted = _convert_message_content(content)
|
|
239
|
+
if isinstance(converted, str):
|
|
240
|
+
return converted
|
|
241
|
+
return "\n".join(str(block.get("text", "")) for block in converted)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _text_from_part(part: Mapping[str, Any]) -> str:
|
|
245
|
+
if text := optional_str(part.get("text")):
|
|
246
|
+
return text
|
|
247
|
+
if text := optional_str(part.get("input_text")):
|
|
248
|
+
return text
|
|
249
|
+
if text := optional_str(part.get("output_text")):
|
|
250
|
+
return text
|
|
251
|
+
return ""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _copy_if_present(
|
|
255
|
+
source: Mapping[str, Any], target: dict[str, Any], field_name: str
|
|
256
|
+
) -> None:
|
|
257
|
+
if source.get(field_name) is not None:
|
|
258
|
+
target[field_name] = source[field_name]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Responses object and output item builders."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def message_item(item_id: str, text: str, status: str) -> dict[str, Any]:
|
|
9
|
+
return {
|
|
10
|
+
"id": item_id,
|
|
11
|
+
"type": "message",
|
|
12
|
+
"status": status,
|
|
13
|
+
"role": "assistant",
|
|
14
|
+
"content": [{"type": "output_text", "text": text, "annotations": []}],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def reasoning_item(item_id: str, text: str, status: str) -> dict[str, Any]:
|
|
19
|
+
return {
|
|
20
|
+
"id": item_id,
|
|
21
|
+
"type": "reasoning",
|
|
22
|
+
"status": status,
|
|
23
|
+
"summary": [],
|
|
24
|
+
"content": [{"type": "reasoning_text", "text": text}],
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def encrypted_reasoning_item(
|
|
29
|
+
item_id: str, encrypted_content: str, status: str
|
|
30
|
+
) -> dict[str, Any]:
|
|
31
|
+
return {
|
|
32
|
+
"id": item_id,
|
|
33
|
+
"type": "reasoning",
|
|
34
|
+
"status": status,
|
|
35
|
+
"summary": [],
|
|
36
|
+
"encrypted_content": encrypted_content,
|
|
37
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Reasoning and thinking conversion helpers for OpenAI Responses."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .tools import optional_str
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def reasoning_text_from_item(item: Mapping[str, Any]) -> str | None:
|
|
12
|
+
content_parts = _text_parts_from_items(
|
|
13
|
+
item.get("content"), item_type="reasoning_text"
|
|
14
|
+
)
|
|
15
|
+
if content_parts:
|
|
16
|
+
return "\n".join(content_parts)
|
|
17
|
+
summary_parts = _text_parts_from_items(
|
|
18
|
+
item.get("summary"), item_type="summary_text"
|
|
19
|
+
)
|
|
20
|
+
if summary_parts:
|
|
21
|
+
return "\n".join(summary_parts)
|
|
22
|
+
return None
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def combine_reasoning(existing: str | None, addition: str | None) -> str | None:
|
|
26
|
+
if not addition:
|
|
27
|
+
return existing
|
|
28
|
+
if not existing:
|
|
29
|
+
return addition
|
|
30
|
+
return f"{existing}\n{addition}"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def responses_reasoning_to_thinking(value: Any) -> dict[str, Any] | None:
|
|
34
|
+
if not isinstance(value, Mapping):
|
|
35
|
+
return None
|
|
36
|
+
if value.get("effort") == "none":
|
|
37
|
+
return {"type": "disabled", "enabled": False}
|
|
38
|
+
if any(item is not None for item in value.values()):
|
|
39
|
+
return {"type": "enabled", "enabled": True}
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _text_parts_from_items(value: Any, *, item_type: str) -> list[str]:
|
|
44
|
+
if not isinstance(value, list):
|
|
45
|
+
return []
|
|
46
|
+
parts: list[str] = []
|
|
47
|
+
for item in value:
|
|
48
|
+
if isinstance(item, dict) and item.get("type") == item_type:
|
|
49
|
+
text = optional_str(item.get("text"))
|
|
50
|
+
if text:
|
|
51
|
+
parts.append(text)
|
|
52
|
+
return parts
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Translate Anthropic SSE streams into OpenAI Responses SSE streams."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import AsyncIterable, AsyncIterator, Mapping
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .anthropic_sse import iter_sse_events
|
|
9
|
+
from .stream_state import ResponsesStreamAssembler
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def iter_responses_sse_from_anthropic(
|
|
13
|
+
chunks: AsyncIterable[Any],
|
|
14
|
+
request: Mapping[str, Any],
|
|
15
|
+
) -> AsyncIterator[str]:
|
|
16
|
+
"""Yield Responses SSE events translated from an Anthropic SSE stream."""
|
|
17
|
+
|
|
18
|
+
assembler = ResponsesStreamAssembler(request)
|
|
19
|
+
async for event in iter_sse_events(chunks):
|
|
20
|
+
for chunk in assembler.process_anthropic_event(event):
|
|
21
|
+
yield chunk
|
|
22
|
+
if assembler.terminal:
|
|
23
|
+
return
|
|
24
|
+
for chunk in assembler.finish_if_needed():
|
|
25
|
+
yield chunk
|