refactor(fal): render the managed billing message once in fal_common
Image and video callers were each formatting the same four-field dict into the same sentence. Return the rendered tail from _managed_fal_billing_error so the wording lives in one place; output is byte-identical.
This commit is contained in:
@@ -224,16 +224,10 @@ def _submit_fal_video_request(endpoint: str, arguments: Dict[str, Any]):
|
||||
from tools.fal_common import _extract_http_status, _managed_fal_billing_error
|
||||
status = _extract_http_status(exc)
|
||||
if status is not None and 400 <= status < 500:
|
||||
billing = _managed_fal_billing_error(exc)
|
||||
billing = _managed_fal_billing_error(exc, "endpoint")
|
||||
if billing is not None:
|
||||
raise ValueError(
|
||||
f"Nous Subscription gateway rejected endpoint '{endpoint}' (HTTP {status}): "
|
||||
f"{billing['message']} ({billing['error_code']}; "
|
||||
f"{billing['code']}: {billing['detail']}). "
|
||||
"This is a Nous Portal billing configuration issue, not a missing local API key. "
|
||||
"The managed route cannot run this endpoint until Nous enables its billing meter; "
|
||||
"a direct FAL_KEY is an optional bypass."
|
||||
) from exc
|
||||
f"Nous Subscription gateway rejected endpoint '{endpoint}' (HTTP {status}): {billing}") from exc
|
||||
raise ValueError(f"Nous Subscription gateway rejected endpoint '{endpoint}' (HTTP {status}). This model may not yet be enabled "
|
||||
f"on the Nous Portal's FAL proxy. Either:\n • Set FAL_KEY in your environment to use FAL.ai directly, or\n"
|
||||
f" • Pick a different model via `hermes tools` → Video Generation.") from exc
|
||||
|
||||
+16
-12
@@ -46,8 +46,12 @@ def _extract_http_status(exc: BaseException) -> Optional[int]:
|
||||
return status if isinstance(status, int) else None
|
||||
|
||||
|
||||
def _managed_fal_billing_error(exc: BaseException) -> Optional[Dict[str, str]]:
|
||||
"""Return normalized Nous billing details from a managed-gateway error."""
|
||||
def _managed_fal_billing_error(exc: BaseException, what: str) -> Optional[str]:
|
||||
"""Human-readable tail for a Nous managed-gateway ``BILLING_ERROR`` response, else None.
|
||||
|
||||
``what`` names the rejected thing ("model", "endpoint"); the wording is shared by the image
|
||||
and video callers so the two surfaces never drift.
|
||||
"""
|
||||
response = getattr(exc, "response", None)
|
||||
if response is None:
|
||||
return None
|
||||
@@ -58,16 +62,16 @@ def _managed_fal_billing_error(exc: BaseException) -> Optional[Dict[str, str]]:
|
||||
error = payload.get("error") if isinstance(payload, dict) else None
|
||||
if not isinstance(error, dict) or error.get("code") != "BILLING_ERROR":
|
||||
return None
|
||||
raw_details = error.get("details")
|
||||
details = raw_details if isinstance(raw_details, dict) else {}
|
||||
raw_upstream = details.get("upstreamPayload")
|
||||
upstream = raw_upstream if isinstance(raw_upstream, dict) else {}
|
||||
return {
|
||||
"message": str(error.get("message") or "Charge authorization failed"),
|
||||
"error_code": str(error.get("code") or "BILLING_ERROR"),
|
||||
"code": str(upstream.get("code") or details.get("chargeIntentErrorCode") or "billing_error"),
|
||||
"detail": str(upstream.get("error") or "Nous Portal rejected the charge authorization"),
|
||||
}
|
||||
details = error.get("details") if isinstance(error.get("details"), dict) else {}
|
||||
upstream = details.get("upstreamPayload") if isinstance(details.get("upstreamPayload"), dict) else {}
|
||||
code = upstream.get("code") or details.get("chargeIntentErrorCode") or "billing_error"
|
||||
detail = upstream.get("error") or "Nous Portal rejected the charge authorization"
|
||||
return (
|
||||
f"{error.get('message') or 'Charge authorization failed'} (BILLING_ERROR; {code}: {detail}). "
|
||||
"This is a Nous Portal billing configuration issue, not a missing local API key. "
|
||||
f"The managed route cannot run this {what} until Nous enables its billing meter; "
|
||||
"a direct FAL_KEY is an optional bypass."
|
||||
)
|
||||
|
||||
|
||||
def _require(value: Any, what: str) -> Any:
|
||||
|
||||
@@ -134,16 +134,10 @@ def _submit_fal_request(model: str, arguments: Dict[str, Any]):
|
||||
# (allowlist miss, billing gate): give remediation instead of a raw httpx error.
|
||||
status = _extract_http_status(exc)
|
||||
if status is not None and 400 <= status < 500:
|
||||
billing = _managed_fal_billing_error(exc)
|
||||
billing = _managed_fal_billing_error(exc, "model")
|
||||
if billing is not None:
|
||||
raise ValueError(
|
||||
f"Nous Subscription gateway rejected model '{model}' (HTTP {status}): "
|
||||
f"{billing['message']} ({billing['error_code']}; "
|
||||
f"{billing['code']}: {billing['detail']}). "
|
||||
"This is a Nous Portal billing configuration issue, not a missing local API key. "
|
||||
"The managed route cannot run this model until Nous enables its billing meter; "
|
||||
"a direct FAL_KEY is an optional bypass."
|
||||
) from exc
|
||||
f"Nous Subscription gateway rejected model '{model}' (HTTP {status}): {billing}") from exc
|
||||
gateway_message = ""
|
||||
if status in {401, 402, 403}:
|
||||
gateway_message = "\n\n" + nous_tool_gateway_unavailable_message(
|
||||
|
||||
Reference in New Issue
Block a user