flushActiveState moves any in-progress text, reasoning, and tool calls from the active tracking maps into result.content and result.toolCalls. This is called on interruption so that partial content from an incomplete stream is available for persistence.
( result *stepResult, activeText map[string]string, activeReasoning map[string]reasoningState, activeToolCalls map[string]*fantasy.ToolCallContent, toolNames map[string]string, )
| 1642 | // partial content from an incomplete stream is available for |
| 1643 | // persistence. |
| 1644 | func flushActiveState( |
| 1645 | result *stepResult, |
| 1646 | activeText map[string]string, |
| 1647 | activeReasoning map[string]reasoningState, |
| 1648 | activeToolCalls map[string]*fantasy.ToolCallContent, |
| 1649 | toolNames map[string]string, |
| 1650 | ) { |
| 1651 | // Flush partial text content. |
| 1652 | for _, text := range activeText { |
| 1653 | if text != "" { |
| 1654 | result.content = append(result.content, fantasy.TextContent{Text: text}) |
| 1655 | } |
| 1656 | } |
| 1657 | |
| 1658 | // Flush partial reasoning content. The matching |
| 1659 | // completedAt is filled in here with the interruption |
| 1660 | // time so partial reasoning shows the time spent before |
| 1661 | // the interruption. |
| 1662 | flushedAt := dbtime.Now() |
| 1663 | for _, rs := range activeReasoning { |
| 1664 | if rs.text == "" && !chatsanitize.HasAnthropicSignedReasoningOptions(fantasy.ProviderOptions(rs.options)) { |
| 1665 | continue |
| 1666 | } |
| 1667 | result.content = append(result.content, fantasy.ReasoningContent{ |
| 1668 | Text: rs.text, |
| 1669 | ProviderMetadata: rs.options, |
| 1670 | }) |
| 1671 | result.reasoningStartedAt = append(result.reasoningStartedAt, rs.startedAt) |
| 1672 | result.reasoningCompletedAt = append(result.reasoningCompletedAt, flushedAt) |
| 1673 | } |
| 1674 | |
| 1675 | // Flush in-progress tool calls. These haven't received a |
| 1676 | // StreamPartTypeToolCall yet, so they only exist in |
| 1677 | // activeToolCalls. We add them to both content and toolCalls |
| 1678 | // so persistInterruptedStep can generate synthetic error |
| 1679 | // results for them. |
| 1680 | for id, tc := range activeToolCalls { |
| 1681 | if tc == nil { |
| 1682 | continue |
| 1683 | } |
| 1684 | // Prefer the tool name from the toolNames map since |
| 1685 | // ToolInputStart may provide a cleaner name. |
| 1686 | toolName := tc.ToolName |
| 1687 | if name, ok := toolNames[id]; ok && strings.TrimSpace(name) != "" { |
| 1688 | toolName = name |
| 1689 | } |
| 1690 | flushed := fantasy.ToolCallContent{ |
| 1691 | ToolCallID: tc.ToolCallID, |
| 1692 | ToolName: toolName, |
| 1693 | Input: tc.Input, |
| 1694 | ProviderExecuted: tc.ProviderExecuted, |
| 1695 | } |
| 1696 | result.content = append(result.content, flushed) |
| 1697 | result.toolCalls = append(result.toolCalls, flushed) |
| 1698 | } |
| 1699 | } |
| 1700 | |
| 1701 | // persistInterruptedStep saves durable content from a partial stream. |