'object', 'properties' => (object) []]`. * * Critical: do NOT include `company_id` or `user_id` in the schema. * Scoping is the host's responsibility and is injected at execute time. * * @return array */ abstract public function parameterSchema(): array; /** * Run the tool with the given arguments, scoped to the caller's session. * * Implementations MUST query within the given `$companyId` and never trust * any company/user identifier the LLM tries to sneak into `$arguments`. * * @param array $arguments Parsed from the LLM's tool_call JSON * @param int $companyId Injected from the current session — authoritative * @param int $userId Injected from the current session * @return mixed Anything JSON-encodable; will be serialized and sent back to the LLM */ abstract public function execute(array $arguments, int $companyId, int $userId): mixed; /** * Convert this tool into an OpenAI-style tools array entry. * * @return array */ public function toOpenAiToolSchema(): array { return [ 'type' => 'function', 'function' => [ 'name' => $this->name(), 'description' => $this->description(), 'parameters' => $this->parameterSchema(), ], ]; } /** * Normalize a model date field (which may be a string OR a Carbon instance * depending on model casts) to a YYYY-MM-DD string for tool output. * * Many InvoiceShelf models store dates as raw strings; others cast to Carbon. * Centralizing this avoids cast-guessing in every tool. */ protected function asDate(mixed $value): ?string { if ($value === null || $value === '') { return null; } if (is_object($value) && method_exists($value, 'toDateString')) { return $value->toDateString(); } // String like '2026-04-11' or '2026-04-11 00:00:00' — keep the date part only. if (is_string($value)) { return substr($value, 0, 10); } return null; } }