dispatchDelta forwards reasoning and content as events, then accumulates streamed tool-call fragments into index-keyed slots. Reasoning stays out of fullContent (must not round-trip into the assistant message) but is forwarded so the UI reflects thinking. Fragments key on the provider's `index`, not
(parent context.Context, d streamDelta, budget cloud.BudgetStatus, fullContent *strings.Builder, slots map[int]*toolSlot, order *[]int, out chan<- Event)
| 542 | } |
| 543 | |
| 544 | func (e *httpStatusError) Error() string { return fmt.Sprintf("%d: %s", e.status, e.msg) } |
| 545 | |
| 546 | // retryable reports whether a pre-stream failure is worth resending: transport |
| 547 | // errors and the statuses that signal a transient server state. 404 is |
| 548 | // semantically permanent, but LiteLLM-style proxies emit it for transient |
| 549 | // upstream misses (the Agnes-AI freeze in issue #7), and the rising backoff |
| 550 | // bounds the cost of a truly permanent one. 401/402 arrive as typed sentinels |
| 551 | // the UI handles; 400 and other client errors fail identically on every |
| 552 | // resend. |
| 553 | func retryable(err error) bool { |
| 554 | var un cloud.ErrUnreachable |
| 555 | if errors.As(err, &un) { |
| 556 | return true |
| 557 | } |
| 558 | var hs *httpStatusError |
| 559 | if errors.As(err, &hs) { |
| 560 | return hs.status == 404 || hs.status == 408 || hs.status == 429 || hs.status >= 500 |
| 561 | } |
| 562 | return false |
| 563 | } |
| 564 | |
| 565 | // errorMessageFromBody extracts the user-facing string from a non-2xx body. |
| 566 | // hamrpass wraps errors as `{"error":{"message":...,"provider_hint":...}}`; we |
| 567 | // prefer provider_hint (providers stash the human diagnostic there), fall back |
| 568 | // to message, then to the raw first line so non-hamrpass backends still surface |
| 569 | // whatever they emit. |
| 570 | func errorMessageFromBody(b []byte) string { |
| 571 | var env struct { |
| 572 | Error struct { |
| 573 | Message string `json:"message"` |
| 574 | ProviderHint string `json:"provider_hint"` |
| 575 | } `json:"error"` |
| 576 | } |
| 577 | if json.Unmarshal(b, &env) == nil { |
| 578 | if env.Error.ProviderHint != "" { |
| 579 | return env.Error.ProviderHint |
| 580 | } |
| 581 | if env.Error.Message != "" { |
| 582 | return env.Error.Message |
| 583 | } |
| 584 | } |
| 585 | return firstLine(string(b)) |
| 586 | } |
| 587 |