partsToMessageParts converts SDK chat message parts into fantasy message parts for LLM dispatch. resolved is a lookup map for file bytes, and policy controls whether missing file-backed parts are dropped or replaced with text placeholders.
( ctx context.Context, logger slog.Logger, parts []codersdk.ChatMessagePart, resolved map[uuid.UUID]FileData, policy missingFilePolicy, )
| 1497 | // bytes, and policy controls whether missing file-backed parts are |
| 1498 | // dropped or replaced with text placeholders. |
| 1499 | func partsToMessageParts( |
| 1500 | ctx context.Context, |
| 1501 | logger slog.Logger, |
| 1502 | parts []codersdk.ChatMessagePart, |
| 1503 | resolved map[uuid.UUID]FileData, |
| 1504 | policy missingFilePolicy, |
| 1505 | ) []fantasy.MessagePart { |
| 1506 | result := make([]fantasy.MessagePart, 0, len(parts)) |
| 1507 | for _, part := range parts { |
| 1508 | switch part.Type { |
| 1509 | case codersdk.ChatMessagePartTypeText: |
| 1510 | // Anthropic rejects empty text content blocks with |
| 1511 | // "text content blocks must be non-empty". Empty parts |
| 1512 | // can arise when a stream sends TextStart/TextEnd with |
| 1513 | // no delta in between. We filter them here rather than |
| 1514 | // at persistence time to preserve the raw record. |
| 1515 | if strings.TrimSpace(part.Text) == "" { |
| 1516 | continue |
| 1517 | } |
| 1518 | result = append(result, fantasy.TextPart{ |
| 1519 | Text: part.Text, |
| 1520 | ProviderOptions: providerMetadataToOptions(logger, part.ProviderMetadata), |
| 1521 | }) |
| 1522 | case codersdk.ChatMessagePartTypeReasoning: |
| 1523 | opts := providerMetadataToOptions(logger, part.ProviderMetadata) |
| 1524 | if strings.TrimSpace(part.Text) == "" && !chatsanitize.HasAnthropicSignedReasoningOptions(opts) { |
| 1525 | continue |
| 1526 | } |
| 1527 | result = append(result, fantasy.ReasoningPart{ |
| 1528 | Text: part.Text, |
| 1529 | ProviderOptions: opts, |
| 1530 | }) |
| 1531 | case codersdk.ChatMessagePartTypeToolCall: |
| 1532 | result = append(result, fantasy.ToolCallPart{ |
| 1533 | ToolCallID: sanitizeToolCallID(part.ToolCallID), |
| 1534 | ToolName: part.ToolName, |
| 1535 | Input: string(part.Args), |
| 1536 | ProviderExecuted: part.ProviderExecuted, |
| 1537 | ProviderOptions: providerMetadataToOptions(logger, part.ProviderMetadata), |
| 1538 | }) |
| 1539 | case codersdk.ChatMessagePartTypeToolResult: |
| 1540 | result = append(result, toolResultPartToMessagePart(logger, part)) |
| 1541 | case codersdk.ChatMessagePartTypeFile: |
| 1542 | data := part.Data |
| 1543 | mediaType := part.MediaType |
| 1544 | var name string |
| 1545 | resolvedFile := false |
| 1546 | if part.FileID.Valid { |
| 1547 | if fd, ok := resolved[part.FileID.UUID]; ok { |
| 1548 | resolvedFile = true |
| 1549 | data = fd.Data |
| 1550 | name = fd.Name |
| 1551 | if mediaType == "" { |
| 1552 | mediaType = fd.MediaType |
| 1553 | } |
| 1554 | } |
| 1555 | } |
| 1556 | opts := providerMetadataToOptions(logger, part.ProviderMetadata) |
no test coverage detected