toolResultContentToPart converts a fantasy ToolResultContent into a ChatMessagePart.
( logger slog.Logger, content fantasy.ToolResultContent, logMalformedAttachmentMetadata func(fantasy.ToolResultContent, error), )
| 851 | // toolResultContentToPart converts a fantasy ToolResultContent into a |
| 852 | // ChatMessagePart. |
| 853 | func toolResultContentToPart( |
| 854 | logger slog.Logger, |
| 855 | content fantasy.ToolResultContent, |
| 856 | logMalformedAttachmentMetadata func(fantasy.ToolResultContent, error), |
| 857 | ) codersdk.ChatMessagePart { |
| 858 | var result json.RawMessage |
| 859 | var isError bool |
| 860 | var isMedia bool |
| 861 | |
| 862 | switch output := content.Result.(type) { |
| 863 | case fantasy.ToolResultOutputContentError: |
| 864 | isError = true |
| 865 | if output.Error != nil { |
| 866 | raw := json.RawMessage(strings.TrimSpace(output.Error.Error())) |
| 867 | if isSubagentLifecycleToolName(content.ToolName) && hasErrorField(raw) { |
| 868 | result = raw |
| 869 | } else { |
| 870 | var marshalErr error |
| 871 | result, marshalErr = json.Marshal(map[string]any{"error": output.Error.Error()}) |
| 872 | if marshalErr != nil { |
| 873 | logger.Error(context.Background(), "failed to marshal error tool result", |
| 874 | slog.F("tool_name", content.ToolName), |
| 875 | slog.F("tool_call_id", content.ToolCallID), |
| 876 | slog.Error(marshalErr), |
| 877 | ) |
| 878 | result = []byte(`{"error":"marshal failure"}`) |
| 879 | } |
| 880 | } |
| 881 | } else { |
| 882 | result = []byte(`{"error":""}`) |
| 883 | } |
| 884 | case fantasy.ToolResultOutputContentText: |
| 885 | sanitized := strings.ToValidUTF8(output.Text, "\uFFFD") |
| 886 | result = json.RawMessage(sanitized) |
| 887 | // Ensure valid JSON; wrap in an object if not. |
| 888 | if !json.Valid(result) { |
| 889 | var marshalErr error |
| 890 | result, marshalErr = json.Marshal(map[string]any{"output": sanitized}) |
| 891 | if marshalErr != nil { |
| 892 | logger.Error(context.Background(), "failed to marshal text tool result", |
| 893 | slog.F("tool_name", content.ToolName), |
| 894 | slog.F("tool_call_id", content.ToolCallID), |
| 895 | slog.Error(marshalErr), |
| 896 | ) |
| 897 | result = []byte(`{}`) |
| 898 | } |
| 899 | } |
| 900 | case fantasy.ToolResultOutputContentMedia: |
| 901 | isMedia = true |
| 902 | persisted := persistedMediaResult{ |
| 903 | Data: output.Data, |
| 904 | MimeType: output.MediaType, |
| 905 | Text: strings.ToValidUTF8(output.Text, "\uFFFD"), |
| 906 | } |
| 907 | // Tool renderers only receive the persisted result JSON, while |
| 908 | // ClientMetadata is consumed later to append sibling file parts. |
| 909 | // Mirror attachment identity here so promoted media can be |
| 910 | // recognized as the same durable attachment downstream. |
no test coverage detected