(msgs []chmctx.Message)
| 605 | }, tokens, promptTokens, nil |
| 606 | } |
| 607 | |
| 608 | // dispatchDelta forwards reasoning and content as events, then accumulates |
| 609 | // streamed tool-call fragments into index-keyed slots. Reasoning stays out of |
| 610 | // fullContent (must not round-trip into the assistant message) but is forwarded |
| 611 | // so the UI reflects thinking. Fragments key on the provider's `index`, not |
| 612 | // slice position, since a call's fragments span chunks whose position need not |
| 613 | // match the index. Returns false when parent cancelled mid-send. |
| 614 | func dispatchDelta(parent context.Context, d streamDelta, budget cloud.BudgetStatus, fullContent *strings.Builder, slots map[int]*toolSlot, order *[]int, out chan<- Event) bool { |
| 615 | if d.Reasoning != "" { |
| 616 | if !sendEvent(parent, out, Event{Kind: EventReasoning, Content: d.Reasoning, Budget: budget}) { |
| 617 | return false |
| 618 | } |
| 619 | } |
| 620 | if d.Content != "" { |
| 621 | fullContent.WriteString(d.Content) |
| 622 | if !sendEvent(parent, out, Event{Kind: EventContent, Content: d.Content, Budget: budget}) { |
| 623 | return false |
| 624 | } |
| 625 | } |
| 626 | for _, tc := range d.ToolCalls { |
| 627 | slot, existed := slots[tc.Index] |
| 628 | if !existed { |
| 629 | slot = &toolSlot{} |
| 630 | slots[tc.Index] = slot |
| 631 | *order = append(*order, tc.Index) |
| 632 | } |
| 633 | // id/name usually arrive in the first fragment, but updating on any |
no outgoing calls