alpiecode 10.5.5__tar.gz → 10.5.7__tar.gz

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 (55) hide show
  1. {alpiecode-10.5.5 → alpiecode-10.5.7}/PKG-INFO +1 -4
  2. {alpiecode-10.5.5 → alpiecode-10.5.7}/pyproject.toml +1 -7
  3. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode.egg-info/PKG-INFO +1 -4
  4. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode.egg-info/requires.txt +0 -4
  5. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/__init__.py +1 -1
  6. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/backends/openai_backend.py +135 -11
  7. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/tools.py +35 -55
  8. {alpiecode-10.5.5 → alpiecode-10.5.7}/README.md +0 -0
  9. {alpiecode-10.5.5 → alpiecode-10.5.7}/setup.cfg +0 -0
  10. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode/__init__.py +0 -0
  11. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode.egg-info/SOURCES.txt +0 -0
  12. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode.egg-info/dependency_links.txt +0 -0
  13. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode.egg-info/entry_points.txt +0 -0
  14. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/alpiecode.egg-info/top_level.txt +0 -0
  15. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/agent.py +0 -0
  16. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/assessment.py +0 -0
  17. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/backends/__init__.py +0 -0
  18. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/backends/base.py +0 -0
  19. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/backends/local_backend.py +0 -0
  20. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/cache.py +0 -0
  21. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/cli.py +0 -0
  22. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/client.py +0 -0
  23. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/compaction.py +0 -0
  24. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/config.py +0 -0
  25. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/context.py +0 -0
  26. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/discovery.py +0 -0
  27. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/doctor.py +0 -0
  28. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/dream_rsi.py +0 -0
  29. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/executor.py +0 -0
  30. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/extension/alpiecode.vsix +0 -0
  31. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/git_ops.py +0 -0
  32. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/github.py +0 -0
  33. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/guardian.py +0 -0
  34. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/guardrails.py +0 -0
  35. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/ipython_ext.py +0 -0
  36. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/local_model.py +0 -0
  37. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/media.py +0 -0
  38. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/memory.py +0 -0
  39. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/metrics.py +0 -0
  40. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/orchestrator.py +0 -0
  41. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/progress.py +0 -0
  42. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/prompt.py +0 -0
  43. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/rephraser.py +0 -0
  44. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/server.py +0 -0
  45. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/session.py +0 -0
  46. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/system1.py +0 -0
  47. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/updater.py +0 -0
  48. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/verifier.py +0 -0
  49. {alpiecode-10.5.5 → alpiecode-10.5.7}/src/codeagent/vscode_installer.py +0 -0
  50. {alpiecode-10.5.5 → alpiecode-10.5.7}/test/test_adaptive_resilience.py +0 -0
  51. {alpiecode-10.5.5 → alpiecode-10.5.7}/test/test_cli_ui.py +0 -0
  52. {alpiecode-10.5.5 → alpiecode-10.5.7}/test/test_context_and_trajectory.py +0 -0
  53. {alpiecode-10.5.5 → alpiecode-10.5.7}/test/test_dream_rsi.py +0 -0
  54. {alpiecode-10.5.5 → alpiecode-10.5.7}/test/test_task_assessment.py +0 -0
  55. {alpiecode-10.5.5 → alpiecode-10.5.7}/test/test_verifier.py +0 -0
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.5.5
3
+ Version: 10.5.7
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
7
7
  Requires-Dist: rich>=13.0
8
8
  Requires-Dist: click>=8.0
9
- Requires-Dist: openai>=1.0.0
10
9
  Requires-Dist: httpx[http2]>=0.24.0
11
10
  Requires-Dist: beautifulsoup4>=4.12.0
12
11
  Requires-Dist: yt-dlp>=2024.0.0
@@ -14,5 +13,3 @@ Requires-Dist: imageio-ffmpeg>=0.4.0
14
13
  Requires-Dist: fastapi>=0.100.0
15
14
  Requires-Dist: uvicorn>=0.20.0
16
15
  Requires-Dist: sse-starlette>=2.0.0
17
- Provides-Extra: search
18
- Requires-Dist: duckduckgo-search>=7.0.0; extra == "search"
@@ -4,14 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "alpiecode"
7
- version = "10.5.5"
7
+ version = "10.5.7"
8
8
  description = "AlpieCode"
9
9
  requires-python = ">=3.9"
10
10
  dependencies = [
11
11
  "huggingface_hub>=0.20.0",
12
12
  "rich>=13.0",
13
13
  "click>=8.0",
14
- "openai>=1.0.0",
15
14
  "httpx[http2]>=0.24.0",
16
15
  "beautifulsoup4>=4.12.0",
17
16
  "yt-dlp>=2024.0.0",
@@ -21,11 +20,6 @@ dependencies = [
21
20
  "sse-starlette>=2.0.0",
22
21
  ]
23
22
 
24
- [project.optional-dependencies]
25
- search = [
26
- "duckduckgo-search>=7.0.0",
27
- ]
28
-
29
23
  [project.scripts]
30
24
  alpiecode = "codeagent.cli:main"
31
25
  codeagent = "codeagent.cli:main"
@@ -1,12 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alpiecode
3
- Version: 10.5.5
3
+ Version: 10.5.7
4
4
  Summary: AlpieCode
5
5
  Requires-Python: >=3.9
6
6
  Requires-Dist: huggingface_hub>=0.20.0
7
7
  Requires-Dist: rich>=13.0
8
8
  Requires-Dist: click>=8.0
9
- Requires-Dist: openai>=1.0.0
10
9
  Requires-Dist: httpx[http2]>=0.24.0
11
10
  Requires-Dist: beautifulsoup4>=4.12.0
12
11
  Requires-Dist: yt-dlp>=2024.0.0
@@ -14,5 +13,3 @@ Requires-Dist: imageio-ffmpeg>=0.4.0
14
13
  Requires-Dist: fastapi>=0.100.0
15
14
  Requires-Dist: uvicorn>=0.20.0
16
15
  Requires-Dist: sse-starlette>=2.0.0
17
- Provides-Extra: search
18
- Requires-Dist: duckduckgo-search>=7.0.0; extra == "search"
@@ -1,7 +1,6 @@
1
1
  huggingface_hub>=0.20.0
2
2
  rich>=13.0
3
3
  click>=8.0
4
- openai>=1.0.0
5
4
  httpx[http2]>=0.24.0
6
5
  beautifulsoup4>=4.12.0
7
6
  yt-dlp>=2024.0.0
@@ -9,6 +8,3 @@ imageio-ffmpeg>=0.4.0
9
8
  fastapi>=0.100.0
10
9
  uvicorn>=0.20.0
11
10
  sse-starlette>=2.0.0
12
-
13
- [search]
14
- duckduckgo-search>=7.0.0
@@ -17,7 +17,7 @@ Supports:
17
17
  from pathlib import Path
18
18
  from typing import Optional, Any, Dict
19
19
 
20
- __version__ = "10.5.5"
20
+ __version__ = "10.5.7"
21
21
 
22
22
  def run(task: str, workdir: str = ".", reasoning_level: str = "thinking", **kwargs):
23
23
  """Run an autonomous coding task programmatically."""
@@ -7,11 +7,138 @@ import json
7
7
  import logging
8
8
  import re
9
9
  from typing import Any, List, Optional
10
- from openai import OpenAI, NotFoundError
10
+ from types import SimpleNamespace
11
+ from typing import Any, Dict, Generator, List, Optional
12
+ import httpx
11
13
 
12
14
  logger = logging.getLogger(__name__)
13
15
 
14
16
 
17
+ class NotFoundError(Exception):
18
+ """Raised when remote endpoint returns 404 Not Found."""
19
+ pass
20
+
21
+
22
+ class APIError(Exception):
23
+ """Raised when remote endpoint returns non-200 HTTP status."""
24
+ pass
25
+
26
+
27
+ class _Namespace(SimpleNamespace):
28
+ """SimpleNamespace subclass that safely returns None for missing attributes instead of raising AttributeError."""
29
+ def __getattr__(self, name: str) -> Any:
30
+ return None
31
+
32
+
33
+ def _dict_to_obj(d: Any) -> Any:
34
+ """Recursively wraps a dict into a _Namespace so attribute access works seamlessly."""
35
+ if isinstance(d, dict):
36
+ return _Namespace(**{k: _dict_to_obj(v) for k, v in d.items()})
37
+ if isinstance(d, list):
38
+ return [_dict_to_obj(item) for item in d]
39
+ return d
40
+
41
+
42
+ class _HTTPXChatCompletions:
43
+ def __init__(self, client: "_HTTPXClient"):
44
+ self._client = client
45
+
46
+ def create(self, **kwargs: Any) -> Any:
47
+ stream = kwargs.get("stream", False)
48
+ timeout = kwargs.get("timeout", 90.0)
49
+
50
+ payload: Dict[str, Any] = {
51
+ "model": kwargs.get("model"),
52
+ "messages": kwargs.get("messages", []),
53
+ "temperature": kwargs.get("temperature", 0.1),
54
+ "max_tokens": kwargs.get("max_tokens", 8192),
55
+ "stream": stream,
56
+ }
57
+ if "tools" in kwargs and kwargs["tools"]:
58
+ payload["tools"] = kwargs["tools"]
59
+ payload["tool_choice"] = kwargs.get("tool_choice", "auto")
60
+ if "extra_body" in kwargs and isinstance(kwargs["extra_body"], dict):
61
+ payload.update(kwargs["extra_body"])
62
+
63
+ headers = {
64
+ "Content-Type": "application/json",
65
+ "Authorization": f"Bearer {self._client.api_key}",
66
+ }
67
+
68
+ base = self._client.base_url.rstrip("/")
69
+ url = f"{base}/chat/completions"
70
+
71
+ if stream:
72
+ return self._stream_request(url, headers, payload, timeout)
73
+ return self._sync_request(url, headers, payload, timeout)
74
+
75
+ def _stream_request(self, url: str, headers: dict, payload: dict, timeout: float) -> Generator[Any, None, None]:
76
+ http_client = self._client.http_client
77
+ req = http_client.build_request("POST", url, headers=headers, json=payload, timeout=timeout)
78
+ resp = http_client.send(req, stream=True)
79
+ if resp.status_code == 404:
80
+ resp.close()
81
+ raise NotFoundError("Model or endpoint not found")
82
+ if resp.status_code != 200:
83
+ err_text = resp.read().decode("utf-8", errors="replace")
84
+ resp.close()
85
+ raise APIError(f"HTTP {resp.status_code}: {err_text}")
86
+
87
+ try:
88
+ for line in resp.iter_lines():
89
+ line = line.strip()
90
+ if not line or not line.startswith("data: "):
91
+ continue
92
+ data_str = line[6:].strip()
93
+ if data_str == "[DONE]":
94
+ break
95
+ try:
96
+ chunk_dict = json.loads(data_str)
97
+ except Exception:
98
+ continue
99
+ yield _dict_to_obj(chunk_dict)
100
+ finally:
101
+ resp.close()
102
+
103
+ def _sync_request(self, url: str, headers: dict, payload: dict, timeout: float) -> Any:
104
+ resp = self._client.http_client.post(url, headers=headers, json=payload, timeout=timeout)
105
+ if resp.status_code == 404:
106
+ raise NotFoundError("Model or endpoint not found")
107
+ if resp.status_code != 200:
108
+ raise APIError(f"HTTP {resp.status_code}: {resp.text}")
109
+ data = json.loads(resp.text)
110
+ return _dict_to_obj(data)
111
+
112
+
113
+ class _HTTPXModels:
114
+ def __init__(self, client: "_HTTPXClient"):
115
+ self._client = client
116
+
117
+ def list(self) -> Any:
118
+ base = self._client.base_url.rstrip("/")
119
+ url = f"{base}/models"
120
+ headers = {
121
+ "Authorization": f"Bearer {self._client.api_key}",
122
+ }
123
+ try:
124
+ resp = self._client.http_client.get(url, headers=headers, timeout=3.0)
125
+ if resp.status_code != 200:
126
+ return _Namespace(data=[])
127
+ data = json.loads(resp.text)
128
+ return _dict_to_obj(data)
129
+ except Exception:
130
+ return _Namespace(data=[])
131
+
132
+
133
+ class _HTTPXClient:
134
+ def __init__(self, base_url: str, api_key: str, http_client: Any = None):
135
+ self.base_url = base_url
136
+ self.api_key = api_key or "not-needed"
137
+ self.http_client = http_client if http_client is not None else get_shared_http_client()
138
+ self.chat = _Namespace(completions=_HTTPXChatCompletions(self))
139
+ self.models = _HTTPXModels(self)
140
+
141
+
15
142
  def _repair_and_parse_json_arguments(raw_args: str) -> dict:
16
143
  """
17
144
  Robustly parse tool call arguments from potentially partial or malformed streaming strings.
@@ -125,7 +252,7 @@ class OpenAIBackend:
125
252
 
126
253
  def __init__(self, cfg: Config):
127
254
  self._cfg = cfg
128
- self._client: Optional[OpenAI] = None
255
+ self._client: Optional[Any] = None
129
256
  self._resolved_model: Optional[str] = None
130
257
  self._max_model_len: Optional[int] = None
131
258
 
@@ -143,16 +270,16 @@ class OpenAIBackend:
143
270
  return self._max_model_len
144
271
  return 262_144
145
272
 
146
- def _ensure_client(self) -> OpenAI:
273
+ def _ensure_client(self) -> Any:
147
274
  if self._client is None:
148
- self._client = OpenAI(
275
+ self._client = _HTTPXClient(
149
276
  base_url=self._cfg.base_url,
150
277
  api_key=self._cfg.api_key or "not-needed",
151
278
  http_client=get_shared_http_client(),
152
279
  )
153
280
  return self._client
154
281
 
155
- def _get_available_model(self, client: OpenAI) -> str:
282
+ def _get_available_model(self, client: Any) -> str:
156
283
  """Auto-discover the served model name from /v1/models.
157
284
 
158
285
  Uses a dedicated short-timeout (3s) probe to avoid blocking the agent
@@ -163,18 +290,15 @@ class OpenAIBackend:
163
290
  return self._resolved_model
164
291
  try:
165
292
  import httpx
166
- # Use a dedicated short-timeout client for model discovery only.
167
- # The shared HTTP client has a 120s read timeout which causes
168
- # multi-minute hangs when /v1/models is slow or unresponsive.
169
- probe_client = OpenAI(
293
+ probe_client = _HTTPXClient(
170
294
  base_url=self._cfg.base_url,
171
295
  api_key=self._cfg.api_key or "not-needed",
172
296
  http_client=httpx.Client(timeout=httpx.Timeout(3.0, connect=2.0)),
173
297
  )
174
298
  models_list = probe_client.models.list()
175
- if models_list.data:
299
+ if hasattr(models_list, "data") and models_list.data:
176
300
  for m in models_list.data:
177
- if m.id == self._cfg.model or getattr(m, "root", None) == self._cfg.model:
301
+ if getattr(m, "id", None) == self._cfg.model or getattr(m, "root", None) == self._cfg.model:
178
302
  self._resolved_model = m.id
179
303
  self._max_model_len = getattr(m, "max_model_len", None)
180
304
  logger.info("Resolved model: %s (max_len=%s)", self._resolved_model, self._max_model_len)
@@ -760,14 +760,15 @@ def _apply_patch(workdir: Path, path: str, patch: str) -> str:
760
760
 
761
761
 
762
762
  def _web_search(query: str, num_results: int = 5) -> str:
763
- """Search the web using httpx (DuckDuckGo), with optional ddgs fallback."""
763
+ """Search the web using httpx and DuckDuckGo HTTP endpoints, parsed via BeautifulSoup."""
764
764
  import warnings
765
765
  import urllib.parse
766
766
  warnings.filterwarnings("ignore", category=RuntimeWarning)
767
767
 
768
- # 1. Primary: Pure-Python search using httpx (no Rust/primp native dependencies)
769
768
  try:
770
769
  import httpx
770
+ from bs4 import BeautifulSoup
771
+
771
772
  headers = {
772
773
  "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
773
774
  "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
@@ -779,65 +780,44 @@ def _web_search(query: str, num_results: int = 5) -> str:
779
780
  resp = client.post("https://lite.duckduckgo.com/lite/", data={"q": query}, headers=headers)
780
781
 
781
782
  if resp.status_code == 200 and resp.text:
782
- try:
783
- from bs4 import BeautifulSoup
784
- soup = BeautifulSoup(resp.text, "html.parser")
785
- results = []
786
- for item in soup.select(".result"):
787
- title_elem = item.select_one(".result__title a")
788
- snippet_elem = item.select_one(".result__snippet")
789
- if title_elem:
790
- title = title_elem.get_text(strip=True)
791
- href = title_elem.get("href", "")
783
+ soup = BeautifulSoup(resp.text, "html.parser")
784
+ results = []
785
+ for item in soup.select(".result"):
786
+ title_elem = item.select_one(".result__title a")
787
+ snippet_elem = item.select_one(".result__snippet")
788
+ if title_elem:
789
+ title = title_elem.get_text(strip=True)
790
+ href = title_elem.get("href", "")
791
+ if "uddg=" in href:
792
+ parsed = urllib.parse.parse_qs(urllib.parse.urlparse(href).query)
793
+ href = parsed.get("uddg", [href])[0]
794
+ snippet = snippet_elem.get_text(strip=True) if snippet_elem else ""
795
+ results.append({"title": title, "href": href, "body": snippet})
796
+ if len(results) >= min(num_results, 10):
797
+ break
798
+
799
+ if not results:
800
+ for row in soup.select("table tr"):
801
+ link = row.select_one("a.result-link")
802
+ snippet = row.select_one(".result-snippet")
803
+ if link:
804
+ title = link.get_text(strip=True)
805
+ href = link.get("href", "")
792
806
  if "uddg=" in href:
793
807
  parsed = urllib.parse.parse_qs(urllib.parse.urlparse(href).query)
794
808
  href = parsed.get("uddg", [href])[0]
795
- snippet = snippet_elem.get_text(strip=True) if snippet_elem else ""
796
- results.append({"title": title, "href": href, "body": snippet})
809
+ snip_text = snippet.get_text(strip=True) if snippet else ""
810
+ results.append({"title": title, "href": href, "body": snip_text})
797
811
  if len(results) >= min(num_results, 10):
798
812
  break
799
813
 
800
- if not results:
801
- for row in soup.select("table tr"):
802
- link = row.select_one("a.result-link")
803
- snippet = row.select_one(".result-snippet")
804
- if link:
805
- title = link.get_text(strip=True)
806
- href = link.get("href", "")
807
- if "uddg=" in href:
808
- parsed = urllib.parse.parse_qs(urllib.parse.urlparse(href).query)
809
- href = parsed.get("uddg", [href])[0]
810
- snip_text = snippet.get_text(strip=True) if snippet else ""
811
- results.append({"title": title, "href": href, "body": snip_text})
812
- if len(results) >= min(num_results, 10):
813
- break
814
-
815
- if results:
816
- formatted = []
817
- for i, r in enumerate(results, 1):
818
- formatted.append(f"{i}. **{r.get('title', 'No title')}**\n URL: {r.get('href', '')}\n {r.get('body', '')}")
819
- return "\n\n".join(formatted)
820
- except Exception:
821
- pass
822
- except Exception:
823
- pass
824
-
825
- # 2. Secondary fallback: duckduckgo_search if optionally installed
826
- try:
827
- try:
828
- from ddgs import DDGS
829
- except ImportError:
830
- from duckduckgo_search import DDGS
831
-
832
- with DDGS() as ddgs:
833
- results = list(ddgs.text(query, max_results=min(num_results, 10)))
834
- if results:
835
- formatted = []
836
- for i, r in enumerate(results, 1):
837
- formatted.append(f"{i}. **{r.get('title', 'No title')}**\n URL: {r.get('href', '')}\n {r.get('body', '')}")
838
- return "\n\n".join(formatted)
839
- except Exception:
840
- pass
814
+ if results:
815
+ formatted = []
816
+ for i, r in enumerate(results, 1):
817
+ formatted.append(f"{i}. **{r.get('title', 'No title')}**\n URL: {r.get('href', '')}\n {r.get('body', '')}")
818
+ return "\n\n".join(formatted)
819
+ except Exception as e:
820
+ return f"error during web search: {e}"
841
821
 
842
822
  return (
843
823
  f"No web results found for '{query}'. "
File without changes
File without changes