Builds task input payloads that optionally include attachments.
| 9 | |
| 10 | |
| 11 | class TaskInputBuilder: |
| 12 | """Builds task input payloads that optionally include attachments.""" |
| 13 | |
| 14 | def __init__(self, attachment_store: AttachmentStore): |
| 15 | self.attachment_store = attachment_store |
| 16 | |
| 17 | def build_from_file_paths( |
| 18 | self, |
| 19 | prompt: str, |
| 20 | attachment_paths: Sequence[str], |
| 21 | ) -> Union[str, List[Message]]: |
| 22 | if not attachment_paths: |
| 23 | return prompt |
| 24 | |
| 25 | blocks: List[MessageBlock] = [] |
| 26 | |
| 27 | for raw_path in attachment_paths: |
| 28 | file_path = Path(raw_path).expanduser() |
| 29 | if not file_path.exists(): |
| 30 | raise FileNotFoundError(f"Attachment not found: {file_path}") |
| 31 | mime_type, _ = mimetypes.guess_type(str(file_path)) |
| 32 | record = self.attachment_store.register_file( |
| 33 | file_path, |
| 34 | kind=MessageBlockType.from_mime_type(mime_type), |
| 35 | display_name=file_path.name, |
| 36 | mime_type=mime_type, |
| 37 | extra={ |
| 38 | "source": "user_upload", |
| 39 | "origin": "cli_attachment", |
| 40 | "original_path": str(file_path), |
| 41 | }, |
| 42 | ) |
| 43 | blocks.append(record.as_message_block()) |
| 44 | |
| 45 | return self.build_from_blocks(prompt, blocks) |
| 46 | |
| 47 | @staticmethod |
| 48 | def build_from_blocks(prompt: str, blocks: Sequence[MessageBlock]) -> List[Message]: |
| 49 | final_blocks: List[MessageBlock] = [] |
| 50 | if prompt: |
| 51 | final_blocks.append(MessageBlock.text_block(prompt)) |
| 52 | final_blocks.extend(blocks) |
| 53 | if not final_blocks: |
| 54 | final_blocks.append(MessageBlock.text_block("")) |
| 55 | return [ |
| 56 | Message( |
| 57 | role=MessageRole.USER, |
| 58 | content=final_blocks, |
| 59 | metadata={"source": "TASK"}, |
| 60 | ) |
| 61 | ] |
no outgoing calls
no test coverage detected