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.
Files changed (288) hide show
  1. superqode/__init__.py +33 -0
  2. superqode/acp/__init__.py +23 -0
  3. superqode/acp/client.py +913 -0
  4. superqode/acp/permission_screen.py +457 -0
  5. superqode/acp/types.py +480 -0
  6. superqode/acp_discovery.py +856 -0
  7. superqode/agent/__init__.py +22 -0
  8. superqode/agent/edit_strategies.py +334 -0
  9. superqode/agent/loop.py +892 -0
  10. superqode/agent/qe_report_templates.py +39 -0
  11. superqode/agent/system_prompts.py +353 -0
  12. superqode/agent_output.py +721 -0
  13. superqode/agent_stream.py +953 -0
  14. superqode/agents/__init__.py +59 -0
  15. superqode/agents/acp_registry.py +305 -0
  16. superqode/agents/client.py +249 -0
  17. superqode/agents/data/augmentcode.com.toml +51 -0
  18. superqode/agents/data/cagent.dev.toml +51 -0
  19. superqode/agents/data/claude.com.toml +60 -0
  20. superqode/agents/data/codeassistant.dev.toml +51 -0
  21. superqode/agents/data/codex.openai.com.toml +57 -0
  22. superqode/agents/data/fastagent.ai.toml +66 -0
  23. superqode/agents/data/geminicli.com.toml +77 -0
  24. superqode/agents/data/goose.block.xyz.toml +54 -0
  25. superqode/agents/data/junie.jetbrains.com.toml +56 -0
  26. superqode/agents/data/kimi.moonshot.cn.toml +57 -0
  27. superqode/agents/data/llmlingagent.dev.toml +51 -0
  28. superqode/agents/data/molt.bot.toml +49 -0
  29. superqode/agents/data/opencode.ai.toml +60 -0
  30. superqode/agents/data/stakpak.dev.toml +51 -0
  31. superqode/agents/data/vtcode.dev.toml +51 -0
  32. superqode/agents/discovery.py +266 -0
  33. superqode/agents/messaging.py +160 -0
  34. superqode/agents/persona.py +166 -0
  35. superqode/agents/registry.py +421 -0
  36. superqode/agents/schema.py +72 -0
  37. superqode/agents/unified.py +367 -0
  38. superqode/app/__init__.py +111 -0
  39. superqode/app/constants.py +314 -0
  40. superqode/app/css.py +366 -0
  41. superqode/app/models.py +118 -0
  42. superqode/app/suggester.py +125 -0
  43. superqode/app/widgets.py +1591 -0
  44. superqode/app_enhanced.py +399 -0
  45. superqode/app_main.py +17187 -0
  46. superqode/approval.py +312 -0
  47. superqode/atomic.py +296 -0
  48. superqode/commands/__init__.py +1 -0
  49. superqode/commands/acp.py +965 -0
  50. superqode/commands/agents.py +180 -0
  51. superqode/commands/auth.py +278 -0
  52. superqode/commands/config.py +374 -0
  53. superqode/commands/init.py +826 -0
  54. superqode/commands/providers.py +819 -0
  55. superqode/commands/qe.py +1145 -0
  56. superqode/commands/roles.py +380 -0
  57. superqode/commands/serve.py +172 -0
  58. superqode/commands/suggestions.py +127 -0
  59. superqode/commands/superqe.py +460 -0
  60. superqode/config/__init__.py +51 -0
  61. superqode/config/loader.py +812 -0
  62. superqode/config/schema.py +498 -0
  63. superqode/core/__init__.py +111 -0
  64. superqode/core/roles.py +281 -0
  65. superqode/danger.py +386 -0
  66. superqode/data/superqode-template.yaml +1522 -0
  67. superqode/design_system.py +1080 -0
  68. superqode/dialogs/__init__.py +6 -0
  69. superqode/dialogs/base.py +39 -0
  70. superqode/dialogs/model.py +130 -0
  71. superqode/dialogs/provider.py +870 -0
  72. superqode/diff_view.py +919 -0
  73. superqode/enterprise.py +21 -0
  74. superqode/evaluation/__init__.py +25 -0
  75. superqode/evaluation/adapters.py +93 -0
  76. superqode/evaluation/behaviors.py +89 -0
  77. superqode/evaluation/engine.py +209 -0
  78. superqode/evaluation/scenarios.py +96 -0
  79. superqode/execution/__init__.py +36 -0
  80. superqode/execution/linter.py +538 -0
  81. superqode/execution/modes.py +347 -0
  82. superqode/execution/resolver.py +283 -0
  83. superqode/execution/runner.py +642 -0
  84. superqode/file_explorer.py +811 -0
  85. superqode/file_viewer.py +471 -0
  86. superqode/flash.py +183 -0
  87. superqode/guidance/__init__.py +58 -0
  88. superqode/guidance/config.py +203 -0
  89. superqode/guidance/prompts.py +71 -0
  90. superqode/harness/__init__.py +54 -0
  91. superqode/harness/accelerator.py +291 -0
  92. superqode/harness/config.py +319 -0
  93. superqode/harness/validator.py +147 -0
  94. superqode/history.py +279 -0
  95. superqode/integrations/superopt_runner.py +124 -0
  96. superqode/logging/__init__.py +49 -0
  97. superqode/logging/adapters.py +219 -0
  98. superqode/logging/formatter.py +923 -0
  99. superqode/logging/integration.py +341 -0
  100. superqode/logging/sinks.py +170 -0
  101. superqode/logging/unified_log.py +417 -0
  102. superqode/lsp/__init__.py +26 -0
  103. superqode/lsp/client.py +544 -0
  104. superqode/main.py +1069 -0
  105. superqode/mcp/__init__.py +89 -0
  106. superqode/mcp/auth_storage.py +380 -0
  107. superqode/mcp/client.py +1236 -0
  108. superqode/mcp/config.py +319 -0
  109. superqode/mcp/integration.py +337 -0
  110. superqode/mcp/oauth.py +436 -0
  111. superqode/mcp/oauth_callback.py +385 -0
  112. superqode/mcp/types.py +290 -0
  113. superqode/memory/__init__.py +31 -0
  114. superqode/memory/feedback.py +342 -0
  115. superqode/memory/store.py +522 -0
  116. superqode/notifications.py +369 -0
  117. superqode/optimization/__init__.py +5 -0
  118. superqode/optimization/config.py +33 -0
  119. superqode/permissions/__init__.py +25 -0
  120. superqode/permissions/rules.py +488 -0
  121. superqode/plan.py +323 -0
  122. superqode/providers/__init__.py +33 -0
  123. superqode/providers/gateway/__init__.py +165 -0
  124. superqode/providers/gateway/base.py +228 -0
  125. superqode/providers/gateway/litellm_gateway.py +1170 -0
  126. superqode/providers/gateway/openresponses_gateway.py +436 -0
  127. superqode/providers/health.py +297 -0
  128. superqode/providers/huggingface/__init__.py +74 -0
  129. superqode/providers/huggingface/downloader.py +472 -0
  130. superqode/providers/huggingface/endpoints.py +442 -0
  131. superqode/providers/huggingface/hub.py +531 -0
  132. superqode/providers/huggingface/inference.py +394 -0
  133. superqode/providers/huggingface/transformers_runner.py +516 -0
  134. superqode/providers/local/__init__.py +100 -0
  135. superqode/providers/local/base.py +438 -0
  136. superqode/providers/local/discovery.py +418 -0
  137. superqode/providers/local/lmstudio.py +256 -0
  138. superqode/providers/local/mlx.py +457 -0
  139. superqode/providers/local/ollama.py +486 -0
  140. superqode/providers/local/sglang.py +268 -0
  141. superqode/providers/local/tgi.py +260 -0
  142. superqode/providers/local/tool_support.py +477 -0
  143. superqode/providers/local/vllm.py +258 -0
  144. superqode/providers/manager.py +1338 -0
  145. superqode/providers/models.py +1016 -0
  146. superqode/providers/models_dev.py +578 -0
  147. superqode/providers/openresponses/__init__.py +87 -0
  148. superqode/providers/openresponses/converters/__init__.py +17 -0
  149. superqode/providers/openresponses/converters/messages.py +343 -0
  150. superqode/providers/openresponses/converters/tools.py +268 -0
  151. superqode/providers/openresponses/schema/__init__.py +56 -0
  152. superqode/providers/openresponses/schema/models.py +585 -0
  153. superqode/providers/openresponses/streaming/__init__.py +5 -0
  154. superqode/providers/openresponses/streaming/parser.py +338 -0
  155. superqode/providers/openresponses/tools/__init__.py +21 -0
  156. superqode/providers/openresponses/tools/apply_patch.py +352 -0
  157. superqode/providers/openresponses/tools/code_interpreter.py +290 -0
  158. superqode/providers/openresponses/tools/file_search.py +333 -0
  159. superqode/providers/openresponses/tools/mcp_adapter.py +252 -0
  160. superqode/providers/registry.py +716 -0
  161. superqode/providers/usage.py +332 -0
  162. superqode/pure_mode.py +384 -0
  163. superqode/qr/__init__.py +23 -0
  164. superqode/qr/dashboard.py +781 -0
  165. superqode/qr/generator.py +1018 -0
  166. superqode/qr/templates.py +135 -0
  167. superqode/safety/__init__.py +41 -0
  168. superqode/safety/sandbox.py +413 -0
  169. superqode/safety/warnings.py +256 -0
  170. superqode/server/__init__.py +33 -0
  171. superqode/server/lsp_server.py +775 -0
  172. superqode/server/web.py +250 -0
  173. superqode/session/__init__.py +25 -0
  174. superqode/session/persistence.py +580 -0
  175. superqode/session/sharing.py +477 -0
  176. superqode/session.py +475 -0
  177. superqode/sidebar.py +2991 -0
  178. superqode/stream_view.py +648 -0
  179. superqode/styles/__init__.py +3 -0
  180. superqode/superqe/__init__.py +184 -0
  181. superqode/superqe/acp_runner.py +1064 -0
  182. superqode/superqe/constitution/__init__.py +62 -0
  183. superqode/superqe/constitution/evaluator.py +308 -0
  184. superqode/superqe/constitution/loader.py +432 -0
  185. superqode/superqe/constitution/schema.py +250 -0
  186. superqode/superqe/events.py +591 -0
  187. superqode/superqe/frameworks/__init__.py +65 -0
  188. superqode/superqe/frameworks/base.py +234 -0
  189. superqode/superqe/frameworks/e2e.py +263 -0
  190. superqode/superqe/frameworks/executor.py +237 -0
  191. superqode/superqe/frameworks/javascript.py +409 -0
  192. superqode/superqe/frameworks/python.py +373 -0
  193. superqode/superqe/frameworks/registry.py +92 -0
  194. superqode/superqe/mcp_tools/__init__.py +47 -0
  195. superqode/superqe/mcp_tools/core_tools.py +418 -0
  196. superqode/superqe/mcp_tools/registry.py +230 -0
  197. superqode/superqe/mcp_tools/testing_tools.py +167 -0
  198. superqode/superqe/noise.py +89 -0
  199. superqode/superqe/orchestrator.py +778 -0
  200. superqode/superqe/roles.py +609 -0
  201. superqode/superqe/session.py +713 -0
  202. superqode/superqe/skills/__init__.py +57 -0
  203. superqode/superqe/skills/base.py +106 -0
  204. superqode/superqe/skills/core_skills.py +899 -0
  205. superqode/superqe/skills/registry.py +90 -0
  206. superqode/superqe/verifier.py +101 -0
  207. superqode/superqe_cli.py +76 -0
  208. superqode/tool_call.py +358 -0
  209. superqode/tools/__init__.py +93 -0
  210. superqode/tools/agent_tools.py +496 -0
  211. superqode/tools/base.py +324 -0
  212. superqode/tools/batch_tool.py +133 -0
  213. superqode/tools/diagnostics.py +311 -0
  214. superqode/tools/edit_tools.py +653 -0
  215. superqode/tools/enhanced_base.py +515 -0
  216. superqode/tools/file_tools.py +269 -0
  217. superqode/tools/file_tracking.py +45 -0
  218. superqode/tools/lsp_tools.py +610 -0
  219. superqode/tools/network_tools.py +350 -0
  220. superqode/tools/permissions.py +400 -0
  221. superqode/tools/question_tool.py +324 -0
  222. superqode/tools/search_tools.py +598 -0
  223. superqode/tools/shell_tools.py +259 -0
  224. superqode/tools/todo_tools.py +121 -0
  225. superqode/tools/validation.py +80 -0
  226. superqode/tools/web_tools.py +639 -0
  227. superqode/tui.py +1152 -0
  228. superqode/tui_integration.py +875 -0
  229. superqode/tui_widgets/__init__.py +27 -0
  230. superqode/tui_widgets/widgets/__init__.py +18 -0
  231. superqode/tui_widgets/widgets/progress.py +185 -0
  232. superqode/tui_widgets/widgets/tool_display.py +188 -0
  233. superqode/undo_manager.py +574 -0
  234. superqode/utils/__init__.py +5 -0
  235. superqode/utils/error_handling.py +323 -0
  236. superqode/utils/fuzzy.py +257 -0
  237. superqode/widgets/__init__.py +477 -0
  238. superqode/widgets/agent_collab.py +390 -0
  239. superqode/widgets/agent_store.py +936 -0
  240. superqode/widgets/agent_switcher.py +395 -0
  241. superqode/widgets/animation_manager.py +284 -0
  242. superqode/widgets/code_context.py +356 -0
  243. superqode/widgets/command_palette.py +412 -0
  244. superqode/widgets/connection_status.py +537 -0
  245. superqode/widgets/conversation_history.py +470 -0
  246. superqode/widgets/diff_indicator.py +155 -0
  247. superqode/widgets/enhanced_status_bar.py +385 -0
  248. superqode/widgets/enhanced_toast.py +476 -0
  249. superqode/widgets/file_browser.py +809 -0
  250. superqode/widgets/file_reference.py +585 -0
  251. superqode/widgets/issue_timeline.py +340 -0
  252. superqode/widgets/leader_key.py +264 -0
  253. superqode/widgets/mode_switcher.py +445 -0
  254. superqode/widgets/model_picker.py +234 -0
  255. superqode/widgets/permission_preview.py +1205 -0
  256. superqode/widgets/prompt.py +358 -0
  257. superqode/widgets/provider_connect.py +725 -0
  258. superqode/widgets/pty_shell.py +587 -0
  259. superqode/widgets/qe_dashboard.py +321 -0
  260. superqode/widgets/resizable_sidebar.py +377 -0
  261. superqode/widgets/response_changes.py +218 -0
  262. superqode/widgets/response_display.py +528 -0
  263. superqode/widgets/rich_tool_display.py +613 -0
  264. superqode/widgets/sidebar_panels.py +1180 -0
  265. superqode/widgets/slash_complete.py +356 -0
  266. superqode/widgets/split_view.py +612 -0
  267. superqode/widgets/status_bar.py +273 -0
  268. superqode/widgets/superqode_display.py +786 -0
  269. superqode/widgets/thinking_display.py +815 -0
  270. superqode/widgets/throbber.py +87 -0
  271. superqode/widgets/toast.py +206 -0
  272. superqode/widgets/unified_output.py +1073 -0
  273. superqode/workspace/__init__.py +75 -0
  274. superqode/workspace/artifacts.py +472 -0
  275. superqode/workspace/coordinator.py +353 -0
  276. superqode/workspace/diff_tracker.py +429 -0
  277. superqode/workspace/git_guard.py +373 -0
  278. superqode/workspace/git_snapshot.py +526 -0
  279. superqode/workspace/manager.py +750 -0
  280. superqode/workspace/snapshot.py +357 -0
  281. superqode/workspace/watcher.py +535 -0
  282. superqode/workspace/worktree.py +440 -0
  283. superqode-0.1.5.dist-info/METADATA +204 -0
  284. superqode-0.1.5.dist-info/RECORD +288 -0
  285. superqode-0.1.5.dist-info/WHEEL +5 -0
  286. superqode-0.1.5.dist-info/entry_points.txt +3 -0
  287. superqode-0.1.5.dist-info/licenses/LICENSE +648 -0
  288. 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
+ )