cade-cli 0.3.3__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 (44) hide show
  1. cade_cli-0.3.3.dist-info/METADATA +151 -0
  2. cade_cli-0.3.3.dist-info/RECORD +44 -0
  3. cade_cli-0.3.3.dist-info/WHEEL +4 -0
  4. cade_cli-0.3.3.dist-info/entry_points.txt +2 -0
  5. cadecoder/__init__.py +1 -0
  6. cadecoder/ai/__init__.py +6 -0
  7. cadecoder/ai/prompts.py +572 -0
  8. cadecoder/cli/__init__.py +0 -0
  9. cadecoder/cli/app.py +147 -0
  10. cadecoder/cli/auth.py +483 -0
  11. cadecoder/cli/commands/__init__.py +5 -0
  12. cadecoder/cli/commands/auth.py +143 -0
  13. cadecoder/cli/commands/chat.py +264 -0
  14. cadecoder/cli/commands/mcp.py +477 -0
  15. cadecoder/cli/commands/tools.py +226 -0
  16. cadecoder/core/__init__.py +12 -0
  17. cadecoder/core/config.py +380 -0
  18. cadecoder/core/constants.py +281 -0
  19. cadecoder/core/errors.py +145 -0
  20. cadecoder/core/logging.py +148 -0
  21. cadecoder/core/types.py +235 -0
  22. cadecoder/core/utils.py +279 -0
  23. cadecoder/execution/__init__.py +46 -0
  24. cadecoder/execution/context_window.py +521 -0
  25. cadecoder/execution/orchestrator.py +562 -0
  26. cadecoder/execution/parallel.py +287 -0
  27. cadecoder/providers/__init__.py +60 -0
  28. cadecoder/providers/base.py +294 -0
  29. cadecoder/providers/openai.py +251 -0
  30. cadecoder/storage/__init__.py +0 -0
  31. cadecoder/storage/threads.py +489 -0
  32. cadecoder/templates/login_failed.html +21 -0
  33. cadecoder/templates/login_success.html +21 -0
  34. cadecoder/templates/styles.css +87 -0
  35. cadecoder/tools/__init__.py +19 -0
  36. cadecoder/tools/builtin.py +644 -0
  37. cadecoder/tools/filesystem.py +315 -0
  38. cadecoder/tools/git.py +221 -0
  39. cadecoder/tools/manager.py +1635 -0
  40. cadecoder/ui/__init__.py +7 -0
  41. cadecoder/ui/display.py +338 -0
  42. cadecoder/ui/input.py +145 -0
  43. cadecoder/ui/session.py +455 -0
  44. cadecoder/ui/state.py +20 -0
@@ -0,0 +1,251 @@
1
+ """OpenAI provider adapter.
2
+
3
+ This module implements the Provider interface for OpenAI's API,
4
+ handling both standard completions and streaming responses.
5
+ """
6
+
7
+ import os
8
+ from collections.abc import AsyncIterator
9
+ from typing import Any, cast
10
+
11
+ from openai import AsyncOpenAI
12
+ from openai.types.chat import ChatCompletionChunk
13
+
14
+ from cadecoder.core.logging import log
15
+ from cadecoder.core.types import ExecutionEventType
16
+ from cadecoder.providers.base import (
17
+ Provider,
18
+ ProviderError,
19
+ ProviderRequest,
20
+ ProviderResponse,
21
+ ProviderType,
22
+ StreamEvent,
23
+ )
24
+
25
+
26
+ class OpenAIProvider(Provider):
27
+ """OpenAI provider implementation."""
28
+
29
+ def __init__(self, api_key: str | None = None):
30
+ """Initialize OpenAI provider.
31
+
32
+ Args:
33
+ api_key: OpenAI API key (uses environment if not provided)
34
+ """
35
+ self.api_key = api_key or os.environ.get("OPENAI_API_KEY")
36
+ if not self.api_key:
37
+ raise ProviderError("OpenAI API key not configured", ProviderType.OPENAI)
38
+
39
+ self.client = AsyncOpenAI(api_key=self.api_key)
40
+ self._supported_models = [
41
+ "gpt-4o",
42
+ "gpt-4o-mini",
43
+ "gpt-4-turbo",
44
+ "gpt-4",
45
+ "gpt-3.5-turbo",
46
+ "o1-preview",
47
+ "o1-mini",
48
+ ]
49
+
50
+ @property
51
+ def provider_type(self) -> ProviderType:
52
+ """Return the provider type."""
53
+ return ProviderType.OPENAI
54
+
55
+ @property
56
+ def supported_models(self) -> list[str]:
57
+ """Return list of supported models."""
58
+ return self._supported_models
59
+
60
+ async def complete(self, request: ProviderRequest) -> ProviderResponse:
61
+ """Create a completion using OpenAI.
62
+
63
+ Args:
64
+ request: The request to process
65
+
66
+ Returns:
67
+ The provider's response
68
+
69
+ Raises:
70
+ ProviderError: If the request fails
71
+ """
72
+ try:
73
+ # Build OpenAI request
74
+ openai_request = self._build_openai_request(request)
75
+
76
+ # Make API call
77
+ response = await self.client.chat.completions.create(**openai_request)
78
+
79
+ # Convert to unified response
80
+ choice = response.choices[0]
81
+
82
+ # Extract tool calls if present
83
+ tool_calls = None
84
+ if choice.message.tool_calls:
85
+ tool_calls = [
86
+ {
87
+ "id": tc.id,
88
+ "type": tc.type,
89
+ "function": {
90
+ "name": tc.function.name,
91
+ "arguments": tc.function.arguments,
92
+ },
93
+ }
94
+ for tc in choice.message.tool_calls
95
+ ]
96
+
97
+ return ProviderResponse(
98
+ content=choice.message.content,
99
+ tool_calls=tool_calls,
100
+ finish_reason=choice.finish_reason or "stop",
101
+ usage={
102
+ "prompt_tokens": response.usage.prompt_tokens if response.usage else 0,
103
+ "completion_tokens": response.usage.completion_tokens if response.usage else 0,
104
+ "total_tokens": response.usage.total_tokens if response.usage else 0,
105
+ },
106
+ model=response.model,
107
+ provider=ProviderType.OPENAI,
108
+ )
109
+
110
+ except Exception as e:
111
+ log.error(f"OpenAI completion failed: {e}")
112
+ raise ProviderError(
113
+ f"OpenAI completion failed: {str(e)}",
114
+ provider=ProviderType.OPENAI,
115
+ details={"error": str(e)},
116
+ )
117
+
118
+ async def stream(self, request: ProviderRequest) -> AsyncIterator[StreamEvent]:
119
+ """Stream a completion from OpenAI.
120
+
121
+ Args:
122
+ request: The request to process
123
+
124
+ Yields:
125
+ Stream events as they arrive
126
+
127
+ Raises:
128
+ ProviderError: If the request fails
129
+ """
130
+ try:
131
+ # Build OpenAI request with streaming
132
+ openai_request = self._build_openai_request(request)
133
+ openai_request["stream"] = True
134
+
135
+ # Stream response
136
+ stream = await self.client.chat.completions.create(**openai_request)
137
+
138
+ # Track state for tool calls
139
+ current_tool_calls: dict[int, dict[str, Any]] = {}
140
+
141
+ async for chunk in stream:
142
+ chunk = cast(ChatCompletionChunk, chunk)
143
+
144
+ # Handle content delta
145
+ if chunk.choices and chunk.choices[0].delta.content:
146
+ yield StreamEvent(
147
+ type=ExecutionEventType.CONTENT.value,
148
+ content=chunk.choices[0].delta.content,
149
+ )
150
+
151
+ # Handle tool calls
152
+ if chunk.choices and chunk.choices[0].delta.tool_calls:
153
+ for tc_delta in chunk.choices[0].delta.tool_calls:
154
+ idx = tc_delta.index
155
+ if idx not in current_tool_calls:
156
+ current_tool_calls[idx] = {
157
+ "id": tc_delta.id,
158
+ "type": "function",
159
+ "function": {"name": "", "arguments": ""},
160
+ }
161
+
162
+ # Update tool call
163
+ if tc_delta.function:
164
+ if tc_delta.function.name:
165
+ current_tool_calls[idx]["function"]["name"] = tc_delta.function.name
166
+ if tc_delta.function.arguments:
167
+ current_tool_calls[idx]["function"]["arguments"] += (
168
+ tc_delta.function.arguments
169
+ )
170
+
171
+ # Handle finish
172
+ if chunk.choices and chunk.choices[0].finish_reason:
173
+ # Emit any pending tool calls
174
+ for tool_call in current_tool_calls.values():
175
+ yield StreamEvent(
176
+ type=ExecutionEventType.TOOL_CALL.value,
177
+ content=None,
178
+ metadata={"tool_call": tool_call},
179
+ )
180
+
181
+ # Emit completion event
182
+ yield StreamEvent(
183
+ type=ExecutionEventType.COMPLETE.value,
184
+ content=None,
185
+ metadata={
186
+ "finish_reason": chunk.choices[0].finish_reason,
187
+ "model": chunk.model,
188
+ },
189
+ )
190
+
191
+ except Exception as e:
192
+ log.error(f"OpenAI streaming failed: {e}")
193
+ yield StreamEvent(
194
+ type="error",
195
+ content=str(e),
196
+ metadata={"provider": ProviderType.OPENAI},
197
+ )
198
+
199
+ def _build_openai_request(self, request: ProviderRequest) -> dict[str, Any]:
200
+ """Build OpenAI API request from unified request.
201
+
202
+ Args:
203
+ request: Unified request
204
+
205
+ Returns:
206
+ OpenAI API request dictionary
207
+ """
208
+ openai_request: dict[str, Any] = {
209
+ "model": request.model,
210
+ "messages": request.messages,
211
+ "temperature": request.temperature,
212
+ }
213
+
214
+ # Add optional parameters
215
+ if request.max_tokens:
216
+ openai_request["max_tokens"] = request.max_tokens
217
+
218
+ if request.tools:
219
+ openai_request["tools"] = request.tools
220
+ log.info(f"OpenAI request includes {len(request.tools)} tools")
221
+ # Log first few tool names for debugging
222
+ tool_names = [t.get("function", {}).get("name", "unknown") for t in request.tools[:5]]
223
+ log.debug(f"First few tools: {tool_names}")
224
+
225
+ if request.tool_choice:
226
+ openai_request["tool_choice"] = request.tool_choice
227
+
228
+ # Add any provider-specific metadata
229
+ if "openai" in request.metadata:
230
+ openai_request.update(request.metadata["openai"])
231
+
232
+ return openai_request
233
+
234
+ def supports_feature(self, feature: str) -> bool:
235
+ """Check if OpenAI supports a feature.
236
+
237
+ Args:
238
+ feature: Feature name
239
+
240
+ Returns:
241
+ True if supported
242
+ """
243
+ supported_features = {
244
+ "streaming": True,
245
+ "tools": True,
246
+ "tool_choice": True,
247
+ "vision": True,
248
+ "json_mode": True,
249
+ "reasoning": False, # Not yet
250
+ }
251
+ return supported_features.get(feature, False)
File without changes