fix: context propagation in LLM calls, SSE error handling, security hardening

- Propagate request context through MCP client (Request.Ctx field)
  — Client disconnects now cancel in-flight LLM API calls (both streaming and non-streaming)
  — Prevents wasted LLM tokens when user navigates away mid-response
- proxyBinance: use http.NewRequestWithContext for context-aware upstream calls
  — Client disconnects cancel Binance proxy requests
  — Distinguishes client cancellation from upstream failures
- Fix SSE parse bug in AgentChatPage: catch block was swallowing error events
  — throw new Error(data) for 'error' events was caught by the same try/catch
  — Now parse JSON separately, then handle events outside try/catch
- Add io.LimitReader to Hyperliquid coins.go JSON decoder (4MB limit)
- Use safe.ReadAllLimited in batch ticker handler for consistency
- Sanitize URL validation error in handler_ai_model.go (was leaking raw error)
- Cache agent tool definitions (built once, reused per message)
- Sort trade history by exit time for consistent ordering
This commit is contained in:
shinchan-zhai
2026-03-23 15:37:36 +08:00
parent 4c73a7960d
commit 3f1dd71f2b
8 changed files with 189 additions and 77 deletions

View File

@@ -503,6 +503,10 @@ func (client *Client) callWithRequestFull(req *Request) (*LLMResponse, error) {
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
// Propagate request context so client disconnects cancel in-flight LLM calls
if req.Ctx != nil {
httpReq = httpReq.WithContext(req.Ctx)
}
resp, err := client.HTTPClient.Do(httpReq)
if err != nil {
@@ -541,6 +545,10 @@ func (client *Client) callWithRequest(req *Request) (string, error) {
if err != nil {
return "", fmt.Errorf("failed to create request: %w", err)
}
// Propagate request context so client disconnects cancel in-flight LLM calls
if req.Ctx != nil {
httpReq = httpReq.WithContext(req.Ctx)
}
resp, err := client.HTTPClient.Do(httpReq)
if err != nil {
@@ -686,8 +694,13 @@ func (client *Client) CallWithRequestStream(req *Request, onChunk func(string))
// Idle-timeout watchdog: cancel the request if no SSE line arrives for 60 seconds.
// This breaks the scanner out of an indefinitely blocking Read on a hung connection.
// Use request's Ctx as parent so client disconnects propagate cancellation.
const idleTimeout = 60 * time.Second
ctx, cancel := context.WithCancel(context.Background())
parentCtx := req.Ctx
if parentCtx == nil {
parentCtx = context.Background()
}
ctx, cancel := context.WithCancel(parentCtx)
defer cancel()
resetCh := make(chan struct{}, 1)
safe.GoNamed("mcp-stream-idle-watchdog", func() {