persistInterruptedStep saves durable content from a partial stream. Provider-executed calls without results are removed because their result metadata cannot be synthesized safely, except when removal would mutate signed Anthropic replay state.
( ctx context.Context, opts RunOptions, result *stepResult, )
| 1703 | // metadata cannot be synthesized safely, except when removal would mutate |
| 1704 | // signed Anthropic replay state. |
| 1705 | func persistInterruptedStep( |
| 1706 | ctx context.Context, |
| 1707 | opts RunOptions, |
| 1708 | result *stepResult, |
| 1709 | ) { |
| 1710 | if result == nil || (len(result.content) == 0 && len(result.toolCalls) == 0) { |
| 1711 | return |
| 1712 | } |
| 1713 | |
| 1714 | provider := "" |
| 1715 | modelName := "" |
| 1716 | if opts.Model != nil { |
| 1717 | provider = opts.Model.Provider() |
| 1718 | modelName = opts.Model.Model() |
| 1719 | } |
| 1720 | var sanitizeStats chatsanitize.AnthropicProviderToolSanitizationStats |
| 1721 | result.content, sanitizeStats = chatsanitize.SanitizeAnthropicProviderToolContent(provider, result.content) |
| 1722 | chatsanitize.LogAnthropicProviderToolSanitization( |
| 1723 | ctx, opts.Logger, "interrupted_persist", provider, modelName, sanitizeStats, |
| 1724 | ) |
| 1725 | |
| 1726 | // Track which tool calls already have results in the content. |
| 1727 | answeredToolCalls := make(map[string]struct{}) |
| 1728 | for _, c := range result.content { |
| 1729 | tr, ok := fantasy.AsContentType[fantasy.ToolResultContent](c) |
| 1730 | if ok && tr.ToolCallID != "" { |
| 1731 | answeredToolCalls[tr.ToolCallID] = struct{}{} |
| 1732 | } |
| 1733 | } |
| 1734 | |
| 1735 | // Copy existing timestamps and add result timestamps for |
| 1736 | // interrupted tool calls so the frontend can show partial |
| 1737 | // duration. |
| 1738 | toolCallCreatedAt := maps.Clone(result.toolCallCreatedAt) |
| 1739 | if toolCallCreatedAt == nil { |
| 1740 | toolCallCreatedAt = make(map[string]time.Time) |
| 1741 | } |
| 1742 | toolResultCreatedAt := maps.Clone(result.toolResultCreatedAt) |
| 1743 | if toolResultCreatedAt == nil { |
| 1744 | toolResultCreatedAt = make(map[string]time.Time) |
| 1745 | } |
| 1746 | |
| 1747 | // Build combined content: all accumulated content + synthetic |
| 1748 | // interrupted results for any unanswered tool calls. |
| 1749 | content := make([]fantasy.Content, 0, len(result.content)) |
| 1750 | content = append(content, result.content...) |
| 1751 | |
| 1752 | interruptedAt := dbtime.Now() |
| 1753 | for _, tc := range result.toolCalls { |
| 1754 | if tc.ToolCallID == "" { |
| 1755 | continue |
| 1756 | } |
| 1757 | if _, exists := answeredToolCalls[tc.ToolCallID]; exists { |
| 1758 | continue |
| 1759 | } |
| 1760 | if chatsanitize.IsAnthropicProviderExecutedToolCall(provider, tc) { |
| 1761 | continue |
| 1762 | } |
no test coverage detected