feat: add streaming support for x402 payment flow to bypass Cloudflare timeout

- Extract ParseSSEStream as shared function from CallWithRequestStream
- Add DoX402RequestStream and X402CallStream for streaming x402 payments
- Switch Claw402Client.Call to use streaming (X402CallStream)
- TeeReader fallback: SSE parsing with JSON fallback for non-SSE responses
- Idle timeout watchdog (90s) protects against stalled streams
This commit is contained in:
tinkle-community
2026-03-16 12:41:30 +08:00
parent 780bb39a92
commit 0f06f9b2a2
3 changed files with 252 additions and 7 deletions

View File

@@ -687,15 +687,27 @@ func (client *Client) CallWithRequestStream(req *Request, onChunk func(string))
return "", fmt.Errorf("API error (status %d): %s", resp.StatusCode, string(body))
}
var accumulated strings.Builder
scanner := bufio.NewScanner(resp.Body)
for scanner.Scan() {
// Ping the watchdog: we received a line, reset the idle timer.
return ParseSSEStream(resp.Body, onChunk, func() {
select {
case resetCh <- struct{}{}:
default:
}
})
}
// ParseSSEStream reads an SSE response body, accumulates text deltas,
// and calls onChunk with the full accumulated text after each chunk.
// If onLine is non-nil, it is called after each raw SSE line is scanned
// (useful for resetting idle-timeout watchdogs).
// Returns the complete accumulated text.
func ParseSSEStream(body io.Reader, onChunk func(string), onLine func()) (string, error) {
var accumulated strings.Builder
scanner := bufio.NewScanner(body)
for scanner.Scan() {
if onLine != nil {
onLine()
}
line := scanner.Text()
if !strings.HasPrefix(line, "data: ") {
@@ -706,7 +718,6 @@ func (client *Client) CallWithRequestStream(req *Request, onChunk func(string))
break
}
// Parse the SSE JSON chunk
var chunk struct {
Choices []struct {
Delta struct {