calvyn-code 0.14.3 → 0.14.5
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.
- package/agent/auxiliary_client.py +58 -37
- package/bin/calvyn.js +11 -2
- package/cli.py +7 -1
- package/hermes_cli/inventory.py +25 -21
- package/package.json +1 -1
- package/run_agent.py +26 -6
|
@@ -2136,7 +2136,7 @@ def _is_rate_limit_error(exc: Exception) -> bool:
|
|
|
2136
2136
|
return False
|
|
2137
2137
|
|
|
2138
2138
|
|
|
2139
|
-
def _is_connection_error(exc: Exception) -> bool:
|
|
2139
|
+
def _is_connection_error(exc: Exception) -> bool:
|
|
2140
2140
|
"""Detect connection/network errors that warrant provider fallback.
|
|
2141
2141
|
|
|
2142
2142
|
Returns True for errors indicating the provider endpoint is unreachable
|
|
@@ -2170,8 +2170,21 @@ def _is_connection_error(exc: Exception) -> bool:
|
|
|
2170
2170
|
"remoteprotocolerror",
|
|
2171
2171
|
"localprotocolerror",
|
|
2172
2172
|
)):
|
|
2173
|
-
return True
|
|
2174
|
-
return False
|
|
2173
|
+
return True
|
|
2174
|
+
return False
|
|
2175
|
+
|
|
2176
|
+
|
|
2177
|
+
def _is_not_found_error(exc: Exception) -> bool:
|
|
2178
|
+
"""Detect provider 404s that should fall back for auto auxiliary tasks."""
|
|
2179
|
+
status = getattr(exc, "status_code", None)
|
|
2180
|
+
if status == 404:
|
|
2181
|
+
return True
|
|
2182
|
+
err_lower = str(exc).lower()
|
|
2183
|
+
return (
|
|
2184
|
+
"http 404" in err_lower
|
|
2185
|
+
or "404 not found" in err_lower
|
|
2186
|
+
or "status code: 404" in err_lower
|
|
2187
|
+
)
|
|
2175
2188
|
|
|
2176
2189
|
|
|
2177
2190
|
def _is_auth_error(exc: Exception) -> bool:
|
|
@@ -4374,12 +4387,13 @@ def call_llm(
|
|
|
4374
4387
|
# payment / auth chains below using the temperature-stripped
|
|
4375
4388
|
# kwargs. Re-raise only if the retry hit something those
|
|
4376
4389
|
# chains won't handle.
|
|
4377
|
-
if not (
|
|
4378
|
-
_is_payment_error(retry_err)
|
|
4379
|
-
or _is_connection_error(retry_err)
|
|
4380
|
-
or
|
|
4381
|
-
or
|
|
4382
|
-
or "
|
|
4390
|
+
if not (
|
|
4391
|
+
_is_payment_error(retry_err)
|
|
4392
|
+
or _is_connection_error(retry_err)
|
|
4393
|
+
or _is_not_found_error(retry_err)
|
|
4394
|
+
or _is_auth_error(retry_err)
|
|
4395
|
+
or "max_tokens" in retry_err_str
|
|
4396
|
+
or "unsupported_parameter" in retry_err_str
|
|
4383
4397
|
):
|
|
4384
4398
|
raise
|
|
4385
4399
|
first_err = retry_err
|
|
@@ -4409,9 +4423,9 @@ def call_llm(
|
|
|
4409
4423
|
except Exception as retry_err:
|
|
4410
4424
|
# If the max_tokens retry also hits a payment or connection
|
|
4411
4425
|
# error, fall through to the fallback chain below.
|
|
4412
|
-
if not (_is_payment_error(retry_err) or _is_connection_error(retry_err) or _is_rate_limit_error(retry_err)):
|
|
4413
|
-
raise
|
|
4414
|
-
first_err = retry_err
|
|
4426
|
+
if not (_is_payment_error(retry_err) or _is_connection_error(retry_err) or _is_not_found_error(retry_err) or _is_rate_limit_error(retry_err)):
|
|
4427
|
+
raise
|
|
4428
|
+
first_err = retry_err
|
|
4415
4429
|
|
|
4416
4430
|
# ── Nous auth refresh parity with main agent ──────────────────
|
|
4417
4431
|
client_is_nous = (
|
|
@@ -4515,10 +4529,11 @@ def call_llm(
|
|
|
4515
4529
|
# back to an alternative provider instead of exhausting retries
|
|
4516
4530
|
# against the same rate-limited endpoint.
|
|
4517
4531
|
should_fallback = (
|
|
4518
|
-
_is_payment_error(first_err)
|
|
4519
|
-
or _is_connection_error(first_err)
|
|
4520
|
-
or
|
|
4521
|
-
|
|
4532
|
+
_is_payment_error(first_err)
|
|
4533
|
+
or _is_connection_error(first_err)
|
|
4534
|
+
or _is_not_found_error(first_err)
|
|
4535
|
+
or _is_rate_limit_error(first_err)
|
|
4536
|
+
)
|
|
4522
4537
|
# Only try alternative providers when the user didn't explicitly
|
|
4523
4538
|
# configure this task's provider. Explicit provider = hard constraint;
|
|
4524
4539
|
# auto (the default) = best-effort fallback chain. (#7559)
|
|
@@ -4533,10 +4548,12 @@ def call_llm(
|
|
|
4533
4548
|
_mark_provider_unhealthy(
|
|
4534
4549
|
_recoverable_pool_provider(resolved_provider, client) or resolved_provider
|
|
4535
4550
|
)
|
|
4536
|
-
elif _is_rate_limit_error(first_err):
|
|
4537
|
-
reason = "rate limit"
|
|
4538
|
-
|
|
4539
|
-
reason = "
|
|
4551
|
+
elif _is_rate_limit_error(first_err):
|
|
4552
|
+
reason = "rate limit"
|
|
4553
|
+
elif _is_not_found_error(first_err):
|
|
4554
|
+
reason = "not found"
|
|
4555
|
+
else:
|
|
4556
|
+
reason = "connection error"
|
|
4540
4557
|
logger.info("Auxiliary %s: %s on %s (%s), trying fallback",
|
|
4541
4558
|
task or "call", reason, resolved_provider, first_err)
|
|
4542
4559
|
fb_client, fb_model, fb_label = _try_payment_fallback(
|
|
@@ -4725,12 +4742,13 @@ async def async_call_llm(
|
|
|
4725
4742
|
await client.chat.completions.create(**retry_kwargs), task)
|
|
4726
4743
|
except Exception as retry_err:
|
|
4727
4744
|
retry_err_str = str(retry_err)
|
|
4728
|
-
if not (
|
|
4729
|
-
_is_payment_error(retry_err)
|
|
4730
|
-
or _is_connection_error(retry_err)
|
|
4731
|
-
or
|
|
4732
|
-
or
|
|
4733
|
-
or "
|
|
4745
|
+
if not (
|
|
4746
|
+
_is_payment_error(retry_err)
|
|
4747
|
+
or _is_connection_error(retry_err)
|
|
4748
|
+
or _is_not_found_error(retry_err)
|
|
4749
|
+
or _is_auth_error(retry_err)
|
|
4750
|
+
or "max_tokens" in retry_err_str
|
|
4751
|
+
or "unsupported_parameter" in retry_err_str
|
|
4734
4752
|
):
|
|
4735
4753
|
raise
|
|
4736
4754
|
first_err = retry_err
|
|
@@ -4760,9 +4778,9 @@ async def async_call_llm(
|
|
|
4760
4778
|
except Exception as retry_err:
|
|
4761
4779
|
# If the max_tokens retry also hits a payment or connection
|
|
4762
4780
|
# error, fall through to the fallback chain below.
|
|
4763
|
-
if not (_is_payment_error(retry_err) or _is_connection_error(retry_err) or _is_rate_limit_error(retry_err)):
|
|
4764
|
-
raise
|
|
4765
|
-
first_err = retry_err
|
|
4781
|
+
if not (_is_payment_error(retry_err) or _is_connection_error(retry_err) or _is_not_found_error(retry_err) or _is_rate_limit_error(retry_err)):
|
|
4782
|
+
raise
|
|
4783
|
+
first_err = retry_err
|
|
4766
4784
|
|
|
4767
4785
|
# ── Nous auth refresh parity with main agent ──────────────────
|
|
4768
4786
|
client_is_nous = (
|
|
@@ -4847,10 +4865,11 @@ async def async_call_llm(
|
|
|
4847
4865
|
|
|
4848
4866
|
# ── Payment / connection / rate-limit fallback (mirrors sync call_llm) ──
|
|
4849
4867
|
should_fallback = (
|
|
4850
|
-
_is_payment_error(first_err)
|
|
4851
|
-
or _is_connection_error(first_err)
|
|
4852
|
-
or
|
|
4853
|
-
|
|
4868
|
+
_is_payment_error(first_err)
|
|
4869
|
+
or _is_connection_error(first_err)
|
|
4870
|
+
or _is_not_found_error(first_err)
|
|
4871
|
+
or _is_rate_limit_error(first_err)
|
|
4872
|
+
)
|
|
4854
4873
|
is_auto = resolved_provider in {"auto", "", None}
|
|
4855
4874
|
if should_fallback and is_auto:
|
|
4856
4875
|
if _is_payment_error(first_err):
|
|
@@ -4858,10 +4877,12 @@ async def async_call_llm(
|
|
|
4858
4877
|
_mark_provider_unhealthy(
|
|
4859
4878
|
_recoverable_pool_provider(resolved_provider, client) or resolved_provider
|
|
4860
4879
|
)
|
|
4861
|
-
elif _is_rate_limit_error(first_err):
|
|
4862
|
-
reason = "rate limit"
|
|
4863
|
-
|
|
4864
|
-
reason = "
|
|
4880
|
+
elif _is_rate_limit_error(first_err):
|
|
4881
|
+
reason = "rate limit"
|
|
4882
|
+
elif _is_not_found_error(first_err):
|
|
4883
|
+
reason = "not found"
|
|
4884
|
+
else:
|
|
4885
|
+
reason = "connection error"
|
|
4865
4886
|
logger.info("Auxiliary %s (async): %s on %s (%s), trying fallback",
|
|
4866
4887
|
task or "call", reason, resolved_provider, first_err)
|
|
4867
4888
|
fb_client, fb_model, fb_label = _try_payment_fallback(
|
package/bin/calvyn.js
CHANGED
|
@@ -18,6 +18,12 @@ function resolveCalvynBinary() {
|
|
|
18
18
|
return path.join(packageRoot, ".venv", venvName, filename)
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
function resolveVenvPython() {
|
|
22
|
+
return isWindows
|
|
23
|
+
? path.join(packageRoot, ".venv", "Scripts", "python.exe")
|
|
24
|
+
: path.join(packageRoot, ".venv", "bin", "python")
|
|
25
|
+
}
|
|
26
|
+
|
|
21
27
|
function runPostinstallIfNeeded() {
|
|
22
28
|
const calvynBinary = resolveCalvynBinary()
|
|
23
29
|
if (fs.existsSync(calvynBinary)) {
|
|
@@ -50,12 +56,15 @@ function runPostinstallIfNeeded() {
|
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
const args = process.argv.slice(2)
|
|
53
|
-
|
|
54
|
-
const
|
|
59
|
+
runPostinstallIfNeeded()
|
|
60
|
+
const venvPython = resolveVenvPython()
|
|
61
|
+
const cliEntry = path.join(packageRoot, "cli.py")
|
|
62
|
+
const result = spawnSync(venvPython, [cliEntry, ...args], {
|
|
55
63
|
stdio: "inherit",
|
|
56
64
|
shell: false,
|
|
57
65
|
env: {
|
|
58
66
|
...process.env,
|
|
67
|
+
CALVYN_LAUNCHED_FROM_NPM: "",
|
|
59
68
|
CALVYN_REPO_ROOT: packageRoot,
|
|
60
69
|
CALVYN_HOME: process.env.CALVYN_HOME || path.join(require("os").homedir(), ".calvyn"),
|
|
61
70
|
},
|
package/cli.py
CHANGED
|
@@ -6959,7 +6959,13 @@ class HermesCLI:
|
|
|
6959
6959
|
try:
|
|
6960
6960
|
if ctx is None:
|
|
6961
6961
|
raise RuntimeError("inventory context unavailable")
|
|
6962
|
-
providers = build_models_payload(
|
|
6962
|
+
providers = build_models_payload(
|
|
6963
|
+
ctx,
|
|
6964
|
+
include_unconfigured=True,
|
|
6965
|
+
picker_hints=True,
|
|
6966
|
+
canonical_order=True,
|
|
6967
|
+
max_models=50,
|
|
6968
|
+
)["providers"]
|
|
6963
6969
|
except Exception:
|
|
6964
6970
|
providers = []
|
|
6965
6971
|
|
package/hermes_cli/inventory.py
CHANGED
|
@@ -157,27 +157,31 @@ def build_models_payload(
|
|
|
157
157
|
# ─── Internal: row post-processing ──────────────────────────────────────
|
|
158
158
|
|
|
159
159
|
|
|
160
|
-
def _append_unconfigured_rows(rows: list[dict], ctx: ConfigContext) -> list[dict]:
|
|
161
|
-
"""Build skeleton rows for canonical providers missing from ``rows``."""
|
|
162
|
-
from hermes_cli.models import CANONICAL_PROVIDERS, _PROVIDER_LABELS
|
|
160
|
+
def _append_unconfigured_rows(rows: list[dict], ctx: ConfigContext) -> list[dict]:
|
|
161
|
+
"""Build skeleton rows for canonical providers missing from ``rows``."""
|
|
162
|
+
from hermes_cli.models import CANONICAL_PROVIDERS, OPENROUTER_MODELS, _PROVIDER_LABELS, _PROVIDER_MODELS
|
|
163
163
|
|
|
164
164
|
seen = {r["slug"].lower() for r in rows}
|
|
165
165
|
cur = (ctx.current_provider or "").lower()
|
|
166
166
|
extras: list[dict] = []
|
|
167
|
-
for entry in CANONICAL_PROVIDERS:
|
|
168
|
-
if entry.slug.lower() in seen:
|
|
169
|
-
continue
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
"
|
|
177
|
-
"
|
|
178
|
-
"
|
|
179
|
-
|
|
180
|
-
|
|
167
|
+
for entry in CANONICAL_PROVIDERS:
|
|
168
|
+
if entry.slug.lower() in seen:
|
|
169
|
+
continue
|
|
170
|
+
if entry.slug == "openrouter":
|
|
171
|
+
models = [mid for mid, _ in OPENROUTER_MODELS]
|
|
172
|
+
else:
|
|
173
|
+
models = list(_PROVIDER_MODELS.get(entry.slug, []))
|
|
174
|
+
extras.append(
|
|
175
|
+
{
|
|
176
|
+
"slug": entry.slug,
|
|
177
|
+
"name": _PROVIDER_LABELS.get(entry.slug, entry.label),
|
|
178
|
+
"is_current": entry.slug.lower() == cur,
|
|
179
|
+
"is_user_defined": False,
|
|
180
|
+
"models": models,
|
|
181
|
+
"total_models": len(models),
|
|
182
|
+
"source": "canonical",
|
|
183
|
+
}
|
|
184
|
+
)
|
|
181
185
|
return extras
|
|
182
186
|
|
|
183
187
|
|
|
@@ -196,10 +200,10 @@ def _apply_picker_hints(rows: list[dict]) -> None:
|
|
|
196
200
|
continue
|
|
197
201
|
# Distinguish authenticated rows (returned by
|
|
198
202
|
# list_authenticated_providers) from skeleton rows (from
|
|
199
|
-
# _append_unconfigured_rows).
|
|
200
|
-
#
|
|
201
|
-
#
|
|
202
|
-
is_skeleton = row.get("source") == "canonical"
|
|
203
|
+
# _append_unconfigured_rows). Skeleton rows come from source="canonical";
|
|
204
|
+
# they may still carry static fallback models so the picker can show
|
|
205
|
+
# choices before credentials are configured.
|
|
206
|
+
is_skeleton = row.get("source") == "canonical"
|
|
203
207
|
row["authenticated"] = not is_skeleton
|
|
204
208
|
if not is_skeleton or row.get("is_user_defined"):
|
|
205
209
|
continue
|
package/package.json
CHANGED
package/run_agent.py
CHANGED
|
@@ -3210,12 +3210,32 @@ class AIAgent:
|
|
|
3210
3210
|
except Exception:
|
|
3211
3211
|
pass
|
|
3212
3212
|
|
|
3213
|
-
def _emit_auxiliary_failure(self, task: str, exc: BaseException) -> None:
|
|
3214
|
-
"""Surface a compact warning for failed auxiliary work."""
|
|
3215
|
-
try:
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3213
|
+
def _emit_auxiliary_failure(self, task: str, exc: BaseException) -> None:
|
|
3214
|
+
"""Surface a compact warning for failed auxiliary work."""
|
|
3215
|
+
try:
|
|
3216
|
+
from agent.auxiliary_client import (
|
|
3217
|
+
_is_not_found_error,
|
|
3218
|
+
_is_payment_error,
|
|
3219
|
+
_is_rate_limit_error,
|
|
3220
|
+
)
|
|
3221
|
+
except Exception:
|
|
3222
|
+
_is_payment_error = _is_rate_limit_error = _is_not_found_error = None
|
|
3223
|
+
|
|
3224
|
+
if task == "title generation":
|
|
3225
|
+
try:
|
|
3226
|
+
if (
|
|
3227
|
+
(_is_payment_error and _is_payment_error(exc))
|
|
3228
|
+
or (_is_rate_limit_error and _is_rate_limit_error(exc))
|
|
3229
|
+
or (_is_not_found_error and _is_not_found_error(exc))
|
|
3230
|
+
):
|
|
3231
|
+
logger.debug("Suppressing auxiliary %s warning: %s", task, exc)
|
|
3232
|
+
return
|
|
3233
|
+
except Exception:
|
|
3234
|
+
pass
|
|
3235
|
+
try:
|
|
3236
|
+
detail = self._summarize_api_error(exc)
|
|
3237
|
+
except Exception:
|
|
3238
|
+
detail = str(exc)
|
|
3219
3239
|
detail = (detail or exc.__class__.__name__).strip()
|
|
3220
3240
|
if len(detail) > 220:
|
|
3221
3241
|
detail = detail[:217].rstrip() + "..."
|