|
1 | 1 | """LLM chat and completion via TEE-verified execution with x402 payments.""" |
2 | 2 |
|
| 3 | +import base64 |
3 | 4 | import json |
4 | 5 | import logging |
5 | 6 | from dataclasses import dataclass |
|
35 | 36 | _COMPLETION_ENDPOINT = "/v1/completions" |
36 | 37 | _REQUEST_TIMEOUT = 60 |
37 | 38 |
|
| 39 | + |
38 | 40 | @dataclass(frozen=True) |
39 | 41 | class _ChatParams: |
40 | 42 | """Bundles the common parameters for chat/completion requests.""" |
@@ -385,8 +387,9 @@ async def _request() -> TextGenerationOutput: |
385 | 387 | headers=self._headers(params.x402_settlement_mode), |
386 | 388 | timeout=_REQUEST_TIMEOUT, |
387 | 389 | ) |
388 | | - response.raise_for_status() |
389 | 390 | raw_body = await response.aread() |
| 391 | + if response.is_error: |
| 392 | + raise RuntimeError(_format_http_error(response, raw_body)) |
390 | 393 | result = json.loads(raw_body.decode()) |
391 | 394 |
|
392 | 395 | choices = result.get("choices") |
@@ -532,3 +535,23 @@ async def _parse_sse_response(self, response, tee) -> AsyncGenerator[StreamChunk |
532 | 535 | chunk.tee_endpoint = tee.endpoint |
533 | 536 | chunk.tee_payment_address = tee.payment_address |
534 | 537 | yield chunk |
| 538 | + |
| 539 | + |
| 540 | +def _decode_payment_required(header_value: Optional[str]) -> str: |
| 541 | + """Decode the base64-encoded JSON in the `payment-required` response header.""" |
| 542 | + if not header_value: |
| 543 | + return "<missing>" |
| 544 | + try: |
| 545 | + decoded = base64.b64decode(header_value).decode("utf-8") |
| 546 | + return json.dumps(json.loads(decoded), indent=2) |
| 547 | + except Exception: |
| 548 | + return header_value |
| 549 | + |
| 550 | + |
| 551 | +def _format_http_error(response: httpx.Response, body: bytes) -> str: |
| 552 | + """Build an error message that surfaces the x402 payment-required details.""" |
| 553 | + return ( |
| 554 | + f"HTTP {response.status_code} from {response.url}\n" |
| 555 | + f"Payment-Required: {_decode_payment_required(response.headers.get('payment-required'))}\n" |
| 556 | + f"Body: {body.decode(errors='replace')}" |
| 557 | + ) |
0 commit comments