|null */ private ?array $sources = null; private ?float $startMicrotime = null; private ?float $endMicrotime = null; private ?int $authorProfileId = null; private ?int $systemPromptId = null; /** @var array|null */ private ?array $collections = null; private ?int $contextLimit = null; /** @var array|null */ private ?array $chunksUsed = null; private ?int $llmRequestId = null; private \DateTimeImmutable $createdAt; public function __construct(int $sessionId, MessageRole $role, string $content) { $this->sessionId = $sessionId; $this->role = $role; $this->content = $content; $this->tokens = TokenCount::zero(); $this->createdAt = new \DateTimeImmutable(); } // Factory methods public static function userMessage(int $sessionId, string $content): self { return new self($sessionId, MessageRole::USER, $content); } public static function assistantMessage(int $sessionId, string $content, string $model): self { $message = new self($sessionId, MessageRole::ASSISTANT, $content); $message->model = $model; return $message; } public static function systemMessage(int $sessionId, string $content): self { return new self($sessionId, MessageRole::SYSTEM, $content); } // Getters public function getId(): ?int { return $this->id; } public function getSessionId(): int { return $this->sessionId; } public function getRole(): MessageRole { return $this->role; } public function getContent(): string { return $this->content; } public function getModel(): ?string { return $this->model; } public function getTokensInput(): ?int { return $this->tokens->input() > 0 ? $this->tokens->input() : null; } public function getTokensOutput(): ?int { return $this->tokens->output() > 0 ? $this->tokens->output() : null; } public function getTotalTokens(): int { return $this->tokens->total(); } public function getTokenCount(): TokenCount { return $this->tokens; } /** * Get estimated cost in USD. */ public function getEstimatedCostUsd(): float { return $this->tokens->estimatedCostUsd(); } /** * @return array|null */ public function getSources(): ?array { return $this->sources; } public function getStartMicrotime(): ?float { return $this->startMicrotime; } public function getEndMicrotime(): ?float { return $this->endMicrotime; } public function getDurationMs(): ?float { if ($this->startMicrotime === null || $this->endMicrotime === null) { return null; } return ($this->endMicrotime - $this->startMicrotime) * 1000; } public function getAuthorProfileId(): ?int { return $this->authorProfileId; } public function getSystemPromptId(): ?int { return $this->systemPromptId; } /** * @return array|null */ public function getCollections(): ?array { return $this->collections; } public function getContextLimit(): ?int { return $this->contextLimit; } /** * @return array|null */ public function getChunksUsed(): ?array { return $this->chunksUsed; } public function getLlmRequestId(): ?int { return $this->llmRequestId; } public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; } // Setters public function setId(int $id): self { $this->id = $id; return $this; } public function setModel(?string $model): self { $this->model = $model; return $this; } public function setTokens(?int $input, ?int $output): self { $this->tokensInput = $input; $this->tokensOutput = $output; return $this; } /** * @param array|null $sources */ public function setSources(?array $sources): self { $this->sources = $sources; return $this; } public function setTiming(?float $start, ?float $end): self { $this->startMicrotime = $start; $this->endMicrotime = $end; return $this; } public function setAuthorProfileId(?int $authorProfileId): self { $this->authorProfileId = $authorProfileId; return $this; } public function setSystemPromptId(?int $systemPromptId): self { $this->systemPromptId = $systemPromptId; return $this; } /** * @param array|null $collections */ public function setCollections(?array $collections): self { $this->collections = $collections; return $this; } public function setContextLimit(?int $contextLimit): self { $this->contextLimit = $contextLimit; return $this; } /** * @param array|null $chunksUsed */ public function setChunksUsed(?array $chunksUsed): self { $this->chunksUsed = $chunksUsed; return $this; } public function setLlmRequestId(?int $llmRequestId): self { $this->llmRequestId = $llmRequestId; return $this; } public function setCreatedAt(\DateTimeImmutable $createdAt): self { $this->createdAt = $createdAt; return $this; } /** * Check if message is from user. */ public function isUser(): bool { return $this->role->isUser(); } /** * Check if message is from assistant. */ public function isAssistant(): bool { return $this->role->isAssistant(); } /** * Convert to array for persistence. * * @return array */ public function toArray(): array { return [ 'id' => $this->id, 'session_id' => $this->sessionId, 'role' => $this->role->value, 'content' => $this->content, 'model' => $this->model, 'tokens_input' => $this->tokensInput, 'tokens_output' => $this->tokensOutput, 'sources' => $this->sources !== null ? json_encode($this->sources) : null, 'start_microtime' => $this->startMicrotime, 'end_microtime' => $this->endMicrotime, 'author_profile_id' => $this->authorProfileId, 'system_prompt_id' => $this->systemPromptId, 'collections' => $this->collections !== null ? json_encode($this->collections) : null, 'context_limit' => $this->contextLimit, 'chunks_used' => $this->chunksUsed !== null ? json_encode($this->chunksUsed) : null, 'llm_request_id' => $this->llmRequestId, 'created_at' => $this->createdAt->format('Y-m-d H:i:s'), ]; } /** * Create from database row. * * @param array $data */ public static function fromArray(array $data): self { $message = new self( (int) $data['session_id'], MessageRole::from($data['role']), $data['content'] ); if (isset($data['id'])) { $message->setId((int) $data['id']); } if (isset($data['model'])) { $message->setModel($data['model']); } if (isset($data['tokens_input']) || isset($data['tokens_output'])) { $message->setTokens( isset($data['tokens_input']) ? (int) $data['tokens_input'] : null, isset($data['tokens_output']) ? (int) $data['tokens_output'] : null ); } if (isset($data['sources'])) { $sources = is_string($data['sources']) ? json_decode($data['sources'], true) : $data['sources']; $message->setSources(is_array($sources) ? $sources : null); } if (isset($data['start_microtime']) || isset($data['end_microtime'])) { $message->setTiming( isset($data['start_microtime']) ? (float) $data['start_microtime'] : null, isset($data['end_microtime']) ? (float) $data['end_microtime'] : null ); } if (isset($data['author_profile_id'])) { $message->setAuthorProfileId((int) $data['author_profile_id']); } if (isset($data['system_prompt_id'])) { $message->setSystemPromptId((int) $data['system_prompt_id']); } if (isset($data['collections'])) { $collections = is_string($data['collections']) ? json_decode($data['collections'], true) : $data['collections']; $message->setCollections(is_array($collections) ? $collections : null); } if (isset($data['context_limit'])) { $message->setContextLimit((int) $data['context_limit']); } if (isset($data['chunks_used'])) { $chunks = is_string($data['chunks_used']) ? json_decode($data['chunks_used'], true) : $data['chunks_used']; $message->setChunksUsed(is_array($chunks) ? $chunks : null); } if (isset($data['llm_request_id'])) { $message->setLlmRequestId((int) $data['llm_request_id']); } if (isset($data['created_at'])) { $message->setCreatedAt(new \DateTimeImmutable($data['created_at'])); } return $message; } }