nexcoder 0.1.4__tar.gz → 0.1.5__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.
- {nexcoder-0.1.4 → nexcoder-0.1.5}/PKG-INFO +1 -1
- {nexcoder-0.1.4 → nexcoder-0.1.5}/pyproject.toml +1 -1
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/__init__.py +3 -1
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/api_client.py +42 -11
- {nexcoder-0.1.4 → nexcoder-0.1.5}/.github/workflows/ci.yml +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/.github/workflows/publish.yml +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/.gitignore +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/CHANGELOG.md +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/LICENSE +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/README.md +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/agent.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/cli.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/config.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/context.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/exceptions.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/indexer/__init__.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/indexer/index.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/indexer/parser.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/indexer/scanner.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/memory/__init__.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/memory/decisions.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/memory/errors.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/memory/project.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/planner.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/py.typed +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/reviewer.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/safety.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/test_runner.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/tools/__init__.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/tools/file_ops.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/tools/git_ops.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/tools/search.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/src/nex/tools/shell.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/conftest.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_agent.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_chat.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_cli.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_context.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_errors.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_indexer.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_memory.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_rate_limiter.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_safety.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_test_runner.py +0 -0
- {nexcoder-0.1.4 → nexcoder-0.1.5}/tests/test_tools.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nexcoder
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.5
|
|
4
4
|
Summary: The coding agent that remembers — AI coding assistant with persistent memory and error learning.
|
|
5
5
|
Project-URL: Homepage, https://github.com/nex-ai/nex-ai
|
|
6
6
|
Project-URL: Repository, https://github.com/nex-ai/nex-ai
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nexcoder"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "The coding agent that remembers — AI coding assistant with persistent memory and error learning."
|
|
9
9
|
readme = {file = "README.md", content-type = "text/markdown"}
|
|
10
10
|
license = "MIT"
|
|
@@ -122,6 +122,30 @@ class RateLimiter:
|
|
|
122
122
|
return
|
|
123
123
|
|
|
124
124
|
|
|
125
|
+
def _extract_retry_after(exc: Exception, default: float = 60.0) -> float:
|
|
126
|
+
"""Extract retry-after seconds from an Anthropic API error.
|
|
127
|
+
|
|
128
|
+
Inspects the exception's response headers for a ``retry-after`` value.
|
|
129
|
+
Falls back to *default* if the header is missing or unparseable.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
exc: The exception raised by the Anthropic SDK.
|
|
133
|
+
default: Fallback wait time in seconds.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Number of seconds to wait before retrying.
|
|
137
|
+
"""
|
|
138
|
+
resp = getattr(exc, "response", None)
|
|
139
|
+
if resp is not None:
|
|
140
|
+
header = getattr(resp, "headers", {}).get("retry-after")
|
|
141
|
+
if header:
|
|
142
|
+
try:
|
|
143
|
+
return max(float(header), 1.0)
|
|
144
|
+
except (ValueError, TypeError):
|
|
145
|
+
pass
|
|
146
|
+
return default
|
|
147
|
+
|
|
148
|
+
|
|
125
149
|
class AnthropicClient:
|
|
126
150
|
"""Async wrapper around the Anthropic API.
|
|
127
151
|
|
|
@@ -140,7 +164,7 @@ class AnthropicClient:
|
|
|
140
164
|
self,
|
|
141
165
|
api_key: str,
|
|
142
166
|
default_model: str = "claude-sonnet-4-20250514",
|
|
143
|
-
max_retries: int =
|
|
167
|
+
max_retries: int = 5,
|
|
144
168
|
) -> None:
|
|
145
169
|
"""Initialize the Anthropic client.
|
|
146
170
|
|
|
@@ -196,7 +220,9 @@ class AnthropicClient:
|
|
|
196
220
|
kwargs["tools"] = tools
|
|
197
221
|
|
|
198
222
|
last_error: Exception | None = None
|
|
199
|
-
|
|
223
|
+
# 429 rate limits need more retries with longer waits than server errors
|
|
224
|
+
max_attempts = self._max_retries + 1
|
|
225
|
+
for attempt in range(max_attempts):
|
|
200
226
|
try:
|
|
201
227
|
response = await self._client.messages.create(**kwargs)
|
|
202
228
|
|
|
@@ -230,16 +256,21 @@ class AnthropicClient:
|
|
|
230
256
|
except Exception as exc:
|
|
231
257
|
last_error = exc
|
|
232
258
|
status_code = getattr(exc, "status_code", None)
|
|
259
|
+
is_rate_limit = status_code == 429
|
|
260
|
+
is_retryable = status_code in (500, 502, 503, 529)
|
|
261
|
+
|
|
262
|
+
if (is_rate_limit or is_retryable) and attempt < max_attempts - 1:
|
|
263
|
+
if is_rate_limit:
|
|
264
|
+
# Rate limits: extract retry-after from response headers,
|
|
265
|
+
# or default to 60s (the full rate-limit window).
|
|
266
|
+
wait = _extract_retry_after(exc, default=60.0)
|
|
267
|
+
else:
|
|
268
|
+
# Server errors: short exponential backoff
|
|
269
|
+
wait = float(2**attempt)
|
|
233
270
|
|
|
234
|
-
# Retry on rate limit or server errors
|
|
235
|
-
if status_code in (429, 500, 502, 503, 529) and attempt < self._max_retries:
|
|
236
|
-
wait = 2**attempt
|
|
237
|
-
retry_after = getattr(exc, "retry_after", None)
|
|
238
|
-
if retry_after:
|
|
239
|
-
wait = max(wait, float(retry_after))
|
|
240
271
|
console.print(
|
|
241
|
-
f"[yellow]API error {status_code}, retrying in {wait}s "
|
|
242
|
-
f"(attempt {attempt + 1}/{
|
|
272
|
+
f"[yellow]API error {status_code}, retrying in {wait:.0f}s "
|
|
273
|
+
f"(attempt {attempt + 1}/{max_attempts - 1})...[/yellow]"
|
|
243
274
|
)
|
|
244
275
|
await asyncio.sleep(wait)
|
|
245
276
|
continue
|
|
@@ -250,7 +281,7 @@ class AnthropicClient:
|
|
|
250
281
|
) from exc
|
|
251
282
|
|
|
252
283
|
raise APIError(
|
|
253
|
-
f"Failed after {
|
|
284
|
+
f"Failed after {max_attempts - 1} retries: {last_error}",
|
|
254
285
|
)
|
|
255
286
|
|
|
256
287
|
async def close(self) -> None:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|