# Tipo: Message / Part (multipart messages)

Fuente conceptual: OpenViking `openviking/message/` (AGPL-3.0)
Reimplementar desde cero. No copiar codigo.

## Tipos a implementar

### 1. TextPart (product)

- **Dominio:** core
- **Lang:** Python
- **Algebraic:** product
- **Definicion:**
  ```python
  @dataclass
  class TextPart:
      text: str = ""
      type: str = "text"  # literal discriminator
  ```

### 2. ContextPart (product)

- **Dominio:** core
- **Lang:** Python
- **Algebraic:** product
- **Definicion:**
  ```python
  @dataclass
  class ContextPart:
      uri: str = ""                                    # referencia al contexto
      context_type: str = "memory"                     # "memory" | "resource" | "skill"
      abstract: str = ""                               # resumen del contexto
      type: str = "context"                            # literal discriminator
  ```

### 3. ToolPart (product)

- **Dominio:** core
- **Lang:** Python
- **Algebraic:** product
- **Definicion:**
  ```python
  @dataclass
  class ToolPart:
      tool_id: str = ""
      tool_name: str = ""
      tool_input: dict | None = None
      tool_output: str = ""
      tool_status: str = "pending"                     # "pending" | "running" | "completed" | "error"
      duration_ms: float | None = None
      prompt_tokens: int | None = None
      completion_tokens: int | None = None
      type: str = "tool"                               # literal discriminator
  ```

### 4. Part (sum type)

- **Dominio:** core
- **Lang:** Python
- **Algebraic:** sum
- **Definicion:**
  ```python
  Part = Union[TextPart, ContextPart, ToolPart]

  def part_from_dict(data: dict) -> Part:
      """Deserializa un Part desde dict, usando el campo 'type' como discriminador."""
  ```

### 5. Message (product)

- **Dominio:** core
- **Lang:** Python
- **Algebraic:** product
- **Definicion:**
  ```python
  @dataclass
  class Message:
      id: str
      role: str                   # "user" | "assistant"
      parts: list[Part]
      created_at: datetime | None = None
  ```
- **Metodos:**
  - `content -> str` (property) — texto del primer TextPart
  - `estimated_tokens -> int` (property) — estimacion ceil(total_chars / 4)
  - `to_dict() -> dict` — serializacion a JSONL-compatible dict
  - `from_dict(data: dict) -> Message` (classmethod) — deserializacion
  - `create_user(content: str) -> Message` (classmethod) — factory para mensajes de usuario
  - `create_assistant(content: str, context_refs=None, tool_calls=None) -> Message` (classmethod)
  - `get_context_parts() -> list[ContextPart]`
  - `get_tool_parts() -> list[ToolPart]`
  - `find_tool_part(tool_id: str) -> ToolPart | None`
  - `to_jsonl() -> str` — serializacion a string JSON
- **Descripcion:** Modelo de mensaje multipart para conversaciones con agentes. Soporta texto, referencias a contexto (memoria/recurso/skill) y llamadas a herramientas, todo en un mensaje unificado. Diseñado para ser serializable a JSONL para persistencia.
- **Estimacion de tokens:** Cuenta chars de text parts, abstracts de context parts, y tool inputs/outputs. Divide entre 4 (heuristica). Util para gestionar budgets de tokens en contexto de LLMs.
- **Uso:** Historial de conversacion, sesiones de agentes, logs de interaccion.
