Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,17 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

## 0.18.1 — 2026-06-09

### Fixed — Venice: distinguish "out of credits" from "bad auth"

Venice returns HTTP 402 for two different conditions: a missing/invalid key
("authentication required") and a valid key on an account with no funds
("insufficient USD or Diem balance"). The service previously classified both as
`no_credentials`. The balance case now raises `VeniceError("payment_required",
…)` so the error is actionable (add credits at venice.ai/settings/api vs fix the
key). Surfaced while live-testing a real key.

## 0.18.0 — 2026-06-09

### Added — Venice AI inference provider
Expand Down
2 changes: 1 addition & 1 deletion clawmes/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
* Tooling that does not want to incur a full package import
"""

__version__ = "0.18.0"
__version__ = "0.18.1"
2 changes: 1 addition & 1 deletion clawmes/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: clawmes
version: 0.18.0
version: 0.18.1
description: Hermes Agent for crypto. Wallet, swaps, DeFi, launches, automation.
author: Clawnch
kind: standalone
Expand Down
17 changes: 14 additions & 3 deletions clawmes/services/venice.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,12 @@ class VeniceError(RuntimeError):
code, HTTP 404, or a message containing both "model" and "not found".
* ``rate_limited`` — OpenAI ``rate_limit_exceeded`` / ``rate_limit_error``,
HTTP 429.
* ``no_credentials`` — HTTP 401 / 403, or HTTP 402 (Venice's x402
"authentication required" / pay-per-call challenge), or OpenAI
``authentication_error`` / ``permission_denied``.
* ``no_credentials`` — HTTP 401 / 403, or an HTTP 402 "authentication
required" challenge (no/invalid key), or OpenAI ``authentication_error``
/ ``permission_denied``.
* ``payment_required`` — HTTP 402 where the key is valid but the Venice
account is out of funds ("insufficient USD or Diem balance"). Distinct
from ``no_credentials`` because the fix is "add credits", not "fix auth".
* ``api_error`` — generic upstream failure.
"""

Expand Down Expand Up @@ -193,9 +196,17 @@ def _call(
# Venice's auth/payment errors are a flat ``{"error": "..."}`` with
# an HTTP 402 x402 challenge. Classify by status code in the
# exception string (lib/http embeds it), with a keyword fallback.
# The 402 has two distinct meanings: a missing/invalid key
# ("authentication required") vs a valid key with an empty account
# ("insufficient … balance" — add credits). Split them so the code
# is actionable. Match on the body message (``detail``), since the
# exception text always contains the "402 Payment Required" status.
msg = str(exc).lower()
flat = body_dict.get("error") if isinstance(body_dict, dict) else None
detail = flat if isinstance(flat, str) and flat else str(exc)
low = detail.lower()
if "insufficient" in low or "balance" in low or "add credits" in low:
raise VeniceError("payment_required", detail) from exc
if "402" in msg or "401" in msg or "403" in msg or "authentication required" in msg:
raise VeniceError("no_credentials", detail) from exc
if "429" in msg or "rate limit" in msg:
Expand Down
2 changes: 1 addition & 1 deletion plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: clawmes
version: 0.18.0
version: 0.18.1
description: Hermes Agent for crypto. Wallet, swaps, DeFi, launches, automation.
author: Clawnch
kind: standalone
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "clawmes"
version = "0.18.0"
version = "0.18.1"
description = "Hermes Agent plugin for crypto: wallets, DEX trading, lending and staking, governance, on-chain automation."
readme = "README.md"
license = { text = "MIT" }
Expand Down
20 changes: 18 additions & 2 deletions tests/services/test_venice.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,8 +425,8 @@ def test_pulls_structured_body_and_classifies(self, svc, fake_http):
assert exc_info.value.code == "model_not_found"
assert "Unsupported model" in exc_info.value.message

def test_venice_flat_error_402(self, svc, fake_http):
# Venice's real unauth body: flat {"error": "Authentication required"} + 402.
def test_venice_flat_error_402_auth(self, svc, fake_http):
# Venice's real no-key body: flat {"error": "Authentication required"} + 402.
exc = _err_with_body(
"Client error '402 Payment Required' for url",
{"x402Version": 2, "error": "Authentication required"},
Expand All @@ -438,6 +438,22 @@ def test_venice_flat_error_402(self, svc, fake_http):
# The flat error string is surfaced as the detail.
assert exc_info.value.message == "Authentication required"

def test_venice_flat_error_402_insufficient_balance(self, svc, fake_http):
# Real body when the key is valid but the account is empty: a 402 that
# must classify as payment_required (add credits), NOT no_credentials.
exc = _err_with_body(
"Client error '402 Payment Required' for url",
{
"error": "Insufficient USD or Diem balance to complete request. "
"Visit https://venice.ai/settings/api to add credits."
},
)
fake_http.responses.append(exc)
with pytest.raises(VeniceError) as exc_info:
self._call(svc)
assert exc_info.value.code == "payment_required"
assert "Insufficient" in exc_info.value.message

def test_response_json_raises_falls_through_to_substring(self, svc, fake_http):
exc = _err_with_body("Client error '400 Bad Request' for url", ValueError("not json"))
fake_http.responses.append(exc)
Expand Down
Loading