superqode 0.1.5__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.
- superqode/__init__.py +33 -0
- superqode/acp/__init__.py +23 -0
- superqode/acp/client.py +913 -0
- superqode/acp/permission_screen.py +457 -0
- superqode/acp/types.py +480 -0
- superqode/acp_discovery.py +856 -0
- superqode/agent/__init__.py +22 -0
- superqode/agent/edit_strategies.py +334 -0
- superqode/agent/loop.py +892 -0
- superqode/agent/qe_report_templates.py +39 -0
- superqode/agent/system_prompts.py +353 -0
- superqode/agent_output.py +721 -0
- superqode/agent_stream.py +953 -0
- superqode/agents/__init__.py +59 -0
- superqode/agents/acp_registry.py +305 -0
- superqode/agents/client.py +249 -0
- superqode/agents/data/augmentcode.com.toml +51 -0
- superqode/agents/data/cagent.dev.toml +51 -0
- superqode/agents/data/claude.com.toml +60 -0
- superqode/agents/data/codeassistant.dev.toml +51 -0
- superqode/agents/data/codex.openai.com.toml +57 -0
- superqode/agents/data/fastagent.ai.toml +66 -0
- superqode/agents/data/geminicli.com.toml +77 -0
- superqode/agents/data/goose.block.xyz.toml +54 -0
- superqode/agents/data/junie.jetbrains.com.toml +56 -0
- superqode/agents/data/kimi.moonshot.cn.toml +57 -0
- superqode/agents/data/llmlingagent.dev.toml +51 -0
- superqode/agents/data/molt.bot.toml +49 -0
- superqode/agents/data/opencode.ai.toml +60 -0
- superqode/agents/data/stakpak.dev.toml +51 -0
- superqode/agents/data/vtcode.dev.toml +51 -0
- superqode/agents/discovery.py +266 -0
- superqode/agents/messaging.py +160 -0
- superqode/agents/persona.py +166 -0
- superqode/agents/registry.py +421 -0
- superqode/agents/schema.py +72 -0
- superqode/agents/unified.py +367 -0
- superqode/app/__init__.py +111 -0
- superqode/app/constants.py +314 -0
- superqode/app/css.py +366 -0
- superqode/app/models.py +118 -0
- superqode/app/suggester.py +125 -0
- superqode/app/widgets.py +1591 -0
- superqode/app_enhanced.py +399 -0
- superqode/app_main.py +17187 -0
- superqode/approval.py +312 -0
- superqode/atomic.py +296 -0
- superqode/commands/__init__.py +1 -0
- superqode/commands/acp.py +965 -0
- superqode/commands/agents.py +180 -0
- superqode/commands/auth.py +278 -0
- superqode/commands/config.py +374 -0
- superqode/commands/init.py +826 -0
- superqode/commands/providers.py +819 -0
- superqode/commands/qe.py +1145 -0
- superqode/commands/roles.py +380 -0
- superqode/commands/serve.py +172 -0
- superqode/commands/suggestions.py +127 -0
- superqode/commands/superqe.py +460 -0
- superqode/config/__init__.py +51 -0
- superqode/config/loader.py +812 -0
- superqode/config/schema.py +498 -0
- superqode/core/__init__.py +111 -0
- superqode/core/roles.py +281 -0
- superqode/danger.py +386 -0
- superqode/data/superqode-template.yaml +1522 -0
- superqode/design_system.py +1080 -0
- superqode/dialogs/__init__.py +6 -0
- superqode/dialogs/base.py +39 -0
- superqode/dialogs/model.py +130 -0
- superqode/dialogs/provider.py +870 -0
- superqode/diff_view.py +919 -0
- superqode/enterprise.py +21 -0
- superqode/evaluation/__init__.py +25 -0
- superqode/evaluation/adapters.py +93 -0
- superqode/evaluation/behaviors.py +89 -0
- superqode/evaluation/engine.py +209 -0
- superqode/evaluation/scenarios.py +96 -0
- superqode/execution/__init__.py +36 -0
- superqode/execution/linter.py +538 -0
- superqode/execution/modes.py +347 -0
- superqode/execution/resolver.py +283 -0
- superqode/execution/runner.py +642 -0
- superqode/file_explorer.py +811 -0
- superqode/file_viewer.py +471 -0
- superqode/flash.py +183 -0
- superqode/guidance/__init__.py +58 -0
- superqode/guidance/config.py +203 -0
- superqode/guidance/prompts.py +71 -0
- superqode/harness/__init__.py +54 -0
- superqode/harness/accelerator.py +291 -0
- superqode/harness/config.py +319 -0
- superqode/harness/validator.py +147 -0
- superqode/history.py +279 -0
- superqode/integrations/superopt_runner.py +124 -0
- superqode/logging/__init__.py +49 -0
- superqode/logging/adapters.py +219 -0
- superqode/logging/formatter.py +923 -0
- superqode/logging/integration.py +341 -0
- superqode/logging/sinks.py +170 -0
- superqode/logging/unified_log.py +417 -0
- superqode/lsp/__init__.py +26 -0
- superqode/lsp/client.py +544 -0
- superqode/main.py +1069 -0
- superqode/mcp/__init__.py +89 -0
- superqode/mcp/auth_storage.py +380 -0
- superqode/mcp/client.py +1236 -0
- superqode/mcp/config.py +319 -0
- superqode/mcp/integration.py +337 -0
- superqode/mcp/oauth.py +436 -0
- superqode/mcp/oauth_callback.py +385 -0
- superqode/mcp/types.py +290 -0
- superqode/memory/__init__.py +31 -0
- superqode/memory/feedback.py +342 -0
- superqode/memory/store.py +522 -0
- superqode/notifications.py +369 -0
- superqode/optimization/__init__.py +5 -0
- superqode/optimization/config.py +33 -0
- superqode/permissions/__init__.py +25 -0
- superqode/permissions/rules.py +488 -0
- superqode/plan.py +323 -0
- superqode/providers/__init__.py +33 -0
- superqode/providers/gateway/__init__.py +165 -0
- superqode/providers/gateway/base.py +228 -0
- superqode/providers/gateway/litellm_gateway.py +1170 -0
- superqode/providers/gateway/openresponses_gateway.py +436 -0
- superqode/providers/health.py +297 -0
- superqode/providers/huggingface/__init__.py +74 -0
- superqode/providers/huggingface/downloader.py +472 -0
- superqode/providers/huggingface/endpoints.py +442 -0
- superqode/providers/huggingface/hub.py +531 -0
- superqode/providers/huggingface/inference.py +394 -0
- superqode/providers/huggingface/transformers_runner.py +516 -0
- superqode/providers/local/__init__.py +100 -0
- superqode/providers/local/base.py +438 -0
- superqode/providers/local/discovery.py +418 -0
- superqode/providers/local/lmstudio.py +256 -0
- superqode/providers/local/mlx.py +457 -0
- superqode/providers/local/ollama.py +486 -0
- superqode/providers/local/sglang.py +268 -0
- superqode/providers/local/tgi.py +260 -0
- superqode/providers/local/tool_support.py +477 -0
- superqode/providers/local/vllm.py +258 -0
- superqode/providers/manager.py +1338 -0
- superqode/providers/models.py +1016 -0
- superqode/providers/models_dev.py +578 -0
- superqode/providers/openresponses/__init__.py +87 -0
- superqode/providers/openresponses/converters/__init__.py +17 -0
- superqode/providers/openresponses/converters/messages.py +343 -0
- superqode/providers/openresponses/converters/tools.py +268 -0
- superqode/providers/openresponses/schema/__init__.py +56 -0
- superqode/providers/openresponses/schema/models.py +585 -0
- superqode/providers/openresponses/streaming/__init__.py +5 -0
- superqode/providers/openresponses/streaming/parser.py +338 -0
- superqode/providers/openresponses/tools/__init__.py +21 -0
- superqode/providers/openresponses/tools/apply_patch.py +352 -0
- superqode/providers/openresponses/tools/code_interpreter.py +290 -0
- superqode/providers/openresponses/tools/file_search.py +333 -0
- superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
- superqode/providers/registry.py +716 -0
- superqode/providers/usage.py +332 -0
- superqode/pure_mode.py +384 -0
- superqode/qr/__init__.py +23 -0
- superqode/qr/dashboard.py +781 -0
- superqode/qr/generator.py +1018 -0
- superqode/qr/templates.py +135 -0
- superqode/safety/__init__.py +41 -0
- superqode/safety/sandbox.py +413 -0
- superqode/safety/warnings.py +256 -0
- superqode/server/__init__.py +33 -0
- superqode/server/lsp_server.py +775 -0
- superqode/server/web.py +250 -0
- superqode/session/__init__.py +25 -0
- superqode/session/persistence.py +580 -0
- superqode/session/sharing.py +477 -0
- superqode/session.py +475 -0
- superqode/sidebar.py +2991 -0
- superqode/stream_view.py +648 -0
- superqode/styles/__init__.py +3 -0
- superqode/superqe/__init__.py +184 -0
- superqode/superqe/acp_runner.py +1064 -0
- superqode/superqe/constitution/__init__.py +62 -0
- superqode/superqe/constitution/evaluator.py +308 -0
- superqode/superqe/constitution/loader.py +432 -0
- superqode/superqe/constitution/schema.py +250 -0
- superqode/superqe/events.py +591 -0
- superqode/superqe/frameworks/__init__.py +65 -0
- superqode/superqe/frameworks/base.py +234 -0
- superqode/superqe/frameworks/e2e.py +263 -0
- superqode/superqe/frameworks/executor.py +237 -0
- superqode/superqe/frameworks/javascript.py +409 -0
- superqode/superqe/frameworks/python.py +373 -0
- superqode/superqe/frameworks/registry.py +92 -0
- superqode/superqe/mcp_tools/__init__.py +47 -0
- superqode/superqe/mcp_tools/core_tools.py +418 -0
- superqode/superqe/mcp_tools/registry.py +230 -0
- superqode/superqe/mcp_tools/testing_tools.py +167 -0
- superqode/superqe/noise.py +89 -0
- superqode/superqe/orchestrator.py +778 -0
- superqode/superqe/roles.py +609 -0
- superqode/superqe/session.py +713 -0
- superqode/superqe/skills/__init__.py +57 -0
- superqode/superqe/skills/base.py +106 -0
- superqode/superqe/skills/core_skills.py +899 -0
- superqode/superqe/skills/registry.py +90 -0
- superqode/superqe/verifier.py +101 -0
- superqode/superqe_cli.py +76 -0
- superqode/tool_call.py +358 -0
- superqode/tools/__init__.py +93 -0
- superqode/tools/agent_tools.py +496 -0
- superqode/tools/base.py +324 -0
- superqode/tools/batch_tool.py +133 -0
- superqode/tools/diagnostics.py +311 -0
- superqode/tools/edit_tools.py +653 -0
- superqode/tools/enhanced_base.py +515 -0
- superqode/tools/file_tools.py +269 -0
- superqode/tools/file_tracking.py +45 -0
- superqode/tools/lsp_tools.py +610 -0
- superqode/tools/network_tools.py +350 -0
- superqode/tools/permissions.py +400 -0
- superqode/tools/question_tool.py +324 -0
- superqode/tools/search_tools.py +598 -0
- superqode/tools/shell_tools.py +259 -0
- superqode/tools/todo_tools.py +121 -0
- superqode/tools/validation.py +80 -0
- superqode/tools/web_tools.py +639 -0
- superqode/tui.py +1152 -0
- superqode/tui_integration.py +875 -0
- superqode/tui_widgets/__init__.py +27 -0
- superqode/tui_widgets/widgets/__init__.py +18 -0
- superqode/tui_widgets/widgets/progress.py +185 -0
- superqode/tui_widgets/widgets/tool_display.py +188 -0
- superqode/undo_manager.py +574 -0
- superqode/utils/__init__.py +5 -0
- superqode/utils/error_handling.py +323 -0
- superqode/utils/fuzzy.py +257 -0
- superqode/widgets/__init__.py +477 -0
- superqode/widgets/agent_collab.py +390 -0
- superqode/widgets/agent_store.py +936 -0
- superqode/widgets/agent_switcher.py +395 -0
- superqode/widgets/animation_manager.py +284 -0
- superqode/widgets/code_context.py +356 -0
- superqode/widgets/command_palette.py +412 -0
- superqode/widgets/connection_status.py +537 -0
- superqode/widgets/conversation_history.py +470 -0
- superqode/widgets/diff_indicator.py +155 -0
- superqode/widgets/enhanced_status_bar.py +385 -0
- superqode/widgets/enhanced_toast.py +476 -0
- superqode/widgets/file_browser.py +809 -0
- superqode/widgets/file_reference.py +585 -0
- superqode/widgets/issue_timeline.py +340 -0
- superqode/widgets/leader_key.py +264 -0
- superqode/widgets/mode_switcher.py +445 -0
- superqode/widgets/model_picker.py +234 -0
- superqode/widgets/permission_preview.py +1205 -0
- superqode/widgets/prompt.py +358 -0
- superqode/widgets/provider_connect.py +725 -0
- superqode/widgets/pty_shell.py +587 -0
- superqode/widgets/qe_dashboard.py +321 -0
- superqode/widgets/resizable_sidebar.py +377 -0
- superqode/widgets/response_changes.py +218 -0
- superqode/widgets/response_display.py +528 -0
- superqode/widgets/rich_tool_display.py +613 -0
- superqode/widgets/sidebar_panels.py +1180 -0
- superqode/widgets/slash_complete.py +356 -0
- superqode/widgets/split_view.py +612 -0
- superqode/widgets/status_bar.py +273 -0
- superqode/widgets/superqode_display.py +786 -0
- superqode/widgets/thinking_display.py +815 -0
- superqode/widgets/throbber.py +87 -0
- superqode/widgets/toast.py +206 -0
- superqode/widgets/unified_output.py +1073 -0
- superqode/workspace/__init__.py +75 -0
- superqode/workspace/artifacts.py +472 -0
- superqode/workspace/coordinator.py +353 -0
- superqode/workspace/diff_tracker.py +429 -0
- superqode/workspace/git_guard.py +373 -0
- superqode/workspace/git_snapshot.py +526 -0
- superqode/workspace/manager.py +750 -0
- superqode/workspace/snapshot.py +357 -0
- superqode/workspace/watcher.py +535 -0
- superqode/workspace/worktree.py +440 -0
- superqode-0.1.5.dist-info/METADATA +204 -0
- superqode-0.1.5.dist-info/RECORD +288 -0
- superqode-0.1.5.dist-info/WHEEL +5 -0
- superqode-0.1.5.dist-info/entry_points.txt +3 -0
- superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
- superqode-0.1.5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Open Responses Gateway Implementation.
|
|
3
|
+
|
|
4
|
+
Gateway for the Open Responses specification, providing a unified API
|
|
5
|
+
across multiple AI providers with support for:
|
|
6
|
+
- Streaming with 45+ event types
|
|
7
|
+
- Reasoning/thinking content
|
|
8
|
+
- Built-in tools (apply_patch, code_interpreter, file_search)
|
|
9
|
+
- Message ↔ Item conversion
|
|
10
|
+
|
|
11
|
+
This gateway can be used with:
|
|
12
|
+
- Local providers (Ollama, vLLM, etc.) that implement Open Responses
|
|
13
|
+
- Cloud providers with Open Responses-compatible endpoints
|
|
14
|
+
- Custom Open Responses servers
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
gateway = OpenResponsesGateway(base_url="http://localhost:11434")
|
|
18
|
+
response = await gateway.chat_completion(messages, model="qwen3:8b")
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import uuid
|
|
25
|
+
from typing import Any, AsyncIterator, Dict, List, Optional
|
|
26
|
+
|
|
27
|
+
import aiohttp
|
|
28
|
+
|
|
29
|
+
from .base import (
|
|
30
|
+
Cost,
|
|
31
|
+
GatewayError,
|
|
32
|
+
GatewayInterface,
|
|
33
|
+
GatewayResponse,
|
|
34
|
+
InvalidRequestError,
|
|
35
|
+
Message,
|
|
36
|
+
ModelNotFoundError,
|
|
37
|
+
RateLimitError,
|
|
38
|
+
StreamChunk,
|
|
39
|
+
ToolDefinition,
|
|
40
|
+
Usage,
|
|
41
|
+
)
|
|
42
|
+
from ..openresponses.converters.messages import (
|
|
43
|
+
messages_to_items,
|
|
44
|
+
convert_output_to_message,
|
|
45
|
+
extract_reasoning_from_output,
|
|
46
|
+
)
|
|
47
|
+
from ..openresponses.converters.tools import convert_tools_to_openresponses
|
|
48
|
+
from ..openresponses.streaming.parser import StreamingEventParser
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class OpenResponsesGateway(GatewayInterface):
|
|
52
|
+
"""
|
|
53
|
+
Open Responses-based gateway.
|
|
54
|
+
|
|
55
|
+
Implements GatewayInterface using the Open Responses specification
|
|
56
|
+
for unified API access to various AI providers.
|
|
57
|
+
|
|
58
|
+
Features:
|
|
59
|
+
- Automatic message ↔ item conversion
|
|
60
|
+
- Full streaming support with 45+ event types
|
|
61
|
+
- Reasoning/thinking content extraction
|
|
62
|
+
- Built-in tool support (apply_patch, code_interpreter, etc.)
|
|
63
|
+
- Configurable reasoning effort and truncation
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
base_url: Base URL for the Open Responses API
|
|
67
|
+
api_key: Optional API key for authentication
|
|
68
|
+
reasoning_effort: Reasoning effort level ("low", "medium", "high")
|
|
69
|
+
truncation: Truncation strategy ("auto", "disabled")
|
|
70
|
+
timeout: Request timeout in seconds
|
|
71
|
+
track_costs: Whether to track costs
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
def __init__(
|
|
75
|
+
self,
|
|
76
|
+
base_url: str = "http://localhost:11434",
|
|
77
|
+
api_key: Optional[str] = None,
|
|
78
|
+
reasoning_effort: str = "medium",
|
|
79
|
+
truncation: str = "auto",
|
|
80
|
+
timeout: float = 300.0,
|
|
81
|
+
track_costs: bool = True,
|
|
82
|
+
):
|
|
83
|
+
self.base_url = base_url.rstrip("/")
|
|
84
|
+
self.api_key = api_key
|
|
85
|
+
self.reasoning_effort = reasoning_effort
|
|
86
|
+
self.truncation = truncation
|
|
87
|
+
self.timeout = timeout
|
|
88
|
+
self.track_costs = track_costs
|
|
89
|
+
|
|
90
|
+
def get_model_string(self, provider: str, model: str) -> str:
|
|
91
|
+
"""Get the model string for Open Responses.
|
|
92
|
+
|
|
93
|
+
Open Responses typically uses plain model names without provider prefix.
|
|
94
|
+
"""
|
|
95
|
+
# Remove provider prefix if present
|
|
96
|
+
if "/" in model:
|
|
97
|
+
return model.split("/", 1)[1]
|
|
98
|
+
return model
|
|
99
|
+
|
|
100
|
+
async def chat_completion(
|
|
101
|
+
self,
|
|
102
|
+
messages: List[Message],
|
|
103
|
+
model: str,
|
|
104
|
+
provider: Optional[str] = None,
|
|
105
|
+
temperature: Optional[float] = None,
|
|
106
|
+
max_tokens: Optional[int] = None,
|
|
107
|
+
tools: Optional[List[ToolDefinition]] = None,
|
|
108
|
+
tool_choice: Optional[str] = None,
|
|
109
|
+
**kwargs,
|
|
110
|
+
) -> GatewayResponse:
|
|
111
|
+
"""Make a chat completion request via Open Responses.
|
|
112
|
+
|
|
113
|
+
Converts messages to items, sends request, and converts response back.
|
|
114
|
+
"""
|
|
115
|
+
# Convert messages to Open Responses items
|
|
116
|
+
items = messages_to_items(messages)
|
|
117
|
+
|
|
118
|
+
# Build request
|
|
119
|
+
request: Dict[str, Any] = {
|
|
120
|
+
"model": self.get_model_string(provider or "", model),
|
|
121
|
+
"input": items,
|
|
122
|
+
"stream": False,
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# Add reasoning config
|
|
126
|
+
if self.reasoning_effort:
|
|
127
|
+
request["reasoning"] = {"effort": self.reasoning_effort}
|
|
128
|
+
|
|
129
|
+
# Add truncation
|
|
130
|
+
if self.truncation:
|
|
131
|
+
request["truncation"] = self.truncation
|
|
132
|
+
|
|
133
|
+
# Optional parameters
|
|
134
|
+
if temperature is not None:
|
|
135
|
+
request["temperature"] = temperature
|
|
136
|
+
if max_tokens is not None:
|
|
137
|
+
request["max_output_tokens"] = max_tokens
|
|
138
|
+
if tools:
|
|
139
|
+
request["tools"] = convert_tools_to_openresponses(tools)
|
|
140
|
+
if tool_choice:
|
|
141
|
+
request["tool_choice"] = tool_choice
|
|
142
|
+
|
|
143
|
+
# Add any extra kwargs
|
|
144
|
+
for key, value in kwargs.items():
|
|
145
|
+
if key not in request and value is not None:
|
|
146
|
+
request[key] = value
|
|
147
|
+
|
|
148
|
+
# Make request
|
|
149
|
+
try:
|
|
150
|
+
response_data = await self._post("/v1/responses", request)
|
|
151
|
+
except Exception as e:
|
|
152
|
+
self._handle_error(e, provider or "openresponses", model)
|
|
153
|
+
raise # Re-raise if _handle_error doesn't raise
|
|
154
|
+
|
|
155
|
+
# Parse response
|
|
156
|
+
return self._parse_response(response_data, provider, model)
|
|
157
|
+
|
|
158
|
+
async def stream_completion(
|
|
159
|
+
self,
|
|
160
|
+
messages: List[Message],
|
|
161
|
+
model: str,
|
|
162
|
+
provider: Optional[str] = None,
|
|
163
|
+
temperature: Optional[float] = None,
|
|
164
|
+
max_tokens: Optional[int] = None,
|
|
165
|
+
tools: Optional[List[ToolDefinition]] = None,
|
|
166
|
+
tool_choice: Optional[str] = None,
|
|
167
|
+
**kwargs,
|
|
168
|
+
) -> AsyncIterator[StreamChunk]:
|
|
169
|
+
"""Make a streaming chat completion request via Open Responses.
|
|
170
|
+
|
|
171
|
+
Handles 45+ streaming event types and yields StreamChunk objects.
|
|
172
|
+
"""
|
|
173
|
+
# Convert messages to Open Responses items
|
|
174
|
+
items = messages_to_items(messages)
|
|
175
|
+
|
|
176
|
+
# Build request
|
|
177
|
+
request: Dict[str, Any] = {
|
|
178
|
+
"model": self.get_model_string(provider or "", model),
|
|
179
|
+
"input": items,
|
|
180
|
+
"stream": True,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Add reasoning config
|
|
184
|
+
if self.reasoning_effort:
|
|
185
|
+
request["reasoning"] = {"effort": self.reasoning_effort}
|
|
186
|
+
|
|
187
|
+
# Add truncation
|
|
188
|
+
if self.truncation:
|
|
189
|
+
request["truncation"] = self.truncation
|
|
190
|
+
|
|
191
|
+
# Optional parameters
|
|
192
|
+
if temperature is not None:
|
|
193
|
+
request["temperature"] = temperature
|
|
194
|
+
if max_tokens is not None:
|
|
195
|
+
request["max_output_tokens"] = max_tokens
|
|
196
|
+
if tools:
|
|
197
|
+
request["tools"] = convert_tools_to_openresponses(tools)
|
|
198
|
+
if tool_choice:
|
|
199
|
+
request["tool_choice"] = tool_choice
|
|
200
|
+
|
|
201
|
+
# Add any extra kwargs
|
|
202
|
+
for key, value in kwargs.items():
|
|
203
|
+
if key not in request and value is not None:
|
|
204
|
+
request[key] = value
|
|
205
|
+
|
|
206
|
+
# Stream response
|
|
207
|
+
parser = StreamingEventParser()
|
|
208
|
+
|
|
209
|
+
try:
|
|
210
|
+
async for chunk in self._stream_sse("/v1/responses", request):
|
|
211
|
+
parsed_chunk = parser.parse_line(chunk)
|
|
212
|
+
if parsed_chunk:
|
|
213
|
+
yield parsed_chunk
|
|
214
|
+
except Exception as e:
|
|
215
|
+
self._handle_error(e, provider or "openresponses", model)
|
|
216
|
+
raise
|
|
217
|
+
|
|
218
|
+
async def test_connection(
|
|
219
|
+
self,
|
|
220
|
+
provider: str,
|
|
221
|
+
model: Optional[str] = None,
|
|
222
|
+
) -> Dict[str, Any]:
|
|
223
|
+
"""Test connection to the Open Responses API."""
|
|
224
|
+
try:
|
|
225
|
+
# Make a minimal test request
|
|
226
|
+
response = await self.chat_completion(
|
|
227
|
+
messages=[Message(role="user", content="Hi")],
|
|
228
|
+
model=model or "default",
|
|
229
|
+
provider=provider,
|
|
230
|
+
max_tokens=5,
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
"success": True,
|
|
235
|
+
"provider": provider,
|
|
236
|
+
"model": model,
|
|
237
|
+
"response_model": response.model,
|
|
238
|
+
"usage": {
|
|
239
|
+
"prompt_tokens": response.usage.prompt_tokens if response.usage else 0,
|
|
240
|
+
"completion_tokens": response.usage.completion_tokens if response.usage else 0,
|
|
241
|
+
},
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
except GatewayError as e:
|
|
245
|
+
return {
|
|
246
|
+
"success": False,
|
|
247
|
+
"provider": provider,
|
|
248
|
+
"model": model,
|
|
249
|
+
"error": str(e),
|
|
250
|
+
"error_type": e.error_type,
|
|
251
|
+
}
|
|
252
|
+
except Exception as e:
|
|
253
|
+
return {
|
|
254
|
+
"success": False,
|
|
255
|
+
"provider": provider,
|
|
256
|
+
"model": model,
|
|
257
|
+
"error": str(e),
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async def _post(
|
|
261
|
+
self,
|
|
262
|
+
path: str,
|
|
263
|
+
data: Dict[str, Any],
|
|
264
|
+
) -> Dict[str, Any]:
|
|
265
|
+
"""Make a POST request to the Open Responses API."""
|
|
266
|
+
url = f"{self.base_url}{path}"
|
|
267
|
+
headers = {
|
|
268
|
+
"Content-Type": "application/json",
|
|
269
|
+
}
|
|
270
|
+
if self.api_key:
|
|
271
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
272
|
+
|
|
273
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
274
|
+
|
|
275
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
276
|
+
async with session.post(url, json=data, headers=headers) as response:
|
|
277
|
+
if response.status >= 400:
|
|
278
|
+
error_text = await response.text()
|
|
279
|
+
self._handle_http_error(response.status, error_text)
|
|
280
|
+
|
|
281
|
+
return await response.json()
|
|
282
|
+
|
|
283
|
+
async def _stream_sse(
|
|
284
|
+
self,
|
|
285
|
+
path: str,
|
|
286
|
+
data: Dict[str, Any],
|
|
287
|
+
) -> AsyncIterator[str]:
|
|
288
|
+
"""Stream SSE events from the Open Responses API."""
|
|
289
|
+
url = f"{self.base_url}{path}"
|
|
290
|
+
headers = {
|
|
291
|
+
"Content-Type": "application/json",
|
|
292
|
+
"Accept": "text/event-stream",
|
|
293
|
+
}
|
|
294
|
+
if self.api_key:
|
|
295
|
+
headers["Authorization"] = f"Bearer {self.api_key}"
|
|
296
|
+
|
|
297
|
+
timeout = aiohttp.ClientTimeout(total=self.timeout)
|
|
298
|
+
|
|
299
|
+
async with aiohttp.ClientSession(timeout=timeout) as session:
|
|
300
|
+
async with session.post(url, json=data, headers=headers) as response:
|
|
301
|
+
if response.status >= 400:
|
|
302
|
+
error_text = await response.text()
|
|
303
|
+
self._handle_http_error(response.status, error_text)
|
|
304
|
+
|
|
305
|
+
async for line in response.content:
|
|
306
|
+
line_str = line.decode("utf-8").strip()
|
|
307
|
+
if line_str:
|
|
308
|
+
yield line_str
|
|
309
|
+
|
|
310
|
+
def _parse_response(
|
|
311
|
+
self,
|
|
312
|
+
data: Dict[str, Any],
|
|
313
|
+
provider: Optional[str],
|
|
314
|
+
model: str,
|
|
315
|
+
) -> GatewayResponse:
|
|
316
|
+
"""Parse an Open Responses response to GatewayResponse."""
|
|
317
|
+
# Extract output items
|
|
318
|
+
output = data.get("output", [])
|
|
319
|
+
|
|
320
|
+
# Convert output to message
|
|
321
|
+
message = convert_output_to_message(output)
|
|
322
|
+
|
|
323
|
+
# Extract reasoning/thinking
|
|
324
|
+
thinking_content = extract_reasoning_from_output(output)
|
|
325
|
+
|
|
326
|
+
# Extract usage
|
|
327
|
+
usage_data = data.get("usage", {})
|
|
328
|
+
usage = None
|
|
329
|
+
if usage_data:
|
|
330
|
+
usage = Usage(
|
|
331
|
+
prompt_tokens=usage_data.get("input_tokens", 0),
|
|
332
|
+
completion_tokens=usage_data.get("output_tokens", 0),
|
|
333
|
+
total_tokens=usage_data.get("total_tokens", 0),
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Extract thinking tokens if available
|
|
337
|
+
thinking_tokens = None
|
|
338
|
+
output_details = usage_data.get("output_tokens_details", {})
|
|
339
|
+
if output_details:
|
|
340
|
+
thinking_tokens = output_details.get("reasoning_tokens")
|
|
341
|
+
|
|
342
|
+
# Determine finish reason
|
|
343
|
+
status = data.get("status", "completed")
|
|
344
|
+
finish_reason = "stop" if status == "completed" else status
|
|
345
|
+
|
|
346
|
+
# Check for incomplete
|
|
347
|
+
incomplete_details = data.get("incomplete_details")
|
|
348
|
+
if incomplete_details:
|
|
349
|
+
finish_reason = f"incomplete:{incomplete_details.get('reason', 'unknown')}"
|
|
350
|
+
|
|
351
|
+
return GatewayResponse(
|
|
352
|
+
content=message.content,
|
|
353
|
+
role="assistant",
|
|
354
|
+
finish_reason=finish_reason,
|
|
355
|
+
usage=usage,
|
|
356
|
+
model=data.get("model", model),
|
|
357
|
+
provider=provider or "openresponses",
|
|
358
|
+
tool_calls=message.tool_calls,
|
|
359
|
+
raw_response=data,
|
|
360
|
+
thinking_content=thinking_content,
|
|
361
|
+
thinking_tokens=thinking_tokens,
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def _handle_http_error(self, status: int, error_text: str) -> None:
|
|
365
|
+
"""Handle HTTP error responses."""
|
|
366
|
+
try:
|
|
367
|
+
error_data = json.loads(error_text)
|
|
368
|
+
error = error_data.get("error", {})
|
|
369
|
+
message = error.get("message", error_text)
|
|
370
|
+
code = error.get("code", "")
|
|
371
|
+
except json.JSONDecodeError:
|
|
372
|
+
message = error_text
|
|
373
|
+
code = ""
|
|
374
|
+
|
|
375
|
+
if status == 401:
|
|
376
|
+
raise GatewayError(
|
|
377
|
+
f"Authentication failed: {message}",
|
|
378
|
+
error_type="authentication",
|
|
379
|
+
status_code=status,
|
|
380
|
+
)
|
|
381
|
+
elif status == 429:
|
|
382
|
+
raise RateLimitError(
|
|
383
|
+
f"Rate limit exceeded: {message}",
|
|
384
|
+
error_type="rate_limit",
|
|
385
|
+
status_code=status,
|
|
386
|
+
)
|
|
387
|
+
elif status == 404:
|
|
388
|
+
raise ModelNotFoundError(
|
|
389
|
+
f"Not found: {message}",
|
|
390
|
+
error_type="model_not_found",
|
|
391
|
+
status_code=status,
|
|
392
|
+
)
|
|
393
|
+
elif status == 400:
|
|
394
|
+
raise InvalidRequestError(
|
|
395
|
+
f"Invalid request: {message}",
|
|
396
|
+
error_type="invalid_request",
|
|
397
|
+
status_code=status,
|
|
398
|
+
)
|
|
399
|
+
else:
|
|
400
|
+
raise GatewayError(
|
|
401
|
+
f"HTTP {status}: {message}",
|
|
402
|
+
error_type="http_error",
|
|
403
|
+
status_code=status,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
def _handle_error(
|
|
407
|
+
self,
|
|
408
|
+
error: Exception,
|
|
409
|
+
provider: str,
|
|
410
|
+
model: str,
|
|
411
|
+
) -> None:
|
|
412
|
+
"""Handle and convert errors to GatewayError."""
|
|
413
|
+
if isinstance(error, GatewayError):
|
|
414
|
+
raise error
|
|
415
|
+
|
|
416
|
+
error_msg = str(error)
|
|
417
|
+
|
|
418
|
+
if "Connection refused" in error_msg:
|
|
419
|
+
raise GatewayError(
|
|
420
|
+
f"Cannot connect to Open Responses server at {self.base_url}.\n\n"
|
|
421
|
+
f"Please ensure the server is running.",
|
|
422
|
+
provider=provider,
|
|
423
|
+
model=model,
|
|
424
|
+
)
|
|
425
|
+
elif "timeout" in error_msg.lower():
|
|
426
|
+
raise GatewayError(
|
|
427
|
+
f"Request timed out. The server may be busy or the model may need more time.",
|
|
428
|
+
provider=provider,
|
|
429
|
+
model=model,
|
|
430
|
+
)
|
|
431
|
+
else:
|
|
432
|
+
raise GatewayError(
|
|
433
|
+
f"Error calling Open Responses API: {error_msg}",
|
|
434
|
+
provider=provider,
|
|
435
|
+
model=model,
|
|
436
|
+
)
|