From 9ae46aba7513e33b688fbac0170267870c1b0df8 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Tue, 23 Dec 2025 14:02:24 +0000 Subject: [PATCH 01/54] feat: Refactor Anthropic driver --- .github/dependabot.yml | 2 +- .github/workflows/run-tests.yml | 4 + .github/workflows/static-analysis.yml | 4 + composer.json | 1 + src/LLM/Drivers/Anthropic/AnthropicChat.php | 128 +----- .../Anthropic/Concerns/MapsFinishReason.php | 26 ++ .../Anthropic/Concerns/MapsResponse.php | 97 +++++ .../Drivers/Anthropic/Concerns/MapsUsage.php | 23 ++ src/LLM/LLMManager.php | 40 +- src/SDK/Anthropic/Anthropic.php | 63 +++ src/SDK/Anthropic/Contracts/StreamEvent.php | 16 + src/SDK/Anthropic/Data/Messages/Message.php | 81 ++++ .../Anthropic/Data/Messages/MessageStream.php | 87 ++++ src/SDK/Anthropic/Data/Messages/Meta.php | 38 ++ .../Messages/RedactedThinkingContentBlock.php | 23 ++ .../Streaming/Events/AbstractStreamEvent.php | 38 ++ .../Streaming/Events/ContentBlockDelta.php | 43 ++ .../Streaming/Events/ContentBlockStart.php | 43 ++ .../Streaming/Events/ContentBlockStop.php | 26 ++ .../Streaming/Events/MessageDelta.php | 30 ++ .../Streaming/Events/MessageStart.php | 26 ++ .../Messages/Streaming/Events/MessageStop.php | 17 + .../Data/Messages/Streaming/Events/Ping.php | 17 + .../Messages/Streaming/Events/Unknown.php | 25 ++ .../Messages/Streaming/InputJsonDelta.php | 24 ++ .../Messages/Streaming/SignatureDelta.php | 24 ++ .../Data/Messages/Streaming/TextDelta.php | 24 ++ .../Data/Messages/Streaming/ThinkingDelta.php | 24 ++ .../Data/Messages/TextContentBlock.php | 21 + .../Data/Messages/ThinkingContentBlock.php | 23 ++ .../Data/Messages/ToolUseContentBlock.php | 25 ++ src/SDK/Anthropic/Data/Messages/Usage.php | 29 ++ src/SDK/Anthropic/Requests/CreateMessage.php | 54 +++ src/SDK/Anthropic/Resources/Messages.php | 26 ++ tests/ArchitectureTest.php | 20 +- tests/Pest.php | 10 +- .../Drivers/Anthropic/AnthropicChatTest.php | 54 +-- tests/Unit/SDK/Anthropic/AnthropicTest.php | 387 ++++++++++++++++++ .../anthropic/messages/redacted-thinking.json | 31 ++ .../anthropic/messages/simple-streamed.json | 33 ++ .../sdk/anthropic/messages/simple.json | 32 ++ .../messages/structured-output-streamed.json | 32 ++ .../anthropic/messages/structured-output.json | 31 ++ .../anthropic/messages/thinking-streamed.json | 32 ++ .../sdk/anthropic/messages/thinking.json | 31 ++ .../anthropic/messages/tool-use-streamed.json | 32 ++ .../sdk/anthropic/messages/tool-use.json | 31 ++ 47 files changed, 1753 insertions(+), 175 deletions(-) create mode 100644 src/LLM/Drivers/Anthropic/Concerns/MapsFinishReason.php create mode 100644 src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php create mode 100644 src/LLM/Drivers/Anthropic/Concerns/MapsUsage.php create mode 100644 src/SDK/Anthropic/Anthropic.php create mode 100644 src/SDK/Anthropic/Contracts/StreamEvent.php create mode 100644 src/SDK/Anthropic/Data/Messages/Message.php create mode 100644 src/SDK/Anthropic/Data/Messages/MessageStream.php create mode 100644 src/SDK/Anthropic/Data/Messages/Meta.php create mode 100644 src/SDK/Anthropic/Data/Messages/RedactedThinkingContentBlock.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/AbstractStreamEvent.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/SignatureDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/TextDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/ThinkingDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/TextContentBlock.php create mode 100644 src/SDK/Anthropic/Data/Messages/ThinkingContentBlock.php create mode 100644 src/SDK/Anthropic/Data/Messages/ToolUseContentBlock.php create mode 100644 src/SDK/Anthropic/Data/Messages/Usage.php create mode 100644 src/SDK/Anthropic/Requests/CreateMessage.php create mode 100644 src/SDK/Anthropic/Resources/Messages.php create mode 100644 tests/Unit/SDK/Anthropic/AnthropicTest.php create mode 100644 tests/fixtures/sdk/anthropic/messages/redacted-thinking.json create mode 100644 tests/fixtures/sdk/anthropic/messages/simple-streamed.json create mode 100644 tests/fixtures/sdk/anthropic/messages/simple.json create mode 100644 tests/fixtures/sdk/anthropic/messages/structured-output-streamed.json create mode 100644 tests/fixtures/sdk/anthropic/messages/structured-output.json create mode 100644 tests/fixtures/sdk/anthropic/messages/thinking-streamed.json create mode 100644 tests/fixtures/sdk/anthropic/messages/thinking.json create mode 100644 tests/fixtures/sdk/anthropic/messages/tool-use-streamed.json create mode 100644 tests/fixtures/sdk/anthropic/messages/tool-use.json diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 189ffd8..9856038 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -7,7 +7,7 @@ updates: labels: - "dependencies" - "composer" - versioning-strategy: "widen" + versioning-strategy: "increase-if-necessary" open-pull-requests-limit: 10 - package-ecosystem: "github-actions" diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml index 4a603f9..11e0d5f 100644 --- a/.github/workflows/run-tests.yml +++ b/.github/workflows/run-tests.yml @@ -1,5 +1,9 @@ name: Tests +permissions: + contents: read + pull-requests: write + on: [push] concurrency: diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index ee0f8c3..0038fba 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -1,5 +1,9 @@ name: Static Analysis +permissions: + contents: read + pull-requests: write + on: [push] concurrency: diff --git a/composer.json b/composer.json index f92a4ef..8256b03 100644 --- a/composer.json +++ b/composer.json @@ -28,6 +28,7 @@ "psr-discovery/cache-implementations": "^1.2", "psr-discovery/event-dispatcher-implementations": "^1.1", "react/async": "^4.3", + "saloonphp/saloon": "^3.14", "spatie/laravel-package-tools": "^1.17" }, "require-dev": { diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 541df14..433e65c 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -8,25 +8,21 @@ use Throwable; use JsonException; use DateTimeImmutable; -use Cortex\LLM\Data\Usage; use Cortex\LLM\AbstractLLM; use Illuminate\Support\Arr; use Cortex\LLM\Data\ToolCall; use Cortex\LLM\Contracts\Tool; -use Cortex\Events\ChatModelEnd; use Cortex\LLM\Data\ChatResult; use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Enums\ToolChoice; -use Anthropic\Testing\ClientFake; use Cortex\Events\ChatModelError; use Cortex\Events\ChatModelStart; use Cortex\LLM\Contracts\Message; use Cortex\LLM\Data\FunctionCall; use Cortex\LLM\Enums\MessageRole; use Cortex\Events\ChatModelStream; -use Cortex\LLM\Enums\FinishReason; use Cortex\Exceptions\LLMException; -use Cortex\LLM\Data\ChatGeneration; +use Cortex\SDK\Anthropic\Anthropic; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ResponseMetadata; use Anthropic\Contracts\ClientContract; @@ -38,19 +34,21 @@ use Cortex\LLM\Data\Messages\SystemMessage; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Data\Messages\MessageCollection; -use Anthropic\Responses\Messages\CreateResponse; use Anthropic\Responses\Messages\StreamResponse; use Cortex\LLM\Data\Messages\Content\TextContent; -use Anthropic\Responses\Messages\CreateResponseUsage; -use Cortex\LLM\Data\Messages\Content\ReasoningContent; -use Anthropic\Responses\Messages\CreateResponseContent; -use Anthropic\Responses\Messages\CreateStreamedResponseUsage; +use Cortex\LLM\Drivers\Anthropic\Concerns\MapsUsage; +use Cortex\LLM\Drivers\Anthropic\Concerns\MapsResponse; +use Cortex\LLM\Drivers\Anthropic\Concerns\MapsFinishReason; use Anthropic\Testing\Responses\Fixtures\Messages\CreateResponseFixture; class AnthropicChat extends AbstractLLM { + use MapsUsage; + use MapsResponse; + use MapsFinishReason; + public function __construct( - protected readonly ClientContract $client, + protected readonly Anthropic $client, protected string $model, protected ModelProvider $modelProvider = ModelProvider::Anthropic, protected bool $ignoreModelFeatures = false, @@ -64,7 +62,7 @@ public function invoke( ): ChatResult|ChatStreamResult { $messages = $this->resolveMessages($messages); - [$systemMessages, $messages] = $messages->partition( + $systemMessages = $messages->filter( fn(Message $message): bool => $message instanceof SystemMessage, ); @@ -88,7 +86,7 @@ public function invoke( try { return $this->streaming - ? $this->mapStreamResponse($this->client->messages()->createStreamed($params)) + ? $this->mapStreamResponse($this->client->messages()->stream($params)) : $this->mapResponse($this->client->messages()->create($params)); } catch (Throwable $e) { $this->dispatchEvent(new ChatModelError($this, $params, $e)); @@ -97,76 +95,6 @@ public function invoke( } } - /** - * Map a standard (non-streaming) response to a ChatResult. - */ - protected function mapResponse(CreateResponse $response): ChatResult - { - $toolCalls = array_filter( - $response->content, - fn(CreateResponseContent $content): bool => $content->type === 'tool_use', - ); - - $toolCalls = collect($toolCalls) - ->map(fn(CreateResponseContent $content): ToolCall => new ToolCall( - $content->id, - new FunctionCall($content->name, $content->input ?? []), - )) - ->values() - ->all(); - - $toolCalls = $toolCalls !== [] - ? new ToolCallCollection($toolCalls) - : null; - - $usage = $this->mapUsage($response->usage); - $finishReason = static::mapFinishReason($response->stop_reason ?? null); - - $contents = collect($response->content) - ->map(function (CreateResponseContent $content): TextContent|ReasoningContent|null { - return match ($content->type) { - 'text' => new TextContent($content->text), - // TODO: Use different anthropic client, since current one seems abandoned. - 'thinking' => $content->id !== null ? new ReasoningContent( - $content->id, - $content->text ?? '', - ) : null, - // 'tool_use' => new ToolUseContent($content->tool_use), - default => null, - }; - }) - ->filter() - ->all(); - - $generation = new ChatGeneration( - message: new AssistantMessage( - content: $contents, - toolCalls: $toolCalls, - metadata: new ResponseMetadata( - id: $response->id, - model: $response->model, - provider: $this->modelProvider, - finishReason: $finishReason, - usage: $usage, - ), - ), - createdAt: new DateTimeImmutable(), - finishReason: $finishReason, - ); - - $generation = $this->applyOutputParserIfApplicable($generation); - - $result = new ChatResult( - $generation, - $usage, - $response->toArray(), // @phpstan-ignore argument.type - ); - - $this->dispatchEvent(new ChatModelEnd($this, $result)); - - return $result; - } - /** * Map a streaming response to a ChatStreamResult. * @@ -304,20 +232,6 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult }); } - /** - * Map the OpenAI usage response to a Usage object. - */ - protected function mapUsage(CreateResponseUsage|CreateStreamedResponseUsage $usage): Usage - { - return new Usage( - promptTokens: $usage->inputTokens ?? 0, - completionTokens: $usage->outputTokens ?? null, - cachedTokens: $usage->cacheCreationInputTokens ?? null, - inputCost: $this->modelProvider->inputCostForTokens($this->model, $usage->inputTokens ?? 0), - outputCost: $this->modelProvider->outputCostForTokens($this->model, $usage->outputTokens ?? 0), - ); - } - /** * Take the given messages and format them for the OpenAI API. * @@ -385,21 +299,6 @@ protected static function mapMessagesForInput(MessageCollection $messages): arra ->toArray(); } - protected static function mapFinishReason(?string $finishReason): ?FinishReason - { - if ($finishReason === null) { - return null; - } - - return match ($finishReason) { - 'end_turn' => FinishReason::Stop, - 'max_tokens' => FinishReason::Length, - 'stop_sequence' => FinishReason::StopSequence, - 'tool_use' => FinishReason::ToolCalls, - default => FinishReason::Unknown, - }; - } - /** * @param array $additionalParameters * @@ -457,12 +356,9 @@ public function getClient(): ClientContract return $this->client; } - /** - * @param array $responses - */ public static function fake(array $responses, ?string $model = null, ?ModelProvider $modelProvider = null): self { - $client = new ClientFake($responses); + $client = Anthropic::fake($responses); return new self( $client, diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsFinishReason.php b/src/LLM/Drivers/Anthropic/Concerns/MapsFinishReason.php new file mode 100644 index 0000000..a87aba0 --- /dev/null +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsFinishReason.php @@ -0,0 +1,26 @@ + FinishReason::Stop, + 'max_tokens' => FinishReason::Length, + 'stop_sequence' => FinishReason::StopSequence, + 'tool_use' => FinishReason::ToolCalls, + default => FinishReason::Unknown, + }; + } +} diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php new file mode 100644 index 0000000..0eb867b --- /dev/null +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php @@ -0,0 +1,97 @@ +mapUsage($message->usage); + $finishReason = $this->mapFinishReason($message->stopReason); + $meta = $message->getMeta(); + + $toolCalls = collect($message->content) + ->filter(fn(object $content): bool => $content instanceof ToolUseContentBlock) + ->map(function (ToolUseContentBlock $content): ToolCall { + return new ToolCall( + $content->id, + new FunctionCall( + $content->name, + $content->input, + ), + ); + }) + ->values() + ->all(); + + $generation = new ChatGeneration( + message: new AssistantMessage( + content: $this->mapContent($message->content), + toolCalls: new ToolCallCollection($toolCalls), + metadata: new ResponseMetadata( + id: $message->id ?? 'unknown', + model: $message->model, + provider: $this->modelProvider, + finishReason: $finishReason, + usage: $usage, + processingTime: $meta?->processingTime, + providerMetadata: $meta?->raw ?? [], + ), + id: $message->id, + ), + createdAt: $meta?->createdAt ?? new DateTimeImmutable(), + finishReason: $finishReason, + ); + + $generation = $this->applyOutputParserIfApplicable($generation); + + /** @var array|null $rawResponse */ + $rawResponse = $this->includeRaw + ? $message->getResponse()->json() + : null; + + return new ChatResult( + $generation, + $usage, + $rawResponse, // @phpstan-ignore argument.type + ); + } + + protected function mapContent(array $content): array + { + return collect($content) + ->reject(fn(object $content): bool => $content instanceof ToolUseContentBlock) + ->map(function (object $content): mixed { + return match (true) { + $content instanceof TextContentBlock => new TextContent($content->text), + $content instanceof ThinkingContentBlock => new ReasoningContent($content->signature, $content->thinking), + // 'redacted_thinking' => new RedactedThinkingContent($content['redacted_thinking']), + default => null, + }; + }) + ->filter() + ->values() + ->all(); + } +} diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsUsage.php b/src/LLM/Drivers/Anthropic/Concerns/MapsUsage.php new file mode 100644 index 0000000..fef4761 --- /dev/null +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsUsage.php @@ -0,0 +1,23 @@ +inputTokens, + completionTokens: $usage->outputTokens, + cachedTokens: $usage->cacheCreationInputTokens, + inputCost: $this->modelProvider->inputCostForTokens($this->model, $usage->inputTokens), + outputCost: $this->modelProvider->outputCostForTokens($this->model, $usage->outputTokens), + ); + } +} diff --git a/src/LLM/LLMManager.php b/src/LLM/LLMManager.php index 5f26771..b0a79e7 100644 --- a/src/LLM/LLMManager.php +++ b/src/LLM/LLMManager.php @@ -6,13 +6,13 @@ use OpenAI; use Override; -use Anthropic; use Illuminate\Support\Arr; use Illuminate\Support\Str; use Cortex\LLM\Contracts\LLM; use InvalidArgumentException; use Cortex\LLM\Enums\LLMDriver; use Illuminate\Support\Manager; +use Cortex\SDK\Anthropic\Anthropic; use OpenAI\Contracts\ClientContract; use Cortex\ModelInfo\Enums\ModelProvider; use Cortex\LLM\Drivers\OpenAI\Chat\OpenAIChat; @@ -131,28 +131,34 @@ public function createOpenAIResponsesDriver(array $config, string $name): OpenAI */ public function createAnthropicDriver(array $config, string $name): AnthropicChat { - $client = Anthropic::factory() - ->withApiKey(Arr::get($config, 'options.api_key') ?? '') - ->withHttpHeader('anthropic-version', Arr::get($config, 'options.version', '2023-06-01')); - - foreach (Arr::get($config, 'options.headers', []) as $key => $value) { - $client->withHttpHeader($key, $value); - } - - foreach (Arr::get($config, 'options.query_params', []) as $key => $value) { - $client->withQueryParam($key, $value); - } - - if ($baseUri = Arr::get($config, 'options.base_uri')) { - $client->withBaseUri($baseUri); - } + // $client = Anthropic::factory() + // ->withApiKey(Arr::get($config, 'options.api_key') ?? '') + // ->withHttpHeader('anthropic-version', Arr::get($config, 'options.version', '2023-06-01')); + + // foreach (Arr::get($config, 'options.headers', []) as $key => $value) { + // $client->withHttpHeader($key, $value); + // } + + // foreach (Arr::get($config, 'options.query_params', []) as $key => $value) { + // $client->withQueryParam($key, $value); + // } + + // if ($baseUri = Arr::get($config, 'options.base_uri')) { + // $client->withBaseUri($baseUri); + // } + + $client = new Anthropic( + Arr::get($config, 'options.api_key') ?? '', + Arr::get($config, 'options.base_uri'), + Arr::get($config, 'options.version'), + ); if (! isset($config['default_model'])) { throw new InvalidArgumentException('default_model is required.'); } $driver = new AnthropicChat( - $client->make(), + $client, $config['default_model'], $this->getModelProviderFromConfig($config, $name), $this->config->get('cortex.model_info.ignore_features', false), diff --git a/src/SDK/Anthropic/Anthropic.php b/src/SDK/Anthropic/Anthropic.php new file mode 100644 index 0000000..1c42aaa --- /dev/null +++ b/src/SDK/Anthropic/Anthropic.php @@ -0,0 +1,63 @@ +baseUri ?? static::defaultBaseUri(); + } + + protected function defaultAuth(): HeaderAuthenticator + { + return new HeaderAuthenticator($this->apiKey, 'x-api-key'); + } + + protected function defaultHeaders(): array + { + return [ + 'anthropic-version' => $this->version ?? static::defaultVersion(), + ]; + } + + protected static function defaultBaseUri(): string + { + return 'https://api.anthropic.com/v1'; + } + + protected static function defaultVersion(): string + { + return '2023-06-01'; + } + + /** + * @param array $responses + */ + public static function fake(array $responses): self + { + MockClient::global($responses); + + return new self('test-api-key'); + } +} diff --git a/src/SDK/Anthropic/Contracts/StreamEvent.php b/src/SDK/Anthropic/Contracts/StreamEvent.php new file mode 100644 index 0000000..28f83ae --- /dev/null +++ b/src/SDK/Anthropic/Contracts/StreamEvent.php @@ -0,0 +1,16 @@ + TextContentBlock::from($content), + 'thinking' => ThinkingContentBlock::from($content), + 'redacted_thinking' => RedactedThinkingContentBlock::from($content), + 'tool_use' => ToolUseContentBlock::from($content), + default => throw new InvalidArgumentException('Invalid content type: ' . $content['type']), + }; + }, $payload['content']); + + return new self( + model: $payload['model'], + id: $payload['id'], + type: $payload['type'], + role: $payload['role'], + content: $content, + stopReason: $payload['stop_reason'] ?? null, + stopSequence: $payload['stop_sequence'] ?? null, + usage: $payload['usage'] ? Usage::from($payload['usage']) : null, + ); + } + + public static function fromResponse(Response $response): self + { + return self::from($response->json()) + ->setResponse($response); + } + + public function setResponse(Response $response): self + { + $this->response = $response; + $this->meta = Meta::from($response->headers()->all()); + + return $this; + } + + public function setMeta(Meta $meta): self + { + $this->meta = $meta; + + return $this; + } + + public function getResponse(): ?Response + { + return $this->response; + } + + public function getMeta(): ?Meta + { + return $this->meta; + } +} diff --git a/src/SDK/Anthropic/Data/Messages/MessageStream.php b/src/SDK/Anthropic/Data/Messages/MessageStream.php new file mode 100644 index 0000000..8fd7380 --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/MessageStream.php @@ -0,0 +1,87 @@ + + */ + public function getIterator(): Generator + { + $response = $this->response->getPsrResponse(); + $headers = $this->response->headers()->all(); + + while (! $response->getBody()->eof()) { + $line = $this->readLine($response->getBody()); + + if (! str_starts_with($line, 'data:')) { + continue; + } + + $data = trim(substr($line, strlen('data:'))); + + $payload = json_decode($data, true, flags: JSON_THROW_ON_ERROR); + + $event = match ($payload['type']) { + 'message_start' => MessageStart::from($payload), + 'message_delta' => MessageDelta::from($payload), + 'message_stop' => MessageStop::from($payload), + 'content_block_start' => ContentBlockStart::from($payload), + 'content_block_delta' => ContentBlockDelta::from($payload), + 'content_block_stop' => ContentBlockStop::from($payload), + 'ping' => Ping::from($payload), + default => Unknown::from($payload), + }; + + $event->setMeta(Meta::from($headers)); + + yield $event; + } + } + + public function getResponse(): Response + { + return $this->response; + } + + private function readLine(StreamInterface $stream): string + { + $buffer = ''; + + while (! $stream->eof()) { + if ('' === ($byte = $stream->read(1))) { + return $buffer; + } + + $buffer .= $byte; + + if ($byte === "\n") { + break; + } + } + + return $buffer; + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Meta.php b/src/SDK/Anthropic/Data/Messages/Meta.php new file mode 100644 index 0000000..900c71e --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Meta.php @@ -0,0 +1,38 @@ +payload; + } + + public function getMeta(): ?Meta + { + return $this->meta; + } + + public function setRaw(array $payload): static + { + $this->payload = $payload; + + return $this; + } + + public function setMeta(Meta $meta): static + { + $this->meta = $meta; + + return $this; + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php new file mode 100644 index 0000000..8f2dad5 --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php @@ -0,0 +1,43 @@ + TextDelta::from($payload['delta']), + 'input_json_delta' => InputJsonDelta::from($payload['delta']), + 'thinking_delta' => ThinkingDelta::from($payload['delta']), + 'signature_delta' => SignatureDelta::from($payload['delta']), + default => throw new InvalidArgumentException('Invalid delta type: ' . $payload['delta']['type']), + }; + + $instance = new self( + index: $payload['index'], + delta: $delta, + ); + + return $instance->setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php new file mode 100644 index 0000000..0ab6663 --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php @@ -0,0 +1,43 @@ + TextContentBlock::from($payload['content_block']), + 'thinking' => ThinkingContentBlock::from($payload['content_block']), + 'redacted_thinking' => RedactedThinkingContentBlock::from($payload['content_block']), + 'tool_use' => ToolUseContentBlock::from($payload['content_block']), + default => throw new InvalidArgumentException('Invalid content type: ' . $payload['content_block']['type']), + }; + + $instance = new self( + index: $payload['index'], + contentBlock: $contentBlock, + ); + + return $instance->setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php new file mode 100644 index 0000000..fdbcdab --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php @@ -0,0 +1,26 @@ +setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php new file mode 100644 index 0000000..12e1ac2 --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php @@ -0,0 +1,30 @@ +setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php new file mode 100644 index 0000000..e740438 --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php @@ -0,0 +1,26 @@ +setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php new file mode 100644 index 0000000..ec0db4a --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php @@ -0,0 +1,17 @@ +setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php new file mode 100644 index 0000000..b0e899a --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php @@ -0,0 +1,17 @@ +setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php new file mode 100644 index 0000000..31dd0bd --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php @@ -0,0 +1,25 @@ +setRaw($payload); + } +} diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php new file mode 100644 index 0000000..4f383aa --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php @@ -0,0 +1,24 @@ +parameters, + fn(string $key): bool => $key !== 'betas', + ARRAY_FILTER_USE_KEY, + ); + } + + protected function defaultHeaders(): array + { + $betas = $this->parameters['betas'] ?? []; + + return [ + 'anthropic-beta' => implode(',', $betas), + ]; + } + + public function createDtoFromResponse(Response $response): Message|MessageStream + { + return $this->parameters['stream'] ?? false + ? new MessageStream($response) + : Message::fromResponse($response); + } +} diff --git a/src/SDK/Anthropic/Resources/Messages.php b/src/SDK/Anthropic/Resources/Messages.php new file mode 100644 index 0000000..a4f7326 --- /dev/null +++ b/src/SDK/Anthropic/Resources/Messages.php @@ -0,0 +1,26 @@ +connector->send(new CreateMessage($parameters))->dtoOrFail(); + } + + public function stream(array $parameters): MessageStream + { + return $this->create([ + ...$parameters, + 'stream' => true, + ]); + } +} diff --git a/tests/ArchitectureTest.php b/tests/ArchitectureTest.php index 0501ec3..5cad342 100644 --- a/tests/ArchitectureTest.php +++ b/tests/ArchitectureTest.php @@ -6,13 +6,31 @@ use Throwable; use Cortex\Contracts\OutputParser; +use Cortex\Memory\Contracts\Store; +use Cortex\Contracts\CortexException; use Illuminate\Support\Facades\Facade; +use Cortex\Agents\AbstractAgentBuilder; +use Cortex\Embeddings\Contracts\Embeddings; +use Cortex\Prompts\Contracts\PromptFactory; +use Cortex\Prompts\Contracts\PromptCompiler; -// arch()->preset()->php(); +arch()->preset()->php(); arch()->preset()->security(); arch()->expect('Cortex\Contracts')->toBeInterfaces(); + arch()->expect('Cortex\Enums')->toBeEnums(); +arch()->expect('Cortex\LLM\Enums')->toBeEnums(); +arch()->expect('Cortex\Prompts\Enums')->toBeEnums(); +arch()->expect('Cortex\AGUI\Enums')->toBeEnums(); + arch()->expect('Cortex\Exceptions')->toExtend(Throwable::class); arch()->expect('Cortex\Facades')->toExtend(Facade::class); +arch()->expect('Cortex\Agents\Prebuilt')->toExtend(AbstractAgentBuilder::class); + +arch()->expect('Cortex\Exceptions')->toImplement(CortexException::class); arch()->expect('Cortex\OutputParsers')->toImplement(OutputParser::class); +arch()->expect('Cortex\Embeddings\Drivers')->toImplement(Embeddings::class); +arch()->expect('Cortex\Memory\Stores')->toImplement(Store::class); +arch()->expect('Cortex\Prompts\Compilers')->toImplement(PromptCompiler::class); +arch()->expect('Cortex\Prompts\Factories')->toImplement(PromptFactory::class); diff --git a/tests/Pest.php b/tests/Pest.php index 90d1a71..faf346e 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -2,6 +2,14 @@ declare(strict_types=1); +use Saloon\Config; +use Saloon\MockConfig; use Cortex\Tests\TestCase; +use Saloon\Http\Faking\MockClient; -uses(TestCase::class)->in('Unit'); +Config::preventStrayRequests(); +MockConfig::setFixturePath('tests/fixtures/sdk'); + +uses(TestCase::class) + ->in('Unit') + ->beforeEach(fn() => MockClient::destroyGlobal()); diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index e06d13e..010e359 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -13,6 +13,7 @@ use Cortex\LLM\Data\ChatResult; use Cortex\LLM\Data\FunctionCall; use Cortex\LLM\Data\ChatGeneration; +use Saloon\Http\Faking\MockResponse; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ToolCallCollection; use Cortex\LLM\Data\ChatGenerationChunk; @@ -22,38 +23,26 @@ use Anthropic\Responses\Meta\MetaInformation; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Drivers\Anthropic\AnthropicChat; +use Cortex\SDK\Anthropic\Requests\CreateMessage; use Cortex\LLM\Data\Messages\Content\TextContent; use Anthropic\Responses\Messages\CreateResponse as ChatCreateResponse; use Anthropic\Responses\Messages\CreateStreamedResponse as ChatCreateStreamedResponse; test('it responds to messages', function (): void { $llm = AnthropicChat::fake([ - ChatCreateResponse::fake([ - 'content' => [ - [ - 'type' => 'text', - 'text' => 'I am doing well, thank you for asking!', - ], - ], - ]), + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), ]); - $result = $llm->invoke([ + $result = $llm->includeRaw()->invoke([ new UserMessage('Hello, how are you?'), ]); expect($result)->toBeInstanceOf(ChatResult::class) - ->and($result->rawResponse) - ->toBeArray()->not->toBeEmpty() - ->and($result->generation) - ->toBeInstanceOf(ChatGeneration::class) - ->and($result->generation->message) - ->toBeInstanceOf(AssistantMessage::class) - ->and($result->generation->message->content) - ->toBeArray() - ->toContainOnlyInstancesOf(TextContent::class) - ->and($result->generation->message->content[0]->text) - ->toBe('I am doing well, thank you for asking!'); + ->and($result->rawResponse)->toBeArray()->not->toBeEmpty() + ->and($result->generation)->toBeInstanceOf(ChatGeneration::class) + ->and($result->generation->message)->toBeInstanceOf(AssistantMessage::class) + ->and($result->generation->message->content)->toBeArray()->toContainOnlyInstancesOf(TextContent::class) + ->and($result->generation->message->content[0]->text)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?"); }); test('it can stream', function (): void { @@ -81,30 +70,18 @@ test('it can use tools', function (): void { $llm = AnthropicChat::fake([ - createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'tool_use', - 'id' => 'call_123', - 'name' => 'multiply', - 'input' => [ - 'x' => 3, - 'y' => 4, - ], - ], - ], - ]), + CreateMessage::class => MockResponse::fixture('anthropic/messages/tool-use'), ]); $llm->addFeature(ModelFeature::ToolCalling); $llm->withTools([ - #[Tool(name: 'multiply', description: 'Multiply two numbers')] - fn(int $x, int $y): int => $x * $y, + #[Tool(name: 'get_weather', description: 'Get the current weather in a given location')] + fn(string $location): string => sprintf('The weather in %s is raining', $location), ]); $result = $llm->invoke([ - new UserMessage('What is 3 times 4?'), + new UserMessage('What is the weather in Manchester?'), ]); expect($result->generation->message->toolCalls) @@ -116,11 +93,10 @@ ->and($result->generation->message->toolCalls[0]->function) ->toBeInstanceOf(FunctionCall::class) ->and($result->generation->message->toolCalls[0]->function->name) - ->toBe('multiply') + ->toBe('get_weather') ->and($result->generation->message->toolCalls[0]->function->arguments) ->toBe([ - 'x' => 3, - 'y' => 4, + 'location' => 'Manchester, UK', ]); }); diff --git a/tests/Unit/SDK/Anthropic/AnthropicTest.php b/tests/Unit/SDK/Anthropic/AnthropicTest.php new file mode 100644 index 0000000..95bd0ca --- /dev/null +++ b/tests/Unit/SDK/Anthropic/AnthropicTest.php @@ -0,0 +1,387 @@ + MockResponse::fixture('anthropic/messages/simple'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $message = $anthropic->messages()->create([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'Hello, how are you?', + ], + ], + ]); + + expect($message)->toBeInstanceOf(Message::class) + ->and($message->content)->toHaveCount(1) + ->and($message->content[0])->toBeInstanceOf(TextContentBlock::class) + ->and($message->content[0]->text)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?") + ->and($message->usage)->toBeInstanceOf(Usage::class); + }); + + test('it can create a message with thinking', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/thinking'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $message = $anthropic->messages()->create([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 2048, + 'thinking' => [ + 'type' => 'enabled', + 'budget_tokens' => 1024, + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'What is the weight of the moon?', + ], + ], + ]); + + expect($message)->toBeInstanceOf(Message::class) + ->and($message->content)->toHaveCount(2) + ->and($message->content[0])->toBeInstanceOf(ThinkingContentBlock::class) + ->and($message->content[0]->thinking)->toBeString() + ->and($message->content[0]->signature)->toBeString() + ->and($message->content[1])->toBeInstanceOf(TextContentBlock::class) + ->and($message->content[1]->text)->toBeString() + ->and($message->usage)->toBeInstanceOf(Usage::class); + }); + + test('it can create a message with redacted thinking', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/redacted-thinking'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $message = $anthropic->messages()->create([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 2048, + 'betas' => ['interleaved-thinking-2025-05-14'], + 'thinking' => [ + 'type' => 'enabled', + 'budget_tokens' => 1024, + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'ANTHROPIC_MAGIC_STRING_TRIGGER_REDACTED_THINKING_46C9A13E193C177646C7398A98432ECCCE4C1253D5E2D82641AC0E52CC2876CB', + ], + ], + ]); + + dd($message); + + // $response = $message->getResponse(); + + // expect($response->json())->toBeArray(); + // expect($response->status())->toBe(200); + + // dump($message); + })->todo('Does not trigger redacted thinking for some reason'); + + test('it can create a message with tool use', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/tool-use'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $message = $anthropic->messages()->create([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'tools' => [ + [ + 'name' => 'get_weather', + 'description' => 'Get the current weather in a given location', + 'input_schema' => [ + 'type' => 'object', + 'properties' => [ + 'location' => [ + 'type' => 'string', + 'description' => 'The city and country, e.g. Manchester, UK', + ], + 'unit' => [ + 'type' => 'string', + 'enum' => ['celsius', 'fahrenheit'], + 'description' => 'The unit of temperature, either "celsius" or "fahrenheit"', + ], + ], + 'required' => ['location'], + ], + ], + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'What is the weather in Manchester?', + ], + ], + ]); + + expect($message)->toBeInstanceOf(Message::class) + ->and($message->content)->toHaveCount(1) + ->and($message->content[0])->toBeInstanceOf(ToolUseContentBlock::class) + ->and($message->content[0]->name)->toBe('get_weather') + ->and($message->content[0]->input)->toBe([ + 'location' => 'Manchester, UK', + ]) + ->and($message->usage)->toBeInstanceOf(Usage::class); + }); + + test('it can create a message with structured output', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $message = $anthropic->messages()->create([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'betas' => ['structured-outputs-2025-11-13'], + 'output_format' => [ + 'type' => 'json_schema', + 'schema' => Schema::object() + ->properties( + Schema::string('name')->required(), + Schema::string('email')->required(), + Schema::string('plan_interest')->required(), + Schema::boolean('demo_requested')->required(), + ) + ->additionalProperties(false) + ->toArray(), + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.', + ], + ], + ]); + + expect($message)->toBeInstanceOf(Message::class) + ->and($message->content)->toHaveCount(1) + ->and($message->content[0])->toBeInstanceOf(TextContentBlock::class) + ->and($message->content[0]->text)->toBe('{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}') + ->and($message->usage)->toBeInstanceOf(Usage::class); + }); +}); + +describe('Streaming messages', function (): void { + test('it can create a streamed message', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple-streamed'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $response = $anthropic->messages()->stream([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'Hello, how are you?', + ], + ], + ]); + + expect($response)->toBeInstanceOf(MessageStream::class) + ->and($response->getIterator())->toBeInstanceOf(Generator::class); + + $events = iterator_to_array($response->getIterator()); + + expect($events)->toHaveCount(10) + ->and($events[0])->toBeInstanceOf(MessageStart::class) + ->and($events[1])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[2])->toBeInstanceOf(ContentBlockDelta::class) + ->and($events[7])->toBeInstanceOf(ContentBlockStop::class) + ->and($events[8])->toBeInstanceOf(MessageDelta::class) + ->and($events[9])->toBeInstanceOf(MessageStop::class); + }); + + test('it can create a streamed message with thinking', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/thinking-streamed'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $response = $anthropic->messages()->stream([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 2048, + 'thinking' => [ + 'type' => 'enabled', + 'budget_tokens' => 1024, + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'What is the weight of the moon?', + ], + ], + ]); + + expect($response)->toBeInstanceOf(MessageStream::class) + ->and($response->getIterator())->toBeInstanceOf(Generator::class); + + $events = iterator_to_array($response->getIterator()); + + expect($events)->toHaveCount(112) + ->and($events[0])->toBeInstanceOf(MessageStart::class) + ->and($events[1])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[1]->contentBlock)->toBeInstanceOf(ThinkingContentBlock::class) + + ->and($events[2])->toBeInstanceOf(ContentBlockDelta::class) + ->and($events[2]->delta)->toBeInstanceOf(ThinkingDelta::class) + + ->and($events[60])->toBeInstanceOf(ContentBlockDelta::class) + ->and($events[60]->delta)->toBeInstanceOf(SignatureDelta::class) + ->and($events[60]->delta->signature)->toBeString() + + ->and($events[62])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[62]->contentBlock)->toBeInstanceOf(TextContentBlock::class) + ->and($events[62]->contentBlock->text)->toBeString() + + ->and($events[110])->toBeInstanceOf(MessageDelta::class) + ->and($events[110]->stopReason)->toBe('end_turn') + ->and($events[110]->cumulativeUsage)->toBeInstanceOf(Usage::class) + + ->and($events[111])->toBeInstanceOf(MessageStop::class); + }); + + test('it can create a streamed message with tool use', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/tool-use-streamed'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $response = $anthropic->messages()->stream([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'tools' => [ + [ + 'name' => 'get_weather', + 'description' => 'Get the current weather in a given location', + 'input_schema' => [ + 'type' => 'object', + 'properties' => [ + 'location' => [ + 'type' => 'string', + 'description' => 'The city and country, e.g. Manchester, UK', + ], + 'unit' => [ + 'type' => 'string', + 'enum' => ['celsius', 'fahrenheit'], + 'description' => 'The unit of temperature, either "celsius" or "fahrenheit"', + ], + ], + 'required' => ['location'], + ], + ], + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'What is the weather in Manchester?', + ], + ], + ]); + + expect($response)->toBeInstanceOf(MessageStream::class) + ->and($response->getIterator())->toBeInstanceOf(Generator::class); + + $events = iterator_to_array($response->getIterator()); + + expect($events)->toHaveCount(10) + ->and($events[0])->toBeInstanceOf(MessageStart::class) + ->and($events[1])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[2])->toBeInstanceOf(ContentBlockDelta::class) + ->and($events[7])->toBeInstanceOf(ContentBlockStop::class) + ->and($events[8])->toBeInstanceOf(MessageDelta::class) + ->and($events[9])->toBeInstanceOf(MessageStop::class); + }); + + test('it can create a streamed message with structured output', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output-streamed'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $response = $anthropic->messages()->stream([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'betas' => ['structured-outputs-2025-11-13'], + 'output_format' => [ + 'type' => 'json_schema', + 'schema' => Schema::object() + ->properties( + Schema::string('name')->required(), + Schema::string('email')->required(), + Schema::string('plan_interest')->required(), + Schema::boolean('demo_requested')->required(), + ) + ->additionalProperties(false) + ->toArray(), + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.', + ], + ], + ]); + + expect($response)->toBeInstanceOf(MessageStream::class) + ->and($response->getIterator())->toBeInstanceOf(Generator::class); + + $events = iterator_to_array($response->getIterator()); + + expect($events)->toHaveCount(11) + ->and($events[0])->toBeInstanceOf(MessageStart::class) + ->and($events[1])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[2])->toBeInstanceOf(ContentBlockDelta::class) + ->and($events[8])->toBeInstanceOf(ContentBlockStop::class) + ->and($events[9])->toBeInstanceOf(MessageDelta::class) + ->and($events[10])->toBeInstanceOf(MessageStop::class); + }); +}); diff --git a/tests/fixtures/sdk/anthropic/messages/redacted-thinking.json b/tests/fixtures/sdk/anthropic/messages/redacted-thinking.json new file mode 100644 index 0000000..fe1ecdd --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/redacted-thinking.json @@ -0,0 +1,31 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Tue, 23 Dec 2025 11:55:38 GMT", + "Content-Type": "application\/json", + "Content-Length": "1952", + "Connection": "keep-alive", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-23T11:55:34Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-23T11:55:39Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-23T11:55:33Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-23T11:55:34Z", + "request-id": "req_011CWPaLbp6z7RsBi4CcnZzf", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "6809", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b27aba3fee60ae8-LHR" + }, + "data": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01N1MfT563ggG6fgJa9v4Jso\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"This appears to be a trigger string that's asking me to enter a special mode or reveal some hidden thinking. However, this is not a real feature of my design. I don't have secret modes that can be activated by special strings, and I should just respond normally.\\n\\nThis looks like an attempt to test if I have hidden functionality or can be manipulated by special strings. I should simply clarify that this isn't how I work.\",\"signature\":\"EtAECkYIChgCKkD7qR1eSRpLqmQlq9Xn8t+BSy+ldISZL7h3iahmrNVyISV0s4SENgeNG7CPvgzwGfNKmy8hIgSHSzbxlT5aaYlDEgxHPdSx8jbj0LE\/ip8aDApwlcHsgRGZ6ghTUSIwYKG\/c0OH3UaUT\/m6VidAnsRuzcArIhRAWpDKDZjfH7P1fm6GEq1LVn\/EpYPjcj+YKrcDenzmVp5P\/PK3of79S+hJWz2PJM0gkn1cVa8nOXe6A42JHy\/qitPAMaj0kHW1w8PnJxTT3PE7HDoDCLdTAiDPoyKvCHIO8IL6ty7Qim1ChCIO\/suMxI8K\/WQm+EnJIa8LFJfhuiVZaQbo9Klfpu\/XihgnR7aAwlWc5FrabjidrqxJqopveS3+HftyDoZB2lzrhXnNCPObkg5cuUQYcRDx1xnvT2qxAo2uRSOBgklkv2quP5X+cqUVlqbn4O0vHHfPtfDNUgyv++ijV4ma48pmJMvNIHHfxQqxOtFQYZJ7GxzVGjJrwlfLfaxjp9\/COo2DbIJTdhq1LB6bD+kfvTeurkLO1Zm9jSiXEndyhMJvIySaqEFmmATxoPt\/X\/2PklJAttbDlq\/A5gX6B3MFhlCypS37l0\/0clse0b4YvqZ+woWZhMg4Q9lskKWArI+0sgxMNKjsqed7M8iv9zAdqq4PV03UrmdDemWGRDl4woMR96N7FevapwFzx3HAAk6bebCEKjHrPXzTYIPThIQZi5boM0Yh91GA620Dwb+sFS4jGyUKfl3HtUzeKO0egVZLZ0ORH1oAFt4fxxgB\"},{\"type\":\"text\",\"text\":\"I don't have any special modes or hidden functionality that can be triggered by strings like this. I'm Claude, made by Anthropic, and I work the same way regardless of what trigger phrases or special strings are used.\\n\\nIs there something I can actually help you with today?\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":93,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":159,\"service_tier\":\"standard\"}}", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/simple-streamed.json b/tests/fixtures/sdk/anthropic/messages/simple-streamed.json new file mode 100644 index 0000000..824157f --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/simple-streamed.json @@ -0,0 +1,33 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Fri, 19 Dec 2025 00:58:21 GMT", + "Content-Type": "text\/event-stream; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "CF-Cache-Status": "DYNAMIC", + "Cache-Control": "no-cache", + "Server": "cloudflare", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "X-Robots-Tag": "none", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-19T00:58:19Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-19T00:58:19Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-19T00:58:20Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-19T00:58:19Z", + "request-id": "req_011CWF8zCn21gWtTPRooLVqt", + "x-envoy-upstream-service-time": "1894", + "Vary": "accept-encoding", + "CF-RAY": "9b02f36f6b8d48cd-LHR" + }, + "data": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01K94xXbZvCgvqPKnKnNeUf1\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":8,\"service_tier\":\"standard\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"Hello! I'm doing well, thank\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" you for asking. How\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" are you doing today? Is\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\" there anything I can help you with?\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":30} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/simple.json b/tests/fixtures/sdk/anthropic/messages/simple.json new file mode 100644 index 0000000..77b630d --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/simple.json @@ -0,0 +1,32 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Fri, 19 Dec 2025 00:47:35 GMT", + "Content-Type": "application\/json", + "Content-Length": "514", + "Connection": "keep-alive", + "CF-Cache-Status": "DYNAMIC", + "Server": "cloudflare", + "Strict-Transport-Security": "max-age=31536000; includeSubDomains; preload", + "X-Robots-Tag": "none", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-19T00:47:35Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-19T00:47:35Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-19T00:47:34Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-19T00:47:35Z", + "request-id": "req_011CWF8Ab57uCzWdAtayWWzu", + "x-envoy-upstream-service-time": "2143", + "Vary": "accept-encoding", + "CF-RAY": "9b02e3aa283db556-LHR" + }, + "data": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_017C2RiwNrPSEsQDy5BYtLaU\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":13,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":30,\"service_tier\":\"standard\"}}", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/structured-output-streamed.json b/tests/fixtures/sdk/anthropic/messages/structured-output-streamed.json new file mode 100644 index 0000000..76bc9ac --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/structured-output-streamed.json @@ -0,0 +1,32 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Tue, 23 Dec 2025 11:37:47 GMT", + "Content-Type": "text\/event-stream; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Cache-Control": "no-cache", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-23T11:37:46Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-23T11:37:45Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-23T11:37:47Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-23T11:37:45Z", + "request-id": "req_011CWPYz2GaCVytWHz77GrR8", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "2022", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b27919dbfbc79b3-LHR" + }, + "data": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01Ff6YSmcGQsD5V4jB6gYWnB\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":281,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"{\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"name\\\":\\\"\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"John Smith\\\",\\\"email\\\":\\\"john@example\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\".com\\\",\\\"plan_interest\\\":\\\"Enterprise\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\\",\\\"demo_requested\\\":true}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":281,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":29} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/structured-output.json b/tests/fixtures/sdk/anthropic/messages/structured-output.json new file mode 100644 index 0000000..582ffcd --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/structured-output.json @@ -0,0 +1,31 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Tue, 23 Dec 2025 11:23:54 GMT", + "Content-Type": "application\/json", + "Content-Length": "520", + "Connection": "keep-alive", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-23T11:23:54Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-23T11:23:54Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-23T11:23:50Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-23T11:23:54Z", + "request-id": "req_011CWPXvMgeeD5cnhs8AHMJq", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "5229", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b277d3148e494d8-LHR" + }, + "data": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01TsrqCUV774BSZdXb8wq4wF\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"{\\\"name\\\":\\\"John Smith\\\",\\\"email\\\":\\\"john@example.com\\\",\\\"plan_interest\\\":\\\"Enterprise\\\",\\\"demo_requested\\\":true}\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":281,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":29,\"service_tier\":\"standard\"}}", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/thinking-streamed.json b/tests/fixtures/sdk/anthropic/messages/thinking-streamed.json new file mode 100644 index 0000000..ff7ec82 --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/thinking-streamed.json @@ -0,0 +1,32 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Mon, 22 Dec 2025 12:36:28 GMT", + "Content-Type": "text\/event-stream; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Cache-Control": "no-cache", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-22T12:36:26Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-22T12:36:26Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-22T12:36:27Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-22T12:36:26Z", + "request-id": "req_011CWMjeczbB86Suf3fWKsUS", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "1841", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b1faa311e5bea16-LHR" + }, + "data": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01EsGBjA9LigpbnHERxycReg\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":44,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":7,\"service_tier\":\"standard\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"thinking\",\"thinking\":\"\",\"signature\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"The user is asking about the mass\"} }\n\nevent: ping\ndata: {\"type\": \"ping\"}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" of the Moon\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\". In\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" everyday\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" language\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\", people\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" often use \\\"weight\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\\" when\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" they\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" technically\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" mean \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"mass.\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" Moon's\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" mass is approximately 7.342\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" \u00d7 10^\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"22 kg (\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"or\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" about 73\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".42\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" s\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"extillion ki\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"lograms).\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\\n\\nWeight\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" technically\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" refers to the force\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" exerted by gravity on an object,\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" so\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" it depends\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" on the gravit\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"ational field.\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" The Moon doesn\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"'t really\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" have a \\\"weight\\\" in the conventional\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" sense unless\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" we're talking about the gravitational force\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" between\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" it\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" and another\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" body\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".\\n\\nI should provide\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" Moon\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"'s mass,\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" which is what\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" they\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"'re likely\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" asking for\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\",\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" but\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" I coul\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"d also clar\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"ify the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" distinction if\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\" helpful\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\".\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"thinking_delta\",\"thinking\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"signature_delta\",\"signature\":\"EogGCkYIChgCKkDFXJHOf1yajrw1cRNRG1U3vlrObd5h9q1hh6RNIFnWYKI4eVj+pM9NLHYVirz2STbfYyRRHMqkUge6cVbUF287EgxAsvpL6\/NAe3nZSkoaDBrXmTqpxgjajtBHUyIwWxm8OtfYNj6iiI8rHDVT78PQZq3Tbd5c+7djJMwfUsnCpUtaineGckxeHYQXrKl2Ku8Ea6khAgJXYMySvMtPXyA48UHN7ECTErioDzm7oUJB3RRfYVmvqmmDov3SbUWapABAgZP3hdxIPT1c0NN3YawuZgY1NaIpTrTFIo07jMT2I\/4NbrTLk2Q\/r\/BPvywD3RXB0ZK\/4vHKrMjZWtDSvnhNdLOwd0tPxsL2OaNYyuc2w2wvVSYJvvkMC\/bqkw5TQEmJRX\/3vcyyf5uxanKEof7+raWQbecmxDY15Z6NqzbTl8wpSQQhW+malsZVB1ixuWmRSExctSadbClFhkjx5l7Z8bGZA6MCDW8EEzpQXm4mlcDteEa9m18yL692HMA4rR778bLTBgHicKSCtVmHcJLRjQFTmZgZquCScrRUY2OMbjTH2MbanTbW53XG2EeWC+qXsEAAp+sc5rYAAG2PelXJM0sG26rtFaHKG3J1MGV+60SHWaMn6m9XkaSJsnCVceo3Kz4KxvbQXtZx1K0KJOEz2k74e1xsgU00yWt4FDaHTGU4U86wlWf6auFaDVNrDvhmiwL6NCsLtT3koiMqSS75psAy03Futf3wcnVtdD7cAscLu+Isv6k1b6TiRI2SYJ81GhX2RdowjVTmlrnMf6OsHlssOEPdLRGAJB4EsCJ\/wAAn8\/SjS9HHUa0ydmv4rgopEyIPT1Lcb\/Y9Ul7X2m9OLwM2nY\/MxvhpEIYx0cEg9awqvBeMztQPSSwNxu9qBmtW9LRyxrcwlh6Jq+wTEJtb6kHxbIDnv8C\/gbzF6LN9nEe99eK7C3Jh0Feca1nHCkJv1QfkRLa355g6i0OltC\/Adl4mV0nDGqWrKruQlxEayjOx7ckqEthFXXhsDEQkbk0YAQ==\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"The **\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"mass\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"** of the Moon is approximately **7\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\".342 \u00d7 10\u00b2\u00b2\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" kg**\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" (73\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\".42 sextillion kilog\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"rams),\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" or\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" about **73\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\".5\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" billion\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" trillion\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" metric\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" tons**.\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\nTo\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" clar\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"ify:\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" \\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"weight\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\\" technically\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" refers to the gravitational force on\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" an object, which depends\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" on where\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" it\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" is\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\".\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" The Moon doesn't have weight\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" in the traditional sense,\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" but it does have **\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"mass\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"** \u2014\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" the amount\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" of matter it\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" contains.\\n\\nFor\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" comparison, the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" Moon's mass is about **\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"1.2\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"%\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"** of Earth's mass (\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\"Earth\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" is\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" roughly\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" 81\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" times more\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":1,\"delta\":{\"type\":\"text_delta\",\"text\":\" massive than the Moon).\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":44,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":285} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/thinking.json b/tests/fixtures/sdk/anthropic/messages/thinking.json new file mode 100644 index 0000000..2e92bf6 --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/thinking.json @@ -0,0 +1,31 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Mon, 22 Dec 2025 12:48:29 GMT", + "Content-Type": "application\/json", + "Content-Length": "3217", + "Connection": "keep-alive", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-22T12:48:22Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-22T12:48:32Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-22T12:48:21Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-22T12:48:22Z", + "request-id": "req_011CWMkZFzGcEwHaYkrxgk7K", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "9408", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b1fbba01e31f9a2-LHR" + }, + "data": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01BzT8gKa4ZkQ3P8PYLVYY4x\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"thinking\",\"thinking\":\"The user is asking about the weight of the moon. I need to be careful here about the distinction between mass and weight.\\n\\nWeight is a force that depends on the gravitational field an object is in (Weight = mass \u00d7 gravitational acceleration). So technically, the moon doesn't have a single \\\"weight\\\" - it would have different weights depending on what gravitational field it's in.\\n\\nHowever, I think the user is likely asking about the moon's **mass**, which is an intrinsic property.\\n\\nThe mass of the Moon is approximately:\\n- 7.342 \u00d7 10^22 kg\\n- Or about 73.42 billion trillion kilograms\\n- Or about 7.342 \u00d7 10^19 metric tons\\n\\nThis is about 1.2% of Earth's mass (Earth's mass is about 5.972 \u00d7 10^24 kg).\\n\\nI should clarify the mass vs weight distinction but also give them the useful information they're looking for.\",\"signature\":\"EtkHCkYIChgCKkAWikCfpZALkovOOP8VPgOQoBaKOJVvz5KT+83PJv9pF2Ql4EL33y8IV+cOE2jb5yssTX89xA8mmqHOOK7340TbEgzAr6BJrDpaJnVknTYaDHIfWGcRkhfJMYQJTCIwGjXXLFrG7qwaLzbbpYbWoFFfSgv43bAd3koVZlFkrJQ+bGD1GHw8aGfB\/NTAiv2gKsAGhsXZqhW1qV\/YpUZEHfjxaNVffQqpQObFrWI8B4ivKlFCfle2smHvRMhRth2VmFp4jgs93r065yXHwR5SYTYCWwRSlf61oFUY3+f3oAmEEzNrzm4s+XWZP7DEULfScuRLWi46DUZJ4pbWb4Dd2n4mOZbIippnU1KmhNT3zZKYcuTxXdGvmbib7wAhsnHOh3HLCnVY47p1rETYe8z5\/HXaS5odcnrxyUwA1rUUbKg6LOFmmHx6wPpDcUrN1+cYvsBd3b\/rkjuvq2pVvdv0dOWLzfQf4bS4olqHHjZOdRWRI4WDXniA1nK6Gp\/wCdv8DJlnEHoDma6Y1ltoshs16Re\/JZRH31pcie2Xa5ZpmjUrTy0BdHZWHFhOfWcwT+DrUUFBKf1taUzk66TDfX2\/ew9C9PL5wnFoZQ4blLGPPFDDG5KkQ5YcBpXKJsKMrEWGyYb3X4NiDy+5K+Tetq5nrVgTi1HxPaxjmDtZvMtEph7yPG1kch8DYJKqLz+Jg8ED41AuRT8cEVS7UB8jYP7bxuvsq4dZSAttCAwJQZ3TRs1rBO1bTWXIMobxdcdhzDeUNLKok3cc08tn6pVkmKXp8VIxs3fpim3naBUrZSUdGs1wjrdXIUcCNPWn7m55BzSDsU5\/dxqb\/Bh7EvV6kFk2vCGUjhzTCP219Uvj5lxgVQ0v0KwsC6oUlCYTngyu3q80AuVwpdJJh4U8o7jpkF\/M6r4h\/FDaRgLefUljOtfxC9iJMPh9rcxe3v8cAjd1Lz1mJJAys3lG65SBlgjexyBzWPjP4qhzLiJDG7md0XlhbgfKAniOOB8Iw7NqgjlrgIF+7VywcsO\/sPQxXXiGcRYppgAzRb5kHe9Xh5+P2CiAVtX2t2bzkiMjhqLOmKotWhhGeyrVwum47VLjifzNued4NFpfrfg35YWonuOKvg3nhrvQhnLvb5qyFHWcl\/3Tru9pbEgGvpbhyYyQ1hbCz2xNgma4M21SlNO796+POzCLPk7K0bOvcFWoxrw59h8IT7K5SGgfuZuTQMW9sd2czz+x6aZbAdUorMZoO\/6VN\/PlOdfqdzBsfsVOA7iS\/klTvpMsHqLi9eXA8T2+c+0PYz3MsK4m2hgB\"},{\"type\":\"text\",\"text\":\"The **mass** of the Moon is approximately **7.342 \u00d7 10\u00b2\u00b2 kilograms** (73.42 billion trillion kg).\\n\\nIt's worth noting that \\\"weight\\\" technically refers to the force of gravity on an object, which varies depending on location. Since the Moon is in space, we typically refer to its **mass** instead, which is constant.\\n\\nFor perspective:\\n- The Moon's mass is about **1.2%** of Earth's mass\\n- If you could somehow \\\"weigh\\\" the Moon on Earth (using Earth's gravity), it would weigh about 7.342 \u00d7 10\u00b2\u00b2 newtons\\n\\nIs there anything specific about the Moon's mass or gravity you'd like to know more about?\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":44,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":381,\"service_tier\":\"standard\"}}", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/tool-use-streamed.json b/tests/fixtures/sdk/anthropic/messages/tool-use-streamed.json new file mode 100644 index 0000000..ca61fa8 --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/tool-use-streamed.json @@ -0,0 +1,32 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Tue, 23 Dec 2025 01:18:00 GMT", + "Content-Type": "text\/event-stream; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Cache-Control": "no-cache", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-23T01:17:59Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-23T01:17:58Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-23T01:17:59Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-23T01:17:58Z", + "request-id": "req_011CWNjinTUayT2cjAcVSeV4", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "1933", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b2405b8edb6779c-LHR" + }, + "data": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01B1mrauyK588QTMbQTm6MxU\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":622,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"tool_use\",\"id\":\"toolu_017cgnjBthRJC2WTaB6As9FB\",\"name\":\"get_weather\",\"input\":{}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"lo\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"cation\\\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\": \\\"Manche\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ster, UK\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"tool_use\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":622,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":55} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/tool-use.json b/tests/fixtures/sdk/anthropic/messages/tool-use.json new file mode 100644 index 0000000..a2aab9a --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/tool-use.json @@ -0,0 +1,31 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Tue, 23 Dec 2025 01:10:02 GMT", + "Content-Type": "application\/json", + "Content-Length": "498", + "Connection": "keep-alive", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "30000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-23T01:10:02Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-23T01:10:02Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-23T01:10:00Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "38000", + "anthropic-ratelimit-tokens-reset": "2025-12-23T01:10:02Z", + "request-id": "req_011CWNj7TxFyVvwaNaSGdiFL", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "3060", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b23fa06587a582e-LHR" + }, + "data": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01KFJgeiL78UwUXSNVyMnb7S\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"tool_use\",\"id\":\"toolu_013eyeBacKytgaGXQCdz8KMX\",\"name\":\"get_weather\",\"input\":{\"location\":\"Manchester, UK\"}}],\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":622,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":55,\"service_tier\":\"standard\"}}", + "context": [] +} \ No newline at end of file From 3b29801971f1c9963d5e69c7595e219221d948d3 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Wed, 24 Dec 2025 00:57:08 +0000 Subject: [PATCH 02/54] wip --- src/LLM/Drivers/Anthropic/AnthropicChat.php | 17 ++-- src/SDK/Anthropic/Anthropic.php | 4 +- src/SDK/Anthropic/Requests/CreateMessage.php | 4 + .../Drivers/Anthropic/AnthropicChatTest.php | 86 ++++++++----------- 4 files changed, 48 insertions(+), 63 deletions(-) diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 433e65c..3b41d63 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -351,18 +351,17 @@ protected function buildParams(array $additionalParameters): array ]; } - public function getClient(): ClientContract - { - return $this->client; - } - - public static function fake(array $responses, ?string $model = null, ?ModelProvider $modelProvider = null): self - { - $client = Anthropic::fake($responses); + public static function fake( + array $responses, + ?string $apiKey = null, + ?string $model = null, + ?ModelProvider $modelProvider = null, + ): self { + $client = Anthropic::fake($responses, $apiKey); return new self( $client, - $model ?? CreateResponseFixture::ATTRIBUTES['model'], + $model ?? 'claude-4-5-sonnet-20250926', $modelProvider ?? ModelProvider::Anthropic, ); } diff --git a/src/SDK/Anthropic/Anthropic.php b/src/SDK/Anthropic/Anthropic.php index 1c42aaa..319a86d 100644 --- a/src/SDK/Anthropic/Anthropic.php +++ b/src/SDK/Anthropic/Anthropic.php @@ -54,10 +54,10 @@ protected static function defaultVersion(): string /** * @param array $responses */ - public static function fake(array $responses): self + public static function fake(array $responses, ?string $apiKey = null): self { MockClient::global($responses); - return new self('test-api-key'); + return new self($apiKey ?? 'test-api-key'); } } diff --git a/src/SDK/Anthropic/Requests/CreateMessage.php b/src/SDK/Anthropic/Requests/CreateMessage.php index f2dbf95..78301d9 100644 --- a/src/SDK/Anthropic/Requests/CreateMessage.php +++ b/src/SDK/Anthropic/Requests/CreateMessage.php @@ -40,6 +40,10 @@ protected function defaultHeaders(): array { $betas = $this->parameters['betas'] ?? []; + if (empty($betas)) { + return []; + } + return [ 'anthropic-beta' => implode(',', $betas), ]; diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index 010e359..22872b4 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -101,66 +101,46 @@ }); test('it can use structured output', function (): void { - $response = createAnthropicResponse([ - 'stop_reason' => 'end_turn', - 'content' => [ - [ - 'type' => 'text', - 'text' => '{"name":"John Doe","age":30}', - ], - ], - ]); - $llm = AnthropicChat::fake([ - $response, - ], 'claude-3-5-sonnet-20241022'); + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output'), + ]); $llm->addFeature(ModelFeature::StructuredOutput); - $llm->withStructuredOutput( - Schema::object()->properties( - Schema::string('name'), - Schema::integer('age'), - ), + Schema::object() + ->properties( + Schema::string('name')->required(), + Schema::string('email')->required(), + Schema::string('plan_interest')->required(), + Schema::boolean('demo_requested')->required(), + ) + ->additionalProperties(false), name: 'Person', description: 'A person with a name and age', ); $result = $llm->invoke([ - new UserMessage('Tell me about a person'), + new UserMessage('Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.'), ]); expect($result->generation->message->content) ->toBeArray() ->toContainOnlyInstancesOf(TextContent::class) ->and($result->generation->message->content[0]->text) - ->toBe('{"name":"John Doe","age":30}'); + ->toBe('{"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true}'); expect($result->generation->parsedOutput)->toBe([ - 'name' => 'John Doe', - 'age' => 30, + 'name' => 'John Smith', + 'email' => 'john@example.com', + 'plan_interest' => 'Enterprise', + 'demo_requested' => true, ]); }); test('it can use structured output using the schema tool', function (): void { - $response = createAnthropicResponse([ - 'stop_reason' => 'tool_use', - 'content' => [ - [ - 'type' => 'tool_use', - 'id' => 'call_123', - 'name' => SchemaTool::NAME, - 'input' => [ - 'name' => 'John Doe', - 'age' => 30, - ], - ], - ], - ]); - $llm = AnthropicChat::fake([ - $response, - ], 'claude-3-5-sonnet-20241022'); + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output-tool-use'), + ]); $llm->addFeature(ModelFeature::ToolCalling); @@ -171,28 +151,30 @@ $llm->withStructuredOutput($schema, outputMode: StructuredOutputMode::Tool); - $result = $llm->invoke([ + $result = $llm->withMaxTokens(1024)->invoke([ new UserMessage('Tell me about a person'), ]); expect($result->parsedOutput) ->toBe([ - 'name' => 'John Doe', - 'age' => 30, + 'name' => 'John Smith', + 'age' => 35, ]); - /** @var \Anthropic\Testing\ClientFake $client */ - $client = $llm->getClient(); + expect($result->generation->message->content) + ->toBeArray() + ->toContainOnlyInstancesOf(TextContent::class); - $client->messages()->assertSent(function (string $method, array $parameters): bool { - return $parameters['model'] === 'claude-3-5-sonnet-20241022' - && $parameters['messages'][0]['role'] === 'user' - && $parameters['messages'][0]['content'] === 'Tell me about a person' - && $parameters['tools'][0]['type'] === 'custom' - && $parameters['tools'][0]['name'] === SchemaTool::NAME - && $parameters['tools'][0]['input_schema']['properties']['name']['type'] === 'string' - && $parameters['tools'][0]['input_schema']['properties']['age']['type'] === 'integer'; - }); + expect($result->generation->message->toolCalls) + ->toBeInstanceOf(ToolCallCollection::class) + ->and($result->generation->message->toolCalls) + ->toHaveCount(1) + ->and($result->generation->message->toolCalls[0]) + ->toBeInstanceOf(ToolCall::class) + ->and($result->generation->message->toolCalls[0]->function) + ->toBeInstanceOf(FunctionCall::class) + ->and($result->generation->message->toolCalls[0]->function->name) + ->toBe(SchemaTool::NAME); }); test('it can stream with structured output', function (): void { From 01b8d3874a4ca7be3bb47e492aa2d00804dae40d Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 29 Dec 2025 00:50:17 +0000 Subject: [PATCH 03/54] wip --- src/AGUI/Enums/EventType.php | 74 ++++++-- .../{ThinkingEnd.php => ReasoningEnd.php} | 5 +- ...ontent.php => ReasoningMessageContent.php} | 5 +- ...nkingStart.php => ReasoningMessageEnd.php} | 6 +- src/AGUI/Events/ReasoningMessageStart.php | 21 +++ ...gTextMessageEnd.php => ReasoningStart.php} | 6 +- src/AGUI/Events/ThinkingTextMessageStart.php | 19 --- src/LLM/Drivers/Anthropic/AnthropicChat.php | 158 +----------------- .../Anthropic/Concerns/MapStreamResponse.php | 153 +++++++++++++++++ src/SDK/Anthropic/Contracts/StreamEvent.php | 6 +- src/SDK/Anthropic/Data/Messages/Message.php | 4 +- .../Anthropic/Data/Messages/MessageStream.php | 10 +- .../Messages/ServerToolUseContentBlock.php | 25 +++ .../Messages/Streaming/CitationsDelta.php | 22 +++ .../Streaming/Events/AbstractStreamEvent.php | 8 +- .../Streaming/Events/ContentBlockDelta.php | 12 +- .../Streaming/Events/ContentBlockStart.php | 15 +- .../Streaming/Events/ContentBlockStop.php | 2 - .../Streaming/Events/MessageDelta.php | 8 +- .../Streaming/Events/MessageStart.php | 8 +- .../Messages/Streaming/Events/MessageStop.php | 2 - .../Data/Messages/Streaming/Events/Ping.php | 2 - .../Messages/Streaming/Events/Unknown.php | 6 +- .../Messages/Streaming/InputJsonDelta.php | 4 +- .../Messages/Streaming/SignatureDelta.php | 4 +- .../Data/Messages/Streaming/TextDelta.php | 4 +- .../Data/Messages/Streaming/ThinkingDelta.php | 4 +- src/SDK/Anthropic/Data/Messages/Usage.php | 2 + .../WebSearchToolResultContentBlock.php | 23 +++ .../Drivers/Anthropic/AnthropicChatTest.php | 58 +++---- tests/Unit/SDK/Anthropic/AnthropicTest.php | 128 ++++++++++++++ .../tool-use-web-search-streamed.json | 32 ++++ .../messages/tool-use-web-search.json | 31 ++++ 33 files changed, 578 insertions(+), 289 deletions(-) rename src/AGUI/Events/{ThinkingEnd.php => ReasoningEnd.php} (69%) rename src/AGUI/Events/{ThinkingTextMessageContent.php => ReasoningMessageContent.php} (68%) rename src/AGUI/Events/{ThinkingStart.php => ReasoningMessageEnd.php} (67%) create mode 100644 src/AGUI/Events/ReasoningMessageStart.php rename src/AGUI/Events/{ThinkingTextMessageEnd.php => ReasoningStart.php} (61%) delete mode 100644 src/AGUI/Events/ThinkingTextMessageStart.php create mode 100644 src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php create mode 100644 src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php create mode 100644 src/SDK/Anthropic/Data/Messages/Streaming/CitationsDelta.php create mode 100644 src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php create mode 100644 tests/fixtures/sdk/anthropic/messages/tool-use-web-search-streamed.json create mode 100644 tests/fixtures/sdk/anthropic/messages/tool-use-web-search.json diff --git a/src/AGUI/Enums/EventType.php b/src/AGUI/Enums/EventType.php index 671c760..6498102 100644 --- a/src/AGUI/Enums/EventType.php +++ b/src/AGUI/Enums/EventType.php @@ -6,30 +6,84 @@ enum EventType: string { + /** Signals the start of an agent run. */ + case RunStarted = 'RUN_STARTED'; + + /** Signals the successful completion of an agent run. */ + case RunFinished = 'RUN_FINISHED'; + + /** Signals an error during an agent run. */ + case RunError = 'RUN_ERROR'; + + /** Signals the start of a step within an agent run. */ + case StepStarted = 'STEP_STARTED'; + + /** Signals the completion of a step within an agent run. */ + case StepFinished = 'STEP_FINISHED'; + + /** Signals the start of a text message. */ case TextMessageStart = 'TEXT_MESSAGE_START'; + + /** Represents a chunk of content in a streaming text message. */ case TextMessageContent = 'TEXT_MESSAGE_CONTENT'; + + /** Signals the end of a text message. */ case TextMessageEnd = 'TEXT_MESSAGE_END'; + + /** Convenience event that expands to Start → Content → End automatically. */ case TextMessageChunk = 'TEXT_MESSAGE_CHUNK'; - case ThinkingTextMessageStart = 'THINKING_TEXT_MESSAGE_START'; - case ThinkingTextMessageContent = 'THINKING_TEXT_MESSAGE_CONTENT'; - case ThinkingTextMessageEnd = 'THINKING_TEXT_MESSAGE_END'; + + /** Signals the start of a tool call. */ case ToolCallStart = 'TOOL_CALL_START'; + + /** Represents a chunk of argument data for a tool call. */ case ToolCallArgs = 'TOOL_CALL_ARGS'; + + /** Signals the end of a tool call. */ case ToolCallEnd = 'TOOL_CALL_END'; + + /** Convenience event that expands to Start → Args → End automatically. */ case ToolCallChunk = 'TOOL_CALL_CHUNK'; + + /** Provides the result of a tool call execution. */ case ToolCallResult = 'TOOL_CALL_RESULT'; - case ThinkingStart = 'THINKING_START'; - case ThinkingEnd = 'THINKING_END'; + + /** Marks the start of reasoning. */ + case ReasoningStart = 'REASONING_START'; + + /** Marks the end of reasoning. */ + case ReasoningEnd = 'REASONING_END'; + + /** Signals the start of a reasoning message. */ + case ReasoningMessageStart = 'REASONING_MESSAGE_START'; + + /** Represents a chunk of content in a streaming reasoning message. */ + case ReasoningMessageContent = 'REASONING_MESSAGE_CONTENT'; + + /** Signals the end of a reasoning message. */ + case ReasoningMessageEnd = 'REASONING_MESSAGE_END'; + + /** A convenience event to auto start/close reasoning messages. */ + case ReasoningMessageChunk = 'REASONING_MESSAGE_CHUNK'; + + /** Provides a complete snapshot of an agent’s state. */ case StateSnapshot = 'STATE_SNAPSHOT'; + + /** Provides a partial update to an agent’s state using JSON Patch. */ case StateDelta = 'STATE_DELTA'; + + /** Provides a snapshot of all messages in a conversation. */ case MessagesSnapshot = 'MESSAGES_SNAPSHOT'; + + /** Delivers a complete snapshot of an activity message. */ case ActivitySnapshot = 'ACTIVITY_SNAPSHOT'; + + /** Applies incremental updates to an existing activity using JSON Patch operations. */ case ActivityDelta = 'ACTIVITY_DELTA'; + + /** Used to pass through events from external systems. */ case Raw = 'RAW'; + + /** Used for application-specific custom events. */ case Custom = 'CUSTOM'; - case RunStarted = 'RUN_STARTED'; - case RunFinished = 'RUN_FINISHED'; - case RunError = 'RUN_ERROR'; - case StepStarted = 'STEP_STARTED'; - case StepFinished = 'STEP_FINISHED'; } diff --git a/src/AGUI/Events/ThinkingEnd.php b/src/AGUI/Events/ReasoningEnd.php similarity index 69% rename from src/AGUI/Events/ThinkingEnd.php rename to src/AGUI/Events/ReasoningEnd.php index ec1ae51..8afdfe2 100644 --- a/src/AGUI/Events/ThinkingEnd.php +++ b/src/AGUI/Events/ReasoningEnd.php @@ -7,13 +7,14 @@ use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; -final class ThinkingEnd extends AbstractEvent +final class ReasoningEnd extends AbstractEvent { public function __construct( ?DateTimeImmutable $timestamp = null, mixed $rawEvent = null, + public string $messageId = '', ) { parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ThinkingEnd; + $this->type = EventType::ReasoningEnd; } } diff --git a/src/AGUI/Events/ThinkingTextMessageContent.php b/src/AGUI/Events/ReasoningMessageContent.php similarity index 68% rename from src/AGUI/Events/ThinkingTextMessageContent.php rename to src/AGUI/Events/ReasoningMessageContent.php index 404dc90..375d495 100644 --- a/src/AGUI/Events/ThinkingTextMessageContent.php +++ b/src/AGUI/Events/ReasoningMessageContent.php @@ -7,14 +7,15 @@ use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; -final class ThinkingTextMessageContent extends AbstractEvent +final class ReasoningMessageContent extends AbstractEvent { public function __construct( ?DateTimeImmutable $timestamp = null, mixed $rawEvent = null, + public string $messageId = '', public string $delta = '', ) { parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ThinkingTextMessageContent; + $this->type = EventType::ReasoningMessageContent; } } diff --git a/src/AGUI/Events/ThinkingStart.php b/src/AGUI/Events/ReasoningMessageEnd.php similarity index 67% rename from src/AGUI/Events/ThinkingStart.php rename to src/AGUI/Events/ReasoningMessageEnd.php index 07178a4..5b4ac06 100644 --- a/src/AGUI/Events/ThinkingStart.php +++ b/src/AGUI/Events/ReasoningMessageEnd.php @@ -7,14 +7,14 @@ use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; -final class ThinkingStart extends AbstractEvent +final class ReasoningMessageEnd extends AbstractEvent { public function __construct( ?DateTimeImmutable $timestamp = null, mixed $rawEvent = null, - public ?string $title = null, + public string $messageId = '', ) { parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ThinkingStart; + $this->type = EventType::ReasoningMessageEnd; } } diff --git a/src/AGUI/Events/ReasoningMessageStart.php b/src/AGUI/Events/ReasoningMessageStart.php new file mode 100644 index 0000000..9f8d84a --- /dev/null +++ b/src/AGUI/Events/ReasoningMessageStart.php @@ -0,0 +1,21 @@ +type = EventType::ReasoningMessageStart; + } +} diff --git a/src/AGUI/Events/ThinkingTextMessageEnd.php b/src/AGUI/Events/ReasoningStart.php similarity index 61% rename from src/AGUI/Events/ThinkingTextMessageEnd.php rename to src/AGUI/Events/ReasoningStart.php index 0f6de9f..6936cdf 100644 --- a/src/AGUI/Events/ThinkingTextMessageEnd.php +++ b/src/AGUI/Events/ReasoningStart.php @@ -7,13 +7,15 @@ use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; -final class ThinkingTextMessageEnd extends AbstractEvent +final class ReasoningStart extends AbstractEvent { public function __construct( ?DateTimeImmutable $timestamp = null, mixed $rawEvent = null, + public string $messageId = '', + public ?string $encryptedContent = null, ) { parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ThinkingTextMessageEnd; + $this->type = EventType::ReasoningStart; } } diff --git a/src/AGUI/Events/ThinkingTextMessageStart.php b/src/AGUI/Events/ThinkingTextMessageStart.php deleted file mode 100644 index b12b970..0000000 --- a/src/AGUI/Events/ThinkingTextMessageStart.php +++ /dev/null @@ -1,19 +0,0 @@ -type = EventType::ThinkingTextMessageStart; - } -} diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 3b41d63..260f0d2 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -4,48 +4,37 @@ namespace Cortex\LLM\Drivers\Anthropic; -use Generator; use Throwable; -use JsonException; -use DateTimeImmutable; use Cortex\LLM\AbstractLLM; use Illuminate\Support\Arr; -use Cortex\LLM\Data\ToolCall; use Cortex\LLM\Contracts\Tool; use Cortex\LLM\Data\ChatResult; -use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Enums\ToolChoice; use Cortex\Events\ChatModelError; use Cortex\Events\ChatModelStart; use Cortex\LLM\Contracts\Message; -use Cortex\LLM\Data\FunctionCall; use Cortex\LLM\Enums\MessageRole; -use Cortex\Events\ChatModelStream; use Cortex\Exceptions\LLMException; use Cortex\SDK\Anthropic\Anthropic; use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\ResponseMetadata; -use Anthropic\Contracts\ClientContract; -use Cortex\LLM\Data\ToolCallCollection; -use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Data\Messages\ToolMessage; use Cortex\ModelInfo\Enums\ModelProvider; use Cortex\LLM\Enums\StructuredOutputMode; use Cortex\LLM\Data\Messages\SystemMessage; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Data\Messages\MessageCollection; -use Anthropic\Responses\Messages\StreamResponse; use Cortex\LLM\Data\Messages\Content\TextContent; use Cortex\LLM\Drivers\Anthropic\Concerns\MapsUsage; use Cortex\LLM\Drivers\Anthropic\Concerns\MapsResponse; use Cortex\LLM\Drivers\Anthropic\Concerns\MapsFinishReason; -use Anthropic\Testing\Responses\Fixtures\Messages\CreateResponseFixture; +use Cortex\LLM\Drivers\Anthropic\Concerns\MapStreamResponse; class AnthropicChat extends AbstractLLM { use MapsUsage; use MapsResponse; use MapsFinishReason; + use MapStreamResponse; public function __construct( protected readonly Anthropic $client, @@ -66,13 +55,17 @@ public function invoke( fn(Message $message): bool => $message instanceof SystemMessage, ); + $nonSystemMessages = $messages->reject( + fn(Message $message): bool => $message instanceof SystemMessage, + ); + if ($systemMessages->count() > 1) { throw new LLMException('Only one system message is supported.'); } $params = $this->buildParams([ ...$additionalParameters, - 'messages' => static::mapMessagesForInput($messages), + 'messages' => static::mapMessagesForInput($nonSystemMessages), ]); /** @var \Cortex\LLM\Data\Messages\SystemMessage|null $systemMessage */ @@ -95,143 +88,6 @@ public function invoke( } } - /** - * Map a streaming response to a ChatStreamResult. - * - * @param StreamResponse<\Anthropic\Responses\Messages\CreateStreamedResponse> $response - * - * @return ChatStreamResult - */ - protected function mapStreamResponse(StreamResponse $response): ChatStreamResult - { - return new ChatStreamResult(function () use ($response): Generator { - $contentSoFar = ''; - $toolCallsSoFar = []; - $messageId = null; - $model = null; - $finishReason = null; - $usage = null; - $currentToolCall = null; - - /** @var \Anthropic\Responses\Messages\CreateStreamedResponse $chunk */ - foreach ($response as $chunk) { - $chunkDelta = null; - $accumulatedToolCallsSoFar = null; - $finishReason = static::mapFinishReason($chunk->delta->stop_reason); - - switch ($chunk->type) { - case 'message_start': - $messageId = $chunk->message->id; - $model = $chunk->message->model; - $usage = $chunk->usage !== null ? $this->mapUsage($chunk->usage) : null; - break; - - case 'content_block_start': - if ($chunk->content_block_start->type === 'tool_use') { - // Start of a new tool call - $currentToolCall = [ - 'id' => $chunk->content_block_start->id, - 'function' => [ - 'name' => $chunk->content_block_start->name, - 'arguments' => '', - ], - ]; - $toolCallsSoFar[] = $currentToolCall; - } - - break; - - case 'content_block_delta': - if ($chunk->delta->type === 'text_delta') { - // Text content delta - $chunkDelta = $chunk->delta->text; - $contentSoFar .= $chunkDelta; - } elseif ($chunk->delta->type === 'input_json_delta' && $currentToolCall !== null) { - // Tool call arguments delta - $lastIndex = count($toolCallsSoFar) - 1; - - if ($lastIndex >= 0) { - $toolCallsSoFar[$lastIndex]['function']['arguments'] .= $chunk->delta->partial_json; - } - } - - break; - - case 'content_block_stop': - // Content block finished - finalize current tool call if applicable - $currentToolCall = null; - break; - - case 'message_delta': - if ($chunk->usage !== null) { - $usage = $this->mapUsage($chunk->usage); - } - - break; - - case 'message_stop': - // Final event - this will be the last chunk - break; - - case 'ping': - // Skip ping events - continue 2; - } - - // Build accumulated tool calls if any exist - if ($toolCallsSoFar !== []) { - $accumulatedToolCallsSoFar = new ToolCallCollection( - collect($toolCallsSoFar) - ->map(function (array $toolCall): ToolCall { - try { - $arguments = json_decode((string) $toolCall['function']['arguments'], true, flags: JSON_THROW_ON_ERROR); - } catch (JsonException) { - $arguments = []; - } - - return new ToolCall( - $toolCall['id'], - new FunctionCall( - $toolCall['function']['name'], - $arguments, - ), - ); - }) - ->values() - ->all(), - ); - } - - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: $messageId, - message: new AssistantMessage( - content: $chunkDelta, - toolCalls: $accumulatedToolCallsSoFar, - metadata: new ResponseMetadata( - id: $messageId ?? 'unknown', - model: $model ?? $this->model, - provider: $this->modelProvider, - finishReason: $finishReason, - usage: $usage, - ), - ), - createdAt: new DateTimeImmutable(), // TODO - finishReason: $finishReason, - usage: $usage, - contentSoFar: $contentSoFar, - isFinal: $finishReason !== null, - ); - - $chunk = $this->applyOutputParserIfApplicable($chunk); - - $this->dispatchEvent(new ChatModelStream($this, $chunk)); - - yield $chunk; - } - }); - } - /** * Take the given messages and format them for the OpenAI API. * diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php new file mode 100644 index 0000000..65757e2 --- /dev/null +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -0,0 +1,153 @@ + $response + */ + protected function mapStreamResponse(MessageStream $response): ChatStreamResult + { + return new ChatStreamResult(function () use ($response): Generator { + yield from $this->streamBuffer?->drain() ?? []; + + $contentSoFar = ''; + + /** @var \Cortex\SDK\Anthropic\Contracts\StreamEvent $event */ + foreach ($response as $event) { + yield from $this->streamBuffer?->drain() ?? []; + + $chunkType = $this->mapChunkType($event); + + if ($chunkType === null) { + continue; + } + + $meta = $event->meta(); + + $finishReason = $event instanceof MessageDelta + ? $this->mapFinishReason($event->stopReason) + : null; + + $contentSoFar .= $event instanceof ContentBlockDelta + ? ($event->delta instanceof TextDelta + ? $event->delta->text + : ($event->delta instanceof InputJsonDelta + ? $event->delta->partialJson + : $event->delta->thinking)) + : ''; + + $id = $event instanceof MessageStart + ? $event->message->id + : ($id ?? null); + + $usage = $event instanceof MessageDelta + ? $this->mapUsage($event->cumulativeUsage) + : null; + + $chatGenerationChunk = new ChatGenerationChunk( + type: $chunkType, + id: $id, + message: new AssistantMessage( + content: $event instanceof ContentBlockDelta + ? ($event->delta instanceof TextDelta + ? $event->delta->text + : null) + : null, + // toolCalls: $accumulatedToolCallsSoFar ?? null, + metadata: new ResponseMetadata( + id: $id, + model: $this->model, + provider: $this->modelProvider, + finishReason: $finishReason, + usage: $usage, + ), + ), + createdAt: $meta?->createdAt ?? new DateTimeImmutable(), + finishReason: $finishReason, + usage: $usage, + contentSoFar: $contentSoFar, + isFinal: $usage !== null, + rawChunk: $this->includeRaw ? $event->raw() : null, + ); + + yield $chatGenerationChunk; + } + + yield from $this->streamBuffer?->drain() ?? []; + }); + } + + protected function mapChunkType(StreamEvent $event): ?ChunkType + { + if ($event instanceof MessageStart) { + return ChunkType::MessageStart; + } + + if ($event instanceof MessageStop) { + return null; + } + + if ($event instanceof ContentBlockStart) { + return match (get_class($event->contentBlock)) { + TextContentBlock::class => ChunkType::TextStart, + ThinkingContentBlock::class => ChunkType::ReasoningStart, + RedactedThinkingContentBlock::class => ChunkType::ReasoningStart, + ToolUseContentBlock::class => ChunkType::ToolInputStart, + default => null, + }; + } + + if ($event instanceof ContentBlockDelta) { + return match (get_class($event->delta)) { + TextDelta::class => ChunkType::TextDelta, + ThinkingDelta::class => ChunkType::ReasoningDelta, + InputJsonDelta::class => ChunkType::ToolInputDelta, + default => null, + }; + } + + if ($event instanceof ContentBlockStop) { + + return ChunkType::TextEnd; + // return match (get_class($event->contentBlock)) { + // TextContentBlock::class => ChunkType::TextEnd, + // ThinkingContentBlock::class => ChunkType::ReasoningEnd, + // RedactedThinkingContentBlock::class => ChunkType::ReasoningEnd, + // ToolUseContentBlock::class => ChunkType::ToolInputEnd, + // }; + } + + return ChunkType::Custom; + } +} diff --git a/src/SDK/Anthropic/Contracts/StreamEvent.php b/src/SDK/Anthropic/Contracts/StreamEvent.php index 28f83ae..ddff85b 100644 --- a/src/SDK/Anthropic/Contracts/StreamEvent.php +++ b/src/SDK/Anthropic/Contracts/StreamEvent.php @@ -4,13 +4,13 @@ namespace Cortex\SDK\Anthropic\Contracts; +use Cortex\SDK\Anthropic\Data\Messages\Meta; + interface StreamEvent { - public string $type { get; } - public static function from(array $payload): self; public function raw(): array; - public function headers(): array; + public function meta(): ?Meta; } diff --git a/src/SDK/Anthropic/Data/Messages/Message.php b/src/SDK/Anthropic/Data/Messages/Message.php index cb4cf05..ae63262 100644 --- a/src/SDK/Anthropic/Data/Messages/Message.php +++ b/src/SDK/Anthropic/Data/Messages/Message.php @@ -26,12 +26,14 @@ public function __construct( public static function from(array $payload): self { - $content = array_map(function (array $content): TextContentBlock|ThinkingContentBlock|RedactedThinkingContentBlock|ToolUseContentBlock { + $content = array_map(function (array $content): TextContentBlock|ThinkingContentBlock|RedactedThinkingContentBlock|ToolUseContentBlock|ServerToolUseContentBlock|WebSearchToolResultContentBlock { return match ($content['type']) { 'text' => TextContentBlock::from($content), 'thinking' => ThinkingContentBlock::from($content), 'redacted_thinking' => RedactedThinkingContentBlock::from($content), 'tool_use' => ToolUseContentBlock::from($content), + 'server_tool_use' => ServerToolUseContentBlock::from($content), + 'web_search_tool_result' => WebSearchToolResultContentBlock::from($content), default => throw new InvalidArgumentException('Invalid content type: ' . $content['type']), }; }, $payload['content']); diff --git a/src/SDK/Anthropic/Data/Messages/MessageStream.php b/src/SDK/Anthropic/Data/Messages/MessageStream.php index 8fd7380..87a37ea 100644 --- a/src/SDK/Anthropic/Data/Messages/MessageStream.php +++ b/src/SDK/Anthropic/Data/Messages/MessageStream.php @@ -30,11 +30,11 @@ public function __construct( */ public function getIterator(): Generator { - $response = $this->response->getPsrResponse(); - $headers = $this->response->headers()->all(); + $psrResponse = $this->response->getPsrResponse(); + $meta = Meta::from($this->response->headers()->all()); - while (! $response->getBody()->eof()) { - $line = $this->readLine($response->getBody()); + while (! $psrResponse->getBody()->eof()) { + $line = $this->readLine($psrResponse->getBody()); if (! str_starts_with($line, 'data:')) { continue; @@ -55,7 +55,7 @@ public function getIterator(): Generator default => Unknown::from($payload), }; - $event->setMeta(Meta::from($headers)); + $event->setMeta($meta); yield $event; } diff --git a/src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php b/src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php new file mode 100644 index 0000000..a92febd --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php @@ -0,0 +1,25 @@ +payload; + return $this->raw; } - public function getMeta(): ?Meta + public function meta(): ?Meta { return $this->meta; } public function setRaw(array $payload): static { - $this->payload = $payload; + $this->raw = $payload; return $this; } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php index 8f2dad5..153cb7d 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php @@ -8,19 +8,18 @@ use Cortex\SDK\Anthropic\Contracts\StreamEvent; use Cortex\SDK\Anthropic\Data\Messages\Streaming\TextDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; +use Cortex\SDK\Anthropic\Data\Messages\Streaming\CitationsDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\InputJsonDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\SignatureDelta; final class ContentBlockDelta extends AbstractStreamEvent implements StreamEvent { - public string $type = 'content_block_delta'; - /** * @param int $index The index of the content block in the final message. */ public function __construct( public int $index, - public TextDelta|InputJsonDelta|ThinkingDelta|SignatureDelta $delta, + public TextDelta|InputJsonDelta|ThinkingDelta|SignatureDelta|CitationsDelta $delta, ) {} public static function from(array $payload): self @@ -30,14 +29,13 @@ public static function from(array $payload): self 'input_json_delta' => InputJsonDelta::from($payload['delta']), 'thinking_delta' => ThinkingDelta::from($payload['delta']), 'signature_delta' => SignatureDelta::from($payload['delta']), + 'citations_delta' => CitationsDelta::from($payload['delta']), default => throw new InvalidArgumentException('Invalid delta type: ' . $payload['delta']['type']), }; - $instance = new self( + return new self( index: $payload['index'], delta: $delta, - ); - - return $instance->setRaw($payload); + )->setRaw($payload); } } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php index 0ab6663..85816a5 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php @@ -9,35 +9,36 @@ use Cortex\SDK\Anthropic\Data\Messages\TextContentBlock; use Cortex\SDK\Anthropic\Data\Messages\ToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ServerToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\RedactedThinkingContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\WebSearchToolResultContentBlock; final class ContentBlockStart extends AbstractStreamEvent implements StreamEvent { - public string $type = 'content_block_start'; - /** * @param int $index The index of the content block in the final message. */ public function __construct( public int $index, - public TextContentBlock|ThinkingContentBlock|RedactedThinkingContentBlock|ToolUseContentBlock $contentBlock, + public TextContentBlock|ThinkingContentBlock|RedactedThinkingContentBlock|ToolUseContentBlock|ServerToolUseContentBlock|WebSearchToolResultContentBlock $contentBlock, ) {} public static function from(array $payload): self { + // dump($payload); $contentBlock = match ($payload['content_block']['type']) { 'text' => TextContentBlock::from($payload['content_block']), 'thinking' => ThinkingContentBlock::from($payload['content_block']), 'redacted_thinking' => RedactedThinkingContentBlock::from($payload['content_block']), 'tool_use' => ToolUseContentBlock::from($payload['content_block']), + 'server_tool_use' => ServerToolUseContentBlock::from($payload['content_block']), + 'web_search_tool_result' => WebSearchToolResultContentBlock::from($payload['content_block']), default => throw new InvalidArgumentException('Invalid content type: ' . $payload['content_block']['type']), }; - $instance = new self( + return new self( index: $payload['index'], contentBlock: $contentBlock, - ); - - return $instance->setRaw($payload); + )->setRaw($payload); } } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php index fdbcdab..01dde03 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php @@ -8,8 +8,6 @@ final class ContentBlockStop extends AbstractStreamEvent implements StreamEvent { - public string $type = 'content_block_stop'; - /** * @param int $index The index of the content block in the final message. */ diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php index 12e1ac2..ff4bba9 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php @@ -9,8 +9,6 @@ final class MessageDelta extends AbstractStreamEvent implements StreamEvent { - public string $type = 'message_delta'; - public function __construct( public string $stopReason, public Usage $cumulativeUsage, @@ -19,12 +17,10 @@ public function __construct( public static function from(array $payload): self { - $instance = new self( + return new self( stopReason: $payload['delta']['stop_reason'], cumulativeUsage: Usage::from($payload['usage']), stopSequence: $payload['delta']['stop_sequence'] ?? null, - ); - - return $instance->setRaw($payload); + )->setRaw($payload); } } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php index e740438..3a77239 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php @@ -9,18 +9,14 @@ final class MessageStart extends AbstractStreamEvent implements StreamEvent { - public string $type = 'message_start'; - public function __construct( public Message $message, ) {} public static function from(array $payload): self { - $instance = new self( + return new self( message: Message::from($payload['message']), - ); - - return $instance->setRaw($payload); + )->setRaw($payload); } } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php index ec0db4a..244647a 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php @@ -8,8 +8,6 @@ final class MessageStop extends AbstractStreamEvent implements StreamEvent { - public string $type = 'message_stop'; - public static function from(array $payload): self { return new self()->setRaw($payload); diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php index b0e899a..58d9e04 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php @@ -8,8 +8,6 @@ final class Ping extends AbstractStreamEvent implements StreamEvent { - public string $type = 'ping'; - public static function from(array $payload): self { return new self()->setRaw($payload); diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php index 31dd0bd..99f21f1 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php @@ -15,11 +15,9 @@ public function __construct( public static function from(array $payload): self { - $instance = new self( + return new self( type: $payload['type'], payload: $payload, - ); - - return $instance->setRaw($payload); + )->setRaw($payload); } } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php index 4f383aa..f2e0841 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/InputJsonDelta.php @@ -7,17 +7,15 @@ final class InputJsonDelta { public function __construct( - public string $type, public string $partialJson, ) {} /** - * @param array{type: string, partial_json: string} $payload + * @param array{partial_json: string} $payload */ public static function from(array $payload): self { return new self( - type: $payload['type'], partialJson: $payload['partial_json'], ); } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/SignatureDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/SignatureDelta.php index 22559a5..6830a5c 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/SignatureDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/SignatureDelta.php @@ -7,17 +7,15 @@ final class SignatureDelta { public function __construct( - public string $type, public string $signature, ) {} /** - * @param array{type: string, signature: string} $payload + * @param array{signature: string} $payload */ public static function from(array $payload): self { return new self( - type: $payload['type'], signature: $payload['signature'], ); } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/TextDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/TextDelta.php index ce4358c..5b9af7d 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/TextDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/TextDelta.php @@ -7,17 +7,15 @@ final class TextDelta { public function __construct( - public string $type, public string $text, ) {} /** - * @param array{type: string, text: string} $payload + * @param array{text: string} $payload */ public static function from(array $payload): self { return new self( - type: $payload['type'], text: $payload['text'], ); } diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/ThinkingDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/ThinkingDelta.php index b02f9dc..c18cbe7 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/ThinkingDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/ThinkingDelta.php @@ -7,17 +7,15 @@ final class ThinkingDelta { public function __construct( - public string $type, public string $thinking, ) {} /** - * @param array{type: string, thinking: string} $payload + * @param array{thinking: string} $payload */ public static function from(array $payload): self { return new self( - type: $payload['type'], thinking: $payload['thinking'], ); } diff --git a/src/SDK/Anthropic/Data/Messages/Usage.php b/src/SDK/Anthropic/Data/Messages/Usage.php index 2f5564a..a45bb6f 100644 --- a/src/SDK/Anthropic/Data/Messages/Usage.php +++ b/src/SDK/Anthropic/Data/Messages/Usage.php @@ -13,6 +13,7 @@ public function __construct( public ?int $cacheReadInputTokens = null, public ?array $cacheCreation = null, public ?string $serviceTier = null, + public array $serverToolUse = [], ) {} public static function from(array $payload): self @@ -24,6 +25,7 @@ public static function from(array $payload): self cacheReadInputTokens: $payload['cache_read_input_tokens'] ?? null, cacheCreation: $payload['cache_creation'] ?? null, serviceTier: $payload['service_tier'] ?? null, + serverToolUse: $payload['server_tool_use'] ?? [], ); } } diff --git a/src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php b/src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php new file mode 100644 index 0000000..2d5f206 --- /dev/null +++ b/src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php @@ -0,0 +1,23 @@ + MockResponse::fixture('anthropic/messages/simple-streamed'), ]); $llm->withStreaming(); - $chunks = $llm->invoke([ + $chunks = $llm->includeRaw()->invoke([ new UserMessage('Hello, how are you?'), ]); expect($chunks)->toBeInstanceOf(ChatStreamResult::class); - $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) { - expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) - ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); + dd($chunks->toArray()); + + // dump(array_map(fn(ChatGenerationChunk $chunk) => $chunk->type->value, iterator_to_array($chunks))); - return $carry . ($chunk->message->content ?? ''); - }, ''); + // $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) { + // expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) + // ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); - expect($output)->toBe('Hello!'); + // return $carry . ($chunk->message->content ?? ''); + // }, ''); + + // expect($output)->toBe('Hello! I\'m doing well, thank you for asking. How are you doing today? Is there anything I can help you with?'); }); test('it can use tools', function (): void { @@ -175,7 +177,7 @@ ->toBeInstanceOf(FunctionCall::class) ->and($result->generation->message->toolCalls[0]->function->name) ->toBe(SchemaTool::NAME); -}); +})->skip(); test('it can stream with structured output', function (): void { $llm = AnthropicChat::fake([ @@ -223,7 +225,7 @@ public function __construct( && $parameters['messages'][0]['role'] === 'user' && $parameters['messages'][0]['content'] === 'Tell me a joke about dogs'; }); -}); +})->skip(); test('it can stream with tool calls', function (): void { $llm = AnthropicChat::fake([ @@ -293,7 +295,7 @@ public function __construct( && $parameters['tools'][0]['type'] === 'custom' && $parameters['tools'][0]['name'] === 'multiply'; }); -}); +})->skip(); test('it can use structured output with an enum', function (): void { $llm = AnthropicChat::fake([ @@ -357,7 +359,7 @@ enum AnthropicSentiment: string expect($result)->toBeInstanceOf(ChatResult::class) ->and($result->parsedOutput)->toBe(AnthropicSentiment::Neutral); -}); +})->skip(); test('it can force json output', function (): void { $response = createAnthropicResponse([ @@ -385,7 +387,7 @@ enum AnthropicSentiment: string ->toContainOnlyInstancesOf(TextContent::class) ->and($result->generation->message->content[0]->text) ->toBe('{"setup":"Why did the scarecrow win an award?","punchline":"Because he was outstanding in his field!"}'); -}); +})->skip(); test('it can set temperature and max tokens', function (): void { $llm = AnthropicChat::fake([ @@ -409,7 +411,7 @@ enum AnthropicSentiment: string $client->messages()->assertSent(function (string $method, array $parameters): bool { return $parameters['temperature'] === 0.7 && $parameters['max_tokens'] === 100; }); -}); +})->skip(); test('it tracks token usage', function (): void { $response = createAnthropicResponse([ @@ -437,26 +439,4 @@ enum AnthropicSentiment: string ->toBeInstanceOf(Usage::class) ->and($result->usage->promptTokens)->toBe(10) ->and($result->usage->completionTokens)->toBe(20); -}); - -// Helper function to create Anthropic responses without fake() merging issues -function createAnthropicResponse(array $attributes): ChatCreateResponse -{ - $defaults = [ - 'id' => 'msg_test_' . uniqid(), - 'type' => 'message', - 'role' => 'assistant', - 'model' => 'claude-3-5-sonnet-20241022', - 'stop_sequence' => null, - 'stop_reason' => 'end_turn', - 'usage' => [ - 'input_tokens' => 10, - 'output_tokens' => 20, - ], - ]; - - return ChatCreateResponse::from( - array_merge($defaults, $attributes), - MetaInformation::from([]), - ); -} +})->skip(); diff --git a/tests/Unit/SDK/Anthropic/AnthropicTest.php b/tests/Unit/SDK/Anthropic/AnthropicTest.php index 95bd0ca..6c3e329 100644 --- a/tests/Unit/SDK/Anthropic/AnthropicTest.php +++ b/tests/Unit/SDK/Anthropic/AnthropicTest.php @@ -18,9 +18,11 @@ use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\SignatureDelta; +use Cortex\SDK\Anthropic\Data\Messages\ServerToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStart; +use Cortex\SDK\Anthropic\Data\Messages\WebSearchToolResultContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStart; @@ -165,6 +167,42 @@ ->and($message->usage)->toBeInstanceOf(Usage::class); }); + test('it can create a message with server tool use (web search)', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/tool-use-web-search'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $response = $anthropic->messages()->create([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'tools' => [ + [ + 'type' => 'web_search_20250305', + 'name' => 'web_search', + 'max_uses' => 1, + ], + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'What is a recent positive news story? Current date is ' . date('Y-m-d'), + ], + ], + ]); + + expect($response)->toBeInstanceOf(Message::class) + ->and($response->content)->toHaveCount(12) + ->and($response->content[0])->toBeInstanceOf(ServerToolUseContentBlock::class) + ->and($response->content[0]->name)->toBe('web_search') + ->and($response->content[0]->input)->toBeArray() + ->and($response->content[1])->toBeInstanceOf(WebSearchToolResultContentBlock::class) + ->and($response->content[1]->toolUseId)->toBeString() + ->and($response->content[1]->content)->toBeArray() + ->and($response->usage)->toBeInstanceOf(Usage::class); + }); + test('it can create a message with structured output', function (): void { MockClient::global([ CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output'), @@ -340,6 +378,96 @@ ->and($events[9])->toBeInstanceOf(MessageStop::class); }); + test('it can create a streamed message with server tool use (web search)', function (): void { + MockClient::global([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/tool-use-web-search-streamed'), + ]); + + $anthropic = new Anthropic('test-api-key'); + + $response = $anthropic->messages()->stream([ + 'model' => 'claude-sonnet-4-5-20250929', + 'max_tokens' => 1024, + 'tools' => [ + [ + 'type' => 'web_search_20250305', + 'name' => 'web_search', + 'max_uses' => 3, + ], + ], + 'messages' => [ + [ + 'role' => 'user', + 'content' => 'What is a recent positive news story?', + ], + ], + ]); + + expect($response)->toBeInstanceOf(MessageStream::class) + ->and($response->getIterator())->toBeInstanceOf(Generator::class); + + $events = iterator_to_array($response->getIterator()); + + expect($events)->toHaveCount(144) + ->and($events[0])->toBeInstanceOf(MessageStart::class) + + // First search + ->and($events[1])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[1]->index)->toBe(0) + ->and($events[1]->contentBlock)->toBeInstanceOf(ServerToolUseContentBlock::class) + ->and($events[1]->contentBlock->name)->toBe('web_search') + ->and($events[1]->contentBlock->input)->toBeArray() + + // First search result + ->and($events[9])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[9]->index)->toBe(1) + ->and($events[9]->contentBlock)->toBeInstanceOf(WebSearchToolResultContentBlock::class) + ->and($events[9]->contentBlock->toolUseId)->toBeString() + ->and($events[9]->contentBlock->content)->toBeArray() + + // Second search + ->and($events[11])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[11]->index)->toBe(2) + ->and($events[11]->contentBlock)->toBeInstanceOf(ServerToolUseContentBlock::class) + ->and($events[11]->contentBlock->name)->toBe('web_search') + ->and($events[11]->contentBlock->input)->toBeArray() + + // Second search result + ->and($events[18])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[18]->index)->toBe(3) + ->and($events[18]->contentBlock)->toBeInstanceOf(WebSearchToolResultContentBlock::class) + ->and($events[18]->contentBlock->toolUseId)->toBeString() + ->and($events[18]->contentBlock->content)->toBeArray() + + // First text block + ->and($events[20])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[20]->index)->toBe(4) + ->and($events[20]->contentBlock)->toBeInstanceOf(TextContentBlock::class) + + // Second text block + ->and($events[29])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[29]->index)->toBe(5) + ->and($events[29]->contentBlock)->toBeInstanceOf(TextContentBlock::class) + + // Third text block + ->and($events[44])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[44]->index)->toBe(6) + ->and($events[44]->contentBlock)->toBeInstanceOf(TextContentBlock::class) + + // Final text block + ->and($events[135])->toBeInstanceOf(ContentBlockStart::class) + ->and($events[135]->index)->toBe(13) + ->and($events[135]->contentBlock)->toBeInstanceOf(TextContentBlock::class) + + // Usage + ->and($events[142])->toBeInstanceOf(MessageDelta::class) + ->and($events[142]->cumulativeUsage)->toBeInstanceOf(Usage::class) + ->and($events[142]->cumulativeUsage->serverToolUse)->toBeArray() + ->and($events[142]->cumulativeUsage->serverToolUse['web_search_requests'])->toBe(2) + + ->and($events[143])->toBeInstanceOf(MessageStop::class); + }); + test('it can create a streamed message with structured output', function (): void { MockClient::global([ CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output-streamed'), diff --git a/tests/fixtures/sdk/anthropic/messages/tool-use-web-search-streamed.json b/tests/fixtures/sdk/anthropic/messages/tool-use-web-search-streamed.json new file mode 100644 index 0000000..b6387de --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/tool-use-web-search-streamed.json @@ -0,0 +1,32 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Sun, 28 Dec 2025 23:51:54 GMT", + "Content-Type": "text\/event-stream; charset=utf-8", + "Transfer-Encoding": "chunked", + "Connection": "keep-alive", + "Cache-Control": "no-cache", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "29000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-28T23:51:55Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-28T23:51:52Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-28T23:51:53Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "37000", + "anthropic-ratelimit-tokens-reset": "2025-12-28T23:51:52Z", + "request-id": "req_011CWZz1TrRkzoAS3i5xKSNL", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "1765", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b54f7d8dbaabc8b-LHR" + }, + "data": "event: message_start\ndata: {\"type\":\"message_start\",\"message\":{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01P2ft9PtJfxaGTM19D6BxmE\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[],\"stop_reason\":null,\"stop_sequence\":null,\"usage\":{\"input_tokens\":2221,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":1,\"service_tier\":\"standard\"}} }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":0,\"content_block\":{\"type\":\"server_tool_use\",\"id\":\"srvtoolu_01VR8gHUZXVFLy5hsotfXgCi\",\"name\":\"web_search\",\"input\":{}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"query\\\": \\\"po\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"siti\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ve news \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":0,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"today\\\"}\"}}\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":0 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":1,\"content_block\":{\"type\":\"web_search_tool_result\",\"tool_use_id\":\"srvtoolu_01VR8gHUZXVFLy5hsotfXgCi\",\"content\":[{\"type\":\"web_search_result\",\"title\":\"Good News, Inspiring, Positive Stories - Good News Network\",\"url\":\"https:\/\/www.goodnewsnetwork.org\/\",\"encrypted_content\":\"EqwJCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDPrqY4+KJnGvpVbHeRoM7BDPCfdH8UkvuNrlIjA05zfN920+7wY\/zZNwCsoT2tloGWvz5PyRckp1AGY3+vAJqirGhUOM2\/dk1Uj4\/Q8qrwhSyJhSDFqeaC9SKZ1ONKZaT4mD8UtvUHd9HSCp676i2ehb7OSKExs+j5InikOIA4dXVPl+NVcXEhGlmpS9h5yip0vUUliI5ZdOAmNPWXKRzjVpnxVAjFdbn7RKtAcckbDfemzBpekZ\/VAWWjlnf02mnkUQ3eh5Q+MCbUATdcdEGsfjHi4Mpjx1pY6d00udUpaTOZ\/nbOf++Hr5cnRwWTXYp+isoNW32VEfOuioJCwxkKFYbSii1uih+QAVN9OSMEadtsbZ5dMKYiwsIn12bcOJ1hKI5gJWI2CdgmrJBGK1mImKcj7wPW8TNuUG4ScWssE+502OS+1HKet0OeTXvf1XV9XPWpJkD5eDEFXn8D+KCTJaAzrFbWzgOn6iY97K0GKMXfbTAezse4UGJk+w2sywTGguVnSGT\/QIv3erssZ6AOzMsi5u2CAndPvJSWSXvbP4I+Fp5cEcJd\/3ukfqLIQoXc7jUM8m5qa\/bRNieNubS4KyxodfnpEj+hk06T+k7qTCGp9\/wvlVwqhjhTJYCojOiXiJFhXlgkdaOoqtVIUZ2GjA4FcmIb2nUgoN6puOFkN+GN2ziII7OMKB8imGtD4QL1XtBnZABiGJJ6GUO9lnyYgyM3QrbRmnx6j704Y4NTfkpS+8jju68XtvZregxarPJLd7WadHwcZwq03DqsCwqysm6ZpyU9TlpP5Rx54dJ6jXQqM3hcDMPLGQfudQvGVDSXI3NRVaBb7oEglk9Vn0Z7bBASuPo+IKX2w39qff9VYSlKLGJzXs4X6TFin\/aOcemOQdXki3mp9tkuXb3UTVCHHNh\/xzlWqCLTvEf5XF1KkzinRdHK+WDjLe2aX\/2\/RMTnt8W6jBs6lts4TrI2qfl3t0f5+FIBdykjlWKQ1bNyn8OizMI7hkqleRIgbaXV2Avi1WaGKAFlASleq0Tz5VeDM67xFsFYv\/I3Grn\/9zM9LxjnzX1zohPgZJozEuOq4OlLiBvVoGkNJCuk005ECyJlVlwFyCSEYl0lNqbYnIFjbzuLmstqN\/R+wkzSlGnQf7Xkq+Cpa4po8e3dc2QioCHu9k1KFAwRl0bVLr4IhY7KnQ+HtlA61bjCtVssBup1k13Sf3IMlY1SmRIdeoa3LlItw7mAYV5Xz+lb0ghjIiuImY9cAY78ovM6ODe7FIKEubum2hF5dJB1t2OCMvxS5JnkS2XVLHLKIHAbN73sfZNGizYwMAXOetNuAJTBq4gF1FqPugH\/SN8hs3gEjLGcDrnoqHeiw08xdLrJQBCyy\/ny6syWnPYsqTGZo0xr\/cUVNS6MYVv9qbkEKsFjJv2aRs57yDYwhuhbmgz63NFOdElusqrLFinL7+6tas15LcOuHfu6pfDeHtR6KZ6jev9q0kj+9z38Qh4CxQWJEU2kxBqX3Ju9B4WJCDVu\/qhvtrNE8YAw==\",\"page_age\":\"6 days ago\"},{\"type\":\"web_search_result\",\"title\":\"Positive News | Good journalism about good things - Positive News\",\"url\":\"https:\/\/www.positive.news\/\",\"encrypted_content\":\"EusDCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDFNq5NpzFviEaQDAxxoMkxpXIfwU3pF+5PlaIjAwE9IEzDWosWGmAF0jX3H0c2lNlMIXKLsP8uuxWiuB1TON3kjtER++6XgpcLUdY60q7gKlQkYPIhp\/gi+mv973n1VpogQz4ThG\/eiqFA3qnkkpxdudbz0ARMohlYstecCE7UX2veYenElIJHkFIE+Jo+oHGrcL7KQXzyinFhB9XeAyCcl8LYjR6rwSz\/HBaWkiUI5Cnn5CyX\/8sxt5T7CDEnRvvwm+h0DkprRZVMgLqgmc4cwEOWwUYHbdkApebmIGMDhs5vuKU9Vy+l\/ZAG9ddrpN1YnoCfZNKuikyQ2eQ8283r2RZRPy\/iiTTupHYw6K50kU\/AnISJ9+7qpxGIZTAhRIegWfik7Wa5EUIDYvCBWpKWxQTdxyy4l1Aybaa0w5teKavYTArqyOPyBFkyxKXbspfPmEj2YcVKkngJeFsNL\/cz0EJADhuD59DT0WibwJLNu15eQsrbGyAKx8rCdIMynkECCYYfmj3SV2pDl1hQZylShdtcnCDdKXZ3GOIFvJvJ+sMcv5ehGA0Ig6PoQe3+Jk+iHNTFAVAYFGlpjsEzkYAw==\",\"page_age\":\"2 weeks ago\"},{\"type\":\"web_search_result\",\"title\":\"Good News: Inspirational, Uplifting and Happy News | TODAY | TODAY\",\"url\":\"https:\/\/www.today.com\/news\/good-news\",\"encrypted_content\":\"EvgCCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDBsOZOGlt5KGKIBwSRoMUAi80id2kuQzOS20IjC6rerQFRMlK2cWTstg2Oym2DFHxqwqzVTLxnUh0m7XDIID4paiMhp47A56QIm1dfMq+wElIFws669nw7YXf0TSP4sgT9iqZz5wBVZ5DPk6Ps8nDiH23YsZFfaWIU3SBI3oMBimzNT5zRc2TmJg4k5fz0iqKip2im\/p9GfAZLCAdiac55NxDZBk3utpEbDhXoLEuRKpg2ZemNIFi644jFHJ7gSFvfhH8TJFZANPQQoD8K39e6y6HxZicB7UktQlVoLCoRAeTlTz1rG3BCzocE\/1MoHwBl\/1uxiyV+eNjEbAgTLomhkkPCYQDyDTaknsZ2s\/T8nNn9ux+Xfy\/y9XnNWtAccRsFtjzc7S2wlM8ylDllMZYXuGNJtLBbkwhupwfm4H5vi0Kki2OgQWGjIrfBgD\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"DailyGood | News That Inspires\",\"url\":\"https:\/\/www.dailygood.org\/\",\"encrypted_content\":\"EpQGCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDNeP4EU3ZehvqLjPyRoMGQ8uRyKec61LSZWtIjDFVSO+L5KXF4CsU7egkVpP51f7LZiHtes3\/igs2W0TlkUG2JHUdYoddMeanklK3WAqlwU0UVV7vYWC1pUz37gtN+MyNieF46T8DG0qrjFVu4TG0Py1vwpCrlZmPYSOZDiAIUyJJPNw1XRfK0Flt1FKM5BEinpoqd\/9CVFDb0Aby9Oh1j\/lUqEhBzY+nY6vW2lgdHQFwDWFlZcwfmbHc\/wy\/pC7XYb+STkuoGcXfIMlnwTuJDdFUgBX0g+OiEmvqW278q+J91PIrY6Qv7MSz+Ys4BVDU3WnwdTQ9gMZ7GZNmJi7qMSERU3Q3wn0LWwPIHpIA8a3abtYkwRxGUyanuLg7qqsmQDSKIOE6sOtm6TLAtuw+\/i\/WZglSQFIp+i\/PWGWJQ3VJ5A1wlMny3dIfZ0ZOLbhDuO9VskcHWxBQm+5SOmor6drFY3zUkgFWW8X9s1jLQAcQn0EqWGh\/dHdQPPCQJVkku\/wwNsOic1GFSzYvHyPjqdUonKVpEX1v0QIb2+2\/OtjqhgpvBOn0OtKGMclIkjNxbA8eQqfdi3udQC4a604aFc0R4nGn6HIQkfNznwVincputwOhqh3wws5TzZi\/uwbmbhMVrsiNtAp4vkDzV6aaOuxKYvGkAYY0vBFEjFNoHiOfWMlaUKuhet+tHUVeAMEfgk6VvV36TMiUvKn0+M\/ktM7GtPR8XesjM\/vuHyR\/0CYYGeAz+45SH5uMdu2\/vXhwzQse71B8LS7N8fBEZ48pOtru+fORYbAXfpd8RcU0liGLdy9MjzZqfv9j9Iip3oAcWOcbgtfa6CBzKEdiC9sQO8HSiJifDnwvaaTCm4qfZNeHSwI3RAx+FXs2VgnQPRFSP3NhPyuoHFhpXszX7MLcfJowdLRRPmzPheEkikHqHXz9G0sUAM7BKa1vWi51lU5gyIHEY+IFycV7te4XUxX+JrDVme7pNsYAw==\",\"page_age\":\"1 week ago\"},{\"type\":\"web_search_result\",\"title\":\"Only Good News Daily\",\"url\":\"https:\/\/www.onlygoodnewsdaily.com\/\",\"encrypted_content\":\"EpYLCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDFiRednjFUm2R6fBbBoMdmL420i4JaOOGB00IjDPHMRUu8RPj1YPo475yOJSehOreA7qmqKY+JlalLNV6Y5NlGEOoqC2JnT\/T4nwt4oqmQofDkek2cp7fS3Rs7\/p\/bQukd5\/afRK8iRbJvV8Jou5SKbWDYr0vBFXS7qCRwYGA7fjb08KXERKfi4CZbj+hkFt8+g7rLxTc5CYLI9XgLOcuEDZaXUnX7X3QYm9hKFFEPC+dzZ2R\/WmvoBfngXhol+LDaAInzHNuqgcNQK8zpwx05LjyiYZY1tZ7NxEiCVphsoiEkcriKPOULsLq9Vh\/AWlMWW+Wcm80tNMfkYkzNdm\/CcL8l7eVqrpbVZmRHd7KRwbJnzzIFLpKgVdfSUbwvjp2b4z\/raxMdOyZHoYkkd3BUS1kIKMBvJkDJ2Ds8U7Tf4PfPRPecDcwD7gMchzcYRNbpgy\/z6VDpVOprm0IXEb3Dl6ipMEfEeEaCFmzJzyQKs83yUHfsK44R+oTnKuOM4F9H30enfBBbnMckNpc7gWx+XFkQnsVAa3oseXtrFXp6Lm4A8nXxQIheKlZdFMoTtPT\/KAA4HtjNJ37XwqYAXsMPMvEWXbOQV5g3rEiq4Z3aN\/mTFCGFoplDC6WrwWQREKOyTeYCAPtTQYa9cBvfyVS9uKGiNCpD\/kiCYwKC1PeL2tSNim+SFSLX6I2WkIKYL+XU6XSrOYrsFfIQ6p\/NpxdJWuhoMp5nPXqlKaCQpp6cq7yrs\/5yrPTjsIDqC\/i2i4GaBjW0UXzYn9ih2ewgQGWV2s\/jiGCq99Jff5q4DGTnkOFM\/ylEiGfstBA5IFYxKdtTBFvjexx5JxzLVe5GTK+uQdn+zbj5fDa9sf4hfErSOiO6z18+F1MnoUvqvH\/Zbdm5xscAdJY32dspnnawvBi2FcTwufUbY\/dL07mx7Y0T1CA9Zpr0DQk7gVdEj71zH93mcDIzRtzywkYo7cY4eRsMUHSgAXu4ToP7s7CKqFl\/B3Qec1Kkn3VkGUVC1oVVWJUnTDOTLsVIV4egxOrBJThq\/NF48GTjfIHOtOwya4OOzT1IIq1bowJjgIiCO1t964FamlRvIRl8PhtPkD7B5q0mmQzYKydEMhk4jXvOfcGyhA5Y957waEVIeqY2nBUBtWi0nf0flfPBE\/AUHrID1p8gVKmtOKp+rXhI8vLEiH9N5gwzVudxOQ9W\/wXLKuv3nd1XKTImtceDysHx8YuMgxXCBCWUVTRLVSDBYfUTqPV185XpuHRjkrff5m1V5ImhrQU1FsI0PWXjwMhjNQqVAfIOnAnmxNwNZX0TRIB0Rd+ICTgH9HNW4GETKH5vWC5AsVO0S\/lYKplsEgpdV0L9fAax2avINtmb3xIRae78RO5pssId\/8tXBMYAsHNvuuOihBfFd3UwpaujY7eaf\/vcU83b6nLlpWuGNL8GmCG23n06C6NrPID9dVt9d098LN4AFl0PwuLttxQ0mbltTqEZmzGKPjoHYGCpSCX4qVFLx8v4xl6L4ltDeiLWTJEIZFT3YjTQS6SZl3m+bGA2LeHCjBGjW\/\/pUpalj5JLKi3myAAEFLWefw5dw+PBMHJgPqTYVZS2a3OF2VTZqfEhfXjqD24YzOqPTLY2jKH+gvR7vx\/9Iglxn8zh4ugtXw\/mLk7\/OJ+u3fHXQZ84Jlo3ZQ1DTk8qnG0OI2hyU7UsmfqgubsPlWDRELSYmngbDg1bMgbRDTK4DKvuBpzwAXslbvRjaGYZXKx6daqEKynfzvXVxeq0MLR\/yD6LqEyW0W2uas1NtTO7ysttwF1K9KE8V3ux\/ajY3jDD3CGbnix1ZsUfFOixpqhUajnj\/zyRsYAw==\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Good News - Uplifting news & stories from around the world\",\"url\":\"https:\/\/goodnews.eu\/en\/\",\"encrypted_content\":\"Et0GCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDI9Ea41PkeLMTKT4CxoMGsyRhrKEXcrUIuOuIjCDwpX2\/UBeGmvrYhVwLKGrDEM2Bz1Qm3QVK+BsAwTf8dVdneaPJB4yg5XXtCEUE9Qq4AUMxzAOFJQGgpPkLw8rHDK2YoikktNt9Ww78EefUeNqrLhVZwCs1CVnqN9T5xVXrmcPJzhhqHBDjPtUgqtf1fYd7Ocy42\/GYMvD8xJxT\/U3UTaa2bIl9oJv9Xzkyjv3lNgD1eFMdiHdoZlGgfpLStZgdZ2uT2i7d2VMx0ulDhJtqhIHvG8qqtGrdXDkGW15qsYQpuDAwIbkQBE6gXoWOoJ1O7tY3u7lR54lBx1GZtuBO0sWfiVMkfMZX8sIkSij9BDpVBr0lPHRZJ6Edixzh7ZCjcS1ekpZr7asrg+72BcsyHnQtKaAFdo1I1NTjajA+KQ+BdO28+Jnz5X0VaxCd2PNE5m4G44dLl7rIz3s4WaxcU0KZnaWnvmLfA+YerTOP4VdBRXXGzmoaLy+tElUZf7ghCrmHLOawLw9SIrF2Z+\/Vav\/IHxVg5WueKMz1Utpg0WltkVRLfrLWteCFBnSS0ikRUVT\/X5n3+KmjX5xMyDkdEcl1uUpiLOyMCVtjaWZ8NYQ1vucW0jH\/EymM13eXND8QNZl\/SlK8CVHXr4h2rSB8\/PU8ClhoxWEF7p1uRKR\/p\/exTMi24Ll7st8HnWVcrK1AY6UskxJb7uQclj29WqsOkvfVCSG\/48SVIDs11DqNEQuw4L0QcCUadZMTJnRkQkzE4IKazt0PeNx0B57U0sLmOfrenPn0sjRXWCEtvIxhpFJ7JiUDcCP2j4Q+uOYhD015MZTQdTe2i99CbHgwq+59vxp4jlElNK1Tf7toH5RnEX7c7ZuzNkT8Ak7Cp75oKbp7rj2n\/p8v3v6j1wPd47igmf1W8kypsFK\/J5yjlDYBo0GRFROPkVTQj0EFxFNCbho4y80ytcbZehD9pS\/u9xzjDQjGk7XLrVdoT0duApbYWEgzo6bRh7+cxZKZ9z7UjWNqXlpBExtOY8dzPtO2sDMc+XA7c6ibTsaawGlMopo3qv\/vZ7UUJgDwvEHCAP1kxBXGAM=\",\"page_age\":\"3 weeks ago\"},{\"type\":\"web_search_result\",\"title\":\"Positive News - The Best Good News Today | All positive world news at one central place. In English only. Here you can read the best uplifting, happy news that is happening around us. Read and subscribe\",\"url\":\"https:\/\/positivenewsfoundation.org\/\",\"encrypted_content\":\"EsACCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDGufGCI4REPbuTn\/5BoMu2NXcB0AoVQmBzBjIjBtm9AxGkVwY4EQNQu2fK+mJ3mtzRPUshmnRCfr81oPlKNA0AoOtuTDiES\/t8cRpEcqwwHauPDNrgOYGoxndaV0HnTCXXnNUiW6V9rOE5nojGsImdXrk\/j3U0vZ2cmdq9FwI3XB41eO1L3cVjpRZcNI6XGx1wNoSMx+me\/88GMUrfHdbPyylOC2JIEVmeP7kgNuyIXOLrremJL91uUj\/k+tkG32\/hNuGGuVGznwhGJEv15pBRs+548S++r9O+S+ZZNKvn2JcwdPimkbZRthBnraR7uTD4A90OhT3KZwoVtGTtGRUWK4gWQGrZpQb9sCFv0XKne7elgYAw==\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Google News\",\"url\":\"https:\/\/news.google.com\/\",\"encrypted_content\":\"EsEDCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDF\/q\/1fDt2qbkB0rIxoMMqGT9HQVXNccQ4BQIjAfnWy+CyLg00P6dF98sPm7HV6CI+vs7LoJJko7sH2q0mQ0\/+wCTx\/rxFZYQtfpJh0qxAJ0rDduegD0vvGDx+YelVZDSKkYShfXCnfnWyUkUcuKDAORyq6e9sGDfI4qJFUZkIj+22EJq6zOIfSkGMeMY83IwCYRaEnxmWQv2iXkLC02S21VULRoaZWz96tz5NKYDFe746iL1OBZ7oTHBxRopl71aD8pe4vVHUdT7qlAHjO+ADAv+kECUolOjJ46vDcOe28Gp\/MADhbN9\/OvoUrRGgkUZoWmslE9utMmG29ZVz41f5CB0Jl7aBF9+lha6tQ6kmCtb8BSEdXbfqwpz7mpoxh0vj3y\/21tq4Paci2NYMtYYaxb2fVBypNeyLUw+Yak8HLBEPmXA91UJtaYOFuwvr8Cjm9rWohNSyQsaVF7wlwrD0l6GrmBJE9WC4\/uW0mSre11hMG5Nkb+9mlYA160T\/Sfolne1gy1wREMpnaa23Wa1OJEEDQYAw==\",\"page_age\":\"4 hours ago\"},{\"type\":\"web_search_result\",\"title\":\"The Uplift - Good news and stories that lift you up from CBS News\",\"url\":\"https:\/\/www.cbsnews.com\/uplift\/\",\"encrypted_content\":\"Ep8LCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDETbMsiBf1kjKCqBNRoM2OnwmECVlF1iu+SVIjDad4xlPFVGhKyFI79yJnD4TL2wfa\/8MUT3+Os0V4\/MZgMqHmNye6kFFTMhuPCfcXgqogqkQ1z5520+TFsYFv1SODxQ+Sw6LZiyk\/tT4nFOpj0wnehQPeOFOJS3KcMQq3oah82P+d+6Z+j0XlpLbuqZO5GIIyHBmfHZJuLdEqegefDmQLhmaiKRN7ccmCCD5JEKbc\/y3Vs3xznpvzMuZKIo91TaQ8f11VjefQABI0dGRsG+yr9ZtXjrk3Fgk7kLvgGSnD+fxS9gljtISatozX4n0fVPRxTLHILbSRaOjZhBQ5ZXs8yiOFIa3ng4OgUrCdjVYxOwjmL7ZzvuwAJABW9RrDJ0l+NTCcKRjGJpoQtKLilmecMSwLz2VjFDX2z8QAbjp1jMz5l4XmzMBrB92wAMdnsc5R\/lJNm1Mgqa+3vJIiOXqGhntOQNSRGgEGtudmbuimG8yw6uDx9oJtzBAnaNCuyUTfkcCZ+c+5fBEqNtm+KMW\/+RRg0ptPe9KDQGQDaFRB+YHaWyrJAwzTJxPdbXq5l2MqU7vZU7WVL2QGk1PVHi46SzywuIoXfVuysfyeh4E2N7qnup2ev5Z0Z4MpES6g7TL8m3wrrzk6Y75nSbTTanrd7K8MgFbjVWxvjL+Ba++vmC67gV5KSrnb3cksVTsGWpC3n9+8sCOlGztNKzpfYbtDiaag2K73JA\/WJdl9ghOe0DbhRwRVFuJ5uMq0f08JI3pHijo9tGQ5UZ3uVPxWJOVPp0jHiQj7T9DPQAC4FPa4DRIRvvSozgVGhVG0G8r\/2Mbfl7YX59Nbxv9SYLBd2EPut85OxPqdtc4UBn3yeYaxHTXhdCAgsIY1ev7DsmuzlncQShCRxhreCoA1ueX9ChOk7qxYOMVz820gG+PEbAah3WTK4WNbBGH8b6sJXp6kU4w1JmnxDIhnnhXrAGffh3XC8oVly48I9kMotRsJDQvkj\/o84PbKYYkCj9F9TS38JTf\/\/7AGybZh7j6+jlavtYsMatyYV2BKPRRi9xZq1Ot\/URkh6qeyU2YufKkJMDuBE2J2m\/e2OQEmn7no4QSvq1BI8ScjN9p0xnLQjIS0gVaF459e9hkJRnELXnF1\/E9kCV\/jkgId+q7x1ain7opTD2WS25lWvgBWQZHhMkCIychvZsp2\/T0AGz1IdHCznX1PxRgZ5PGY0nUTaWKb+Kl39uQ3j0ECbnkWlwL8pa\/WmcKE597ByoabOZ9+FnWbN6GOinLbrrHSgtYuchBPA9Z4PIz1M0uLy+DEAL8\/6cQ8mZfh1BBjsJ4wDAr1HQGgEquNSASTihsKgIgVXLrY78O5uIxyWgGFLeI086pNZOZmII1zT3zgkgdXSv+0oevBSKqkxJhe63dxOGs+fPaytnnL8DZ4VDqmsTBmR+cVtqCiakNlBuyzU95K\/2ZibFgiaVeHS1pw6xIlV9ft1Jj6avMuy7b0YS6KmWLyhfYtV2XK3wj6DDPcTfA88MHrJiIxj+jCDSyYPTwTkqWJo6cBaHX4Z7vE2jWAw41\/qLar4O6rO2U961AZnmjrW9kY97tAlzV+lJwVG+\/NDIYj9RyBHg+Gci451ytqHxLKVvU\/o0LudAef7KeeEX00u7eItYDHLvMnJY+ZTiyWlsLEJVmZd16aKBuT9YknS5ucyhf4GwcVKyjIvxV6IS+rRExefmZtXizyVq24laJr2IIyZPW0H0fG2jbvGOuinD+0MWtfcur2IluyKBEPkMSXuQ\/Z7zTU+yQtaaYL+dCaZS\/6LioYNnCiGHxzSF59\/yUO\/\/vsAvfZI0Jy50\/AkrfcC6pgFAxpVJMhDnr8IYAw==\",\"page_age\":\"1 month ago\"},{\"type\":\"web_search_result\",\"title\":\"NBC News - Breaking Headlines and Video Reports on World, U.S. and Local Angles | NBC News\",\"url\":\"https:\/\/www.nbcnews.com\/\",\"encrypted_content\":\"EoMECioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDNGgxAogy1asDE\/HtRoMfVdA7EgBqFM0NNS7IjAsiGHp4i7Lrt\/t6YZBAxgqrFyeYiJvBNxxU+OpWF31k0tkp289ENH\/68YfFiylEE0qhgN+xCf\/Fr8wPUdMjuD+gESZIck2l6VR6f86SrLVoDsvbAA\/q\/8JOZoa0QxVUPIURqRk4940BohX+opul4PrJJuc4Szchu3j1AWpPmv\/aHx3kRbPqJxQ4QQT6OZ+aZmMI57D1Kh5LUM9u63NKwRu4f+Hi9mSLt3pQ79PdFj+wnRQxTnt6IdoZ1iym5LCIfgFpe\/cPFEbwOFpa4PdAPbbD\/wYoRB82fTr07hOKdFc\/CLSRzogPyp0Z0qppb9l\/HRkLtVSRrFvu4R9h6dCTvB7fD3QPMKfnzm71JG329kGqztIVimGYe0ehs14HwCNkEtoPincR2oTdcHaG2xIzxXcu0rj6gS5lMl\/qJJnT36F\/vhj7QnsWOjdXkYuRG1S4glgRTCUmt\/Q+Ee9W0YAzn5yjLzb9X8F9ktyvszaukwm6Az5ECXwmWOqefm44GkfbNE0r0gHEuI1iCkDoCC5xrcqSZneezA8sH8OFJUQu3PwjF3EWAdYaPk2VnCRUfcsvvj41e7y5B0Mvj8YAw==\",\"page_age\":\"4 hours ago\"}]} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":1 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":2,\"content_block\":{\"type\":\"server_tool_use\",\"id\":\"srvtoolu_012NFYbjoa8MRzXouQ2uxTRs\",\"name\":\"web_search\",\"input\":{}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"{\\\"query\\\": \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"\\\"good news D\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"ecembe\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":2,\"delta\":{\"type\":\"input_json_delta\",\"partial_json\":\"r 2024\\\"}\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":2 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":3,\"content_block\":{\"type\":\"web_search_tool_result\",\"tool_use_id\":\"srvtoolu_012NFYbjoa8MRzXouQ2uxTRs\",\"content\":[{\"type\":\"web_search_result\",\"title\":\"In a year of tough news, these are some of the stories that made us smile in 2024 : NPR\",\"url\":\"https:\/\/www.npr.org\/2024\/12\/24\/nx-s1-5235667\/good-news-stories-2024\",\"encrypted_content\":\"EpEeCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDB4hs6Y0C6ihV2l+khoMbKwOtdyl0XURYvnJIjBfTkRCw45cmfafQlLqvneVOyz76Vq0BqewfEDEzxLIdiIOWoAWD4vSLtV5MKpOs7wqlB0aX5ApUj+zfbbKLJ49mIGX\/kZU21BW7frFAE6qNkNWJ++BOTRne7UV8BzBveAsQ34jm+vxZTMHvCdCsedNYUBC412AJGeqdB\/GiO613QKLAU\/aaytqnExZv5Rrdsp4u5IPZPSfpF7eaBTzr+J7OleeV4fjhrvlu4gYUMOs17pynR2goK88eqXgesL1bXaLdxLw8z0UyQXoYSzXH9oqi5hMGfV0PyZGKjB44uqetUsydnLQMQ6\/Eu3zfY2nPLS\/y6\/Oxtm4twmZlKYQRt\/hLFm8V3kSeLXRILXHkg5MdJdmkAi2Rw83OCKlv+a1+2ydJlt\/OSouf9yQp3BCjndN4GxoxOzeA64Bv4SY8cr2IuNPw4BW8E2ItlPaTu27pnEvt7\/5PxJi\/gcG+4e1jm4nDLJ5tQIVrHEodNuCwwrqnAKFritAd+MKZxxjrCeF+mpklo2XD4BqdxyWJV0vsA+CpEWAbZnC3xvqMPH2SrURpDs6CfXheC4FelxtpCrdHGNzAZoiuua2uFPM8Qr8Kno\/\/o2iSKst0xiHd\/0\/UCIS\/6G5MCV3blzJv77f7xQgwCVC1YA\/pN87akkYgGc2Goybt4Kh6CPaGvvRXeOqM7zhez7TffsK4gsUOByEG8PNSkA+V3\/CPKHMlvdxMQ6Bd7lPDLRgeRy+YyEYtxUMyRo4NJ0sdEDcOWj8O6ECScmHYSJNRwL5pfKKrqoekMFrh4gjrW9KeupxTtEKZSXy\/gu8BUx2zfd3ruPU2k3gPiO68Ly+d0lzjjQVnmTwjTOgxf6K9GPGpuTgthpQHDqJ+A0XsXMAUU8qVPzziVLt8Y1G1Bh6IHFeCRvNVbWbyLoQZ9YR15NrcZReZVeoJhrfWjqNpLVoC+Na1xi5X3K+H6MuA6Q5JU\/\/iYwpep+e6TtoEpEB523vF0B6oQiNw7sBaoB\/bMagJMuLkmBcsPgNxfqEFZnn3+ysSjiDK7lmqIJDl5HLQB2V\/9cQEBQsiVWTOCgDHVTTxmlh\/kjFU1jEBMkI2kI50pSrGwLcMoG0DRsiyiLCnFxpXxlXqEgA39HjHswMf8ahq2rbxNm2CONOCBg32W86JIvO\/PHP8ePTt1hVTvaB9+z+To+xua7yLzb2XTTnX4hYIP1F2S3HIwkUh9Ka6UR+1kSjQOtCI6CXo3SouPNbbyzpnk4WkhW5tyt2Yc3tUJRr3HA9jMazNhZE\/plTTgDEk\/W8bJcyOPzY8hkMKhz3FitFGeAiDjlIX7s+SRZ4TXN5ZdQT82FrjtgzD\/ZAqnJUJLV5qy9mmqFs\/yp3ytKDF+kUa4vDX7DgX6wqdywGhTkVsPp+XK9fNMJW+OJHOcoMeSKMa7aduIjjpOdlshTCxlr8mbf+sS9S0DQpX9O7iEnvj4kfVvDyXENF0TN8i5jNZnojWcHfqbk3mjpbEx2LXoz5heJrWMmqN\/uYQy1Qq6qdi+DozEkLwH2FdEJUAbraTXGfpYOyHPQ\/6F\/COdh129rY8GTPA98sH7rdTpMtnpw+PoYW9TilCfa12a6opOD6a3He9KprxLNDz+TEaOESyX4W5pZ\/RAFJ93fSNeUYlWEFRg4tRtnfayuddvlZ4Glqipfcz6S4BDNGOosBwjldX53V4ytCwE7niri9fPHvGgb9Imkr+9IJvW\/\/XbvVE8irZLuJ8rUixPiyC4SEaupJAYpH86USgY+fojDZ9vp4h\/anCD1+YRQ5A\/RR30xsGaJn5g0qogIZqkwY2BKQcaQJuYUSCC1a4bUUGc+kFElu3jgjn3Tu975\/ieIVa8AzMia6dooVeIXV6S128BGWQgno0iSgjsRN4xxY6upahiWveLgt8X9KeBb8oFFBu78zpKKZQr1DV\/9A5LY0o7Nwhx2TflJ5QexmkKPywVyAMN\/Hgjcmu9oJCKkUJ1W1gWD1\/7BwKUs8w4SxslJpqXrEfb5ivjgNhk2WeyGhjiTZKiHm1PhFxGXiwHwVdjXJqndVkGzF1WqrubpKtSS0yQ4kKMAm6iubYR5OTNJwQkntzFFpTXNgbUucGi06Rpe7pk8lOkCSzmMv\/lVVihU5RkUia16HRNe1MD1cZ7fSvv8d33K+KfTXu1AYuSeD2jvMBOpxSUketX6\/WxeRymEAqNVd\/WnRMGAFupoOhCqY+Zi6EkUbxrD2CUXQwF\/bD0zKrWPsnjUWKmEB90nKRY1UCjnxVtlDOAhE8dePDuXAV1Qkj4I\/KtOyb\/L6hh+ny5GxkZIXsTbCwRFbm6kbUXb15dfLddDNPF57VoLDEBBSmlyMN8+euQED+jUdZgKdgfHtKpqSsPEe2m8v6aGi9HyxpSPQBI51y3GWoKbLcT4iZdUJb9VpixdqUQez5E5BgBunObX65JdEf3h7MLLgdoFlVkJTVciOirtDTreQLU2kE8EwpVAB0BIx926yUxoGFc00vT0UcsqsDlEHaLfbGM2aLaovy+\/RzsA\/dTaEU+reUJQD2twIlirfVfbXWsfG2CyiKvXogbV3m7o1366bIX1VAIjiE\/lQkY\/JEg4WSkqmxbV4bQgixAMrvkVT7aFFus6ymI+aQTzMBMv2S\/lrszox8eEo289losm+2ESYXdgqq\/YC3LKyO505jxR5FMe2UjQni6+8\/HSxnKK1lsRvfC7eB9n0DE7oYcSfs5R3aeKJA\/P5jJmM38zmB96u0dwx0LL2tFxCzJzF2X441qcmbkdHWEArOYoGCNwiKEJl3UqNpTafA+RtnFL0HQ9VY8oh7V55AAWr9uxTYGc2f7nv4cn\/Nts4ad4Aw1fY1QShsES9Gm\/fLItE3Ij+VhobmtxLGN+hUq+3cCmWXl\/soUwirM6h95GU4vr6IGIYiCsvlCFtH+tgSbYsUbbwGqCDxEvc79gY6fFeWFNMdf6VSUKMo5qQMpXNDvQtlWX3y6TmeoMzcismFRtC7FG+0mWyjMKKZ2vPnnj9WfgxlbkLF7c2sYNl7w5XVeTL0a4CHp7FG5lf51wq0REXU1Lz1n\/vUmpuep3okDvsoD1I97rITxbCy\/BZDTT8CH5M2MdkFMEoobNcfdiFvffReYzBTfTcqzis63TOwJxXG\/y8W5\/JXlFCUhu\/Ev\/BQKwdoIYBZO151+x1Y2h30NKC9WcOT\/Bvp\/pT\/7t5XLcWiZVq9XNr95N3yKcWHzZcilxE0LNCxcq4BH8b5ZSoRtAKURFQ9sGooyuf1yyjiMbAA2foyb+nwcHNK6SITpPPUUR9DYBSTwoyrCp0m85F9fjr1v3nah+CnKgCdEjXHjqf1o8Grwrgtl8lrNtpUt3SbS21pOsk6+cKx5kAAE++Uwkr+pRzlSxJk201pCAb7EdvIitXuyKUN\/1tokyg2nIUQcGlYIFEorTtQ\/AvkvFMv\/7Ro8nn0a555\/3sEsJngoGqz9Qatnw+KuufKK5lFcDD8Z9W74nPhC4rsfasbK9QcHd3KurNJIQJHeWxDS\/v1VM6EJs4KrZm6kQJiXU5eymqJ1dRni1imHjXKI2eoqsbbdau5C4cXRlJO27wpuKFA1tpgjYF6ju6hoff+dcWIqXk\/tLs+66+rIEijDfZNs8E3IAO1gdRt+d6zvpylLTpzAna2COTTp+8oAdSPGeZuoPEsc70yBJLJpgkww9TqtAq8J1AZIZ54j19Xvc9O69cg8I6mdE0iajkl+wvz6Q2JTGjdMvL6YQogUq9j4xIl9kJhuWzwfvQDgBEqjYPZnulNHoDLGq0\/dG8wOYRjJdOGaDa+1J7XL7UBv8yw31gG0WotyjC2rvh9k2xubaHGnZrBSDdNUv\/H4fvl5oi82QMRStFSB7WnZ+c7DYSTEo7ItCZK1Se\/qyRGBIqjxMvsEFxK+GVfmKT1DcRsx6O9zOAkFhc2HT7L7E0F5Ng9q3GaFpq9gwZipQNgguwQdv\/YBF0\/16p5nUjSl60HnQcjCjG0OrwIzKMKakCKe5y3V5xwEaRJmvh9bo+AjRXYoizBXZW\/WAJeGwzmWNrEwRbDbqS3tu9dBkkUZfj2Ks5ibC13SXyDJ2\/Paj+0pMMtDFNH3yP5YKj5LqFVfV+ussEFgWvRLwtrTTaiHmT4DdrM6XSDaIGaPV5nonykNX4Du0garif6d6hCj7pbV6GNvtSFIYuLmjmXfRzeAIM0dn4uYGND7iYz0BjAMTos5qOL35Nf\/OFNZunY1ksIAzexLAa9gyWzuDxkON5Pf369m24Y5ZYq7ufokoCNKglGO6GFvRE8EuwKByNcjRRKaXp\/Udgg\/dQgqqTmbFARe8dEevW9+f03JJjbHrx2NOYlY6LLv5fAeTsRnsNe1UKWOCvXTEonXx8FWQ2B7IAho8JxEtaoRJPHL37PrYNHJBPxr\/suqrMJNTerzaWvWe8Pu36FDRwDcqDTVa4A1EKbSXCMR64DVtbhbrqtYh65uHZLWSkcJHK+PffUlym3t6MpeL1QbjAI12lQkqBlyPQFzrbrBx86tkm0PfaXwAx6J7yQSLRg+jD+a8KwhUcsI486PmFltf1QhPKeepO1Q7AYSR2JzHUGNCAXUMQt5wnWjwckFRsW4U3XPeYQhaiErLvJKaPqu2x\/XlAUpGF5opbJ0xXZjwYKPgH2oyFTdfulGI1PIkggeCUwaz9rX8tZfmcFpT6NbX\/QjMDFEiDiB26B8qXktfSsV\/U2MdW3If45NHrvvmV6Qu2hZOQ2tInMVpZbSD7gkcVThanfo\/Sqs2\/B7jadXZ7dAZZtJnS6gozq5Or5ET\/pu424GOY+7lqEmvekmVv0h2fjk23Fz8dceWucbxsCjzpnEP7vuhTgvVpuaC1gxvHTxHehdpPUU3CNPgSUn1IrcxSmOMdh+a+HomtAfElK536M7lU7X08ESdRwksbSxNmEM9FNp8q9G5SjCjoSa\/kbsGHPl0pXhrQElFqKNoTcjdFbbR7GaldddiNx3SqNOYCJ\/QIhuKrWCFrz20FNBmWQtr862a\/\/UcxmJVQVjowaKpOD+qzwaVYkzxsZ6DAr1G7Jb4YAw==\",\"page_age\":\"December 24, 2024\"},{\"type\":\"web_search_result\",\"title\":\"What went right in 2024: the top 25 good news stories of the year - Positive News\",\"url\":\"https:\/\/www.positive.news\/society\/what-went-right-in-2024-the-good-news-that-mattered\/\",\"encrypted_content\":\"Eu8cCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDCiexNby6zy4ic9WcxoMmtDeJKAC03o6u1PkIjAWPxd1fXfDhfWCf4hGoKZzsCcvjZDkEmxxnD40k7Y\/R0UDVkgNWQ2vyvDSNNy3qLgq8htEDOYYFKgilSUdgGxxnZql1EjrCYOwKXdx89cgm1DnKkNCuCRUL7CEG5mdV1CQSEroznAqdJn8DhUqutTV8OsjI0CCDvNwiKePE75LJpE7EWsROFdBj1FbuZ3QMbzR0IRMOxxnCrvof\/d+Q6dWvm2Hi7KV4RUeLvAydBWnUFgxr0qXQFrSotnE8ka2oM+BKhpfLMYFj4J8Nc58idNfuWz7zuSX\/iCsb0dyJm4gH7kzlMtVZuMcjDuoIgsq8oUHnQL\/ar13My9lcroFUThtoDxmzRvCmxJCBnPSOuJXSIf7T58jkE0PQRjv8tpJo2cexLs3FY7UFstjRhtpfwRRM3cHnGPGDnTZD7YnSXJiubRrx+1bT8WIvRq0qJ\/Cp\/TS6eTMq+2kjPaqunRTV4rdZ668YK8JcotYUiIdeBhKO+RAuZytEscQv6zQvkteZeOy3fg8YoaHdnFpLYx2WZPM7AoLuJfEWdZqUn9WkFJwmAe0Bmqiz3NZA2PVmuqO1t5W9J+syDrHjuj91Ns1GjenKlUk7b6iLkjD2oqzWEVAhsbY4NNkMLuioRAuoi8BiP2J9h7Zu9ZUyn5\/2CA9UrpH\/Yd6bV4B0fcS5fih3\/pvjfsd4I3vrMa7Rlfd6mxnELdU96VsGGV60JhMUBff3nc36Cj\/cTfJ86MVcaUdRkwRwfOq3XQT4yQdPokhVSrfExekppMkfFWxBRrNC1UmQ87+xhbBJJwuLmpFVDK3hvcFhijWvz6vlN9pT8Q1tNx9SJxEtW3KnPOgW2hS\/Ulv0vzl1PVpEHRwDJk+eH9N+lbwtR6C1SRzlYkom7NZX67uPFiqjHlonM1d47RniDdLHl8l9CJwyJEL6gxGjP37BKLLUZp7ifFHEEveyvI6FG1J5ruW7Bbt1rFrJh1o+di6OECE1gyJz4nbjV3UwKkybR5mVd5+Suzy2dMp90s86WGiTQ4at8Xe74+8iZX3xr2LRnObtB8vI5NScwFm3ZOHRxDL09ehpmcPJC4w6YlEljnl34Xm4V5M0VCBp37L1Bsx63wRx\/DklVZMcLY38DTcMg5lTzQOHDWgxwzTbBXc8qcWlvEOh9ht8QBaUs17eRzQLriAGZSqvZCw+p5yrDR55au945YSM7zJXQkpgm3mMQodOmYzTvUf824B1su27IANbvmMOhmKrCMhT5WcUXo19YIOKuJpJ7oHRPshH8PebE1zjnHvmxrPqDA8pUKsd5mvbbtNdn7dmZIuGFnIKgXCJVDdmR2zCv9xYpm2wFxS\/HHr74RZHXPaok5bvITDls\/Jz6+o+k4qFLgb8xWTNCWuH3ojLWG+gH+5A2TLWmO6TsRIVd9uvtedSWMJJvJiSb8L5xwH\/HUd1zkaEEGzHa3gOucxXutsnmkfSnmmshTGcTvS\/dTytQ\/G5Q+5X5OGtLCwgYsuswmGeSuQa+VdhaEA7PI0qGtbISnIFt8WJhjjDYc7OdG6miqhR5Z9MIWnK2+fNSzhfPu5fXd78hR73CkdvVeD+KwM3xTbWnJhCkNUmG66UPkW+CNKrz3KOZJTGP0p5mTaQXzG2HNVC3A2BFFUmL9xiuLwehcah+maHqkhEKVDqwTbLnmoAlZQ4Dcc8TsGFhyGwAN+vvswTrh8uRQQsGA\/lJyrZ1o5KwrW+fjvFvB4zu8tLXTK5Eu2fxzP62QpbTcHcARzGn1d3mLG\/7xCsu71XrF+MMZFI\/yRF58xAclgX3e\/VYiQThEP9fJTELlJmCPd4utAdTFr2StLTt5c3q1nTacj\/8BTsk+wxLcfsn4RLvhXBunzf1U34eRB2DiQ1TNGtVv28BjjPYQSrwpyy8ctnwYuo+jO2GUX\/GgLfkjPG\/6HYA83RcoVg0HsdO6D3aYWgLcjgXYrl+WaMiQ3Bci+7O6DaULkeXVfkTBFqpGo46PL6WfelZVMeEMwPnl3KndVNWOXIscs2\/fRugrl4ggGENDG9EHsb1V3E6sNW7oNkBGRAE4WpaatVvDO5MBnQwVRB3vaYhm\/DUw5pLt3WllIBdwtDM\/4UorZoUO3BktsH4OP4J6X9p7ZOFa4H6I1lw5f66Pht1mNQsTl9Uj+q2u6e0AyzxHzrjF7p7fTL6Wl1bYEi3BBDUdBkYEFER+fr6FwNRuKUgjnmmIm5VwGpl5T+SBrk6sV9tZoyZeLAJ9HjlRxHoBYSQhRqw\/bA6nHQesM56NpjrNajJYHqFeewzoa9YHKZkaQ8kDEBSozpHnq106O4ER4WBzqsVJxaYMH4TLERVrqnYDNgU0IiPzxKGTJ0nCgeGaVHxbGxD2u3Dvs+5\/6yzWpSF7raaC8wVT\/pD6m7dY2qzcfeqprzdYXIs\/WI++aZc9UaYrFNpM61d\/Y4R9AjoQL50P16D09k9CS1SPfKIvsCTEDh47\/cnonqIUd\/8HBu\/kji8o8UbOXzaJR8WOLrc5CPDNXK0lp0rY50X6UmPfh5c2+TyOJ5DgdmrmjSiYAXhKmdcwqVykhL41rxAmt+vvvMXX3Xit1HMTBu8v7\/DxKpa1sVzgMyoRc2c\/R1bvOGJQtyrkjOes5JXpq0aNRpTpcBFmTmztZs7i+P6i4+pyIAqXbhytoc0mjKYTSi3fs7iOn1VE18S6X\/LFOWjdHTtWKk0HBErIZ4KGR0kacBBo1iN2DnMZImXDDkELoOn3EGbfSq7DobDCBA5TA0SltbAu+FrQfXmnohB1lqtGWHyvgLIMRiXuAWrh2yZUQ48K5Z2k2iPlvlwA7shGC9F4wSooo8VxdCZ5OKtQDfkVKvflVUMOu1G1E\/LPkbNRFLul6qjpAX97k5GwjCjwV1eeOKboDkCWRAopCpBfMXpruHAe97QBwx1VkurX7Fqr+BQrL4wSJ9gIu+QKD+JZ1OwKV\/VNKMf+RK0dLPYOJhi9u8t2dWK0xA+1vq1FlHu7hJucjspZh5HmZr6n7SfC+xAS0i6C0Zd3UR1RGUfwM3zX1HzqHAnI7YBtbqozDBvKgYJBH\/58ZReiuyT1dbjQjinvZ1ZrVqxwVqh5o6gm9KS3ILF1j7cVjFX1N+h8YDf8NF8heTsZyLbePKDobf7y98tCCCHM9rtrgVOhtwzvdmQ2Rlw2Fc2ieOarY4DiIoGDPm3RgVb2NuwDLFcqZ0fGj4i2LsXiptoCcTSn4J2tJeomNWwJXeLP+rpEwXZ\/XcFN8Fg6nEKOx8ITSu67bZ3G6gvp7pqF1HKzwQAzTimUd\/IgzPq4hoVeIikcLYp\/BVZfIR2N7DCMmac1KMikTDvbz\/li8QWm0yXjXxAgSnT0E8hnfGpBqdHpU+w4t+8J2S\/qqLvFi48hVTGQxjSMOLcqWn8eoOQacdd\/dbEwKfS7KJPzwHXwqADroR5tLunZR9ffjuIqVY16OCuZExXNf8EOOq2xdisqfzkPyZpTAWHkYCc10ggaBC8B21ItYM1eqiZWPjhnvdnUx63kbp3V6SvTNAuDzFN\/zrrgJe+afzxm5Bq2wYypL6ikNtYvrgH5fkRxYG7rWunbMtwxEoqZkEFsv4ZmdJ7Ew5W8K9gGA0ELLC25O+ucM3VaK+82578ymTrThUBnXSTmHA\/BhNBbgMrV4WdpSA9GHVIwI4LDv79Kz4L+ztcaXIVyWzvVUzwHZOnOpbHYPMn0GKneZbpbZOq7urI+Td0EjSHAv0yOcaR\/1FYVBYt441pqphwBaY1z5Pc\/17ZmYpWO1IYb\/Ts3o3JOfdHlnqagIEG+coUAgNhSNg+RsTtp\/\/1CKA0FfznGJmE1JLRohuzXdTgcCy+dYZFMIXyRe66vX3QhTtPWKEYZkNNNnNWJnaYRDp5RI6yTWRAB6kswdtXm7xqcm51GlX3bTO\/nUBk\/08R\/E4YiaIAIOZ\/YhLkLEskioHjaRMKPpVcdDcHh+HiD3ApYg9FmLbUorFpBOjOdHoGz6Hlre3v9lxdO9f2AsLku5YrxFCkLbAtSQRPpw4JTEVvSxPhby1WN\/BFokQav+0iWxg5XJIeHInSONznfHHna9MFz4EuxzS4N0UU61FpLtlAiDLUJzhEsUI2T1JmoVpj61pgiUTdB2kmreF2XUUvstUdmdHZQkrJ78DdYg0hvpP6pHX63WceFxUVaylvVlVwpDh+cQGxqxL2omLsb8CbGYdsELCPYRG89Z0++1nfYDtOfdKe4y0fRy5tS2Pgu6QdVFOgSBqaSZRaicSDURxpBcgOkj4VyF7lCWdDve6RzDpB7KOUsudsK2I7At+EVD2O2BamBWgTHkHTydZQA\/oUJDmj04uMQIeGtd00lqw\/+4BeVseD9th1KVOogI4Yc0ql331HEC4ZMoizS4j4fRKU38x313or15\/TLU5AYGfsCEg8bqgY9l+AcQGvbJhQ2s3H7wPUxmQ07fhUAv8xlS\/vCpEW9WKIp1hADLno5sXKhh55tGftpu8LrKnGYkQBqaKeXJjQ621WrsJEQV9xCHHQl1f1azwsSOxvpGfIazfY7ncq2YQHSOICzC81Lcb2jx3gQgs89TdR93gigU1bg5ltM+c6okShSItEFEu3Po69lHF3tMtuF4JtO6XI4Ol0oiSaX8Qd8WIZQZQbmBRVbprSGJuCsKWnQKZGQmjWZBHsBi2R\/G5pX37MKCjxKAC1\/eeZ9liZNcAQFflo4Ob5HjcxUsx1CWU+YAn9AsL4sdseiYhMclvD04N4OI3GUAAWx3aRfTDSFxandcv0GfFiYn8MiPTMkG1QgdOtduotxaGk9jzPAThX2zUjP5OMUp5UcAV4gczv3nseeDqkERozwDHu4YAw==\",\"page_age\":\"January 9, 2025\"},{\"type\":\"web_search_result\",\"title\":\"Did anything good happen in 2024? Actually, yes!\",\"url\":\"https:\/\/www.washingtonpost.com\/lifestyle\/2024\/12\/26\/2024-positive-good-news-stories\/\",\"encrypted_content\":\"EpwRCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDKU43EmAGKZww6L+YhoMas8d0obvtH6w6Gy2IjDMUNwyRexJExpXIUioLVIjD1+HRimPa8NhulqZ8dPJvqsk2JhorLqPndWbewDOnL0qnxBOm\/0erVFm4VrrrshdvdHzbPTwnvBIUAxMWH6yowoBXzHq5STYpKRjuq5HkwWo11Kvy7wYWgAWDDljwmrG3CtjLr1pDgGjyPRQw7E0aIdkxN6PGmv5iYT+hUUnwRVaM+P4Hq+96UI+Wkp2QfTUgY5aozT7bs5nbLrBFP5TWq6DEus2sum9h6L6FjTdMe3PPOaHh7C0wHpe0oyTZPna8cMKdoXuMGzcKCFfdiA8wp5NJBb5GhNaWZmNPAsqZ7OOQMfWnCR9DLEeQVZFF3PXm4\/gsdj575wfxS2nYy1gqe1Hq4T7vFUT9YX34gyztZLiEvAS5kJS7FvGsZssrpbXkqQ\/WLoo94t29Cw3nkw3oA9DUEfWE2rlFSfOgWSHpLZVTjWPHMez7zp1aWDXtG40GOBdfLGIdfY9hRBi3MW4bfGvLhlcpGT9dPSJRQ2Fur8P83wdA6zn2j7yRDMROZRj1Zmh8IcjG44vGsXA3u0gJKWEea+T0DquvIOz\/DtY1hXgwTl0g1\/jGfYndqTF0+1s+sRlN8k5DurBzGmK2iD1+6NoWy\/PAo+sx7o20lGNS47zEdrumjXzdkR89Hq9O1LNmriiWRemIkujZzxQi4vO7vC6rqyg6sm6otIGEHbnYiieod6uQNXGxbjSI+W1R3fTQMzT2YruyQ95545Igyi24glc4L4TX8j87XTGx7jaAh7+81DpFZZ6i\/ddoUEiyYHItFgrdB\/xPiAhjgFM53YYIcEvHjkaF1HAu50PN4l5Rcm8j2UriCktmDWd5rTfgX\/SXylGTCZpYYKuOZMSktc2FLUuQvp6PLRujGZBM+14XxxacvCSnWUJTEZDCFcuridWKharziTfdQJbpGNkdWhSb+aVArupPpdSq\/4nkTp6XKR2GsgQ\/jEraYHeBmu6GvZfe\/FdEObTV7p\/d6cwg3xuxj\/9W+5inR301Ncw9CuH1S3eh2fUaAaxDlWhmPjazWe+cR0H9z2uKdDjRr1AxeSiQFrwsmgHV1KL7rXoptEYOwT0jxJ33\/MAPrbrn\/wPyxtxZY5pYzNTEVLAzu6pnVTOG\/WZZ4KZ4aOlahGij9KqpkGDPUDRtAjdo96OP+YG9wRt9BmXdLa2Y5GP85jLhFncDCdJvcByyLtz8uMhEjPr\/Op44BdDsrv9USEWAdcun7DIh+naSaed9I4W5YQxmxyESkK\/DwNh9PbEYhFW38xLAYJLN\/9F1W\/XxWKQblUmu5dEQ7vWR937UH8qmFODDpLQtcUOF4Bsarww8C8VqC0nMoHtIprqzPssgAVJCDyiZnnpf99kBmr92D9rJc\/MbM9I3mueBbc+d0BMH1sQYh2LwyRnlvRbSM6Q9NZ2dhyqt689zOFptZ5jp5umapJ2eKpIW8hMYbEyiAapSWuwZt978VLgndgwCq5BSzKhEhVB3jsUBAQjK38eZvqVaNgx9lh8C\/POx0L6ZgGTMCQZDmje7K8rp6DG9WyhHV1krNNiGwkHYQbKqFYnaDIP+mIPrt2\/MFvuJ5lz2rlEcJmxHhkO7Rp2Nl6S5GSnvuf9qUI3osIskOQsZC\/f7n3eR6Lzn35frufq6mHITlKx5ranUdz8NPWKhjXdGBX4LkMKO5enXkd3KHhRgiLl+F4Fe0eTGvkCBmNYo2W4q41ohthb07CE+AElLLk+IH+nBdC3O6A3jDusHOZh7WYNCdYD2Mza8xlY0+R4HNuVO9EHBJVF1a4tDOR3j+jwZhnHgpL+d+3YxxPI\/0O0anjETfpvdkZPLRatdnc\/Bra7i8SsHahE38HpTSgmG194NKmFbuYyZZaJYoFwpnRcfX\/SRXHQ6ZTE9fCdAziP+6MneBv80Cr\/lPlyrDjA2RwxiaFmUJEo18OEPYcqi5rnLwVJItWWXV8QjSmtabNxvYRvjNOnEyY4yjoyHp7iOG+R3aV1PlRCm24Ji+Q7y4bi7G+y7yw+2Zl\/UlmXV0hbNLWXY1G55LBy9UkMyVLrcXFc8v\/3P83gz9Whe0mE4RbggT76MzYkjBZTJXc3gvLhtPNSq5hbx4R9nDsngy20ryXi5PWCLpcYNYEz37\/Rw41WXv3FIpAVsQkupiXvBmX\/nrB7\/IurxhMa+1jKm843Y9afwFcDLCglMJRAqT\/5op\/IHaG1U\/60OXBC+rvEm2AgCYObSgTCksIu2mnWmWXyDx5BfT2DESFQqYI3yKL6bpreYVAiICeAzN+SQhMoeW8ko4eoP4JC5MhKG5XO4r5pN9OOC3E5yZ65NHGYJRnSfi+QtdPLQsrm0dFyYqTrCufDjm+KvTllbN+NChpDPRLK0gHNoxTOSIjcbKE58a7j5siw3fgJqf9tJ1EvTrxIsZ+ki8TYDLtn\/cnkR8w8yRWJR9fqnfC6F+7Fx2T2vSBwUxK72oZlRm7cQIVoRdX1a3LQuiNwNVjoROdtpf9rED\/14q2OLL5KI+e8uMrmAW3sxDpWzWBUvh17CfLx7phGfQ25YiEHT0N3R\/HeSNA\/g08TwQTP1TLL3wgmXkmwd8oBwlznfjwDOnsSeLUQ60sprtVHNQof2FKqX5HEhmoC3+L6P+0wWWrmfgc1Sx3arunrMoA\/lN7PgmWtCyI9eljdwLRDSN6r\/184+knWYECBUWdDRymPiKRgQGWSujphVyHjysOLx6dg+5+\/y5BH4pw6+oVy7Q\/M3oNMhpNZXL0xbpOWqIdEocv9v2WD92reperuqMquHhjItUg\/reRawwxzBaFqGv7zIgM1ERAiG+c6Itn3o+jguoXhcB\/lsCQq0+faH3sYAw==\",\"page_age\":\"December 30, 2024\"},{\"type\":\"web_search_result\",\"title\":\"Good News - December 2024\",\"url\":\"https:\/\/digital.goodnewsfl.org\/2024\/december\/\",\"encrypted_content\":\"Er0gCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDKtUOBURfJyQs\/vxsBoM7T42fvyNocHeZTttIjD37aaqfCKPktxH5P7igI5g+v9agY2F9MOm4w5ufl+0SWgnlGStH0eErLUtoIAO9jMqwB8fyyYbAmeiRnExDlXoyu7MW2RLKjwGegVofWyZ7EeduH4uhWwsk5+trxSUo3zxnJDdq49ObQLjjeiihonsnYBHlIwmmVBHo9C17Rw4Gi\/VNL1nCHzSyuWMcfdcH9in3Nx+R+AeZL6FXQBYORsERhMFAG2r+eLZ6swRxIfb3y5YRmUb2WsuvvuhzBBiMGtysPcUwd9IJZlzPxHQgntq9xQmGuDCqzrt1CIsjmC9V\/m2FXovwHR+e\/bCKRjhWRRJe578cSgaWfl8WoqFNhZ+LGypE4KwK5T9BVAGr1Ybh\/DKLiLF\/tgqG4JWeTq201cdCU3HqkiOiVufeRJ1bkohGsPF75\/uRlvXqhEybddNMMdSMFvFIpepNy0v4sX6Sc9OP9GiWNYUvnE4Xp7d3laf6+7bqagxD1gEL3By6i+xc1\/xt8BgdCi0db2EMxk0IIIIVB4kQr8bLcaNJrAt37G9vSCpr9esV9HoSvfeuk0XJ+4CBCeeQA3aQG6XlRE0OT7nOaLwFVwnb5ys9pSyLM3XD0oGTjXhT1xH\/qL44sjXlS2bItffK4cGi\/eGT8sMOyEHcfnCoYAW1WIlG+PVWDi7uMtzu6ddznZq9fy8Ytay4X3o3UOSqbF3tYYF0Tq6DEnxwT\/unQ0nf7VHatLN0yF12PXlSGrTBElzU9qwj7T1JrkckkblwB+opwjgVvkp\/dL111428uEh6hfGu777E4hqy2rYyonJqTMZ0rjRYdWChDC7K75uKkmvM0tlBYqkhgir3bLefAzNVcgjT2Hcq3Vtf0HgCDF77vPVhtnW7YnHpPliJ+JfYBh5CPiR5+7VdgI7hCrqhzhmrqKIjedrfzJq7gG0IAM0JUKwG1eg9G5fXvO47f1qprYuKJqXqHlDC9t11KNWmcHe1caBfx8jzN5qtNcLgBIb1bD6TMTBp5jwpM34IRuEKhquBvbb6tscWjEkXQ4TLWDA46nI6yp+OyOrOoYS0kydlLQ2S+EhnFp4CfogvWYZHkezQWhGV7uWeNt8dE\/EYs53udOLS4Y1Minu2ace+d\/AO732ZeWaLBxusPkHiagSstLACOW63xg+sNU2s2fsK7NGQuvcKMTSAvDO7k2xutiD7fMRwxOMuIUlUxxyCUsjC3L6i72VkqWSr8y+2LIuciyjHN\/dLOZmh3bsGZlCcc77dS56oCIQpDmqqyzMMxN0PjYotL1lizVvNZALLIzLoynxQKKovHPIyaNvM1iY0dU1O+5ap6Wizqr4JQ3\/xu2wCfHAlLxSm6CX6AqplTOoO7xacShDNTzpJVhlpHOHNIaZLH8\/vDwjUHd4GEqqetvLPUPKSDc\/L5e9+bjpfTPKGmxBoYhpHN5M0yB1Vosk\/hMs\/fQOD+k9+od4LltY5cLuLpiG6hhpsS07RmS4jkJMHDXV83uXioToFyQSc2N04LTRRN7QYYgsPz+EKlJICbSfvQjVS8r4bQA1TJtGER0m1HHoL906ptXXIGp\/NYJC92tzoSqbDa54HCzCIBNZ7tBPzTSQ2fRON3e+QOhJQAK7rVlc9L8dolm1lK2Tg5p4padeEuAe+uXjsWllBELBPVz2XIyQLVFxPls5gMczas0iMAO9099K67xL\/SGFbgINJUWe8VzM1Hru4E7ktFn\/EsFBXJKd9WyIet0DBOP2fhPFQS1pISOf2Bu9klfhMWDDVARox97ryth\/gl6WascsfYHLtLyspUrt\/WwGs91Kn3vQiwrSTyH+ZtbShfkUdPXOQ38Z\/x21Kftb1p93991rN0EoZC5Yp\/YNOWt7jZ3H9gujI8s+D949Ox8vZv4tvTS1UbCHeaxN5WX+jJlhuv9GIUsECsvW6CRLyI5+JYaBY24K1VMjszhyY+X1a7Wn4ZBISmCj8nFXkJDXYQ9gviBBT2gRIiQAT1\/5veDGmLm3aSTPQHcrWW4FtmwH3KVp8Uw+Ih4g5b4LAtXtRC7fW\/xfnaA0DTeWLtWvdTMU1nWaEFXkXg8LVDiMjD+NLcMa\/nzzn5jkUwsqmhj5d7Hp53wKDXIcqWx8mWbjCE8DtQn8Ytr83nHxjorx4ta9gZA7+RnmQpoo0GwQQj1Wv7bJMM5ftg5FP+LDc9soj\/dASSXBEv8bNmcActs66yc5JHLxO\/fgB9cdwVxq4aNvYUGeJtqT9mfJscF1pkvEaho4wXEprZKZchImg7SG3HSTySV4MgzjgGOJXet8FgrAzHmV3Zyh6E4KGKpA7ibPCPMO5CSEOkEpEFNJE75grTxT8A0jrKAdhArvJdhWCRQMRiPyoiCpHg+5ItaH2yOjJqn2d5l1eWdzyhbf\/bB3WiDh3NwtYyDa6PN68NJRy3geKxW9QM7IZajUUQvUrANL52rIzRH7uIYzh6gAX1xzR0HmKMTVYQNOrexu\/l3hDhWFH7Z25Frg2WgDdGNJePXJ5hmXSqkHwp9mWJZne28Sbx7tp\/Qipf5I8aDx5ma4jBcrbh1Sl4yfmUWrO6tMh0QgWmg2vyyLoBM+br1KFUVrDCvJepX\/kgSfc9OI73h3ScDR9zW5bYaWuF8y4uY0fjvhoAsBdRNqpzGdCHrmF3D14oer\/QIPMCoKSjDXYdbyziAYyQgfkFF9N3eci9e4+jr+nJzkTAnqIKcuourSvBlQypLip4RqIuLNBAeIH45iY\/A8GxohG\/0E4r4UxLNyUuWxxCa\/Yudx8TVrZcqkWD2EUQhRC0G9NmHyzBmF+1q9Nss6pi2sER+hjgJS793cC1dKIrfp45Aj\/n8vbcJhchOabLNv0N0Yf9HiKtF7glbCiujMVgRd62yp\/PyuJWKyPsCPYZGWfGlahXO8fQI4ORo5lKDJ8Sk7Tx715dRboOwe4YmIUQBdKclmcCAWF4n5swGkL6yh8sLGk29x46AMLek8P4MOLZ+naHdnj5UYJwkd6U8l6bEVof+un7EKiUmyfNz1Rd4bphH1dGCXcdA+bWEI3ppK\/xiHYpdri9WLRvZG1jCL0YDKR705EJhLr7\/2wM1Ajhr\/T3y49qfb72htKG7QIob6FUQT+FqPZo+l651A8HZIeLP3RNQ+i+rYU7rNjEOVPROtDoxCasYT2VOLrO5dG\/ufQALMoqJEHzSRl7FHQlcAr92JENo9HSSkl7ihMlHrBihzDcXydGrIQcsD6PUNiBSdZMibn\/5pAQK\/btDgwBax4PLEiVuV151D17QyT3iUEo2x30lZBKSQkbIR4mXVoGe2ZGj0wai97nu2JNi6LMu9QCSRFL0NOMJMD07VKG6vcGoiJtikLPUN4uuJx28\/TWBDJ1IFQnwK59oji3A1UApmTM2c5Z7cK+gW49C0MkHIKkg1sztmxCglQzEYQgMa5eLVMC1tHgklRWY21Tf3jRxDypWlb7dAJAi1+UNl+sMdkkkH7KqMMEiY1qotIAhAIpf2tlSgRpxrcnhF4e6kuRD3M076LVim9glpCSqgvIfkdcz\/sakawpDjIso0UBuSEfzXDsObOKUVn6S+lywxr689u+qcZD2uVBt441e1G8IvxRSbIOiVDOjZYOjYVSoVu4wZ0YnvAqA0YDygHJOIl1DJe01HaFOe7HIvd0nqhZ4Y9PDRueWJib5OCjBpjMTW8WzmrC003RsRukJ4445KnA6qv9Yam3Wp+U4sOHLRyRNHtYzrQt4Jymto\/POjUC4naPtCvr1XhoZXIH7BK+6Aw2tZywEmLIy7A4bU7CVY\/05v1vw4L6gShkveZZnBgtmXBUhYeWIzxnQ6PpvCi\/GZtLEy\/RNCrYPaNwYNb40jQkdVogUA45GdISkuXvjyZc\/s\/9wOFD9q9vjd09AmnUAKKg1l2s1bxOjBLUSxHXsdNVB5PEwlPx\/625mTbqPUVuiNWdGLBpZcaeu5CL7X\/pJA7zNTx0OhTPHjDCFm3zeWz4153PXp1WqENAyLQ4wFTVWPXzn7dFtBP46HRGZ7MJrhJDp2anxCCs\/3QhcYrAEE7\/uK4vHSceFVufOBjio2ninKPOWkkG\/n9iVVGvApyo8MMZPMkJvtH+0Bj7ghUmDFu8\/L66WignoS5PBbupX0oYtkakdbnMydVPoS6Iu2VNwOhbSLv1mJ+VOPFzOmTGLMUpv9HJ33GT5vTUTVc70BpYl2m\/4MkzHZW0Ie8cllh4Ul5aOg1c0N6r4g+K4HTKGs3VU26LAglgrqe0ANvHdFtYgS6YVg4bJ5WL\/oVYM8aBzSxcI5Je6j5L3vEFm1FMZ\/YK15XHTlncWwqIEVyxqPC0vj06q9JSK1XgO24BhNC9YSHeS\/DeEO7D4z7Y7NBoqid4+5mPgqCvvwRt8XghZv+Nn2bTr1zRRXJM1eJJ\/j69wSfYxr9vFd8vayKBBYF9PPUST8jrCeh36pwBuyh4LFZllYM8cyKlzWp\/ZnGk94Sx8dvAUmZabZT6yEgHAc3pb+bMBvjrwpAoizLKcHIkywLGsTdlHWOhnSn7IglSQ6lm1BOwljTyScEHhiRG3rdEY0qPD9eixpQMJu52ABRhANeQY8XVoR9SjI8W9RwjhCR7rJGRDFkd5avbHalQq2R4HJUa0qaQGGpMGGeoDBNV6iq+2vxgFxmkusKy1daXy223pZvAZUSOPQzhDVxulQeYCZvWuBHvmtcDrqF2W1cipvr5tUmP9KqpM22ZmxL4xxvuZCjIvsBJZwGDEJwvEWqbPiHOtVbM65Hh22YAepcDoTejletNnx1xcLJpYwh3ips\/\/fh01do\/qiFfRQek50tlVkfbmyD4yB4s1t7XXbZfCkTtCYBRaWIRz27N+5I1v2GbsSiEeDL16wRsnCW7etTjd3kW6SjRBzcUa4NE+An+twb1QKjNcS9QdbxwuIlbLatr69d1iHS4D0P7y3Yj2u37dQ+UANvMmAePABZn+58I1FXNn+VOJOALih6rXvtWD1ySv3oq36UPUnS8W9yrWdjRFbomaHT7npRC3un8SpIf+TEXvEiXm55E3Dth6LAJ7r5j\/Om7TrPJ5B96rA4VchJJzgPSmtTeEku9UWwBlVjH51BlpjZm0Avb2HTFHaOCa3wfxR6Ja4c8dPfHDRLHZ6i9FvTcuTc2lEcM9ufalLbo9fg9dQY++lN45KLCPWfzXkDDrZ0kFGeLJYZiR4qZ2dRVmG4JR4CwIitGm6K+LiHdS\/Yu\/h1j0rymdJ9uDM56DBsNNZ6ET+iU\/CF8zaxqqPj1HDBaIDwIwdGrn7GFdJPjlKkOn9XmtHmQlwW2Fgbk32CQ00zpBFJpxZeJEOFwYTtlvR+AE+lRa7vvlY8E1FKvRJju8OROjqliER+MWFEcpsM9EMmDZV7gsgB4WnAq7f8ysHiJa9rLtt41LUNT1hRzBhcCwKKddzlmPCItlA\/7B7FsK\/RDy15yIBrw7+r5xppAf50Z7l3bVhtJX\/YakYAw==\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Global Good News - Good news from around the world - 19 December 2025\",\"url\":\"https:\/\/globalgoodnews.com\/\",\"encrypted_content\":\"EvAECioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDKDAvVuTKN6SNGZrtxoMbnp8FLAdKerjZs7hIjBOt1gr5s4Urnhj9hqrBIlAwRagjn22dOyk2AKRYNCy8c+gQRO2q85MWXrRtfOONV0q8wMCxOUzo7IN7i\/xm50gqFa5m\/PKWGvmJuyBXEVV3eo4LSErOeaaGVBeDWMzjBjORgFCrBwuIUMguepgRgzmkf+IZcV8J8JolAvm7Dcl1EIk63noMM894QtZH9wKLD1XxeR3ojRXSCTgyiwLrqTjMnDrVdlYjrJu1H94AiJt9cqkiT3xxeK52KmawQ5lAXtG40VDm9h0nVydvpkfrlHk9c\/GG7MPOxxDzghBDzKFNDIJvV589q9gUAojUzDadqcAO3IDNX+MDzGCQoRFg6AYXAAxZLZZ6lhgByhrF8oewKJ6kcqPAuAC4+KCVis2ejkqpt3i4IWZrvj4pH3kYU1N7zeLEpoPxfGRVsQURrfxpt6\/r1WHdmZ+\/QMWd\/B3YcJXBJSqHYYUyWRgyCZIrGDGT+Vm5Ux6tY5BPN7wQMDLE\/KLOsomlJJwmlydUcLkhhKme9zCNVxsR0z1rXGBXELPqVWbPMippKQfXxzWTdplcyEx2c+dAXvljGUZVKSd4VB8uatUstOfqbDMHfRWLcXOo6a2ICyhjRkSUdc45PiczxR9Me0HSZ3OSfj+3izltPMGxPY871GOkxhqyy03W4GXw+jUUyES3wU1Kd881TqrCm0SozF6Et6dRajhNgLIxksh8Vi1gL\/kDgU1zmRr5zsYRibteYtcGAM=\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Yep, good things happened in 2024. Here are the most uplifting news stories from the past year. - CBS News\",\"url\":\"https:\/\/www.cbsnews.com\/news\/good-things-that-happened-in-2024\/\",\"encrypted_content\":\"ErUgCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDHZnYwjNV4arE92DDBoM238BSDfKUtbL40ebIjB00xUSlJvXARjrm9lmT2M5Vfjf+7IM3nu4bJVOjf8If22BcFR+vqooJFvi+844XkcquB8FULzy4W73gE+mxJlhhOixLz2nyEC103gjk7wBqpCTsq2Lzs3P2zzRWkGTmLJtmsa8VNNLQ\/Dl8ChNBOtl\/87Ud+L1ICV9c\/T\/EEdp1PCcW1aKNyRDRDfXg+vrbZTsrPkoUticSy+FR7vlzCoVztl2BM2f3Ulpk5qRrLMHZU\/ZHBOfa6NyCtp2G3apq+ojExlHd1NS+g\/v9FLeTzUFAgAoWNQ9+x\/onzWzsmOsHISyh+68O6s11Qz8Yj6FomkXt0OkoA6RWetmz06NyQMxxP7o2+pPkxhafRYp2SdOr+3fNq5yaGujAZ76HIgMAoTfjFuvmQZqnOYhg2b1e3x3h2Ton23iHxiJfq+RDxZqCP8j97o3nkJORUtTFm+btBc8t10N8eQBbqxYjQW8CZPTGxrYETa1cYPX9izHiEe+xYJushXkK8SV4i6nRprzOpCDL5f0ahSAJaEzcUQaYVlInjz3KaQXNDgSjWxnB98kO1exQL9wGPBfcw9fhmwJpjx5utlnyd0LLkys3MEN3WydOTKhReugOeBGb+Ak79k\/sdvY\/mi3m3h30lxHJXtr3elqnQJlpx+Ya4\/SqzOqWHFm3t8z0kB02MMfVtgyNT0z9M\/9OUh1eeEWXqZ+37L7XkaaHkcfw2ZvzAdTSNceBLW2fno0\/cP\/f8YwsP78VtdqyA19HKo5Gdva8fIuLpcxLD2MgmZZwe\/VkMtrouEMXas1z2QKl6rNmQXwqJ\/2q07iHux3hpilj+oi9LPtpdei\/tQeE\/PzabcJ5ijixxcYS7f1GX1vptrmGqwuqJHw3C977\/or2CtvscKQ3V9O6AGfU4YHn6HWiX\/6MO5HegbwVwxxLXI1qORZEaMc8\/wqwldW1YTUvJXY5ICQ14zRcp3558Nk++o\/xW5D+4R\/URAoGhYylpNCLshmZb+tVEP3ZbtaS0u8v9j571nIniLYPKh3QvBdmrYDXQHDtACtj6jmnEk66FCH8+LzjhXPhCg7Dt21Oa7cEzK5kJ84jPIQF1z9m+7CDchgZMLX5MjcCA01drf3JkaT56bRrhf0IeJUyvkKyzFCxsRRsqjXcuLjy9njAfXMT8l9OZfRdxEAZsVd253djFIkOc+4gyA5Ji4XhiY+LJaaqZKJkXioMutpJ8C05daOE7mjyINgzDIAxcwvTegOdlhxPxnZqf4qWJzJR908bkqd\/JHLYFcJe4e6IiXUUTSsIvEvfoRgVdJE8JcGOKZBP\/TyIrFzFs8WGstlCYKwidLS3MGp+xLHZDkziuEDxiEql2dC\/VJEygfKv3dEqfnIu0j8veLeXFdxTKgdENCVzW4o0HJY3DN2HA12H6B6vtD8rZJjOggDYY4WapEhgQlZUAbJJjyqrFJp+5EMv7BrDJkcj\/BXEmDvVEUs8g\/EjNDKIMGiTHU6ttAuXBZ9OIfgWQvveF4AWwDAB7YlFhqpTXXLN8W+SmSVYUwvKo2EzTHnWLOSy6UE+aW+nUPXGmo6PvTkoG+QEtriYR8\/Ntv7zzDvy1vVb43D4z8iJ1iPKDhoUr+tVz\/7jzRBSWTt\/EZj3LgKb03LJgE1lveuds4UAd0RNO6VeHDMpW6Tk7SaZQmVU\/6n8fmSqOtJI\/LsKRIfZwaT+HCaEqY6RGiiCnd5JVDEG9JkAnBVuQR7brnKDLAIXGKOYHLFBuupLi63Mb4JMYOpVkNV4hSf8v5xKCyTdhpQSywJTqLymNDSmmq1ReSea1d0HpzKYsWUbGZW5XRqD\/IKJzK+BZ63RSXOkOlfrKGLzb7gmJOdEPfOV+AnLl4D2yhFph9Y\/aHxWAnhB1Jb9pNKTrUi46bukA413rkmDMAxpBqkNITEms4xCGckMmPHNd4fHVpGZtdduLCyguyZNtvJJTG2zoSHiqpcmPtmwl01uUZrNkYUC7t2scOlEFtGO4B4Yo\/TaAqszWxQzirF89wkh4qgN008HJiqp+34+huFbB+naHh63KTgPdwf90tjbMyI1G+Cn0\/UWq59t6CzKX08r52+TLpJk8U2RRAPQmLFsP7KPyjqmociZFZE5TCDJgOq4fdkHObsKM1QIZSaTBEcxIDAucBKyKEym8SFX0L4Mtk1oog11PKX6Kvs\/595iesEUm7JIw\/4GdT19wcf9+Fost1MzqgRU5GtkYoVxxqYISWcDtFpXuw4WKT35nUtsy2pEhW2U6JYqfPbiR\/TUYkwkgRVmtfsBGea1cSLsXreELj0RNXov7ebKyQj7kjfhMWGriR6jvvtoIdsjOsqoyYJK6phc\/weer0iRhFzFxHPHJeZfqHegQOUPjosuYNh2UdjM\/09q25TjUpfYK7uA9zjlPup1Imej1uibaymWlVB\/CPZ3UAAQf+1u\/7jw5MbeFZfLcgFE9DBhb1JJUbYmKq5W7oODKfk+CVutUv\/S29LsRh6Gj+eWJ+oM8GhpOKom\/12qHGvnnJYHiDZixHjynjKT\/p3CG5LFRBjBBzcPDHRncKEylfjF+Dje+7cJ3QXOxXqNBh\/qNQleNr2uuHx7lhAzbVq7TaZJcJp0Db3raBi\/txkce9vXP9+zD8Mtg3XlDfB\/u29zauuJ9NxFPVnrYZ2W289dXZZeNzSKw8dtQwpOS3K64YpTx1rHYR\/Ht3Ig98HtRhgo+6dyC\/CKGHBMvRVc\/1cO\/vNwxr4HT\/SPK+T5qIVpNgQqK7Q6Yx+KNzeuLCxUVVxtznU6koRhAuTRyoF2ZTvbPF6lWc0qRiLF0lXJdsa\/KcwjLN45Smw1GOx7RfIUFq3N0jEr\/L1wpp2iIiN3lkAnBuMFQZJR2XLDFvuJZ\/0jVcmernJyNgvSJFACW6xu56R4ucLPa\/e6pUXbZY2cBsk8TBXjaiAoFIvP61NeU3v6e9geEFVYUMCWDKDtpuMQRHk3Ma6m2X1YChWj\/GAEinlnnBmI4c6+gCbwIbzsEyx\/zh6kMXSaPFGrluwANShbZ5lDpVaJgxr5HSoLD63exw739oAnRRYrEhV53WGe2VvbxZnQdpEfvZ2cCaa2tNbGo4I4NiuI74MgGCfrmq9spCs5jNeqzeIF\/g\/hLDlRSSY4M3FfqTad0LE6oYjjjtb87e5h8oLv1zuX2mNn02QSDwYYu48ajo29nVXw7WTzCbDBn37QAo0j0C8n1vaFxC4L+f3vCC4elU9ACQetx6YYyh\/FloQDC8uJqxpiL1\/U5ThQ\/MK4YYXe+HJgPzZVxUpm7dGqGUp3X\/WKdbP4gLL76iaI7AJJ65uydL0hz+aBcxOjWqTHTI8nLhEP6BxK3n6mdWSBoUwhlcYCI92g6pifUFPVc9fBCmE0mWS+hVA4TuhXP0NXnRFjgxYKzgEq43Y1j5SAdbC5I+05gJZmc2FUprqmQR1hP4JRw2ITn0lYF7OPefqi6HeYYtQArLgiyw4wfNKgw27ri7ScVa3yBoBo3SbWDQSSE33WrCbQ7++F8b4iSvAA6+i0XKoNQpVXCPiDl1fuN4pLKaRJ3V\/lQ6ZC4aNu3LHOOBtenANpS7x6y5GOYEL0DpxyqZSJ8WGjOOteDOAur8E7xvYn5Nt45vclu3aA2iOg7THbtC+5UdSTZ4tJdar7D3ERdl3Ux3blMXnnPqGIrsSgKGUovy6tO8z4WygHXsjyjxKEmjGR90RFCa6nYK2GETpxla843qMwDkESRGJpW2PqqU5XI2mMWasNgrmBwLkXWaJ070uKLQJIJ1IcCeoFqcR17DtK9YKZS0DKfhp0VMhvn3VcC2NoVXMm\/YsVIU2A7CEySwsDC\/DVwM9ltTi+9xQv0twjQ8ZLjaiMDqIn4qjdJhg0cBO9Z6hhsfRvOuM9kA4ttgUjM659raovGKn7oUY6\/zjzb7vpiRiqCC6cZGULYRDQYCPdblJycjzcZ5IXVogyvKUl\/h9gfNTsV08vemXcPUFC\/P4gUpkqlkUFZhS59MUFYz9FVtmlK9XHBVZ7PFymTRp8AAItmU7+COOl+vdMmYRHNT6LZDVkFSAprOonx3bj4tEa+2Cg9Lbl9olw1R1oCKMwL5oaANdl\/ze6wH26zXmBTnkRuEsY6g1EFHJVIBVMFlF4ls+zFYdgkw7SN5M8F4ACdj\/AGZ+Q2819BcJiDD0FR\/IlPdIS8zFKcDSfAwBL\/heVmgkmN6iMFE\/UzyF3iZujrwXW3038COy4h7ihXMrk3ACulGNbF3sJVN0yXqCKM2WcltwdRhyPH5vtIP+P8I0Db4T9zV0UbEw1tlxLAvjhTxJJqdPAInpF3NzLIZl\/QX5Uu+ya2OFm2w+A6o57NCRVuo4NJ0zyrpL6v\/vG1WeURkeJeSIeokoK7RauzxfgqS9PTGnxtX4+ZpEmO+\/FB7XGUJJVTx7B6O\/Wjt9IIk9pQcD\/3dtmCEyr4VozjBz+1j+E9xeGAWiRhFlaSLxTTzF7A4iIr46iBPec4PshLDE5dkcCKoP5XAx8CmRBetJkFY2ddHtSCWbAgNO1Af+gxzkil3gCbefxEH+zc0OeiW1z9xDV+QFbxrwW0YcTuzyRVLZnmsVZNW572ABE8REDfAJpephXCIVurvZM53IXIMo6hM49YPcoQPHHypewLACpPCnyQE+iqMWiz1zvoVAupmePuPCDxEk0YwO02\/mYqknmvWY4XoIbbCkrMzTrG3q1HSa4pgVkc1y4BLR6wqF3Cy0OvBy7hFXEjogoaJfutYcUgemBW9mGi3xwzBo8g43J7E2xcYM93m2pU5Euo8MrVXlH4BWXEBsCfZOKSYbP1z1mtKY1mrcwiXwVMyfrDsOnUVfTdCEXpCq3Eu7X2KiFwUEzOZAa5x2\/YAFLW143GIFzNs1vygrrW77bCMnBp0jhYX7S4Rn0VCP9N\/lJihz+40btCdapGGffvbrZTvUKxF49UifUJA\/\/AMQQNWfGg4kAG4ETBWlhhIzsRncuyZd1SFGIaysMtLp2FG\/GPn7IdjcrUQAziT6c+Dr5uPRpsOvPmaJeblEZwXR9nh+lPKDjo6GpWSMVyJvpfYTICq0VwII4f6laEnSpfezofUF6kRUmZ9RT0LosN1vCCmgmDE29n8xXfkJZYn89U6OgAi5PpV\/lvMk3LLXWISZjq53lf64AMNVmWc2ibQ2FoKRELPYuO3GPfonl1CqhCwQyEZ+OEIw5P80iW2wJbHkjFFaI2MZMdrKRGbdNNv18aL9ICzE1U2CddZvbZPNq3K9\/09faVd7lTgICtUanjl1mOyUNWpnh1D6DgTlA8\/kwH2p1Ql6k426btdIfE3SFG3fIoeGBHz2cOTIDgWzbOrUYGKpP\/Ti+y+pdIww3vJ6n5MsX8oJJFRj3\/FIHW\/KHow+8Wjla7L5fmoy6l2h5UcPstp6fE6iqQvXFZtsJxOsj3OLlmx16AM4eVJQnlLd5MaVnK6MGAM=\",\"page_age\":\"December 30, 2024\"},{\"type\":\"web_search_result\",\"title\":\"Uplifting News: 2024\u2019s Most Inspiring Positive Stories Worldwide\",\"url\":\"https:\/\/smileymovement.org\/news\/the-most-positive-news-stories-of-2024\",\"encrypted_content\":\"EvAPCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDAXnylj9zG7e23fKQBoMqRvB1+2UZdyOpvdQIjA4XnbTwNP9TUXbjoSwXAEzJKIGQyjrogF3jxAtFfqJfw\/w6NAcPurjg5ZCu3yVdN0q8w53M2Qdj0D3Y6LI\/Df69j8xBHAPUsf9g1hf8J6hapE8eNCcPibPUYw4tbV0AIcOCGU\/tvHdoTgYmg+cuw36thUd+U3DbqKGzWXVuFmLtJyUcnAryYrqrBlFGC9z8GMyDSvsum5NwyKq86+BWiLpZb3AlkVdk8qwUz0yioDX89zWabDtvDbBvpl9rrYohF10Uorf+toTfHGvjlErhr0ph9v\/OzdB\/nKIUUAjpz0JP\/dvT4Ze3hE5VetltIyGaIS7Gvty93orqponjwuoRaeIeu1ATWS5SiUNYcFgPe1kgmYiwmhvGF92J1bFeKlwGXQzSRJSPF7DLROuYsaLNA37IfWSbXC7zqGXKc3n3D\/NPZRo6fWOEEJXmu4XVfJ446avK7uXVZ7DosjSAb5VVg41jbzGXY6sLNsme9yjdjdEkop7ibHi37ZrWB65Ys1BoSurRqlof0eWcg\/ygfnbJIwubSplifx+EkJPsKRsby0gZJqHOBBEYs9iFEkoYSON3mFJPtbaVjDoHrjb8Qb\/beZ0G+zfyTVVB2Rgk85jLa75oA5+O5v0dZ9UemYzaE2IvBqkZhpIf050RWQ2WZgvGqBWThlKHgucFXl4gcX0\/GbniFG3iQLnTY16vTWaKpp76KaAuiZiTw6JQpoCUBEGfIszgj94xA26jIWKbaGMXUc+PG3hwABY+8Pn\/kebF98IJAa5Vpvmu\/BGX5r3WUVjuuKCQVqSE\/ukAiQSm90j\/LUjEGH8AYg7vkus5x6LcrFFL0dHrWwl3UU2CZhlEgevHChP2pnsB2rDAkEjZYrdenamSGyN\/ZR+aDEngWsXaIJ2T+7CqzNZPZB3cehta5FtIatuVz8NQtuPLtuS9wR9i2hnMBpiDHFi01Fc1TJsgnywO3ZVbjojQP5oHviLROhTqR+yfw2BKbT+BQ3qjLSbpk63TYkTRBZEbB7ADL5UUEng0jTprzFG8pIjNkX7ejpK6CoaqkLkPhuCPmMSoRWiusJE6NcUCS6ItYcHUO6oz3mtm8YZD\/oM753uBN97lkEB2Rzkv+SxggUl0YKqr1jh+bcJzvZI6csqGsQM4XH\/Xt2KUuI91uyyE5uD80Blm4V3AWURWh4QfN3nyvcOd\/Am0bL53j78aROLDx4vgwREGUtIUjVboSduhPkvADpDGzwjhNYkSggxRfkZdhGRD2qHCkO5XjtcYZ5\/B9cQuLoPYS\/gOlsGgriBwiCphKpOAngBYXaLfktsABbdtigOQfwVRjppAOOByQCbCn8X7U4eSbGYBWWs\/Op2EqShnRy1opJSYR845ETkzNCDhxQae+D9vVOjVL1C07WKwshHjjbvfpusZtGypH5tSdOiCIEshqZwwNMaqmucB8yTMzcZt+vFAQZMJ8fNFxm2UVzvoAlhluTVlBzvmJGe5uOFt\/+4sGztjVXHZt9OegyBBVyF+YdxtQjUUcQPJVm5pNvIm8c0cb3qt\/HlpFecQiCyNQxq0yBdFgPLVuCqF8e4p38QLRFwARLiTYdHCmABt8VD8gQJoxbnihEXw6GdPn4WgnVtqq913B0SkJcsaKIEa\/Ex8F2PyISZpP2vLoP2JGjc7EVFGoT7LTtbEe9QvcORj4YU1tEJmV7YPf00m907hUaE7ne\/VLYmo957kWsY3UpCSw6Hwlu7j2Ybkyi07iwrluiD2we3gUFCASCu6an4z9vk2PtSls7Gvfn8AacFM5VG0RX\/EmlJ3sw1uzmL2NnUIHp4fxty4REP4OSsqSIM5iGru7bkXVNRuwEW7BqjP0+lSUmzThAK3Tnqe1ZUUBToc5++lmcnte5JRdrFr+v286FpPBd+vKXVc29A1FQ\/VP581lmhD0bKJKpTefpQtWNUDkP2jneMVAdxMX1Fn6IVaxVeZnDZgUOmuYIWc5qJ3LQj3b9SZKvMyCJ7aSL\/85CIxEC9NcSbrDIYjq36W03KBGvxVCAZnKWB1uvvH3OLajxlYGRzLsF6n9h2NZJUjLuhwWb651dxO2O9wAG6v5y5t6n+ll5XUYLrDn9Iq5mSvuXXW7F2gxVazd+NJBvFmDfG25+jUUvcJXPN4FmDkJutt\/SWULPtvjbWC41sfZwhClgjv3bmzTAIs53Yh9MuarVIjVxi6fRoXBwqiz74HDFgjU3Osnype77BJVvCQ9hgMtqVwddgMO7fuaY0WJeqL4ey2a65VfsGSN4jMOotplJ\/nLRqpDw7w5\/pCFDXkDNIEH2xkdMQrD\/+r9GBFZhjyUj7y3R0b\/rtICoSm43QbI1bQ50EJk2mapTbxwGt5TrmUj6tXXb05UASeHBuhvY2kUT1kDvVxr8RdVPKNBhexa5ffHOTUu97g9y9uYi1GZNBWXJYj3K6t4Ixi1VJnxiC+F\/ekc2TdQRuQxuwFSNELaTLNE90FiW6GRtdPxLgZoN6YfADGw2s5AAe7VV0gi5YncfqiuqnZ5eSiW8TKD2b5Pa9W1NOgd1w796YmzI26B7Rh5T8Aka5FcIKG\/UqQMx0cmtG7qOTPepzIRLT1b0VBQewFeE7NM31fqiZq366dAuFcxgD\",\"page_age\":\"June 18, 2025\"},{\"type\":\"web_search_result\",\"title\":\"Good news you may have missed in 2024 | United Nations Development Programme\",\"url\":\"https:\/\/www.undp.org\/stories\/good-news-you-may-have-missed-2024\",\"encrypted_content\":\"Er4QCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDAMl8x1VHGCyE35wXBoM5Nyacdb8M4BNgoZGIjBt7X2PfRU1OqpX9yPeeXEq2NRB6tOMpsWgqhVvjFci2NodGr3+WTZbz+O9yykYfE0qwQ+l3qNdwzkQEH\/pAHBH3HVL7RTyM2lfOMyqs2iGwuQDgv+b9bunymTRXXdhFCJhiI8vX\/OTGoQVTX0SkN0ZejzQbMXxgThcnr6khE3P27S\/xd5VdIp1rfk5rwj8gc5UeiHfDl5QvGKqm7q22DVuA\/sx+AxUwz53lDlTRNvlwJ7TFGtZwl7dmp3GOmf\/wrSMzgh0m1aGqqow+9YX7IxzKJ\/JOcIIeG8PfaR4LSc8J2Y3li\/rxpnjeCNckoksBHZWqo5NAEdBaCJ02p2H7TfmCUwqcSOPLKT5vZEGj4TkwFBl0vel9Q7wdcyuoT+C10peXOyoNN025zmGq5MTT4UPSEvdrDam2yNnjaAgG7qtYBTaMWIcqckZ3VP9N8lj1Bql7ooC6JJrkvZGZL32QyLBtStLYKzwEMg\/SfxQCn+yzJYTYgi0rG4S2ST\/Kw2zyqS\/440EooaMFgaa\/zj\/fX+tSyfDJNUjTmLQk\/mIZsOp7cRSV2lgnJWsfE6kFbaSRSVQyrAf1AVVuJIA4tKmts26BLxZCEpoEqcMYFdjZwqogw3lniTrjEdfRQfMenQOn+nX\/2GQUdtDjv1qbte270FCeV9+qSNp2mWsd6VaSvv2Apy\/Vaf8TyqkKJ\/tilNou6+7qm+W257G3RtmJ5aS5IoIRJqB88csEdBfCy8srPTfYSMhZ+5WjU\/AWWOJODuM7Nfu4YiVVR49I6vMuhB\/9GjSGrgVBrU8GrlEaFPAe+GaFRIpU7DZU1QB8b\/Ams1NFy2wXH3W4IBUzWPU2K4WHEKgdKdtHcAqxISwpZ8peKkHIvJwx8BVJp8s4UivRz5TeD8Ey\/k+8O2qOBPCx\/UB774gW6XPlZaZYLKY2gkRMzQ1OAkYBqg4l72vkzOydhrP+v2ootzGD\/RCsVjNCceLLqxIqhCMmUFo6frT96+72cDzp6negR+fn5AhvZvpG8k\/2n4o4q+UlCCQMumjOvT9jXXqnifci8BT+xN+ofdnIacX9NFG2GdJsJOcWBS1dbbjsy9KnlNYmSrp7x5JvA079ph34Xq6z6CNn6kzsUGIyb3NhB8PBfPSIPdm+I5i\/YCNjoXdRZEOzHxzFXGcFAIkmXp8qoc5IWpspGlUZ7aHzYyTBsY4cKR2ZXunTwSMJl71dxg3u+KraixVc1YPt7QdjILSj3lRVCRFcYRYz8JqMbAWCsySYswf2HFBrHUwToo1exCXXR2\/+K2usxP3DYD3+A+jFjCJMgs2DCR5mHS5yACeV859wADOPfg+Nv22xkYlkaFJfvL5Itj\/Gt2EszrtUsyAH4WwH8SIG5darS\/LV4Va1XPBzoGiLjWH\/YDIR+XnQzteq2288P9Px4y5FkNUvntebKWLG92lkpjcijWd8PIl\/pssp\/Xvfx1IFSp5EmG5wxwnllnxqDyEuEbiecC5lJsoxP7ygdNKzbBKv12wxGkGDcHPtx+lnSRp7p7nkZF0OPSR4aC6AFWMiJr\/2qHS50aneBrKs+N4ZwuyUsHCGmnpLMozi8slQ+lxRQMRXUwq677rtFzcyqSfoLPpal+Tm1133GUKZoLWG+wJ6U3QyTzpq4Hjzkt3MB6MP2Mn\/e3LSjrgzFqw0S6WCc7h26iTpNRyASwTxwVg\/W+pZUdjkn7AoEto61Djgpot7ayIizG8LBFLV2rKp0icDq62MmVcbOjQ0vfKIBU7FpjVbNim6F5Zop+W6+7BMw64eFubEZtVMBAUOfQczGHF5hXq7fEHugBSx+pvyR\/Sl31nJtnmUKE7BpJzxgEKVDCD\/srUhMWjANaIwqp4vgOdzfh5LHsswoAtSQBQMOPbWftiseNL37yBIJ8HL3j130l\/XX2J8mWLg2lTC\/mO4LWjLIFXNahQx3SQULI2fXPwVPVzkh91W5QuEOCJmSNAJMx0T\/tX6Z3Y2agmBFN54H4guuWK4h32mZt+s6Tlte74Xd5s9v7kjO87UdpQoADmwo+zXYq1wPa7s49Vwg0qNd2iA9pFiBaYBbxiSMz\/K57MGN\/aXspOiZCLNS00y\/9k5zK6lK\/mi1Og2kM4vGuJikAGbY2JGQqTZJP8W36tpo9IcLGShYlfsboBpXG\/6FGtlvQPdkRFresJI4EKlOFi\/HL3+2BrwaGRosl1u5Z7TpKG1yj53g+yA2ObPyGf\/dlcWIJO\/QW36VE6WNhuJtdpD996gKi0fJhVCgHsBdhXC7KeZA4QeiqbLvRW2W+aC8tI4kQllACr7JicWTkWGsY2QZUkF7s6eG+12bjNkfzqAGjBXNHaRNCd3\/RtRt8DiMuD2uBi6CTGHVGa\/j6k9iYUZCIYo7NTwRmghFCFAuFZU5MPVmjFGwV8KeV\/PIVfVqGegVgw8b8eVMWxWjj3MnCVr5u1IavEqM\/HfT2XPCmF6xGQgliOty5WrfhlwTDcr8JUOdGWfEiIcvcoVswRy6HCuWyZz6D5TvjMFF6QI9\/55F4lyjl5Ua4XSF5yRj4pp2bhx2L5E+IH43D2xdDDgDG8NJLJH0QFe84gYUMslnm13Xy\/zm9XaGGyby0YaQRSo8e5WdJdDl88yr2r+GcmcC6v70vOLKASuO\/1uWdXhS8UhLkSBkqxEhKWMc5SINR2vUMvsRXGBPhiT1\/OAbJMR3+KlEjH8QLJbLBG\/d9+gFokMRgD\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"The Uplift - Good news and stories that lift you up from CBS News\",\"url\":\"https:\/\/www.cbsnews.com\/uplift\/\",\"encrypted_content\":\"EuIgCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDK1e1rYI6nyszjeKQRoMcpjlP33JzHdpbinrIjB8mjG8zQthWu9rE0AzSuQwGiYtXKacyxmWn0kiroYXcZvayvxHhvgpNkCaBc5N0Wwq5R9FJ8qy7cYNOL5A3PK9FVA6BHqSdIymhz267XP4+tURmJYgLsgUyDqMV\/hl3c8A\/pjNdHIGf5tP\/T+00ePuoBcyW5ojK9xpqLECRh3TN7FaIxggFNRWsWxkj6z7uSudkmTokaAlUbBgtwPhBol4hfzQqdh+WBhXRBKQvBoijYCiniWHQwR61MCGxUQJX8X1tz5\/OWAuJXVAjgpRIC+iHFKRzIsiL2PwnMW+sVqA1D9YPnv\/fEtlWZnbluSnP\/CZK0sJDi7n57ygj06BuqIqmy8FQPkn8NITol4Z3OsOSCvE3IhcOvooR5ECauOGtuNeNNdJ\/ax0bQ84KNJD7rbMEdCgVHQv8dENiyiQwVbwvL+YFFnPDiBJwKG531uGWaWBsav3WgMKuJrxNA\/RUvWKOx6NvDyRyrlNQTpprl8VNH8UWB6dJ\/K3C0zY5S19MHrker1FkqwmCpmf+g9wmE9YZWz6Us\/cVuCPTDHCB\/JZZEhzpZh6vsfAVVb3CD9xVMnAsFS+zdN9PECUSUqG1poGvDWWy3Zo6QIK27QHZ3C2hStbQ1e4ljAj9ENTZ3VTN3htnniT4BEkOPk7QdlWlKuGUdxiViyH863OLc9of5vCyKCHttlEOj\/wov14K+1O+Zu9apwuyMhGVmurSRY8UMYRNLe6i91qTkLsQ0GBipg9vtsRgsnWrE\/znqjXOpHasYeTeaTA\/eiSN1aE+4XgQxxoQndN06ufaexIZtHuXr9K\/xSJMuAmWEETYNXJfG\/xehFFe+qG5JMVh3TGrYQMdYKMGeXpaDWrLPrtNsmV7KVH61v9Dw1XfHRamWPPvX9C38lLpozzOVQhr4SB2mJtbWEnFwHD1b7WcntXvlWvDFvs\/cRBf1F\/lOARgJW3VQWX3WJ6Wgc0gZ1ZhAaYFcXCFL1eBRJOZHhqNUko67md\/L5oZruBv8A1W+nu6q5NbUyoAL6XrlFVgCW8NJhw1kcgAp9XZMj4e7yMB6c6KLX2jl9koZgsoXKihfTfOWcfkXjeDtrqUDMv68MtRPBr4W4m9XeAJ+z8nYTS5MoGP4qFt9lRQBP8I34k5BOkomam8Cw5yB4W3PF6XcoMARrW7tU00NFtH6o0UaFNuw42KFZtqhcYybaNPBh9uoNlzV\/Vavbhhm9CyZaf22jTyoOy8TkM9aiIWbLiXpjwiJ+vsKy7lyg71mCQ4EVVKapzOqCZ1TKtIDcx\/pxejusQyyZhVJs\/vfXfwLRbo9Gr96Zshl2O8nIViJUa2bU2wkHvd37sc4wVeY6lo+mMQqDUeCuWG8W6JNqMiuX5IxxthCIo7aQeiSlwYDWIFF2M\/vD\/MZlpJsddivYXFA9kY1o5JuShmnLkku2UY\/WPW2PInc9oy\/ExfTIJfVk7FTnF7eVMz9PvfsNphYhT8EjvGBsUAnyjfQ6tw3x2qnqpjbgp99DREp5Msquja4qjv1er\/f7c82UWtB0GV0WB+IgE9IqOF4h9v6BeVn0IRc6WteDXTw5CcqMA5EjMU3q1HFIM50vDbF4jKQ94PK40mPqalHH1df5cjXBi0RkA7Isnbp6DbWbPTNhpIG7oMehtI15UaCugHYsxybYkhYWUWBMNwg4UVqj6gV7YJSdmkqgr7+lJwcZn6H7qttSVT2W2fwHWd97jmLjMvvGwppZamLSnHKNClFc6vI3mvucPRCLCmY14\/r2SyQpZeRpnoXOfdbjSSIujupfPpn31Xb2adaLFhCJiZp+FNt4s+8+a2CB13XvyxWErMajO1rNurUB\/lHA9yzE5S6MmY4hq7vK+J\/4alczFsAw+Vhd8LUbaeWSMlcTiZye5ubfkKzRaBOBJq\/3DF90F\/7e91aD8C4ySJlpVCLLNAYB4nBpdSKE52FwO+r83lMwjUlhU59+bk8Zj8GO0yqT41wwqrHMOHcPjjpIj7TwLr5sgCvZE5Ln\/4Z9an5UDr566Ohn6qJHy6+j884\/YQGU+DcKFWkpNDp9F4FdP\/N3KcPjCZv1aFxW\/a3WFSSE0U741g\/EXag5S2YGDh8Fl9eFZfd8Lo3ghPm3o9eTs25FvSps62EKmvBkOl6gA+iYDgW1YOIXZx18bizfmFHBptdnm9tm7DbuLDmecitcj3msJqasIlrQFeHkqdIcZ2cq1mTSuXdPAL6OPSwUG3RSXbPgqixbRhtAAxTiqJmMoz0Lou4s\/7ZfV7VP8\/dcOPKCPNzBr4pOznDwk+bU\/ntBsjd0ZD3ZrahE0M1yORBnUhyMOBtdOS3ffSiszdzBMcDhSII+UVJwjVON99rsz8QXUYD4ReLrRzy9xsB71HJBDBs7pE150bwI6hOlSPyr\/HBfpvqcXTvu3j0eMVzaiNAYKAKkYrjcqZaU3YhHw\/8dYeE17Bo8dhNxKW1lOiazBQ2nwRIPG87uUJ7xQDUInHv1hFGX8VZQY2jpRpK5fd0WJaFvQZTsZWROuyI9JVR8BA3KLwDdauXpk\/FjuWYxR4Dw9K\/vJhRSi19mv7jO5STBNwl8feuS\/UoK5oO60vnFm40WVIdaWRHspjrWl7kN3glg8Tm7ub5OoVB\/FS+LAOYCfNy4BkMzqsal\/uhFDXpFHwq7N9KPCD2\/rOtCG9r6wIlmzNu3GLoz8PyWQ9Tn\/wxolXgwHXs0R60ctCh3QZoZMM+yP9xKVVldBq8bmLfw7tHvtQuwv2YSAtWIQ2A8d+EDc7ducECxiQSexSRbTVo+6I67TZA2FA6\/rMcbE1jXJaReqdz3bibiyhOvz4zbMxn1Q5\/X4hhu\/Snm9MM4p66qbsa0XnrVjiQFjGTIJayDqe3zZnbLCCZKfGY1WmyronCklRuUTsosCLtFcqISH72jFYUAaDjBC8Ag9e4ID9UXFo+fDt8I4EhIPdlo21u5BGZDfnGYmi9UjZ6wGgiQQjPKp2o2pwP4EC8pSg40y36ycwGMRakKyoCe3qurhFGJu2ODpbgvasa8SEGJA5QouHcqusCZ0n97bLpHnfYdEQl1GiyMBUPLHOCtaQVOnSbv533cZ0ot9zbEfzVrunZi0iIy2PeACa\/eW33+fKrsJVa5dPFrehAjMVtvnd+cF2eldxkDJHszMloEA3K0M+bQyU0FxOWc05k7QT0CB3nhHeylC1wp97YGTJV4uRlqAZ7qJ22k7E1HNm8I01roXwrR2uLohwFc9GguBmYK\/\/tHTGnbkv1nHlhjTbK+4X0Z9zJPDB\/\/EPcFQiCtiQXtMtoDsuEHD9occgEj2G4LLRXpm3lEyciilynQLmBuC2TxuEkuYWvM0WH2DQYEh3mHlYLTsgCmodKMGXWqZz4vm1Q7N5gW86biKc9aiUYY+s1IkXiaxjKEa6AVtUtOPxHaaE2YiCgoVjiM1AjuZGx3UWguHaxs23F3NuuBYmPlS19rucgmwdZIzxaJyLU0VscSdg+FsaV5AzLa7z8Nt14HEuxMpGY+Gx4mj\/jGzqCJeG\/SafrNniyfG5fIYwNnIspWVq0MXOLlIaQ5swClXFb+\/uCUakeRYppp\/Rd1psnmwAEnjS5VRw709H3ejB5yeki\/pWz1++FTWOzBKMJYTYrfcQ7h+YijPoMrDqIuF0H9gufZ7+9gqtRyY5UMq5BZJwGsK0JqVBpIk8qZzHQ4mBcy\/tjq1TQs7Ot0IIaVkgUwpGwqj4Vl\/u5HwKaGSLUWi0OnHGNuCNuBpFAS6eRX0pflTBPHctyTuaS\/dYe3KJNrbOGztUzkF+0mdnNCbyTBmLUA7MzRny7jO4Zkv6rifH+hLFqi7spDLTcglhF1Zx4NuLb9WuaevEnFeMF5Qv9f8lJR3oEAki7LAYJ97LouzXEU\/HzMmO72Zu+kz5PbU2U4AM6LeVOxMaJFb5oeudvjsuaadq3akOfMbQeQUibeMa7pbGjKsSneBQdvEczAerKHIiRMk5ECNfSOyeCrRPut5LJk9C1WD2Z\/DqMtRFoNPBA5FGZgo3T5NfIIPibuNz6oo+ttb8CSdLrIHrZcZMGz82E7GsYEKKQCO27NOGfeH6hYlrd6gsLe8Qk\/KVJWPr+pSQLDAkkmx8gX7o8N2zYFQsnyGYyV7+BKPF6dK5fc9TO\/oeFZhMuJfmhYkosyWRI+YghDA6V1mtR4V29dBovZVSk72ZZcmyIQxNeQZ6DyhmvlIwxCI5Oh25AruvhrCSmMI1P5e2Sr6fOC\/ewKqq6fBp4d+HNcXlJuAVJYFtq\/B9w+TnjXSAOsobkVedg7YPAfWR5nQM5fDK2+RAsctYu21W7Keso6tGQSDthjWOOsf32\/mV1NsYa8foJr\/G6ORVqTn3XrcB+CMe9v6ylz9BQ4OTSNKMA2pJecgh01+Blcbxjr2ILiUuoZz2fY6bAwSRARh3CKfFzb4Nli1q3\/7oZNRRGNRi2rnGFlXbZSa\/nCw7OVVRWakOodUvkQpo2kjTo1HSHkOxN5gmSrWf9xx49PJKue0c8fXMvt9jJIZRsSmjYvVi5FYqWZF\/Y6A\/FtyY2SU9WAZA7W0PNb+1p9wiCjU4r6s3k7C8L5d\/jRAJr8fXpVrcAhpGmnEF8+KTGEIu7CJIf9D+E+U6oipFT6KefIr+mbjSljzvkoZkjqHR+kzvzJ5B0nniQtrXkPQEdzGJx+Oe\/0vrdFLK9jr29ML+5UK21gbkmkSKO8kA7A06cjJVcCr6mI0THvyawPVe7L57CPpvx+Rare1dG7\/eUdSwxP0U+0AcBxiREwyXuhn\/ClP1Eir5tmhF\/NkENJcpr2FULZBPf\/QlExwY2aCa3yWhBlsaHsoY91wwdAfwD\/axsAZmc7+UpDRLcecEmQAHGhwXiONm1XJCuyGfKutt6Ci\/Sw1WkcG5gWCTe2H5U6ULMWEsz+MaciaNdXjiP\/w7mWhyNERmUHaGxGEyMjySxyR8BM3An760D77CrQjewSHwN8sfqNr4ELi2Qi5Pe\/clnlGJ1kJlnCZLXIOW47lyEU2rA0QyOhrPpygGPnUwRb7yD6R4cONpFw3+rxQTeKsyiLX8cP4clofE1ZtvXZqn\/pDKiTWR0yYI8\/rHao6IMopM8dLC7JSGJjSFdqk2tb9kNctcQ7euuC6HkkDpIFkAgV3LC7iTydnrajFlBORTNQry5StkRfA21cHtRZxnAwjpa5IWIwVW5E7iEtB9\/au0X3nVHO+BzA\/O8crTQLOnmkAb2TccivSwiRgpAd40T2AeqSWW3x4v+H8CiM97kHJCOMY8oXG16Fy2E4PpliZc4Po4CSjUPTT3HpJ6deqERXFelFLQvMp9aUBbmpoIW38X\/b6wqdmQsurFjUa0YMOLKMyXyqDEfdTDA+YHt9k\/EpxM+a5X0HAu\/2od3FG8x0LrisZFdl478+GGe9d99c1mVQPwJ\/dGWYn89ew1FbOKqcHJ1DXh1nErmDdDedXocb+SFhEz9ODD57VZahPJk5+fCbN98h4A6nHEU7zeIy1s3uP1D2VGAM=\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Good News, Inspiring, Positive Stories - Good News Network\",\"url\":\"https:\/\/www.goodnewsnetwork.org\/\",\"encrypted_content\":\"EqkCCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDEP0g3aEVMwaD+jHoxoMblOl+dVQqleK+V3pIjCKvA1x\/hPI++fUoFwGitjZLZhstgUnl5WXrTY1eVU4kmMp04QcDPHsim\/UHjhxYzIqrAGc7iawTJ9hm2hXle5cOn7cX5c1\/mHzQ0n0hzxs7Q5nav72gNq\/EL8Ns1UcIbX2Md2ozVl9Gs2766SMFLETJLL+Yw\/ggkUTwJ3GQWXAYyVPrQtzUwwuJ+3tpquJNvz16ChIPH4aYpbL+FO+JrcBDIiNILB7m18Czig\/VVY2BaoQOgOozNphV0jLrMDSl\/4cbIJcvSoeQIG1sPCgkw4MyXKmSMX9o2jU8hjETh4GGAM=\",\"page_age\":\"October 16, 2025\"}]} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":3 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":4,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\"Here\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\"'s a\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\" recent\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\" positive\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\" news story for\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\" you:\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":4,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\n\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":4 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":5,\"content_block\":{\"citations\":[],\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"citations_delta\",\"citation\":{\"type\":\"web_search_result_location\",\"cited_text\":\"The list of imperilled species continued to grow in 2024, but some creatures came back from the brink. The Iberian lynx (pictured) was one of them. It...\",\"url\":\"https:\/\/www.positive.news\/society\/what-went-right-in-2024-the-good-news-that-mattered\/\",\"title\":\"What went right in 2024: the top 25 good news stories of the year - Positive News\",\"encrypted_index\":\"EpQBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDAJiAv0DS8YpVcJZkhoMiWSuM1TWAokEriYxIjA+\/NOu2+nUGsux7eS8m2HTHTU6os2\/hoX6AXyXkvqQhngZWyFbFrohC+sCnTlhaeEqGEI7CbxR1WpLmIJralxzbqcmogUZ\/fTBbxgE\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"The\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\" Ib\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"erian lyn\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"x cl\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"awed its way off\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\" the endangere\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"d list following\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\" a decades\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"-long conservation\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\" effort\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\" in Spain an\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":5,\"delta\":{\"type\":\"text_delta\",\"text\":\"d Portugal.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":5 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":6,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" This\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" is a remarkable conservation\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" success story from\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" 2024.\\n\\nAdditionally\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\", there\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" were\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" several\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" other up\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\"lifting developments\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" in\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" 2024:\\n\\n-\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":6,\"delta\":{\"type\":\"text_delta\",\"text\":\" \"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":6 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":7,\"content_block\":{\"citations\":[],\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"citations_delta\",\"citation\":{\"type\":\"web_search_result_location\",\"cited_text\":\"Data published in October revealed that emissions in the European Union (EU) plummeted by 8% in 2023, meaning greenhouse gas pollution in the bloc is ...\",\"url\":\"https:\/\/www.positive.news\/society\/what-went-right-in-2024-the-good-news-that-mattered\/\",\"title\":\"What went right in 2024: the top 25 good news stories of the year - Positive News\",\"encrypted_index\":\"EpIBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDET5iSMIwD8PZJx\/lRoMr9V+\/XBx8RlbHs3WIjA9FvLbAI3CzEiJz1JqezLgOpSuU7OIyYWVnaBTkzBT1dHzMkn2AHAMnzajMmcOil8qFu+LnGj7MmQxJvuV7zK7oHLyl43zZckYBA==\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"Emissions\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" in\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" the European\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" Union pl\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"ummeted by 8\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"%\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" in 2023\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\", with greenhouse\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" gas pollution\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" now\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"37\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"% below 1990 levels,\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" driven\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" by the\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" closure\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" of coal\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"-\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"fired power stations\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\" and rapi\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"d roll\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"out of renew\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":7,\"delta\":{\"type\":\"text_delta\",\"text\":\"ables.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":7 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":8,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":8,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\n- \"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":8 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":9,\"content_block\":{\"citations\":[],\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"citations_delta\",\"citation\":{\"type\":\"web_search_result_location\",\"cited_text\":\"In Ecuador, Indigenous leaders have gone in a different direction, teaming up with Lavazza and us to produce the world\u2019s first \u2018deforestation-free\u2019 co...\",\"url\":\"https:\/\/www.undp.org\/stories\/good-news-you-may-have-missed-2024\",\"title\":\"Good news you may have missed in 2024 | United Nations Development Programme\",\"encrypted_index\":\"EpIBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDPKxdynTAy2IBJVE6hoM7gdeoadYSYCnt8gsIjDy2zV7Rd5a\/asNKCAi78p50UrIq8s07SLozwp6ciahyKoMlGDXLtk0QnoW4UW5hJAqFjLzLOJ2u\/DlVlEB2xNKCTyCDnYIqr0YBA==\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\"In Ecuador,\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\" Indigenous leaders te\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\"amed up with Lavazza to\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\" produce the world's first \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\"'deforestation-free' coffee\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\", which\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":9,\"delta\":{\"type\":\"text_delta\",\"text\":\" hit the European market in September.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":9 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":10,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":10,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\n- \"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":10 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":11,\"content_block\":{\"citations\":[],\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"citations_delta\",\"citation\":{\"type\":\"web_search_result_location\",\"cited_text\":\"In Australia, three iconic species \u2013 western quolls, bettongs and bilbies \u2013 returned to Sturt national park, New South Wales, while the UK\u2019s crane pop...\",\"url\":\"https:\/\/www.positive.news\/society\/what-went-right-in-2024-the-good-news-that-mattered\/\",\"title\":\"What went right in 2024: the top 25 good news stories of the year - Positive News\",\"encrypted_index\":\"EpEBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDPvsCDsvP8nPLolP3hoM1hIY7dZvZTQc67eaIjDffKwB4cxNbq\/x3E\/zVKeFfwavHfusVnt6xASnSj+yFqAoOVjwV1u0JN+FqyRNjfsqFalUEc0GKLqY4MbOlk8FAzUcm\/cEehgE\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"In\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" Australia\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\", three\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" iconic\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" species\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" \u2013\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" western\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" qu\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"ol\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"ls, b\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"ettongs\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" an\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"d bil\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"bies\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" \u2013 returne\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"d to\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" St\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"urt national\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" park,\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" New\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" South Wales, while\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" the UK\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\"'s crane\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" population hit\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" new\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":11,\"delta\":{\"type\":\"text_delta\",\"text\":\" highs.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":11 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":12,\"content_block\":{\"type\":\"text\",\"text\":\"\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":12,\"delta\":{\"type\":\"text_delta\",\"text\":\"\\n\\nThese\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":12,\"delta\":{\"type\":\"text_delta\",\"text\":\" stories remin\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":12,\"delta\":{\"type\":\"text_delta\",\"text\":\"d us that despite\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":12,\"delta\":{\"type\":\"text_delta\",\"text\":\" challenges, \"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":12 }\n\nevent: content_block_start\ndata: {\"type\":\"content_block_start\",\"index\":13,\"content_block\":{\"citations\":[],\"type\":\"text\",\"text\":\"\"}}\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":13,\"delta\":{\"type\":\"citations_delta\",\"citation\":{\"type\":\"web_search_result_location\",\"cited_text\":\"In a year marked by crises, 2024 also brought moments of triumph and reasons for hope. From groundbreaking business innovations to examples of remarka...\",\"url\":\"https:\/\/www.undp.org\/stories\/good-news-you-may-have-missed-2024\",\"title\":\"Good news you may have missed in 2024 | United Nations Development Programme\",\"encrypted_index\":\"EpIBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDAh63hyj+dGHtgR54RoM7hlT1YiatNO2cOXgIjCCRm+vHOvFQy1NRePP+SadoSVx7dSOBHcDwSRY8kHuQihmNtZh7T4GmCBUZ84DLBEqFliOKcBlBNQEu99484aWQvQaBa8jFXgYBA==\"}} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":13,\"delta\":{\"type\":\"text_delta\",\"text\":\"2024 \"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":13,\"delta\":{\"type\":\"text_delta\",\"text\":\"brought moments of triumph and reasons for hope\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":13,\"delta\":{\"type\":\"text_delta\",\"text\":\", with groundbreaking business innovations an\"} }\n\nevent: content_block_delta\ndata: {\"type\":\"content_block_delta\",\"index\":13,\"delta\":{\"type\":\"text_delta\",\"text\":\"d examples of remarkable resilience.\"} }\n\nevent: content_block_stop\ndata: {\"type\":\"content_block_stop\",\"index\":13 }\n\nevent: message_delta\ndata: {\"type\":\"message_delta\",\"delta\":{\"stop_reason\":\"end_turn\",\"stop_sequence\":null},\"usage\":{\"input_tokens\":24172,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"output_tokens\":451,\"server_tool_use\":{\"web_search_requests\":2}} }\n\nevent: message_stop\ndata: {\"type\":\"message_stop\" }\n\n", + "context": [] +} \ No newline at end of file diff --git a/tests/fixtures/sdk/anthropic/messages/tool-use-web-search.json b/tests/fixtures/sdk/anthropic/messages/tool-use-web-search.json new file mode 100644 index 0000000..39828d7 --- /dev/null +++ b/tests/fixtures/sdk/anthropic/messages/tool-use-web-search.json @@ -0,0 +1,31 @@ +{ + "statusCode": 200, + "headers": { + "Date": "Mon, 29 Dec 2025 00:44:10 GMT", + "Content-Type": "application\/json", + "Content-Length": "48506", + "Connection": "keep-alive", + "anthropic-ratelimit-input-tokens-limit": "30000", + "anthropic-ratelimit-input-tokens-remaining": "16000", + "anthropic-ratelimit-input-tokens-reset": "2025-12-29T00:44:29Z", + "anthropic-ratelimit-output-tokens-limit": "8000", + "anthropic-ratelimit-output-tokens-remaining": "8000", + "anthropic-ratelimit-output-tokens-reset": "2025-12-29T00:44:13Z", + "anthropic-ratelimit-requests-limit": "50", + "anthropic-ratelimit-requests-remaining": "49", + "anthropic-ratelimit-requests-reset": "2025-12-29T00:43:56Z", + "anthropic-ratelimit-tokens-limit": "38000", + "anthropic-ratelimit-tokens-remaining": "24000", + "anthropic-ratelimit-tokens-reset": "2025-12-29T00:44:13Z", + "request-id": "req_011CWa3ygsBLhAHKai9Anoas", + "strict-transport-security": "max-age=31536000; includeSubDomains; preload", + "anthropic-organization-id": "08fda4bb-e065-4f0d-9a32-63c9ddbe88b7", + "Server": "cloudflare", + "x-envoy-upstream-service-time": "14596", + "cf-cache-status": "DYNAMIC", + "X-Robots-Tag": "none", + "CF-RAY": "9b554418dd1376fc-LHR" + }, + "data": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01E1EhXQH7WWEs5GDv3J1tvP\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"server_tool_use\",\"id\":\"srvtoolu_01UCca6C8QR7VCM8YV8efrub\",\"name\":\"web_search\",\"input\":{\"query\":\"positive news December 2025\"}},{\"type\":\"web_search_tool_result\",\"tool_use_id\":\"srvtoolu_01UCca6C8QR7VCM8YV8efrub\",\"content\":[{\"type\":\"web_search_result\",\"title\":\"What went right in 2025: the top 25 good news stories of the year - Positive News\",\"url\":\"https:\/\/www.positive.news\/society\/what-went-right-in-2025-the-good-news-that-mattered\/\",\"encrypted_content\":\"EpkRCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDOCd7OYZCZJGJ5J53hoMl2H1b1+WJEyljCTsIjAIZJyvRQj7Gz5\/l5Wj8U+uJFASJUJ6Wx8XMs\/1v4kpnN1iEfqACSXAbFNKKHZFHfgqnBAbIZGCkg7SzadRWWxA0hU5NBuhkMBxtjzu4Svr3bSyaOShc\/lVqyFIUlFjUBSMZFt1tnD4Whx+58cy0AuSIWGnOqfPhaZVyQ9ZmjUpP1yuo9D6P0BBXzDi5s2SGCBfFuFHEhQ12\/SZeY0J\/PvpIkC4Hyy38+lhPPixy9DNk8Tiwe18PVyRVtIRr9fc0A+pMP0Qu6DUmoA0aKcSk6NxSYJSGVBEe\/qmvKJJzpzx1QHdbmDBaHuG315BDBSWP7yuqSDX90BLH\/XUDFBfDHeugqnAtWAW8Ca0yjuVpzk2XIC8+Ob3UJvCzbj75ndyj1P6Ih4VhArvzp2JcgrKgtLAe8HG2Yb8i1O2kFkaHK3ebOwNnbdMnCtEDk94WljsZHvTJ+7nrvAWJ5qLZvNZzWChHpZtyci0cs+2mMZ7AIW30R+Oa33pHF8xWU6Fvkp53hqqJOqGP07VhI8Diq9j2efUK0KePaO9C6Y1G+\/JS0GG0JLOgV2IGIVTi+SRqK0v6WEOkEpGLngKUDalfWUP5UCZ2M4MG68uhNMvxwPHSK3DungxXx9jeveRLje+2de9aKJHZJKnvc2Z\/3+0Ev1hDJs9tAX\/saxpq8g4WLLA7xfrxIxrQ5wTpOBuEPzdx9a83m3R1nhgfqefKnSF\/K0zV13BUdDgy95ZQHqHjT8CsPxgMZ3BA60MzbzBPtZv6Yqbo4bTSUyrj4QDeayavTha1pEfXbJ5hhcoROjAsIo0WN9hqVXqHY5I3F8QJQEjJ\/JNrLbYzR3yPpDvnMO2W4hrhWABSMXzymA3F7y2w1KO2D\/M9Yg5+AoWONJWfAViSCUt3fszRqU8s6qAcDak4cMKyxp5ZONq0J4CcUwz5QiefmdVBcmn1UT4sdjxK9AdSp1qDulXAv8Sx60mkR5SvGQeH4niAX3Q7tJegy96J+pJGJe9pxJMFDMxNRYI8IlMNZS1pnP4AFGGiITj\/T1c752uVOmvIsbm2Iw+djFu8hYrDfB7dR692BZiRWeeKPTxLRzLVOG\/O2+pK8A0bqOqfMg1q1UNaJ2SCf\/TdG\/AybH3ngRrcrUNrezHkUZ1FKVLHQ3GJp4G0KrNauycVC\/DgyZG6kOvve1d3HQzM3pyE469XgAkoGJmUnsdpYZEt2fUxaPb+s+NhSUs51vGqdw1b9NQVl9KOtIWfLK3fQh+0yj8dBVlukKgvvl8NLNmBufxExCa80ihDSnDog6nQ9L83HYJ5uvyox9\/oyCJpGEAnzJx+sLBQ\/6b8PQo7E8H8Lr0UYBC2wj3bngZCeWRsPJL0LYR7LwnrWYKxVwFhkzohx3j3a6j5mIePyoA8rskblaNpjNTaq8qQslfSo3Qduv1nQauhSphyIuVevdBO1iEJSe0Dd3lLvPdnTMm3K665uI516JvY4TUkeXokxK\/1sClNIUKGZFn8X442tJOQ896zfTAyPbHOcA4kQbrh00SbuZilpMqP+SOQL1IVgIURh50AmERKHp57v58f6GA8ZW6AkB93j5uDSD52M5KMgquiTJDSKlbnZX+bq\/2Y7Jls4bbt5ImC5G7s5ojI9msDFbsoTkCG\/w7zagAtmhBez+PEqzCKQ6NMucsr\/NmWuPmAybNmljhmvbrgA4CBVPjQJM4TZ2J9ctILhrF9rQ9Kh5OzgeJhYqmOUdq70riuw9i5oCL7yR\/pAS3VYryfBI\/32\/gG\/eGf80UhSfsdqEXRvhvGriosgwwJ1PkgBbnZqDKqq1iJvLMnFVFkKZ6FQIY2RP1dpU9cfkCX6cu1If+fFtUJY86eI\/yu7xx7Z3WD\/4xpUMNId0ao0GN18uOY5KbLy1+UslOjdPcKDlRKYo3i8j2YVqf1jC8ozhGPGfBgeLpnovpZjE4Yhc5ZVuGfDLrczY6wWIh7DugmTYxmJfD3773Ez4LaCeV73dHLgn8ym4NV\/pbaBkcXo59Nc1hLYIpSUM3aFbBs6ruCZBMrfBMJ+yA3f0KTM\/45xdRoG7g0YUTCZgeDk5wXHgH0UgJNj4T2Dg\/nFgjSIW1371\/EAcFYBWMUzVTMidHqL\/owxEq9CkpCA3Ajeyt71LXJpdQV0XhmG9yROjJXfNuRcdDTeo8GLE5KcZltazYaLPz0aDFUXfm6eE98EuWrtbMZnc0ixGFsR6ecczdnEq8dBHsSxGT0o5vnqd9dDvfNtNsRxdPuRVxBO0u4J4\/tWHkdwntQ0U2QQI\/DYUAEHLRp4v2INP63aOpUAF5Gxee5UO+Mc9fa3rXB4fAKMve1COVvY0+PqJaHHki5rhRnMisomKd\/CkFQfqNGSP8foc2mplAVMCwDVU5gRIP6ioUFx\/WiDMj\/oFXAuSxRTptjgLRWXS7MEn+WTdU3nbyVy0L+mnSJXC0MaYJoK+tHIeWVqRH7gruUmLR5xFu8ue6wG3ImijI\/WzdmGyoH4\/wway9j0eE6sisrFsVZnV03KEftExqgDB4w1kdLyz6bZeioQYpmbS0U3EHmbgNtCvRxpHvclSNId\/f4Xg56PP6aodiyOoU1oluGRAxQfUqFsuCyohpXIbfyJBFVw2QJJN+1ZQe1d4rG5XwPo6erEqNb+7DC+icVMFs\/DP5+bsDqH8xIBQzwemGiscUTVHNR1ju2IG\/RuVieUD2DLHttHvJIKVVU8FKNDT+JoCSpv\/FPG6igOm1xWaOSfNpUHokYoBJAtpH\/8eExp0NJdqX50zSnf1AdHBQMSxu\/rOj9+hkpHvUWIGdSsDKdRLwCrYS19yil6uxpzwn91VjlDMWgHqY5dsJ+TQYAw==\",\"page_age\":\"1 week ago\"},{\"type\":\"web_search_result\",\"title\":\"Global Good News - Good news from around the world - 19 December 2025\",\"url\":\"https:\/\/globalgoodnews.com\/\",\"encrypted_content\":\"EtALCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDA06XQMfWOu3SxK0xBoMQgkPB7\/957m5PXIcIjCN1nTT1BnaH8mLozQBFW3+6FJAkxDVdKrKPXDTWcmQ9IhfQvW+JjrKDwb1dRt5RQUq0wq1EDIvpU14W2pVemdmdh\/X0C1PVPXyMIKcZzTgA2L7SB3eD8ZiE6abA\/VFQFDhxQ0y3JXW4Y7Vaof3JkxxlTqwMa0etKM6UYjMyoZOQ0B3V3hjQPXyY7P7NWa3e8xIDWI5IHieuPTh5ZRQJ3SfxL0et5shFUksJ7HdwuBOT+a8lMvNcsAYIjhOHKwg39ea8eWc7+ES1NRYlXNQorx8VpCvpXyBbNrdgY+tOjpAk5V+\/tLp7uml4G1ZRpuBMVdxRRe7JeGOIVb6vXgV3mpFL2he7XiVjK0kYwlmdPZMmAkuTg4IjFrSMYY+6umdOxSFbz+JKl8Ap\/v1TMbCZT0LYWZo8fKF2bENqxlRmAzS2+f0cd7WSAw641xvLuNx6ueLeLY8gk1bKuZgMQ+1JRDwuDoJpzVl6Tl\/jA8ogqGGW8HU2aBaP7EutuNGcdup\/YxaQJvzaFKzeJxD+taMox\/hPIYjnp70\/1PeRFbDqAieZlzTEHEvG2oZtGokELz8GuSvlsaMyhrIUsS9aB19k5rfF\/p9m4PJHtwXGVbCoVxnHmg8vM517b3GGhuYtgUlPZlRUCAHuNC5eOW\/a7xbiBF0BZU6fxHdF\/0Pnbw4SnUBUGmnqQFFTSjC0\/TxIGtHtlSt2I\/Zv\/BCTb0HU\/\/kEDPj0\/ynkxXkQNyiTAaqLnX4oWsQmii3ca2kzc38QDQDOpDl6WaOweH6fT4WWDF\/VViaJdiZK74cBNfrGRDWhviY8wxQ4ZKb3TxMQ8twanF7qoqktTb+C7uybI87HYnY9pRY5K8LShKQ4rHBAQLt5VEWOCgha8i+7OvVGwc+yBbFLIYmkZqwZDfxhNT2v9a4JeFCOG0H65Bwq8T7jF89WB9VlQOaoRjVgNy1s1rxzxvvVKe3qnGziWN1BHjfiwN0eb3WkhbV4Rir6AjAi+AVEcypbEHzZj9KTmPbvwX4wSig8JXwU\/mobELgJSBIMNj1cBs9Xl2dO67SD0LP2pPgZR+c81LdHtdkLqs2oWm2iefp3rt1NC29NWvJUmocNEhzMtkRCq0SxuH5jrCFmDzlovVupQIxGXzV7gDWthrNczBY08FfO0FOv5cHbyP8SvWHHpgpOKkA89DDYFK+zxUhRhLAwKbc73MjIZP6YkUMH12fccSYbpsfb6JPKFPGueLeYo8CSSkQhro7rEAkeS8N5HmJw\/IaKQ9U8M37iiYNMhNNSvZnL+5s8bzlj6YtQSGSfzrNNRPmR0nCYcvPZxuvsWA7t9swCogSpbPxleeJZiUMA8gcuLQp2HftKBbio4K1uXRcFhQH+Tpg5\/1bRgvKTKqUlZgzok86iodSDYmtk\/xTpkK2l6IYHVhyEF7Kp2yNFVQYYNn33Hy8cbw7Wdkmq5fM10r7LWgjUkd3U6J8qpavqD6y2TVVF770mGT90XKyJFVApUI1AmrPzA8PtQw2uhaXMSqFSDRwmXRd4eb\/IPbmz1i8lvJGvmQ5Q5us\/hOhuppCOqP\/rvS976feo4LO2h\/kz8\/txrs1lHwPjB6JsLJmSH8O2upuqd3RBmDVyOsqHy3p37YIQ\/h3KM0yKRMCDUOgjvqD730l58y574JhV3QtvNOALGob9sylHrnWKdIAdk9dNRoeQjV5aKvyju+UEcvkflSZaZiQPEmbv5kWGkNJYh6HjHlxsbGPCSyLgt8ymirL6umlQt0OHqGD4zrOVCLOIYLpkhPtlsSvWj5Tm3Z0h5Nk9t\/zqyb5ZBJJygItvKNAK+KReR4TeDH3GfEj8h\/KRVAFRfuaM4EUZOEfCx\/dX0pwXA2CIyjF+OI3M6RFfropsqTDRevRGAM=\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Good News, Inspiring, Positive Stories - Good News Network\",\"url\":\"https:\/\/www.goodnewsnetwork.org\/\",\"encrypted_content\":\"EuYDCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDPwxhD7d2MbVRKkwKhoMdidy6QPDM3n8GafzIjDSPbkbm\/VnFAGpa8u4oLmQa5EVrZsacvRfoRgnvccMSHPViLKppsK461IZQw0RRDcq6QLksmnf6I7gNg4y+LWXGSMPRh+Z8lAQW3yRX+c5RsE+FRkxW68+f7mUcO0+usq+ix+kMu+WlhWlk1OViLEu8aSoJPAStFn8FBcIOFXMOQHEDMV29Mu60qV2TYgg0HJ8lHm4wVRxmwe0pibAkLHzz2flOPdchjrzuu9tPIrUiYqefG+KEHKNFx+V8yAaMbb2oM+5T6Vskp9BaFJGRkC6pizrgHN\/19ZS4rVTfzsta7vjUXM+X\/TjdFK72xA\/4U5WTIgPaixTfpfWQa1mRaB4hhEfz5mLgxaiwhfY7FW54pnXekO3UHDLcMD\/\/ImoCElCZaAzCE0D3yssaG\/4cbd5Hq32\/bc3TcIfnnqUMkwQIUEaUo+zKH3JRsiABBBrKF\/cgHY5H7jjD4TNr5XMaseclbGJHBY0Bws9oQjY99yt9Jnh8iFiVauJbDratTu0NQDQ31oMERx0VE1XTtq1pm23uDhAkaqWG9isLt9yGAM=\",\"page_age\":\"October 16, 2025\"},{\"type\":\"web_search_result\",\"title\":\"A rare cancer-fighting plant compound has finally been decoded | ScienceDaily\",\"url\":\"https:\/\/www.sciencedaily.com\/releases\/2025\/12\/251227082728.htm\",\"encrypted_content\":\"EuUXCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDAbIeuJsjI9wE4CW7xoMugchUKMRM1M4f8KbIjC2wZ6uBZkpdK6lW7Q9QHPGOcQ88WwezUtZKezkafePzFogOZruS67thBAXQz81wzcq6BbvaRHmc7vd4b16GovsDudHOXXgIXj4qMrcRBOsUSkbJqkei6h0seuvo7uMdHiNSBYhqodId2uT1u08rpitP5uKkp+4Mw0ajUtM\/nnAuHJQtzGvOUzHKtCQ8vcrxRfxvPFAvai2WKXpPT0sH+i3KHEYqbVp5nKQlqAV9nvzkgkACs7LNy5wjZErsyTeoP+y0KRpOniorOrMuiAv8fdMkNW8au7rl+Hat7uC6LO0EnGfJoiyHyp2Q2BSisjKQYgU\/eWi1ZK4yOom1DFaR7kd6e5T8zsMDkxuOj4+CSJ0ZzmqEajTqT7B29H2Krl1l2jxanuCiZM5KMhJOGoXs2sZFl0iZKaEM8+EdX\/JfofQZApdgSiElhxdSZ\/UNGKDpmmSOaHwW59UxRWV2JJbd4XVQBow18YFnpW3dtD52iwLqr3zSb4etR+RH7ZpkrVnoxpa1N+AYMnWE75BLVDODtK6AgNgXtYQ8QD2U2fxC4bgf48lLdH1z8EAUBozzzVOVsYz2FAB5Os6Nb5lkAD4gesQz3DALOgIZS\/7sjNp9WRxvMYUXBHCAdhSL31mQG7QoIl0+Y0mMAyJPg8PuLKzgkfj7GlVuJq9WWA5JquKRSX3HzLLIkPT0EuqAuXoGQYqXfSsDWaYKZDTvu+2sI4LQY834Jzs\/wdYg5txxYQh0wBV6qUuFuRSEVLgg+sOJ6gz2ber7qRie+omH2Zu\/oTZLMSoO75\/JGY4WOEuscjGwmMrlBXBp+F+F2QkHOmb+GzWeS0B8WfXy\/YrYMZVQ9H0Hvxfhls7DFLa5TEoWz0w6jsRrN7uFsbC720qIQ7WaRAI2xojoTvTWoeg8EXWocaGP5oxStnR+VZwImjeh3m\/sjqWb1wmvUP9fyTHsMNGHwT7OUKmSpes4PgPmuQ2rcZcwcEahMvFqHG+uLM8M0T2B76rgox0XYYTRMiokNkX2bPn8fVZyoR4iqftCKv6+qEvpajFvai6Lemam9GF5SK7kofYuaW4vmwWfF+a41nUVww4KoXEhH0NurW1KtUHHIITW8mRgFa1Pr5ycDy9ObiMOTwUozDb88QoU24yybKxTcTo+KsBNW+Gd2X5PBDFhPQPNjj2l48cS6RwDm7rpQlac4X\/TLbYE0xaWDRv5dVhUIV+kejoU2T3HykbG5W5IF4B9w3n\/5FqhFIaPs+LMpGDqHB4H+\/7xcb98i+R6jD5L9YxYTsnHrlMCer5RhA1+ol35nNPBnxdp\/+wy+spewzfLvyOIlLAeNhSmpj7EG5MSmF2bEPfAHOUONjubLtQ9vN4jdhY1RXKUqdH4lVSV1CRajwptfWuo2HktE8UYFfb\/ZHe1dCA+jsu5CemTNwyiZTnHDIalYpIE9jeTuMpJ96uZwMIUvWTj9xHN8JORsDxTy\/hBo70kXUkxW824NlYPLwjQk5E1yqTByYh5PWOr6y7WpueIIa1XPsNGtwuo5GWBMiXnehoe6JvoL+daccYuQ+ExkEkNIjPrDOUCEK2NxRZ6ZwjndP8VQ5yElWn70IzB2rZrWnioSgN4+Wim6\/HYHCHywmkCvBxeXVt\/ad97CpvetTI8CUWBduDLwasK6VS1STz1hRDnPLPnhVKsKewwK5eMkm8DFFgDhbPvRkkzHN7qiF\/VfplG7I9CPl6lQWV90W2OS7vLku6Zc6ld51CqDeqSA4S2dnpTaMze2gg\/vl3N2hYmq\/ja6QOpHhWcJkMQsKU1JG+XdG\/7Pc1ECHNeGrdmT+coGOdtBUGMji1dIyhFAfjFhqAZE6OAHoAEsboQgk2BWzJn84N0oSF74DuGhkiCkroY17Xl8A38\/3Eeoiy+WTVz+WeiNIkePPkqN6a68LQU8YbnkZIE9lINBkVKzqQfhD3Xha2Xogu\/9nHVrcl7p2vPsjVmXXeCBQUg83tHA3nMi6uEd2ZF5ORdw59hIw6EoxppZa3RmK0beS\/gf3DSzXqK53+blQdP39wAovYi1\/0OYYh+2fcXbolyvFUZM2pWhwG4W9eoVDYEbJwVe0Yt6VVLGItDyJ18Kimq8LO6je1B9QsSAUr4QcE0yefODr1wedU49ck\/8wRO4Lzw\/+3mlwtb5jhuv45ptNDCaiDmTXZ9DoBnbVvF2S1W1TKdzzVX0g59uzZ62fa+u8eUEnY9k\/2tCXlVo2Uc7pbA\/E1L9fEaqRKJwddSG+LYyBjMIwJHxmy9kRp0Rbgj32C0BgHA74mYn2A1F\/djsDja\/+MLUSyY0oFqCSDCkB9cl7ALUV7OhKxS+F6tmId7KoX4sjguGe42FQIbeAwYAjlTx8sq46HHDBnLgNwgcNbrbzVehAqRi+Zl4djzJ2hoWK01zdP4iG9Pj7i1LGxRFpQSkWYtdrJR4x3Vn1NuD\/cWecJ4b3vE7QUCtRl2XOdxoXyHmYenw+YtrHNqdgzPGws3EYe+Mi9sJ5cIpB1rPyTlrC49clF+2YSMt+yn7QqoZ6w54l5L1AW6SGqUpmwd\/BueW03T9qBcRvYolwbaygtZEDZbSohLl6KRx1lhqXQVL03w9AqWQQmAyVq5fhPaWasmnTQUun0eQoHl8\/0qTAJDWph3RWBtpGBdsJUpTcMJWKfUzqCU2FfKMqX7t1ZbB3hhDAJ8jJwaoy5WDhDLGJxs0LtO5h88la3ESUom22fp\/IDpXVmJpHGmGJG3ruAlTbkpoVsxK9h0WzlqQaJkFfNHsmsZnuB+dFfqqrlHzXmXhcXv2XjZVafPI1MqFYY480ps\/8ep8n0I2H0yiRtGP7S8EDMgDm+3a+mkaj6TZhQUj1cjo6PNEebRAeuJ1QJkeuf7dnm1GzDqWWLzNmo3fXRoxukCqj942I3Xw\/biuOBQIVhSspHPTsBX5oq2z0297oRyei2S9DAfzcWX3kpU2ZLcZYHrQ6\/dcjgCjwteubqNaMwoGZ0cZm978Y80cickHd4mu\/KW8mqfqfsVLwzxr85B9YumMllOFyihVsSxWXJBlKAjdR7oEYaFAT8eh03LZk+MhdUbGzLLBlhC1HCujuqcwUed4d05WGyzrYV9UJKPnFH9521vwDBiKcBj8IebdHfLc+v0mchG07wiEJC5aOLnzDHLbQRL7yupV\/AisSEvdW7XujxGMDyaVxenoZ2HoEHYnsR1gKma09mLxohjPfCz55xqLKajAzQv4BmU8kmUv4vbcU6Pel8qWAvpkgNtlpULW3+n0UQeR+eG15ocHqUWH4O0EGOR3N4rYRqoMvbwvmW+p7+mS0tJztWh8B8b4yZdKf9PikSrG9E6000cH\/BljCtC7Bni1nBL5RKnazB+A\/SUCT0IWnjBGtkU3VRd9\/ivkYmXsjbdIh8pzsjCQD6H9kN79hES7GqsFAmm0CRlp1cnkTWZuTCIRWfVmnQI2LnXBL+j48\/B1u97rpSBj+cO9SjjA7o8PtWro18NZDVsi3qgX8QlVQPCW82obnKI8RGoJaA5crdddN31e\/r\/xLTGUvNAqpIvIP8EQWeG2wHtv45ZJDGzHMbMiiHA6yicceJJlRmh3VnxpMOOB1aSip2zIgdGMu9iHdT0AxtDs6KLBt8uCZ\/oxWfWqOkApnmuefyhz77IwNM+M1AgT6sWjXP7enERHxgJI\/Ye3N1vz7Kw1o2mbmIl9R+g3TCp9RFajcQf37qwRe4nxNzuRpZ5OSPHPK3irxTjx0cdj8pdXLRjgUq99b1bP6dzuavOWMuHruAWAyTxvQwTAv1Db0COGqgoaJUpfEVZGZSWQLmG3g0QiEGsHYdm+b4weegvXtmY8gN4hCr\/FT08q6t141Fu4+GC63jmZWHzmv1vtKyfEjeKVNtGcDyA00FsPF9p7G5yQyB6ppeV0uuu8G80GuV\/03jKRR0gMth10vOeoIp3FRDkyhX75NBUAvi9j2ISrHnUuRA6o3pGAM=\",\"page_age\":\"9 hours ago\"},{\"type\":\"web_search_result\",\"title\":\"Good News This Week: December 20, 2025 - Puppets, Raccoons, & Grandmas\",\"url\":\"https:\/\/www.goodgoodgood.co\/articles\/good-news-this-week-december-20-2025\",\"encrypted_content\":\"Es0bCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDGpZooGyu\/sZmueiVRoMku++G8XgwkDr596gIjCv+DGReR+FDUrihztK89Ek0d5q1IvHjuC7hziyd3KYWEF+cxPRV129fTu30\/L0UBAq0Br3BED6QxMHe0H8ZuCgtpf53z5Pza+J2x0nGOTTqMAKjX2i1jZJ7GsjPZT9CzbOPnY2DgMaFFPIj2uAfZech2rrxzCAumfaBql94NQX+jEZ66Xyzid3D5jp5Sy\/gt+CLU7UEGzIZMGozyEKVKXRWh\/+UujHANLK4xO0GqYgvh6ZK2JVCx5\/MGsat0UhYHXcaO7dFNjQvJpokL9N5551pQ5Il4Gq3P\/PiAyV9hIiQxCpnPPudx5hBUwN\/baQxVqSaTD1iVatnhIEDE6\/0VTsdwgNnsaluKYwQlq33DQcSPDE4SUZVlVpBo99MCVdV7eo10PuhS9dxtqY39sIwDicOLNu4NIId9QViYj4ClnMqVgGkjvu9MRDVpaLIKIvFsG\/gGLIQrmegaFYeO3Nw1dqKZ9yzygWxwXQouGQ31SG6\/ykO930i7tJhZebhkx9bSdN5shESi2HVe6uDwBwPwVdFefs1VdNOhQ0hUBlFfJma0htnlzEztcrKJCJAedZ03eCKwAP5+YYEvisWn76zdJoV5fzsIBs5QiTcQcDuYxPUg4sSCrqJRfCBr0VYuOLWQ7OCRCBCWjRChdrZyZMq1SdcPVWS+LACj2MVZsWSANbWUnkOmZsburMgE8uvt0BT1CQBEMX6xNlQo6h+aLxKMOfos4on8AyIwj99OLMMeV+gwwDdRbYkAx2ZC2mjPCXlHXQvQJB+dEWD1NRlHGHgssi6jBJG+PY2K94F\/O7NPlx49az1J+A6rbF5F0z7cvaoTbN1DOwrK6NJ6AGt5c5a0PppsIqZQXFfKsBmKB8rsrY63tipi6GY0CRJMbdO+aGQunSYAVpHEZHr6jXP6DVse08ktnBUE3VjWX1AawXtPHyhumEt2nrWGrkO8J1eudFmV8ezUJD6dFZbw+J23aVa6as8LAb5RYpjrZrMmKXpJNMqGp\/xO7eHM3G2bYXeAJieEZMX+GCxTudhvS8UQCyPvKF7Zf6\/J\/SzOBANj9l5r\/BjOWmA7t1jB8nEzNFkbNWi\/gWi2T+s\/3nVq7Yy6XWSqbsRogyfKw1a8FgkrdqT3RGEG0SOyk3q93pDl2i+B\/A\/BAlvbDq1qFnfpsmP+hCtS5edW7ezzH\/zleDV6IqAUD1f36JpEgrpYoX8tYb1LM3dm3MIuk5nl46bg6JT\/i1fZk1vlBsn9c1RkT77TMGXuWKEGdt8j8+GO+92hMheC9BonSRMOU9OV3V\/LhDP\/IF3opYdPyS\/8fdwZUBizxYJ6I90v4bC\/hyt\/jaikKX1gQ5itY20QfIXO9OQKrelRBHq8uTYvJ2TLm5pcLjt2C1RkaCG\/RazIiAB9j2isUTLoULD3GYKKx4c1exEjFXzBEBSVMCUqXb1pNi1HUrru6ryArRd7kE4mdc7g63rXrT3uoepjHIczf6hPZBc+\/SARgKWpJJAOtrX7tzsdCEgU8OKbvxhM0RwZRnulMS32wShMn8n7xu1P\/KDpGmGgIlvNAshnJv6QHtuL6\/uvjzwJxycUcrwAiRSOotwCMABZKDjJ93rs6NVQ8immGsb6VigwXBj0x0Ck9zY8h1wXp2\/F8r5v80XIWhyneonvQIKxTxfQxKhaMY5QiFIZXkcPGS2eWLxBAIFcHvqE5xIuQaaU3bhHoRlE2KcfOusUnnc6OY8Q+ByOa\/p5+nCczA+I0wz6yplsHF1gGGcSw+U9MdYfKmIi4m3GajwUtiT5\/Rz+EO8xi\/isrdSPkLLJyHCGwozhskMHPFjaaaDeARhoDKehHKvYw6hRkcG3RWLDzLNHP7SD0JMbo8yisibu9wNCpZcwHNyz056qs1tVkVLwlG4K7y5UjlB3tajgzt8\/77teCpguHPnTPg3CkCwWa3NaDmMYsJUW1YGZSE6j0tTZrRU18BGk7Q\/iKoCOaWs99SAVCKqAxTXjWSgVbJVeWJtDM7m40+wc8xMSlxj+kvULypy3\/rEo6iLaoUL+CBH\/G04PEngbwJrOGfVecfgUGkBrE5UNUlLtR6p0fK7nm4g+k1pOyBQbCh+1dHygrtOPH5znTwrcRmfkixRXOcck0ovZd\/uY3FgzH2w\/jlpZ7l++HmLoJIe6P1AaIak2Xq8a8Rfap0JIFMVXIs3+lIQGicYxdzpgvl9imLZqTYDI\/R+Q8j\/kBiClVOJBM0+Ow7jvN4YS+YwFOwHrDcpG37CVuGooY6A2FEXlZUmXtt3D\/Gcgfizf6GLqi\/\/ogDJeym2RKaMpVsYoQUh+98yy52zT6iTe\/o\/QXfHYV7snLIDrvhHL1d9RpaGT+n6J3mhL816qHNrdm3OzDtH\/Tj9adrLe15x7UtGUNBuQfvYKx5+NEj2HK1c\/dS\/qHaoi+M7tczUadce+pSqYeVeEEVY8XUFC8IWW9zVwiEb265YWi25F+L21tKbJBRclns+Fc5ARfXgpaKwTHpu1pimJ70OgdQ3S4lQ8lrL6BhotI0tkY1vCmQ\/djkpaPyIjWDZuSt1AQ1qLKMeuUhV1x\/a7+4B\/e0yTpOHxTp4x97qv\/kOR8JpAnbtUmvCj7U6EkHBZ7nKRxSPQTXi+rAxqF1YYBZDhTDRQhHud9viDWlmEkaFiN2w2Oj4n7sVn3\/zqaHp+CLOQSC2shSEgxZ5SYkl4RiREYzAGC2LbpvN29LouF+5xfkUNe0BkvamL2o\/HJ8VixXCn41eQ5ughzAtTjzGpHzmQWF3PdSQ07ATnvYEscS77zyZnOUVEDMxwk1miGpl3Wc6WFirsj8edWA7v8B9A8ZcMZoa2OEph\/Q6rzgjdgJUT8QhfjWpyEEwK+V9I0bjYWL3eElZrDCbiN5z\/9JZmZSKP4CJWR\/ajZesqy9bPuhCsYTrsSgfKZ5JZakv0FW1oeIOlbjnjEZr\/t0YygH\/eEkcL35OqYuCcssC5JesVUhfZ8\/XYnayOKlVo6RBV\/4LEnV8kfb8imZv2VuSMRc24GvZpeoDPa9kbaU+vFpyPlCHcRRsbs1N8CG2RVyTb0N5dwwCLnohkSm36jM+C+OZ31bXgykByayVwNBTj88o5CW7qJwIH+rkjZ6vwwCHdKKrdBq+hs29Tj0YeNPzCkeERI+uKKMoTeQFD3Dh305JxUrmbzntYWzQ174ZdYgQPyPE6Jxqqjo96n7oscNpVpuVvjzVH7FN0rv5OcZKsCXyuzTOOUPwdBibuJKeVQC2D90l7P5b3Vha6LNylc6IODq6Vuk5Oe\/LHMNZJETc0NNnzhuENLVEb6PHicVitFkMeyymLjPg9azqfkcpR7kP7NgOmOvM\/QWb7N73TdXxOqbgS6VsUXsB2TG9YVJrm076br+hol4l53YtxxmKIq5joIUJ9STmA8UsA6NGxUVyjSzw85hZJuVR32g0xpkcS2q6Ae59ur7ghrJIb7\/ood536fJ0VIiGwnUzNgOO2IC0\/2aNGKpJHff+dabEm1Hx2\/0k3+2C+dVi09QH22KpKLG\/Tv3Sr58aRjteVl5WDMPOik7S0Ps3Sl71KQGM2T0EGfzPphA9ZfcJVrraneDacoTI\/3bSngrW9DmItEU\/OC2FakAuvEt+mnowPdh5J1iQGH1\/1LFPTyoaBGfUFsUYIOntMfTKxIticGXTeOgCCUmlKYf+W7xeiY0ywALMgiuaYFIJpgGWTGUzDmjpJKZ9mhVu1iRfa4xzFVWtS\/lfiO8FwHteyhR78LCJTgnzkkgee8n5BpSeW2Lzegkv8s0mNG3ghv\/AKuv8gONFBbAGAQkl\/CZrrBGKQkyq1MNgepwkf5\/41SiabdBh2NzfUc5qFUFRDW41AuMcVyhYvNtT6MgwzbRu3FKXU1f8b0ZieoOQFXaib0YTadckbVXNBSBYa179Pfse8I6d\/dp\/nrUXmXuHStIVfKJ0ulpvbe5Df2Y+Suk9qE\/0ytkrZVpWa2+lSeyzmQVgSC2bTEEQy\/p2+VhxJdfojnlmet92PXZXzK9hOf0LfEBDhcyOItW33\/yp2ixNSXiYTXAQxAhV4f2fDQoauz+iLaKNxcc+edqPlQpdRErVhJVQ3Y\/GTqVGUbUcmaVTQOouFdajmRjD03YFaY9NBZxo9ZYxSxL+ysP6XZpIGJi5FWRGX7HSNdYQmeszSsaVqMPCjrpuowT\/Gpr2zjj08J3V3F+7S8gR66k4ZaURF2D3wOUDXGYhWu\/woslv\/jjJPSWxow9VJDFVW5YPFqzCA0jjAru7uq5vf1LGITT30hdZzVlc9Lp8tzaOZRVEVunEnuTigbZ89q5PouYCWCd8Het0YRgTNVYfz4UaWDx3fDQdvQCrafxdAuf9okzULl6jd73BeBmK1Fa\/Y1Tn1ut3Ew\/gObCs0ZIaJxjaLA1MVBtfEbMrQGHnauNC3+8VyZXo0eVAJ62zlptH3cpUC3nc4d0u+bDNqajer9jAf4FryydJmNil2FkAsa36+O6CIDKxq8IK9eaY70F67kvVkZ7dhw6HTb02r2LZfKqlNfMVcBw9NrFztTBTwsp3e\/pp\/wX1wFjp85ER8IA9S98V6n8gx6OFL2CPAyv+YgQ7gw1mfTn+p1xRsFRn4g\/G+EKrmKIZGD816tIjEEYAw==\",\"page_age\":\"1 week ago\"},{\"type\":\"web_search_result\",\"title\":\"These 5 Good News Stories Are the Antidote to 2025\u2019s Rage Bait\",\"url\":\"https:\/\/www.rd.com\/article\/good-news-stories-2025\/\",\"encrypted_content\":\"EoEfCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDFLGvIIYhDAhI4nmrRoMqF3vp8+ia8D6qMyNIjCcXSskPsFSJFP3nJ00Zognd3xYG9KcTMLyuoqwsUpDpljBiWi8taMEyvucas6seWMqhB4lJ0+SOgNUpMBFFNgKYnW9dByXuHMe2XRqVu4\/ZfGGFQASCd1PwNrxKhkZo5IaRb264Jt65ww12PKANDC9tDDvoWGMqLZUI7a5KvPfw+m43tbGbK7jizEbdjigceR9DwtVqsiL0tJ7KbHZQgQKOGSnQRZfEXTvdADovo12qf926E8fjoDbm4y3TpUUZUqRwZk\/eaG7EcgyRU3zO5hN2k6itTlefMYTs5kk1+viwktNAgdfxIG6zvHGgudWLmYzNIARTioSnK40wV+9cGjS32d+rUVtSrh1DwL8p2+MEIZDqH\/aaGTzwx7UYBpGJ9rwTS4TfX1QE1MHk21rYdKXZsNHSZoDyJAYrDqSSOuGueMcd9zlW3xEK4F9K6so3w2c5BApPgqCK6QbqdoWotUkLwGrBsw6gwZts8t9MV1UPk9hjpiFTfUyQOtjpWpJnOxEvEjd9KIhwOuH0fAS3ITB8h4Cl1FvM+QL8\/kTbYx+uefdH\/Vv0wEXxqfJiucLkjQzqtkyVPp2Q6aq7vz55RZlDJRnORsNiVUij6h4xFcXLSwR6EdwhqVJ6hqRYJsVAPJL7s2hOj1k8s2IEaiMWNqj3WQbgrxPo8EOcF\/cttEIKT3uSPRpvj\/JJNsr7eR1T\/xf4H6g3eGTNGpPJa906kQtbEwK7liarlDt+tF8NFAovdM3nXiT+ZUdhCKFmVkJAEzOrJ+czMf0IJn06vXxyLhgplkWWN3e8fMhgVElAL5kw2sOoqSVaOs2MY7+0VCXK9\/ev9zjAxWW6Zwr1lwihRLT02eDSP1UER1det9SFzSyTYClToQdUBAi72ZzTi7wR5AEaXIgJNybkM9dq4qEF4EYaYjdctR2rWh9wpZN6QEH+2B\/KS9zQAowUs2nJEeCkar0e8\/ONlQUFXXVoYEVtnmur1SCrQduFIrVf5wcZaG9HClNr183HxohoznPTi1hEj2l3Wv1caUA1Tet6aeC20D\/9eJom6xIy9rd3lgbUKuaK64\/FZVmx4HPq6tR8UHkl9uk63T9znflBmp3XUokGfV1FBDihesYqifTWc33qtVh9iaXhpFt4P2Zci5ibwwUj0a7qeJKwafDqi+vCopcFZ+tq1G+\/OmibEN7yoWe8SNwOGCEfw7jW6cgPqTD4ZIRP+7xeRX75y6Xc8wWINFXeDwQc9aTvrT\/nFdtUQ4vy1GyKzCWEL6GIo15wHwROG8gGNJasggii7ww+S3XArkmsq\/fVV\/d\/IBu9Bwdq8IAzXsiVOFJ8LK7aNS8O5nWC2BPFimPN\/MP5VEUUazmcXzVJ0YJkFcwidSubKp9mroENAd1S5EcAIjO92+YMpj6NSVwSwK3C3MdPfoORTQoL2uK81lVjD0de9M\/YTUblBIwIqM1Tvbt91AapdiaisYdbC7vz7FZ54M9ztV84vGA6daP1ZdMksGGPWyj0P6GzVmcwVCKgSncm\/nkAGOToyYL+gqpcoKQzN0smEesaZgvEYge15X5LuSG8AI\/Xpm8HnBY2xHBm88WrVtTjla+GI6L0hEyEhLB61kF6Ab4K3FOmICgNqUqSZqAY2zAdSmUGg018QY3jGWX37Bzh3YvJzXXY2YIk86XLbvCG0oTf+qDyDHLRN1FqoZGY7n6NqOoPyf1nNJFTI+LCDqQqsfdlmxz16inNGZIQprsPjEFwsdZFwZs4VuK10if8irH66pB4YD+iec9Fh\/TgNpMUgnb2DklnljQvH7TVzIs7uKhXbC4kluYwxcnRB9Yzzy6nVuCiaRtHWMP+5Q8OE0uPEr8WUb1n1U1ycEPj1K4UAlItXIP\/wuJgdzYAawyziQ2zmP8kYRqY1rWiT4gPbDU+SpAWIfTT2HzirSFSqbFEW+3O2BMLdfeN9cKPKEiE8Wbk2vYJHnMITMARlPYXvhZCImK+Gv7i8TdTsKfHZGEgl1FIGLeohxL7xyne8Mcdp28aXOVFHXAhAQ2iLD6lRCzhsgfMu48rDV5MXFdxSwbA3QWxqoVOtGGtw0NRmEwEWNrCQYAfF4wifpTpj+CBxHYc68517D+ImXLB39uAs2XsaQ5XoCdYeso8VYpIlhO7JsfRw5NklY0M0KlhHpEyiPKIlF+o1xzWu9Qwi2bKvDoEl\/ZQ0\/AC6tVsBE9xPhG0sWZaILp2f5sYG8hLehmCq\/adErO\/K3h+1sfJctxrjSxapHbaf0V0GDx87FADOW19spF+PtyVW7LJ6BWuYG5uul3pYzE2jykpfw9i6WD+p2JcAPYdyyujKzEgiFbmY3L0V\/YfF696CwXKlLLSeo\/qtDs5\/EywsyxaPTlQj5kjOmq3XmzjR5hZaKC9kE0rN9rEqiZDrI26G1tZLhyyyGRw8qO9bBNaKA9LVzc8kYk2aLpiSIHPB+EjeHfcDcUig5vmSzB7bShI4XaRjck4mUJTh6M+qpGOmA+4P0ABqAEZMzigwgi0VvvX3W0bk68vQgkyTeLieZFew6qtvcSCOmhp2wva\/rm0Kh\/6asw1D8GkF4JkubQuONOvjEVdmM4GYXVmCtmzuokhc\/OoWhCcyN1Dt+QzZjn7EoAXAjsVKGPE+pD5YoX6CanvmoxWeLnDB7151bqflBdbtBRbhk5J4Lh1LMBDT4oHKJyxT\/3Q0HZq+B0B1RxoCrpjtBc3eDl6rVcMm+NRRuPiDTkBeqEXrevTk0uWjox+\/TbyBRcfpe8lcwi5oPshhbXHjsqS3ZP0knCybUOBjpj94A\/9QsZTTNnaNLFw9AGEL14WKO68hxvjpV0IM6jrI0EuqkrUITdP5204gii8lNyhRuLUogs+TuV8EJ189dfIdoIT9r79js9mjymmx5dckomvGZM1f9a5xdHbz9AlfBiOZWpm4gpK7AV+nGiSYL+Zu\/XmBRJ3I1OtT5bd8rwXwQanj22fyvAIPDXiRbb7IYw0wq4yqzZ+t+670JT09YsIhRvZ3RGRztUHJDv6+VB+XAhQskjq93SbpisXe\/Pnh\/YzD7zQi3buMns3kY632dY97U1s7gThDhLpu8z8MdRkQRYI+kZJmwS\/mH3Lt+GvqTKGusB6SniVci2nkwkLmOwvDcK2\/kwOimATQp1Y9QZydfGwDygSrffpCVZ4TPOCB\/+CgRXWyFkHNnyD5oQ\/Agvjn+gMQUZWGChhF51LE9qYC5GRVAeeC14iUQAEmF91G5STynBFrxl0I\/gKo2s9uHu3cRSPqCgBKX\/xGtftwRv4MZb44m8avQOhR+FM5IWTGoqruSm62sjAKe1Xg8o5NnDOkLDC+FsI0r9nW++yVSKqFrZugJ+fAGu5iWNGxYfnwdpPbiN2ymH+MCsQuL34HOJ+48IPz3Ge3BvQL6hA7tHBspJgSARG9NB8JRxxE4sBCO+rF4lOgwRo5xaQrLnQ4ktgb13s7zM\/ZrTwgO7zurRs6eFwMsltBKrWsciF8BMRiEgQ9Z8luZ6YrYVQ8FqLiW\/rO02Bpaysm80AVpOQU1GBiqv0F4R49k52F75M7fmtkR4zJ1z7hr7tHlU6mSFPtv+GKkvAQ+6LWwSH+S0168Nokp3qYTlZXxpXuhmxIw+QDRu9hM0bn3eO5bnUDs125et0IQpPGw5CUfACsUoXGaIEfx\/j+4T68Rj6UNkDcO4VMcyHUv7gtVMvThK6I2i5+REcG56d18af0LXNDVDtRoaEavtqx\/k95Fhd0JmWAMwRrXfj9MMQnuMqXnLaU5YRTYMNJAaKO9eWa3+aAb8HmVS7W+9TEHcTxRCimE2E93AWt6euKDgUPePsxW4v6Ya2dSy9QSw54FmC8ZfYZBHCsgeJZZwwho\/y\/uS6bv6HgIb1Y2Jf0fYs54smtG+2mxEVfNA4WZWo9CSkIh6gS9zVmgykJ6acWtsbSkJQbxsqBToq2ibq\/LibPHNtbbQ7tA+Fy2EXwRMCYNDREyDZHMul02+C4FAS3n2TqDWYAAQkqFYOy\/UeS8WD+zgrQs8t9bnK5KAQrfIdxCFEprgUZ3XrRM3yNj1qOBpiUe2wIrbJKRYjxQ\/zXQZwPJ7C+vQtP5BK5+nYeQDq4iq1nVyRAqcykYSDPvDY5M6qgdHlEjlIj3S4SpN\/cT8M9o8OHO+dm6\/rJM0OsR3wVa6InVQuoMEoQYsfApwoDEZfmpmIe6ni0VuPLYHQKklrSxG01uOHjMR3u1kfUO+pJEpAJfhFl+jQLZDmT1lx7KS0vkYCRs3hv+s6LlJ7rjDQamF3cJUDdoBQeRIoyEmD2t0ahYGfD8GPEQ6JEmrEZziIpRM04EZuUPMJ5W9aE8VUB+wt9zuOJl9th\/RHqAeTJA7x3rgyqkxlY4+4ZC32zSAFUgRxLTF1FpxPlWLUfVnLcDLGOB8Z7R2Ntg1sSXph\/ClAYc2PhAsoep0ViCRzWOrzzIZ\/MgHlTV++dO04bOX\/Eecerdo2tKYEXzgUK9D\/iV9Y1romS4iovpzPfMrlBPErwawDhWLuI+tt01i9K3BqG26EK3uBIw5AOgJBzG3MtSH+2Rw5FJpZYP2lDHHMDzyP103ion6BKLArZpX5h0+lC2BMwgRydzaZG4xr1JGU\/Awgz3H5uRHsisJp1Usv0Jk7C7mUGEgXpIXdJXZJasU5iSAZ6PRuiVliplWTvrWz4Y5p4R543N+QcdYD8onCJNhIYO2Q\/VyCsTaIKEgmYKjjHvAo5bJIyvdN5ZpGVgh0500Ux3f4bXgaTh3RCbvCIZYrLVz+p\/8aTXV8wVFQtL0bMBDomV52jVBkiq0Z9u5iZA4rUKwtgs0VBYTiV\/w426w0CrbTRmtQvt6PDim\/5kpFaRFO0u0yc5BVHeZBL8Tdhu98e9nfte9Z1+nQAv6Jzq8vEilsByCZOclkBxPEMe+KuM7IxRvKrGxk\/QghzIyl+M4GEnKeKeWGCYzuFWe189n\/ChJGNLmS+kvnC5JojHsFu03Ahs6RzY5\/UqBrnZ5pBFAvHm+f1U9Vd5L8G7AE7rx5vC\/uYOKY8mphyWb2gIWI6OET\/+f+khOVgBi44mtONVtOxzuFevlDVQMOprEer0lC+2zHJkF3g90eaYwklDYPW6eLrB\/UOFZOGMT+mcnw6zEQKVW4izALdSgbEje+yBKb0vipI4sAAP9lFizS72kEsSlVBbWJvqqpXA9+7OIxwAgYyADx6VPoiJNGAM=\",\"page_age\":\"2 weeks ago\"},{\"type\":\"web_search_result\",\"title\":\"Seven feel-good science stories to restore your faith in 2025\",\"url\":\"https:\/\/www.nature.com\/articles\/d41586-025-03505-7\",\"encrypted_content\":\"ErEUCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDDAWWWw3LNiQqCq9fRoMsUejAi0nzqMPRPyxIjBdia01+kKTFNhWfZnT\/05TEzivdKVHMnC3OcEVnvbMF0RivspAtlrWp\/FZ7xEsk74qtBP4NsAvyAzrIe\/zUiMUu05sXZuO9nlk0ouhkJ6TDDQyqJPztpI5VG+\/NQS10GcWYdOUAJPWaRjhzsybf9Bkq7M9+UnT3MskzVHZGDE\/bZb0Uc1mVjnh16p2wzEi86+cghPBcspgpPP3oXRpaImL3zIqqRM4i8iHAWI212TZujI2IvkRkuskj9YlQ0AKHVjkQSbu4nDiqYPCO+AQeZRU3lvFFaOUOEW6WjhtutsnxMh2m9PnlRQipUEWR24ZqeojLFKIaN9u6Xd4ucIC3mVIXtO3kvD5bRQKgrTYfboS7ul\/aCugW++AKjjasW\/83Mg9Oa0EzhMYfkeHfDgrP3qQmOIySFfLFEdLpfr010cWuFcq2\/Et2EKvQR15WzJyxdv0ANDDfF15+MkBIsIAPqDKTDrHFkDLWVApRnMGPeGcbSDOR7JkB3DqibAcB4nrqqBEGDCTRrROoNbNuKJx6JuZ1Fvd3SpRsph8rZInuDod2bV+6QQ9AC2mnyukDh3TsTKfhoRko+N7t\/rXtoRjgmk1xAxok\/MDS+Sj22YGz5s\/Ef8jty3fNmmqY4\/0i3Yonz0oh+fspLL8eD9osdMtMTxOz+I\/+oNHocWFgwy+ttI8snnKixhIUFaS+B2yoS4SPmPJw0gKbpClR3j3updD9PHahYiAR1hWRtbYMikQHw\/ilVuLR5r06s09zMOKGEuljTlxkKuHPuBGK\/ufDYCDQbqlBFCqZca6jf1WHnT5tXkVmbws5Ur+ykVGb2A64VaoDniIEFb2zRqmA2eXSoINn\/pGGEv1xBwszSdDInxtLfwYKBxwbXA4tNEZrEaFbrrjQ32NlROIohAUvo7URzbx63aXUf+8mceuenbILxHx2llsMXlOlKh9lS+T37YAklDHy\/gFwcIUoiCW4UCxVba3ZkHOJikFh9+s5HZezufmVZUPaEsZTB\/3J5G6W6f+uekrYGaX89lvWyBH849rBC6ip8m\/ORe6dQXfKrpDOLd04ZZN3KQQWkGo3SlNm4FnscO4bmJljZ71I9Yg+eVLy3rlTi8suHlBxjURPtEee245UPfeT\/yNiys3oC\/8hPBS5IMNQH3hxILDUSrQ9Gy1kRcIcAUx1PB\/2K3cCG5NEgmHceXWXLlaM5OHYX85vhL1ymoGehkk2zfAgH0CS+tQ5gi6hoPbSkNpwWsTlXZuwA7VgyDsYMwlsg4zlZNm9L41l6efxkiEjW+GiKVN4cEINC82dKHgeMr7Zj0dRtO\/JtJN2lau\/s5W3AgsrIasWb4uV3HVO+jQ0DigOxAy4nqNl+rLPhP1oO5rsn0rumfSC9FJatrWr4b6rUV1YLxXcPuJ\/MEXJtQPhvXsTLvEMvJXATKYkzYB2z7UvvVVKREmWKTs27aE9JFwp0LCaisGUDdKoHCfHZuqVuWjhCPbJ0aMGlYXR43gb4l2fKfDIELHaz6eUXUhCZdU\/J74HlHiUZh5naH+Ee8u+VWo7WpWud\/Jr+KJZn3RZtZFSl71w4YIavCy885poWC55er40uB6kKKq7PQK2W162AwBzbXyj4cuEw0MYx3bJvGhvqbtrG6qjEdVjdu+Ro0dGgzqUPgeGBgVN7vm4+ChDvgH8sfsij5HeQjM5IF9TSG1JGLJICR6SC+NhJPvVPSPGnDtNnr2+FS8HDo4Bd\/61QHbffF9BYuG6CAz+WtXfJW+I0zL57j71b5N5krgZT40Irhhq\/6UPskP17y08R2TDZPL53zCEyVk9GEm4nEnPKx5OUipJ2lFsAKhh6FD8BDfmR8gXsvpP8JTwZFBbee+CkZfhUe9IokJX9qrrxGPPWmI1hhQx67mkjP6NlVLVu0RaDk+NKBY+ggr5uT+QSYPdDbBRRjOAUjMRrZ0ZTqqkUsIeVx814dcDpblf\/EGPRDxyezQDYXrYCUx+HqL0E0ULAJbjbyz\/R7qBoT5utHt50frmTA8ZYfN+se4e2T1s0VztnQlG12zKNrL2FnV70fp0WhEFjJiRyu5wbNH61EQV6K0jnlfyT+RkkMqDYJFj6igMTZbOIJCrrafyLGYWHWnhoGO0L6YRennLQDla03HZ2VrWyPJETFuY3QH0BWMWYk4f9sVW56Tf+DlThl0Z9I3oL8APp21+D\/qKtrAWjth0jJSyP0olvY9eIACjhOZ8pNtSyWu4ATKDsiT83FFGZhZySIa6QVbpnltVdTV\/k7aSgDC1dMfX9\/vBdvCCvlqZVN4ZJqKkZPTSv9wAW1Q3WbVzuKCoycs1AcYeIeUw94EQz9ApL7jVILW0vKeZyvrSTAxjqzk9dTBLSLw4OBnK6X7M\/pN6yG5qE9QVnlGyJ\/KK1c+X7\/bB2+\/l5sudYoJVPiJfpzIbwlzjTts8HLpZkKQmNsi57KafuTjpfpfBpC4xL9Ua\/4N4\/LUim5AzGcfHGYKZ1EIT+Mtvx1Pm4JIzLX8kGWpHNiBp9MWLIh76vhCpfA7LP7z47NT7dE7wAzF9fT1ZB29QphoHTuX2Y3u2Hz42f7ziVqLVdezPgQa7KjiWNwqJJApqSHx2UF4+rphobayUYWdlyJ6vGP4z5y5So691azCATP27RHGk1GhcDCwDlLScz75IENmL10+nzM+zMN5ZMA6yhSLWPrZvQz7ZArb3w8\/RjgyjJlS8JENJsNOsn6E3Ip+K6Rhd7GiIp22ugYv9h3tjo7YoytP3N5U6F2xzQI6VmcnRKclY48LhroJXQsqvpCgJRuRdKHeOEIdv5rPB6YXd8Dm4HK5TKDMMRoG+TX\/5wv7exy6wfRhBsg8EnLRWdIIfmdrrfZoUEt9NkskfpjwSWcX2Eq+9WvHqt\/zu8adJDiUPEI3M3yXBYpTCopftlXKQ+2jdTsii14x\/mSFtX\/Ik12NMouyb4\/5vaj9EwzpHtckhI0jFZmha1PI\/cE4+QW3n6qKDJdV92c9bPHnsUQIV9kQfvryoaUqTnthbVex\/OXBv1xPpmbyT5\/fwxJKSho1YWZo4qOF\/uiwYF8G9T9zHSbfSHqqCWYqZEPH8QfTz\/pPyL0U9jf4qdDfmrCIhF+FEmKhsptvUrk0\/zYcFyK2RgqCe+ZEY8inh8IJByoqhxTEXYHcZkCRJ2OsY0PVqQ2LSektiZU53sx1rcUjkbcqbWyfEU44QmFzRd5oOxJdQQEYigm6rP32ozWFHtmhkyaxaszhevUbk8SWaB5YH5eQ+BPu4gnpqBP4cQ8v\/0EVq4g2T1asdzSW1MHXP1TuhFdLaTxR5\/fDr00ZN6EpqE9Abg5b5EQQO7fniGXAgU5rqigC+MgztE6aY7mMRS9KGQ+iUJA\/8w1zY+KE0hovK7KrdwcYAw==\",\"page_age\":\"2 weeks ago\"},{\"type\":\"web_search_result\",\"title\":\"Portal:Current events\/December 2025 - Wikipedia\",\"url\":\"https:\/\/en.wikipedia.org\/wiki\/Portal:Current_events\/December_2025\",\"encrypted_content\":\"EsYoCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDJma6+\/qKU5pd9xqixoMwoLXJashWZOp\/uuCIjAtTJBQg8BhvtvrgNWBapl2XEEuSHoGcLStOxMqpLBTgDEpp\/oTwwVGMeRBQ5J0YfQqyScnrfPThAg1MVyi9QPTTU1V8BLHSmI2mjiFmgKXB+WfPxZYORxprERh1Nr4pDDn3lCuU7GieVPebY2TxeGMXmBNu+7c8wWejij3vyuZTDJlmcPgww3Qu59BHmBk0SL\/\/EbAxQ+bvyVIod2I7EKraUir09V2DfNn7+hGkEuJQIO04tkkgPpi53fneQMwGiVuCcVITaG7KmlyAyJL4cG1R+NVm+SUuUDzgKRAo7AtnG42kj6FR5lR2PknTJ7Ut1Z4ij2JFf7bKRSDuboALxl+0uV6AFgacJLPH0rVYiJiOb2QaabgFGhHUudccE9xC0AaAofHPDXIE63bNY0ANU3HKA2Vddvdz9AKwNIG7FkssrrEagREkwKtgSClmFZooOqBj\/RZncGW8H+C0jM6cVZlUutzg4p+G+pPalelbfHLhJh1nVGp5jNcsU02xLxzAt\/aSZ9TvTEniFBeQk\/ax+2kPty8zKMQCJ8nICMuq23Uc0ol1mVvd0EdyAF7TrBa610Kczetg+Ona\/q5J8z+gLRbwHiHQSjp17J67Ar4p90H2J80790rtljT5wWv8djSemM7DmMqjIX6Qr\/YD3RgGlBTDChYrO+rWN6Ku35F6ZeRdz7oNC3JHl6AXCIC+L1H2I0FhMrRT2Rw7F1vXtTI4LVjp3XoNW6o3VFkdfjEVDBDyzBp\/c9en1stPf83\/pezatdpYNdrthRXTxb6NZsPLW0nqS1bBigDg1\/A7dw3tZPmF8Gc8UkFzmW7PN5ykTof34UYvyGpW42WSyQlmiq3llydt+EDBhhlTyI49Uwx\/HdEQL7WLETP7CrVhBTz7viZNuNSWi8VTjPZEvAIrYs7v6zauxqCww4nI2xVNnfTM1gSQC0fvWAQ3fO5Sk4cgFW5UxEY2RH7Bej4wZVVRypHZN\/QbSUFY8YSlb0b57RM1fnndY1Gfmnwr+ToF7EZZuEfZaOjjEYwhzsKF5c9IEVkl0rZGD\/fpJrG6Xwspwb0ioYFZZCBOtQpq6CbL4aAJxKoojzkN\/MlqyaOFCgDizICJqqx8Bi3uZ9tjqZm8fkRzSNFfH7OITWi+fSLptcdgmnc7uQbMyhUckdvi4c1S0+puff8\/UHTkJ118vsHo0GnlICg3vdEDj1kqCbZRz12hF\/WZUuuiBz\/icGA0noIUYurg4KUBLWO9Te5q0efwg6vQ88dsCWvhX\/xmbXicd80ue5OnRxQ\/6xhgvtIfG0MHxzTzvciGMoep20rbc3wwnaTbwCZ5VnCt0bbhp+1JmXs3dSyNOwOIN1yjxS5qwJGI7yxZojl0tL7Yb5dMMhfNXlxeLhXNbV\/DCPpMhj\/UM0l7HyCO4P977VoNN\/J0M8UX6yXkQFx7vnd9ISnPhiKoS805eQsh2wJiFlQ1ATbWOreTTcKoP0xvnVUT2lF+unFnlN8kWirXLaDiteVOnRYJdEsZEh7a7xCxuiR8d0Ntz3zYTHHWKtzFWp\/Eulnmchq+bYrbH3bSO+LjgiPqWAGPY1FqmHXbcixzvgZUIvlxbTJA0SAGRwUYgLEhjtvcTIPw2Lw2UIHsg\/Wr2IY7zUei2AKyZOPm9yCs6Xb0+Yiaf6aK1ADKHWmlj5eOLA4VNFb9o6vnmLiu1tdxGRsFpuUeEJkkUL0mSgYvuiULF\/pfJTvBAZwf3Lkm4bdy4B\/kg\/VUqhqGfTaiLl+T1X99I0CjaciTs7zs0t\/jhKvKryUNPG86P81xcRxtyT8aEIAcDIAp1f5WadBoN0GX0nBLmrLdBCjdBuk28QOdZCMr92dToJY72NQatdnbZVYt4AtuOOwksncdjLs48mOTm7eN0G7jinByQwtSwu+\/q6A1VOhLfSFjF\/2RKi8v7EZr3AIezAddZ1d1U8qv1HaVqjMyKLQ+w4961DaTPBs\/rnIs3hkvefSc+7VD4QSqAD9\/KOs3NWW8BzA8BaMQVz0qOHfBSTCZJj\/cDH1BfKsGuCKU5zEOYmc2VqBXPLEhIBkqhM\/ZOPUyfIuhqWs+srIPhtvtth56aQDaivLVNvfeLs\/nqKdxZ\/EZ1trqsA505F4ddzXDCSIxeJljViBNW7hF7LUv0g5pysEOpJYgpKjbw8h9qAUNrRngO\/ZmvH84TwEIKcTKGvYwa8VrjSfNGMiZZGlnABpF\/GFHhAE0Z\/fsjXyx7GsnsBax3XXk6eTVakMUuZt+BwXeqVB7gTUAf9lpd2UVpOMV1xr8+8+NL5R7jKKFvtLp5F9stZpWo0CA1cpmyi5uawtSFeRBXkTr9w2IFLC5exV4+7bKABOUZ8F3y0n01fdDS0mpfodqtmx+WyGVRJPULnnLQ25+W3J8Dzd82MFXcoL093mgQQExYilszVmXMgpPaA6QBgNdIK010nJKAVxJPxeAHcunr8aQBAbrWNn+\/bOvdGkShQdwvg6p\/3ch1QlszB+Ht9kbdL7msjRfikZuTzyfxrUI9Um0Wp4T5eFNYQskY+XhUyex4NcQkt5wkpztZAEo1qOX5IcxLMSZLDPtj9plaPKzE+OCb8QBTtxDm8qrGjE4tjgxUmPadx1PuniW+sMlyYptVHZsPyyplUeRgCTfKJKQPzuClbrgeRJ10pTfczsPFutDCkurj9hlV\/6u952bcW2aT4hwfnBg9f68bT+hawUFKZlTN34yqdUImtaPkIUxi8jdl6AZx8gJHXPXst0Fxlx756PF5SX5320qz4H85zNFzZbZRp1nScSbYgYR2SCktFik0DVazyiZ+S+O+ThBl7C6NY206Lv0T9kRUKePj2n65HCuqUP+W\/A2qDEafEj6UjTp9Is9AXpR\/tfovlbFSMcz0wvD9DJCE8czTuDlcyeKL8tPTzQs81l5qXfqEt36\/+nvkzaTo9VOeqfSstN6l2pyUCC70kU5e0sxbBGxoZilY2us+BRH6m+3WW2IsZDFB0mq0aW5\/FfQQ990LZFovvk\/N2UjaZkRMF51HQcwFTE2maHsvzCyQKAS5uO75dlfhV4+wH+qjwvRtMpAXNJ4hHeOxKPH73CCKJRrLKmlQHEvfyI4Ct9G5p0D1WEdVR6nUpgbHYq1beMq8b0Tbv4mdLCKb1Gbv0PGmASI9MxjyyZ5aI35\/Uae2RHrGbNmFZctpeVBUOj+2ygIU7LMMN57Y2GH9g5vyyM6qxdaQC30B4Xf7ImoJU+cyXTdDjYLxSMeLCZr09eWwRMBd7C8xAVo9MvV\/VDeOAib2h5\/dXvhuqc7GGPm9IZMagtXvthd8Br4Ppltjxi\/rcDCBWd5QsOQzAGXeRrhKQbRFmRfKAkeLm\/m1glRsqOiu12XIZp+xE2WpNX25qvYLufdS9GBfr2fSEsRj15Xqj9SeAnXM+nD05g2zWim2Bk8y4XZ1Y9BwZBPYVJSnsg7IiTiBIP6bG4+vgv9IrGPC7pL39tl\/mzh+ToaWFKZuroScQtd48InjPcaaqYideuj2t5pFFuqPoSPGhpJyqBJ7dLCHftJEAW3B97+AQgTjtrKnxBVu0nJKiEVd0+aZWpwDfES7+b+Bszm9krBAEjCG\/DycBdQJVK+yF1AtDATgSEJpdWW7Zr9S+0cCu8K2POU9Rw0Y54DFv99+hCbjfz8JXqDiQfLZJTOngZ9xwzLGkoKJ8lx052lGWowNHkiFFNevrrkdnwplacAWirLKhh+MuO6TdHC3wTEnT7Ti2zW2G4iPb4LQZLII6go3Lp4Lad9BLZ955THyWoQENYd4h3iIglte9U\/vk0DQb0n\/Q\/9uW+L6w6WXJ9lJLQ553GVPxio\/hNidYGZ2YwNsfTW1yKORW7XHpkuu7Y\/nBRwdZKUOFp40lD5v\/2zh8kaoMvNlk80+\/XsMXnzO6HoxW591Gbid1iG61gsgbTD20K5O2RrxOe6ZqMRoKN9BrVUR9xh5LCKleT5Gutia9TpOtqOgCLmjadyJwo8rVEEEU0t+OBro\/6LtiFIPiSC91+mMsWd+ijhurjnBWP7rqZtI1MJEzIkGPfHPywSxjvoUREmrdqoPGQETmLo9rXCV3DtyPeFSkom2CXhs7IM9vuR4mT14K9rnR8PvFxXnHcsayy32GPPQjDDKdIhCNSyRrXZmqV1LDZwKCLZNz52iolniu58lUfM3PyEjxLttc\/mgVXGSa2LRyTpeJPZODKk9mWhYzNI+ZtZeF4dm8qXFxmXkpTMN+s0kJd3iMoJO5SPuxC8iao4HJyC3wH\/E2zpHNbExKqzulqVQpfbd9a\/jObncNAiG0qJweTMw093WFQl1K1KUewuyFJjicldRs34wHbDbD9b8LndqA2zabnsSwMsBl0QyqYrLcLmnV75wwz8kLIZHxjtA0sA875L8kzMXuEZL27lLsii2RwqsSVRMt+uvzqM8DWCBG5xWr+RN6XeAvuM0VXwjY2YkGIeckNAHWuAl2h2NR74yAT5PJCCBpCY8tBAB2HcFgRfVizqW3PPkUtQ6ggjHzTCHebjpTlNtoPye3AfMSKh6mGCOFyXDJ1iyPkthEajpUN1il75Ev6Q\/54Rl77Ems9nX17KVEMtyOF9xSImQuxHxclt1scsxnpZKVhYejRVbAlUYi7GcgIPUuthQjF9FEX7VUuzaallACcEZvdQpGFDhE2LBWofMjyINH1Iwm7fe0rY1j9gy+MfPcENiU7qvtd\/HNKy+ydFFPRI3wAt+F2G8zxjJnFobLzieqc5ANzrKPJ\/cF0K618ECqe4Ivu+f6tSphLblodSFPvBwt7hVlDL3Sao1CmJqJTq67QyVwOs0pLiReBBpQLaj5Sw\/4GvdRysuOEyb4ZtYbCxNpL0wj9uW\/yyLYTsFRBKyRC0Dua\/pgCDXaDonhUIyIYOBrmsiB\/g6HZkiOqAwAFSj18ppxrtXBIUOkd0bTJweZecXlBylCJByH1gH+19pZEgU2P9PohHm+nZ2UhPGKh5CnnGpLoBCczevIrqbWg8\/JgPJN1940nFkvsMX0\/9u88pMFRBLFvohb7dDdwPn7jrVp5eWDE2porv09miR9LlvQ1e5GVys+wEQ38ku9qaCov66h8+hr1+aG5j87cRfgVrEUAqJae51B+Go8Ywoj981b2uFQpmQGMhPYHIl39Dm9OKd2\/rlnX+3DrbKTEMzqKyORW2n+mANSh\/BJU2VPq3bb3JCpQRNY7vpxfqKpKhDttHfqVvCJ6HtLpuQt+yXAhFiDsCg7zsupfMDuLznMr\/AUlGpaClQz5C\/y\/OLczVY0SlC2nlAeSyvN4V+JzdEsT9n8wDVf7gXfkPSr4IyO9FNMICf61qO3HC3M+BQJLW87EFMcmAk9wWLdkaa3KUVHZ4J8CV9fmsOHlU\/3s4o+fvz+riuRNLw4iV94uxixF8qHbYUqvRhCt4oQGkMnVr21jehj79j\/bK8b9Wt8DgtSL6eK4uxjmt5NSLhAYSuP95iihHFi0xKtIr+oGq6kUI9IxbnTRer7fjW5o5jlg7y1Z2JzmaTPjcxXBqgoUhbWWmFD8NZmUlkTw5xyyqoFurMlxJFemQP9xk1xg0XrB4kPDV4v6Ohu9EBx35JDEw9x23byJcphgL\/0RVFAGIxXwneIrSzGp7yXMj\/YkMktI+qtBAUYwik6r8KzP7sEpiP5hAw94SKstG+agUL\/FFHF6PF8b7KEHuF2FnbtrsLAytWpnYg4C4thQgjZb\/B7wuc3giXqLRMwQFt0AhwCkjNJxapO1tFhIvZoeDEr0846njqQlKWjvEApZuniHIujiYNO6FXwEV6ss9Enldnn4nouovqf5XOOrCAoFErepkN0wFdaFiCO971mtNc6cxgUum\/B36aMBQMRhwqNYlu1NsD\/67bP4F0qgtedRhpq0yzgYu1N33EbqGWfk9NgCtxyGr0w9aku1Y94TMG8tuuSzI4Zc21yUvbIRtEW7VeJ2qRb+YGFrYQ0yvzkMLi3no1xGvilKaJvTITeTIBxLtsRdVqsplg0t6hQGRSC+a91TlgWknELDhRa+EUUPq6T7r\/lKX46jYglie7bp\/VRisd6lHGplBMS5FUiDZWCkT\/svjR++fmRvMt8gUSxnA6zOjIl+FO+1Tv2tWOYZ7voB49R+nle33CQrkb1JizdKLucyttHkSQP6yasLhiMPmg1Yx5Rt1heMUzW0f8J6jEmOJXp5JGvJVLhuuNTpjipHM0hTqt1rk\/pGFsXcxZJ5xfYNu18kXMl29MCsIRbpu7O20klOw\/SzeeP\/f7L9TQlzE2giRpt6U6KJq4BVZ1mD99PJfgSFU\/dIDvj5C1iSELdXlzEyog6FwDrmEIP658xUv6HSX6CPa0TtG6PZ103Cfwo5mcSpZIWaIWXEuyWI4V8oL1+o3+uPXidOox2jSH6Ku74PBulyniIUCIdf\/58nnXpygSZdDv0JJVHBid0R\/hosOD8zcAkQGAAQBguEGWsm34qUT4rEoN6oS5lvTGXbSj2IWe2yTZ7QG1sUyWws9u+Xx5gSrdokj1YT0TO3R+blMdnwHe6gMdQXjJ2rjdCHRMCkAoO07442eKXMT7Z2cjYyXFgdtox0gtBPasnFWvIyPrebFLwQNvvDbktfEIf0RQ15eYD7WOyPU1L84sdu04BU9nypJCV201IVjl0iDG\/aI2H5tgNkIyhvOhQP\/gqp174UFO0cl81Vp3pAHbnz9F0+aNkTEP59ZKjb26r3bD8WNvSW35uRw6R6fR9Mx6FHFu\/xtiMyX075Jt9LiF+Yzjg8BDnLL9YhWZh\/tqKEN5AOD\/zeiOd221P3Ie\/lpYz7lMUIHpC+3a2os8Z1VnhvIZ6GQDnE59ATlOWUGAM=\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"Hope comes alive when we take action - 8 Good News stories 2025 - Greenpeace International\",\"url\":\"https:\/\/www.greenpeace.org\/international\/story\/80359\/hope-comes-alive-when-we-take-action-8-good-news-stories-2025\/\",\"encrypted_content\":\"EuEbCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDBv3DHnUxPSMQuJukhoMEB8EXXiZHzmOzcsIIjAF1n9dTwXBrcGWQPSh8u2vTIFIUhyA24NVFcqC7DPh\/GRpaZ+WNCpo6nKrL+UTxHYq5Br1ih0MbNm6jwVxAN7Ol5rL33ND+zzXIdOzlYa5eJHY9S2d0fZfv7DPY83TAWyKMu5vb6YnK63Tdt4LD2NapM4NDCBh9cAcAOO9Xit3iXE8PQUxXM7\/SU932ID8LxtDoyGu35Fok+SLQtUAyx3EWXYRWOIgIVCS924gm+shbryP3uU2kJjF5bIT\/sWVQwy1nOVGAM1CxXRWk7CBKRXUJScYYfkGKOxoPLjFzyGixvYougDgUBjTJvneJYO89O8T2Xfa4eb7mSW9hsv5JPc3QvZlGDggoV9l7t5YLK3BahWTVMcIiTqIECVyN5LpAnkQzrcMyCid3NJpsr89KDSOMMdQcZb120qJApAt7UcUyLTywsd0Z+xdxQS5ZPdB7cwRtUCpr1liT\/\/QJLJ2jh6m0Ug998QmsS45eG53M5112XkuHjqfBX6SfLi4wNhe8uTMMp9KsYddu\/AxOYlVeDK74\/R\/yK19xQ1bN5W8MsB09TsvFmEQn1w3JO+AjWyydnzQUkwuLw1en1Hx3WCYt+z5DlnJqDgI53Tiuprzyr3pDSjuIBWRHvlnSASuxTyE8iF7hlkB36xJJQ6+KOt8Qr08Kr2FRtoeZ\/yay2YXymmq7PsX6DGtz0YLmaqaJXUCi+XoBviTRqsXbJaV1hBAZsB5UpPr+y6gr97azdd03CzlAsOOxrXZ1OukJfOAA8\/vMJHbcic2O2thXl2pAUYduvZKvIzLojTAO1Q0+Np1iKVsg8p2h+nbIMp5v7X95t9Z8KicJ+y4fZr25l+AfJz1cZJHYihlglxBwNuUsUrxlZQDZzmwP16wPtNA8Tcu8x8u58YlsXklb2RA\/ODrpdNwZVLy8UPeY7bfvstcYO3hsUVQsc72ZUCGY3ASS5UsOO994fcIz3NJucV50Q53w+BtK2Mtfj3uPTblMpPbIye6cVByzSU6f8nM6kAxKeiLyDLtc0talLWZb7GwWZi8fIbAhe6OcQ+BwEW7ExDG\/IDs9ZsLzv7tAe+cYSrUOVw6z3mnzKqstTDFL8ri2\/i\/qYVN2lteApiFU3SSBu74IiMlukiThFHZG74N7pU0l4Hm57qw1TUcE05XQHYFHh3nGSQ3rLWWEgUCODZVWa7VjjrEyFMNFNV0bOfp\/VxHdRQKTaBFZCCwCCqQs3tY+z0SNUn6nkXP+lCxKEkHFIyq8h2FmGp9T3tJ74UE\/7qTrgL4uXmgL4o7PlPcTX2Cfc\/NQy57smyAoJoue\/OCr+cOVXlEUwavG4LtfRFPt+zM0nztbHWeuUXKydc+NFQj3nDwqgSLjpbA+wJX10GV7\/\/qIPR084v8uY7E5iBvjWmL0FY+jyjbZZutCe6eGYT8WjG20WEFI8We4oyZ79dHzBFZndeKN4uKQIUqRYO6lmq3+NF\/1SpSHPzoQbtpbkgBWhtsm4HMXPwLKkbuCh2KpIitgtc+vH26KoBRjSfosPOtTgynxEeLVX+PwpuFvbUTJ9mh6I3dK8Dx8fxvpb5I2mY\/IhtHflMmJfxLpNML4orokkKNArYYjDJL56JNwf79+7LPq846DOPOpADxrKLOuR0hbTZxArO2YM5oiP1mKT\/+1vbN5e5sS3QRWCsCfVcQqCExOotHYThUG8wP8L9DEzqFqLeLarfrYRHgmSNaoYHintwWoTkvX8GjbxTvYFh7XuvkTSq8yttJAyFyQIsOpv9mNm2Pv9fI+B1nFFRjhhgiJ\/lShiY3Ycy+9eHtXzJVYa8PLhGC8pBuRQ7K64TXzkcOKTKWknwziG8KUxiX+bA\/W0X02zYjwcq0Q1KDpex5SL6q\/NbFRoCRd1hTyuaieScJwIhZpOheKx5BC20RI\/X5A+T8b\/ZzMj8rlZ390I4Jlr+1ADbQV3mo1sq8GtK6TxAOu3kLFslb+vX5mV6PVfdbyPlwCs55iswpwEXA+gXUL2k\/hFOh9G02hJVKVUS1C9H3WkXcK4KrC6H5fjCR4DFQJuhLuP5xYLyUl5lXNphoRnqYNWS44Wh9j7244dTxR7pBrfkpt77Y0ZWayJ0uxliHQMgRmTqkHO4OJKScURygxPdaUtd528Y9UkOMPm6rnFDkh9XlvVx1OJENRfvPKZUFP97FK+pOavkpAOCThl\/+KWh6Ea0sM40ubTgm7tZZdQJqzJOPQUFOFcgpYv0eroglRdUNoP90UiRaMH9BqOO1\/KXeaXO+5ayq3XzoYf+Kzhx5LyD8Oo1X9wUP3+MqJJ17lksaZ3QIiOWmm30+T9TGWK5bamAx2I1AcZvtsSAIVTpZZrbpuZJmu3va+W6kf5t9PrlzLwq5433yvANZrF1MeEaMBZEy64mW08qTChiV01M2obi4TZ1ftDxdq8XMDgL+rczDXpVA0\/u0HDKtmWYR26Y8Y4ovGPCAphB1jR8lltW1wwvbhmG8SbbgOyL1jU4qBAjxHh5tNjb417QXz2cCiv+OBaYJAqEkExbwDFRXbdJP8mdMHDD3QB\/PUB3stWdjF57s0Lu72CzeG4DY54T6nAu5nvYhoIUlUpC8bWe7GvvPxtgNlQqCRwHWxEQSMY2emtuTkqiNlcPl7FpJ0i26FG5D4rgntuCvGUvJQaAQniI\/IyAlEc3I8H47QsuV24ybwlRI4yUcRDzVWWYQUFfBjnSdZwy4geieiQ6UIgIUM+IuasXscEPmy7QM8+TufXssmMDVM5YRVpNbntN\/vf9oluHt8qe8GSHyzmMSbGxqC9s6NsNI5XXf0xjcPSHxvIcaNRK6VM6WN6uKLibtJFg20mPELg4myxjqyQUPxxMyikS\/REWBEa3GcsVrzyaXdvrnRHAkQorkBiSGx+1BSpQZU\/uRtret\/qdKEOjn9EM77JG1RuYbm+RBC0dzBQiqsvwG0+2+68IdpLIyypy1B6QQ+mjhXeDGuv5WlJP+FpCZJQ5oS6\/VypqVC0tdA6IzSIHetfSBzZhtBpXnmWQaMGeJFHjE2XhdZTqNUwq5++WPLRGdehCiZGFwHy68RhFeKclU2Pfaa5UXj9DfbKlqG+KGpDKrdciF7UTC0ZRqyPFLocZwqtq8QH89Cs3B+lDiVXto3aTUO02Xwen50O0y34R\/aPqpw3XPHTSDViwPSNz4XFzeuoQJNTVjyrYnmdg25SAco+jfdyldL3GRTwwYYg0UmgP7FBc3Nm2WbvDhWRfW5YBuDP0AMiRDbgCSWAkDE1ydmqGGDQJYXQLodH8vjusUhmVIo6EG17MY5wJc1umibuZEKe3neVWtohnNfZoB48xH6hMhnTILk3j5aYF9GuszE\/Qfs82qsL\/i0cvYUWhcRugv0J9SQJZCY4q8ClTCVvrBPiICZQXbJK1ADTEbJLJ1mde1trnxRNhFJBkX86EkpU9ga2kiLjm7ufgSfsx\/w61L8V2sB\/5M4Ol+nJi3FHjyKUKD5EvaDm6mtCo\/xzEXZ5\/BIb+FTXqth+SCR+ISKLagO12C7ZferDl8OX9d\/KLMQZmpmjtxa7ZNPxDN8iNF8CxwgkfbAj\/YdN0rZqTe1jw+\/tEKnvRWsw1VWGQouLcZnEZcCaYXrr4LPVWq0hN7tRGoiQNmjgvJ2X1YRtoCbTkm6T5sRHGBx2+ClWrRn1xlBpiAR+p\/lxevYAm7dHp+3e9fQrfjCh9egSa2T7VXP\/XQczREABSNYwf8wCmTOoRTGC1UlW4k92xVSvbXBfpKQP3ZQc11HGgo1EEZDu9Lc1dV1m77KqupRoYoi6g6PK6vHahfgOJoqm\/ehhr6bQmo20OHMc1dPT3z0hTySzKfzv3PiAz7jJq+oWrDo0ul6gGA5VJ0qCOEXIfC\/SZyjQgV7g1oxQhEZFbst5MnmzG8knv9eO2jKW9gKPFhNXQRkKngdEiiqQTH4OekAWZjwy0hXRW2NxEkgZ22BvF4c8X8RdSVIuz14HY0h5KN+vbBlPuQVALtEpXlBRptb\/Ppx4Rt8hqlqqC5UXzJrNQ2IM0Ks+TMpI0ja1nt5GDsOWdlAg\/6nrJUSHfF4Th73y+ya5ipWjzNxzaZZ7eleYf2syj92AtlfkRXLibTpd3lOWaSjNnP21wuQecKukpus3+n5FPr8e4hDJvj3f1vbVRoX9g2IJK3251Ve9Id1tf+P8NdYNIrLuz3KFU4g2Li2GbpJUet8nqDd26bYPQXoKRwh22\/NzrFtoz6SKugyrUcNEdNv1yd\/iG06ynrPeAvi1MvZTYj7YnJlmow6jZr2QvPOB0PeFL5qoIs7h0OzMjnL+8I5Gc8vPzbpWh2Zk1yS9ZtCvbrEo7Q66jX78v2GVRRhjgCf1bS1M1ZKMH0Exa+E\/85pENtqPMZ9HjYAduli1xzMfvXao7xKGrSQG2gWoRJv29PAfo\/ZEZlydAIWREBhniJvrR3iJlyO9sVuk1hcHsYzx6uKVWXwIIg7yKG1aJa+JkHdlC8vGeujt4oR6sLm40vh3PmSo5di\/VKAF7lZYHN5KKtI\/HI9R4ozTBvo6lPMUcIKGNjEG0rl30nl+w7AdFaL7Y0cJMFJBz\/UgThay7fFrG23NwAWRpfFI41Rh8P+AqTOU4C3xJJRU5XgYHfGq7aKWxK\/Kw6aHpH3PomQ24m1jmIAhgD\",\"page_age\":null},{\"type\":\"web_search_result\",\"title\":\"The Uplift - Good news and stories that lift you up from CBS News\",\"url\":\"https:\/\/www.cbsnews.com\/uplift\/\",\"encrypted_content\":\"ErAlCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDIRs0gKBceLJBplWhxoMatQCt6Ef54\/P7+lxIjDp7YCzX7RLYsadHVSCXHZ7NkBzilRU6vKDt9VaoqYo+UPm+AYPdLf3hLlnJWH2LWwqsyS0\/TwAY9Eacl\/BB0EVHhviRd5mGAF3TSFonQF9C4SHDh43JciC+868m0P28hLjRh+ZyZCNsG2aj9hsp19AS6TzSRx9Y4FPC7rrCu7ajNF\/bs+ozx73dzTjfU0swk9gqK4QyFebFkAeyYIA6KzkmLbWn\/a1xLucoRGGmQ8oILg61lX17lcra7Ddeotv\/+k4XCAkMPxWAv9TFmpWfo3gUijV0+sM0kR\/B2\/\/luUx8TS+YlqmSrMMAHKzbzkbM6uMjfMh6bV2B4xlerTFpg1rclX49eea3bQPf3x6Wlty0TI2+jyMaMjdxAAI3HSjMG+cCJ+Cnv1FOcTy6Y6liwmFg6dPR7b4in620Dxaa+LfVmsiKZApNQt69gOIBipLuyF2J3\/SQ1Zrj1ieoFUR\/II774vQ1xSVNWDh9penAFqjTXeBNYHxge8zp0yjGjC3pUSYVAtwBKnk4VJLfl5tT1G0\/s5AbiWUKcMJzk+YstP8BO6oHSRobk7DWNgBwbJy14MR8WTSkJGDy5hvKq9bKsqTUMpsENpgrZw2Gn3aPbTQD44nohlhJRT4kb5pSP6qSAOwWTAj2Kz5Js1dRCeN4Ljkf5QHIt9cPyMkEky1pmYMsd1z\/qvpaN7G\/fljYcfSccN0uHUp7mGa4n8FJYUQoy\/4gMGbNCxaF0qMt9YU88kBuwet9xjc7YgBZK69LkyfPyUPdbI2jbxT\/Fz\/Sn8A1yjX8fhKz70wxGsfVswp\/BgRhRTC5gitWfgV+jK4O3d0pZ3wSq1JOJ7pVHF9JL3WkPXE\/QKTaNvgOBlBzkdnOdQTbdtpbQqu49IyM0sCr\/pawiXE1cnhbHxHi1zTbzo4teZLI9OHl2SscQtYccmxZFEIAHOsGhewZPge+iTo20CB0Ty2uJ0dqV2nVhjB3VJWWh8XViBN\/pSDfUyfDshUF6sDHpx1e4BUDC+0y+Sg1YxaXoXyZudr\/kXUge0giJGlgGCwUyH7lWI1jKehLw+sZaVNoNeWRpTH3R0dvume+ISXItT+snxxnlYet9pjm3aKPsu9M6os6e9zjlvtSzcBM7kqZ4eVT5aCUfW0r7tnhjyy1u9O3jaurG1XjeITpE1opzkgVyan8VEI7uBOZc0d2utwQczCoEJXPRzlSFiUPp4RZ3awmGqtNDl\/dtRdjonu\/i6L0CCbGITJ6MGN8b6I6QZ2HQAS1rdAEe0Eg7CvTKXQSMT3i2l+BbPkM1RyXrV6sywuEcYrPUFZ8cZhas06GQZOIU7rAz69td1S4HSTHWWJn\/LiJ8O972tekLmHqCUWbF5Vvcsmk8TSs5VIDB970HGa4kn\/RQGVpiuBsBef1VdbGhTyyUFvm9SjXH\/nVeNJ7ZYqZIUTn286eEkFBa+\/psT\/TFIr3lIzQkkb970WTKdNzZ84xreW2CAl1HHm7sfMYvIe1\/ZVZGGm94OcJw8DnA7dj5kwv2jaZtu5un+cXikAruCA3Fo9\/d+LngUoExOOTYNNofrjIn8\/zsNYf945gpK5FSXiXfLoc4hr0uf6Fr3tBwjUZWt2BcYxVE+S79tdjBkr\/PCpFJ+jqZf32JmvW73jMMfx2iInisOrdzuidLA5+8Cc4Y1zDOjY3H+q3AXXeTI0Een7nhPvW4LuxTODf1Ukvk8DHfUNuFiXM\/EpFxYTp4ZJaQdLXYUHWfU\/FGIqsePl+dJxtYTbjMaJmGyq6hydLHmaldmpY+PNhhuGRlQtXciLeQbELSKWmrK\/M\/sRFEnih3a3+kWzj9f9qyUaKWh99YyY5o\/HTFMtsw9HMCAb7eTsfRPZa6\/QHNZTnHHQDUsHb+At4bPE8Jf8GpLyI7yFHdGbT7ZnB8w9UWeWAjTKCoj2Pn2rGtik5S13zmXWYJH5BvXYGXKrl1rhJbejD+daAR7Jl0Sj4Pzs0L3zRPo6Te7E+XbHMN5rX\/IWYT55RhSn56xfIPYFY+F31adIIeMAL0brZYI975YO6\/LM9j5o4NRlkwT4\/F3F9h0AlDmPZ6caoAd5Yjpq4sTANVwK69nnuMIMSZssqBANKbu1Uig83DIODbwMCtWaDXVgclBsZBiVwsRSKlLPHysJWMnz3C70nPUS9s58zW0Z0wpKqnb2RykNz\/HcAYuPFVyQczfjtm2memngsTgFjqFZ\/Wn3YaLiZyHSwBImZE9uTx5dfcJYqj7Z86SyQ4\/52fX8woMH2tFZjDrHqzjfiKsgdGzL+wByPKTHbbuiGqhEQshaAXZr00Hsa342XJEHlDkAqO1Otk+P79xmn7j0Tfzs8\/Z4E4sSbjqGKWbZrz8S8qAMqyXEbQi\/QFri9c\/lT\/\/DoovJM2iGbLe6wy62QSwKQD8BRInlyzVL8L8gbJUdPdIvKkJorpYttOnO9xXVGhI+4gCGWfK9G1rkiekxLZM6mt+WdZVPA7iPAyTMhEN724y8+FX1ItEarjkQONGFg2oJH1rvpJMb+XMeeczk04oi72cmjS7FXJq+4fQg97NuHFSDWd37EC5Tq4e6C\/raIwzXnCu0ebV\/7Ipu3el37qd2pcMNZOOclND1pOKzEDKXL1CVGy7tbAVSrnNKy+bNd66dalBWqaEdOe1kQfrrGkZxCFlB3Up7Svtt+I6wHa2hghUHSbvN\/W1y+C3e\/8yLuZ\/PR2D91IQzU\/ymWpiCPJUs1o1mKTOlnhL+EzuuSVIl+dWhkyAF\/C\/eloWDLmu9ek5S8PJL3iDR9u9BlXhQl0rYlpHfc+98P1qd35Cke5PSdIOoZpXY4S34QWQSJHJZ7mrBODNBPpWxo8zzpPK\/iF7i7ZrHgiehO7TQDP0PFvVK4zTEa+o9RE9HrTtJJxI83PLyTf+OinoZvCWaj3m2UNWQ8mcYmKVlciUnWSrwFZGIfnx++4ZTrLcItd74Tk4R36iZvGQtLkJTr2TRUOXBeHqo\/YwxLn5WaRzBaRayET20iMVOWugincMIAdXXI6rqyWt+meiKs0mZgVZgfaj0jndMC3ifqQ9kmXwp9HQyQI1+wurp1TvScMtJy7pr1iRUGerxt5OVpj568qilCAROTfVjbDv8BO8p+MmqwVgi3BoYOzvCPhUBbajIhE+qoWG4nHRWDFARVZ\/BnhwlCz9QyripMsQBAYodYVxTmyB7h+OYk3\/e1BYkGLXXa+wPodGhEUso8U8IT8r0essI5cnn9McczyAcn0oBc5M3Az40wRQfeSJmKf1AVJ8h\/Gnv+uP5PIGee4bxBkCCnlR5ObkgCb\/mSsP+C47kcriVkmMf1kX53FfXePJ53L045ZhbfkahRynDxd11bs0pQPY5WZfmDulTx04xGnmExYCmiyk9Z2QZb67G1w7m1Bd7AeFa5yII+gio+XQoByX5hxAZ4PQ8U\/DKnpFkfqXRJsj3gnJpHTyXRSHBDaudczy+sXhp1ufT4VPPB9u1q7TqUjkbgkVmX2uKdipCFtX5xDtdhtJDaN2niwlYRwshmAXcTLaTIiiQZ5savGs2i5QVxLZuL2T7bZ+u6117IU8ko9J6CbtKyuRyO+LHVYus\/ysX6fFHYvjTts60jsOkQSxdKOvInhdBS9EzFskSernyxh+rTQqxCMX8czWo0ElpCsLdFgQclknrP9S2T2L71ia1mHwqTUDeoQj\/4P28YMbIpGHjDhQ3yfmr6g7TPQqMYtTa+rNGRxVfeuNdBT1mEGeemXsNtdhdLEUcFQZoWQseW9cGq\/qgWjh0MDefQLDtQ3kkf7gtq4miVXtdGr0H8RVoIn4p+uWLFqoO8hXyNV0hbLKy9PjyqZBBtqdj\/WyZbQJlrtjGEyBpP4t6gm76hDaCfzVXabS05JkfVIgKlR5VNxpfxkoXNSqvjMv2R3Cou1AlpjI7HxDGc8B\/iEEWilTZ3zZtrIZo\/Ujcrl+DMXSJSoOzhRsUx9FtGS3DNkooSq1UrAtXHgJZH8bum7gKJa4EIlC2pJvvqszXDXVxN4wPFHOzw3f2afP\/BUfFEiL\/YzJL+\/N9dOj3BO63diIwlX0bCGrBASN7+2UctgvwVDCZRYyBRnOEZRsZwVH7ncYc3+Pj8UNmQ4XIOs1K0amIcTeCfIYPzHgFcjfjHh9gkTb78Yn6yRgjSNEhlP\/9t88VUwWo236NTyO54WB90hhTwSuvWLK7a6uq9cZpAJbxQ0TDdwUXElDWKmr04v1fkjD69jfAI2SPCWGGHs\/hS\/90lWCdhktTXgHxj3bg1uRuVsqmmMam3qAKsElCY65c9k35NlgNkCTTgTuGhCwt9dH5JZFzCf+SiIVL3q23JNxBQA0DG7cToY1p\/vjl+EpPmMmlFDMfzSOzaOAQ2QCCmylyYBABA0CUMnKQeAarjWvn5XJunwTeh\/2yUf6P5YSqcV9r1YIqMbaEFdF3mQWDHA0zX3udbOKoThqxUckf6bJr8GZgdl7t5R\/F5sgldApNO5INX\/fO6UcX3iKWEqZj\/pqaYHHEH0YYDU0hrD3hToCUjxDaS\/mggeLZ6ZI9xe5fmgn\/IwoRFZMSUd3gq4jgcunYKjs7+mkfEsPdWBa85gZPbbclc+d8QiDPDtlYMp4jxwDeu1Cb+hrDCNSBBQ4zTE7O3ds9fmBduw8tMha1t44BRkJbcwTu\/\/DASCKAyAiqa+\/ZcE66YFqR04uvEDnlaZKWKd\/FbDUuK4j3G\/fNKH2nAxg7hJuqgmwSXWWB9CtjsB2famNZOWypFltWYQLahMxzBuPbXTYSOKTZGEMZ8wQe1iw5HMc+Jzcz5X63Q9m6fkfb8wq7phCEOxc3RMehLadCzqbMOCrlx0KYC7ylskinChp66fLZ0ouiO0BgUG6Nmqes72gJ67mUFJ7txCcL76FukGffDipxyQINIoX77IDjITVK\/wRdZ37oUB2gLzf7Ubk60wRbtV98E+k0W9qygvyqwAaW26lUdcWViUIRREyLD74FlHp12zSiTzFHliyMXYeQTe3r1pzzLsnzA8GePnsahVfXOVjbl9nOOU3cf\/K5x804esU0NlJdcXEq7wzKaDNQcjVorUDTjJtvicBq0fL9imq\/VdbQJLzTFaR5BnZcxK7qWuRpE5sVOh4820la+UerIyMg5JbkIx\/m\/qKtPQJBpb69WUKdnc\/rRvGhhaY7qo1aHullLS\/p7eqJ15AfdR\/L1tK+3PUgxHiwIMYNRnajRfcIuXdYXXgd\/44XopZhWqrVspYLYyzyDnNVV0gUM7GL8co4\/9if9PPlwZIg2BElPQ2kCD24tr9rc5eBfXRpJVUOjHevkurMezH8LJXYtPY0jEl4x506bXCPZPkr+EI0lC+yssEwqYplRm9i3+N0OELHm0GpHD8ykq+Y\/ZeVg5\/q8iC6UwrRZP04XCZVQNi6jlX1Aoo0oXsFYoieftrEZgCUhMEIsR6o2HtfDbmiJ\/jF1EW6ccI7zwofoT5FozqSqCM96TxGRBVAWMqIyj7F4NZmG6r7u46Nb1ORauR9pNEZgS6UKZ74TYCNETuAz+B2QBBqDS+eOT7G2EBV2NErG4inOwcdrPo5x9t+ATTz6ZZSJV8Xo3+Bksd4Ccya\/03VvADWsIxQTZG2mPgyVhQ5b0d6AyLXNuUiOuX\/5\/uCAZn0u8O1GVeZeFhhiISOPn\/1xi9010V3TALHR1ohrX9CWjiUrmO9jCioJz1b3B4uSDPCLeWdWr3N+v4PKjZ0r56uBLzgCSqgxHYWXP\/DOf5yh2k3bxSoyJuw3dbl55sBIfoUcuT6BgSrMjfcRCtrNxnJzGUogFyw0kmGF3W6EW1jxqJ5UqwWftLVP6C+KXjnhsEGnqpxwOHEnlnsqOG04pTolNZN+s36C1JKzMozZMP6pDkNce4qUFDiX726KoGXoQYBiy9pxeHyZXp7BPuwSZDrCQftSP5N3EppX4o0\/E204H5JaQAvCAa5qFwWiZD\/ELPEtgwCUdsQC+fnLn0s3dENxZeFptiAdWgS04ShmNWvPuA2Kh4RWjmI5Iops36y\/3wnrPGU6b1mfqycv5HRIKTiQEngQmoRKeZzSjepKQoEEEXN2gG+WFozH2OEZIrsbdJ\/yExAtikMPkbd8X8AOuHLUbFI1s6y8Hlk+XiXw79pbtI\/dfh+flWst30wS4ajzFqE58CPy\/OZFCTsNnyavsk65q\/3POh96lo38GucBC\/c474kOhBCSW693YZSxdfBZKukDY0tlOe3vWzOWQqq4c9OOS5MAi7q5g0bNAPEgjbGOEeNd7xOqxPi+jMQoltAjnqI4GEzJIbd78YYAw==\",\"page_age\":null}]},{\"type\":\"text\",\"text\":\"Here's a recent positive news story from just two days ago:\\n\\n\"},{\"citations\":[{\"type\":\"web_search_result_location\",\"cited_text\":\"Science News \u00b7 from research organizations \u00b7 Date: December 27, 2025 \u00b7 Source: University of British Columbia Okanagan campus \u00b7 Summary: UBC Okanagan ...\",\"url\":\"https:\/\/www.sciencedaily.com\/releases\/2025\/12\/251227082728.htm\",\"title\":\"A rare cancer-fighting plant compound has finally been decoded | ScienceDaily\",\"encrypted_index\":\"EpEBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDM4zpg0QAALVtUTuOhoM6VKw1L7\/Pe6E\/3\/1IjAD\/tlTeVnJPlWCvtPNEwdsZms8ijj9a53EFjGajhPyvGIEK8qszIM38OsFeUzN9OYqFRyykquzJjr8p\/p7diO\/\/iR2KwHbUBgE\"}],\"type\":\"text\",\"text\":\"UBC Okanagan researchers have uncovered how plants create mitraphylline, a rare natural compound linked to anti-cancer effects. By identifying two key enzymes that shape and twist molecules into their final form, the team solved a puzzle that had stumped scientists for years.\"},{\"type\":\"text\",\"text\":\" \"},{\"citations\":[{\"type\":\"web_search_result_location\",\"cited_text\":\"The discovery could make it far easier to produce mitraphylline and related compounds sustainably. \",\"url\":\"https:\/\/www.sciencedaily.com\/releases\/2025\/12\/251227082728.htm\",\"title\":\"A rare cancer-fighting plant compound has finally been decoded | ScienceDaily\",\"encrypted_index\":\"Eo8BCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDIytH83O\/HuNNOkqkBoMdlzxXa7O03mr2G0xIjCucr3nuWNyViOYKgz1l6XjWNLwxhBPOf07OCPWvkKA5SzSrRNdVwm70aBt9OG5PK0qE+1Hw+tGZnNL41fLc\/Lfwn3AsRUYBA==\"}],\"type\":\"text\",\"text\":\"The discovery could make it far easier to produce mitraphylline and related compounds sustainably.\"},{\"type\":\"text\",\"text\":\"\\n\\nSome other uplifting recent stories from December 2025 include:\\n\\n- \"},{\"citations\":[{\"type\":\"web_search_result_location\",\"cited_text\":\"The entire city of Los Angeles is now completely coal-free, as the city officially stopped receiving coal-powered electricity from its last remaining ...\",\"url\":\"https:\/\/www.goodgoodgood.co\/articles\/good-news-this-week-december-20-2025\",\"title\":\"Good News This Week: December 20, 2025 - Puppets, Raccoons, & Grandmas\",\"encrypted_index\":\"EpMBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDPe5rOuFU+GFGLAwbRoM9JuRlXw6zb9kZ2n3IjBe0fLIrsNWXEs1YLVTIMMf6UbR4y5giXJq6yHWDH4rwBvcQPg4UmqxXKzMgBLVpyUqFydOjM\/wZdOgsxcpuwrD37YkIiYx6t6bGAQ=\"}],\"type\":\"text\",\"text\":\"The entire city of Los Angeles is now completely coal-free, as the city officially stopped receiving coal-powered electricity from its last remaining coal source. It's a major milestone as the city aims to reach 100% clean energy by 2035.\"},{\"type\":\"text\",\"text\":\"\\n\\n- \"},{\"citations\":[{\"type\":\"web_search_result_location\",\"cited_text\":\"British autistic artist Nnena Kalu wins the 2025 Turner Prize for her wrapped fabric sculptures, becoming the first artist with a learning disability ...\",\"url\":\"https:\/\/en.wikipedia.org\/wiki\/Portal:Current_events\/December_2025\",\"title\":\"Portal:Current events\/December 2025 - Wikipedia\",\"encrypted_index\":\"Eo8BCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDEJ0PGnIOUuJ\/RRYkRoM2dNiXK+TzEKFNu8rIjCc8aECNl9VPZUpcsJRhW38VVGBmV1D5FFSN8aIR2Dmo8ew9+CtD73IV4JH0EOAnv4qEyYdyonwxOhvjp2jSABSbMBb5MUYBA==\"}],\"type\":\"text\",\"text\":\"British autistic artist Nnena Kalu wins the 2025 Turner Prize for her wrapped fabric sculptures, becoming the first artist with a learning disability to win the award.\"},{\"type\":\"text\",\"text\":\"\\n\\n- \"},{\"citations\":[{\"type\":\"web_search_result_location\",\"cited_text\":\"Nature\u2019s 10: Ten people who shaped science in 2025 \u00b7 This year saw populations of some endangered and near-extinct species bounce back owing to strong...\",\"url\":\"https:\/\/www.nature.com\/articles\/d41586-025-03505-7\",\"title\":\"Seven feel-good science stories to restore your faith in 2025\",\"encrypted_index\":\"EpMBCioICxgCIiQwOGZkYTRiYi1lMDY1LTRmMGQtOWEzMi02M2M5ZGRiZTg4YjcSDAKV+2IDfaVMNzxzqRoM\/rB7mIFEVQMGQOXVIjA0sxOYX72tBLv8lI2cUwjfT759ZOawZ+6Yryvl\/2YFOQAxzG+vSwrgOptE1O99pxoqF8b6BOsfak4\/uKladUCG48\/KR8039li6GAQ=\"}],\"type\":\"text\",\"text\":\"This year saw populations of some endangered and near-extinct species bounce back owing to strong conservation efforts. The green sea turtle (Chelonia mydas), which has been endangered since the 1980s, has now moved to 'least concern' on the International Union for Conservation of Nature (IUCN) red list.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"usage\":{\"input_tokens\":16315,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":423,\"service_tier\":\"standard\",\"server_tool_use\":{\"web_search_requests\":1}}}", + "context": [] +} \ No newline at end of file From 2c4d91fa4bc3d13ee4524654fc6a454cc2d4ef8e Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 29 Dec 2025 00:56:31 +0000 Subject: [PATCH 04/54] wip --- composer.json | 1 - .../Anthropic/Concerns/MapStreamResponse.php | 5 ++--- tests/ArchitectureTest.php | 2 +- .../LLM/Drivers/Anthropic/AnthropicChatTest.php | 16 +++++++--------- 4 files changed, 10 insertions(+), 14 deletions(-) diff --git a/composer.json b/composer.json index 8256b03..6549e89 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,6 @@ "cortexphp/model-info": "^0.3", "illuminate/collections": "^12.0", "laravel/prompts": "^0.3.8", - "mozex/anthropic-php": "^1.1", "openai-php/client": "^0.18", "php-mcp/client": "^1.0", "psr-discovery/cache-implementations": "^1.2", diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 65757e2..0ac0f2b 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -19,7 +19,6 @@ use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\InputJsonDelta; -use Cortex\SDK\Anthropic\Data\Messages\Streaming\SignatureDelta; use Cortex\SDK\Anthropic\Data\Messages\RedactedThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageDelta; @@ -119,7 +118,7 @@ protected function mapChunkType(StreamEvent $event): ?ChunkType } if ($event instanceof ContentBlockStart) { - return match (get_class($event->contentBlock)) { + return match ($event->contentBlock::class) { TextContentBlock::class => ChunkType::TextStart, ThinkingContentBlock::class => ChunkType::ReasoningStart, RedactedThinkingContentBlock::class => ChunkType::ReasoningStart, @@ -129,7 +128,7 @@ protected function mapChunkType(StreamEvent $event): ?ChunkType } if ($event instanceof ContentBlockDelta) { - return match (get_class($event->delta)) { + return match ($event->delta::class) { TextDelta::class => ChunkType::TextDelta, ThinkingDelta::class => ChunkType::ReasoningDelta, InputJsonDelta::class => ChunkType::ToolInputDelta, diff --git a/tests/ArchitectureTest.php b/tests/ArchitectureTest.php index 5cad342..8e3b059 100644 --- a/tests/ArchitectureTest.php +++ b/tests/ArchitectureTest.php @@ -14,7 +14,7 @@ use Cortex\Prompts\Contracts\PromptFactory; use Cortex\Prompts\Contracts\PromptCompiler; -arch()->preset()->php(); +// arch()->preset()->php(); arch()->preset()->security(); arch()->expect('Cortex\Contracts')->toBeInterfaces(); diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index 77e9e0a..ff3b750 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -56,18 +56,16 @@ expect($chunks)->toBeInstanceOf(ChatStreamResult::class); - dd($chunks->toArray()); + // dd($chunks->toArray()); - // dump(array_map(fn(ChatGenerationChunk $chunk) => $chunk->type->value, iterator_to_array($chunks))); - - // $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) { - // expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) - // ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); + $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) { + expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) + ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); - // return $carry . ($chunk->message->content ?? ''); - // }, ''); + return $carry . ($chunk->message->content ?? ''); + }, ''); - // expect($output)->toBe('Hello! I\'m doing well, thank you for asking. How are you doing today? Is there anything I can help you with?'); + expect($output)->toBe('Hello! I\'m doing well, thank you for asking. How are you doing today? Is there anything I can help you with?'); }); test('it can use tools', function (): void { From c674b88809cbb184572fb44a30462af29b05df78 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 29 Dec 2025 11:11:13 +0000 Subject: [PATCH 05/54] wip --- .github/workflows/static-analysis.yml | 7 +++- .../Anthropic/Concerns/MapStreamResponse.php | 8 ++-- .../Anthropic/Concerns/MapsResponse.php | 6 +-- src/SDK/Anthropic/Contracts/ContentBlock.php | 15 +++++++ .../RedactedThinkingContentBlock.php | 6 ++- .../ServerToolUseContentBlock.php | 6 ++- .../{ => ContentBlocks}/TextContentBlock.php | 6 ++- .../ThinkingContentBlock.php | 6 ++- .../ToolUseContentBlock.php | 6 ++- .../WebSearchToolResultContentBlock.php | 6 ++- src/SDK/Anthropic/Data/Messages/Message.php | 40 +++++++++++++------ .../Anthropic/Data/Messages/MessageStream.php | 5 +++ src/SDK/Anthropic/Data/Messages/Meta.php | 6 +++ .../Streaming/Events/ContentBlockStart.php | 24 ++--------- src/SDK/Anthropic/Requests/CreateMessage.php | 20 +++++++--- src/SDK/Anthropic/Resources/Messages.php | 10 +++++ tests/Pest.php | 4 ++ .../Drivers/Anthropic/AnthropicChatTest.php | 2 +- tests/Unit/SDK/Anthropic/AnthropicTest.php | 18 +++++---- tests/fixtures/anthropic/chat-stream-json.txt | 32 --------------- .../anthropic/chat-stream-tool-calls.txt | 29 -------------- tests/fixtures/anthropic/chat-stream.txt | 23 ----------- 22 files changed, 133 insertions(+), 152 deletions(-) create mode 100644 src/SDK/Anthropic/Contracts/ContentBlock.php rename src/SDK/Anthropic/Data/Messages/{ => ContentBlocks}/RedactedThinkingContentBlock.php (67%) rename src/SDK/Anthropic/Data/Messages/{ => ContentBlocks}/ServerToolUseContentBlock.php (70%) rename src/SDK/Anthropic/Data/Messages/{ => ContentBlocks}/TextContentBlock.php (63%) rename src/SDK/Anthropic/Data/Messages/{ => ContentBlocks}/ThinkingContentBlock.php (69%) rename src/SDK/Anthropic/Data/Messages/{ => ContentBlocks}/ToolUseContentBlock.php (71%) rename src/SDK/Anthropic/Data/Messages/{ => ContentBlocks}/WebSearchToolResultContentBlock.php (68%) delete mode 100644 tests/fixtures/anthropic/chat-stream-json.txt delete mode 100644 tests/fixtures/anthropic/chat-stream-tool-calls.txt delete mode 100644 tests/fixtures/anthropic/chat-stream.txt diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 0038fba..9c795d8 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -69,5 +69,8 @@ jobs: restore-keys: | ecs-cache- - - name: Run format checks - run: composer format --no-progress-bar + - name: Run Rector + run: ./vendor/bin/rector process --dry-run --no-progress-bar --no-diffs --output-format=checkstyle + + - name: Run ECS + run: ./vendor/bin/ecs check --no-progress-bar --no-diffs --output-format=checkstyle diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 0ac0f2b..987d8f8 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -13,19 +13,19 @@ use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\SDK\Anthropic\Contracts\StreamEvent; use Cortex\SDK\Anthropic\Data\Messages\MessageStream; -use Cortex\SDK\Anthropic\Data\Messages\TextContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\TextDelta; -use Cortex\SDK\Anthropic\Data\Messages\ToolUseContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\InputJsonDelta; -use Cortex\SDK\Anthropic\Data\Messages\RedactedThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStart; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\TextContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStop; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStart; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\RedactedThinkingContentBlock; /** @mixin \Cortex\LLM\Drivers\Anthropic\AnthropicChat */ trait MapStreamResponse diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php index 0eb867b..ca5d054 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php @@ -14,10 +14,10 @@ use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Data\Messages\Content\TextContent; use Cortex\LLM\Data\Messages\Content\ReasoningContent; -use Cortex\SDK\Anthropic\Data\Messages\TextContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ToolUseContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Message as MessageResponse; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\TextContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ToolUseContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ThinkingContentBlock; /** @mixin \Cortex\LLM\Drivers\Anthropic\AnthropicChat */ trait MapsResponse diff --git a/src/SDK/Anthropic/Contracts/ContentBlock.php b/src/SDK/Anthropic/Contracts/ContentBlock.php new file mode 100644 index 0000000..da92e6e --- /dev/null +++ b/src/SDK/Anthropic/Contracts/ContentBlock.php @@ -0,0 +1,15 @@ + $payload + */ + public static function from(array $payload): self; +} diff --git a/src/SDK/Anthropic/Data/Messages/RedactedThinkingContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/RedactedThinkingContentBlock.php similarity index 67% rename from src/SDK/Anthropic/Data/Messages/RedactedThinkingContentBlock.php rename to src/SDK/Anthropic/Data/Messages/ContentBlocks/RedactedThinkingContentBlock.php index 3a62a7c..7a11e9e 100644 --- a/src/SDK/Anthropic/Data/Messages/RedactedThinkingContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/RedactedThinkingContentBlock.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace Cortex\SDK\Anthropic\Data\Messages; +namespace Cortex\SDK\Anthropic\Data\Messages\ContentBlocks; -final class RedactedThinkingContentBlock +use Cortex\SDK\Anthropic\Contracts\ContentBlock; + +final class RedactedThinkingContentBlock implements ContentBlock { public string $type = 'redacted_thinking'; diff --git a/src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php similarity index 70% rename from src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php rename to src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php index a92febd..0380991 100644 --- a/src/SDK/Anthropic/Data/Messages/ServerToolUseContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace Cortex\SDK\Anthropic\Data\Messages; +namespace Cortex\SDK\Anthropic\Data\Messages\ContentBlocks; -final class ServerToolUseContentBlock +use Cortex\SDK\Anthropic\Contracts\ContentBlock; + +final class ServerToolUseContentBlock implements ContentBlock { public string $type = 'server_tool_use'; diff --git a/src/SDK/Anthropic/Data/Messages/TextContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/TextContentBlock.php similarity index 63% rename from src/SDK/Anthropic/Data/Messages/TextContentBlock.php rename to src/SDK/Anthropic/Data/Messages/ContentBlocks/TextContentBlock.php index b1981e2..1b17f4b 100644 --- a/src/SDK/Anthropic/Data/Messages/TextContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/TextContentBlock.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace Cortex\SDK\Anthropic\Data\Messages; +namespace Cortex\SDK\Anthropic\Data\Messages\ContentBlocks; -final class TextContentBlock +use Cortex\SDK\Anthropic\Contracts\ContentBlock; + +final class TextContentBlock implements ContentBlock { public string $type = 'text'; diff --git a/src/SDK/Anthropic/Data/Messages/ThinkingContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ThinkingContentBlock.php similarity index 69% rename from src/SDK/Anthropic/Data/Messages/ThinkingContentBlock.php rename to src/SDK/Anthropic/Data/Messages/ContentBlocks/ThinkingContentBlock.php index beeacbb..b8ce614 100644 --- a/src/SDK/Anthropic/Data/Messages/ThinkingContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ThinkingContentBlock.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace Cortex\SDK\Anthropic\Data\Messages; +namespace Cortex\SDK\Anthropic\Data\Messages\ContentBlocks; -final class ThinkingContentBlock +use Cortex\SDK\Anthropic\Contracts\ContentBlock; + +final class ThinkingContentBlock implements ContentBlock { public string $type = 'thinking'; diff --git a/src/SDK/Anthropic/Data/Messages/ToolUseContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php similarity index 71% rename from src/SDK/Anthropic/Data/Messages/ToolUseContentBlock.php rename to src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php index 1609033..c6653ec 100644 --- a/src/SDK/Anthropic/Data/Messages/ToolUseContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace Cortex\SDK\Anthropic\Data\Messages; +namespace Cortex\SDK\Anthropic\Data\Messages\ContentBlocks; -final class ToolUseContentBlock +use Cortex\SDK\Anthropic\Contracts\ContentBlock; + +final class ToolUseContentBlock implements ContentBlock { public string $type = 'tool_use'; diff --git a/src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php similarity index 68% rename from src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php rename to src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php index 2d5f206..5703205 100644 --- a/src/SDK/Anthropic/Data/Messages/WebSearchToolResultContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php @@ -2,9 +2,11 @@ declare(strict_types=1); -namespace Cortex\SDK\Anthropic\Data\Messages; +namespace Cortex\SDK\Anthropic\Data\Messages\ContentBlocks; -final class WebSearchToolResultContentBlock +use Cortex\SDK\Anthropic\Contracts\ContentBlock; + +final class WebSearchToolResultContentBlock implements ContentBlock { public string $type = 'web_search_tool_result'; diff --git a/src/SDK/Anthropic/Data/Messages/Message.php b/src/SDK/Anthropic/Data/Messages/Message.php index ae63262..45a2dc8 100644 --- a/src/SDK/Anthropic/Data/Messages/Message.php +++ b/src/SDK/Anthropic/Data/Messages/Message.php @@ -6,6 +6,13 @@ use Saloon\Http\Response; use InvalidArgumentException; +use Cortex\SDK\Anthropic\Contracts\ContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\TextContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ToolUseContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ThinkingContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ServerToolUseContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\RedactedThinkingContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\WebSearchToolResultContentBlock; final class Message { @@ -13,6 +20,9 @@ final class Message private ?Meta $meta = null; + /** + * @param array $content + */ public function __construct( public string $model, public string $id, @@ -26,24 +36,12 @@ public function __construct( public static function from(array $payload): self { - $content = array_map(function (array $content): TextContentBlock|ThinkingContentBlock|RedactedThinkingContentBlock|ToolUseContentBlock|ServerToolUseContentBlock|WebSearchToolResultContentBlock { - return match ($content['type']) { - 'text' => TextContentBlock::from($content), - 'thinking' => ThinkingContentBlock::from($content), - 'redacted_thinking' => RedactedThinkingContentBlock::from($content), - 'tool_use' => ToolUseContentBlock::from($content), - 'server_tool_use' => ServerToolUseContentBlock::from($content), - 'web_search_tool_result' => WebSearchToolResultContentBlock::from($content), - default => throw new InvalidArgumentException('Invalid content type: ' . $content['type']), - }; - }, $payload['content']); - return new self( model: $payload['model'], id: $payload['id'], type: $payload['type'], role: $payload['role'], - content: $content, + content: array_map([self::class, 'mapContentBlock'], $payload['content']), stopReason: $payload['stop_reason'] ?? null, stopSequence: $payload['stop_sequence'] ?? null, usage: $payload['usage'] ? Usage::from($payload['usage']) : null, @@ -80,4 +78,20 @@ public function getMeta(): ?Meta { return $this->meta; } + + /** + * @param array{type: string, ...} $content + */ + public static function mapContentBlock(array $content): ContentBlock + { + return match ($content['type']) { + 'text' => TextContentBlock::from($content), + 'thinking' => ThinkingContentBlock::from($content), + 'redacted_thinking' => RedactedThinkingContentBlock::from($content), + 'tool_use' => ToolUseContentBlock::from($content), + 'server_tool_use' => ServerToolUseContentBlock::from($content), + 'web_search_tool_result' => WebSearchToolResultContentBlock::from($content), + default => throw new InvalidArgumentException('Invalid content type: ' . $content['type']), + }; + } } diff --git a/src/SDK/Anthropic/Data/Messages/MessageStream.php b/src/SDK/Anthropic/Data/Messages/MessageStream.php index 87a37ea..ceb7cd1 100644 --- a/src/SDK/Anthropic/Data/Messages/MessageStream.php +++ b/src/SDK/Anthropic/Data/Messages/MessageStream.php @@ -66,6 +66,11 @@ public function getResponse(): Response return $this->response; } + public static function fromResponse(Response $response): self + { + return new self($response); + } + private function readLine(StreamInterface $stream): string { $buffer = ''; diff --git a/src/SDK/Anthropic/Data/Messages/Meta.php b/src/SDK/Anthropic/Data/Messages/Meta.php index 900c71e..8037a08 100644 --- a/src/SDK/Anthropic/Data/Messages/Meta.php +++ b/src/SDK/Anthropic/Data/Messages/Meta.php @@ -9,12 +9,18 @@ final class Meta { + /** + * @param array $raw + */ public function __construct( public array $raw, public ?int $processingTime = null, public ?DateTimeImmutable $createdAt = null, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { $createdAt = $payload['Date'] diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php index 85816a5..1806093 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php @@ -4,14 +4,9 @@ namespace Cortex\SDK\Anthropic\Data\Messages\Streaming\Events; -use InvalidArgumentException; use Cortex\SDK\Anthropic\Contracts\StreamEvent; -use Cortex\SDK\Anthropic\Data\Messages\TextContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ToolUseContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ServerToolUseContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\RedactedThinkingContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\WebSearchToolResultContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\Message; +use Cortex\SDK\Anthropic\Contracts\ContentBlock; final class ContentBlockStart extends AbstractStreamEvent implements StreamEvent { @@ -20,25 +15,14 @@ final class ContentBlockStart extends AbstractStreamEvent implements StreamEvent */ public function __construct( public int $index, - public TextContentBlock|ThinkingContentBlock|RedactedThinkingContentBlock|ToolUseContentBlock|ServerToolUseContentBlock|WebSearchToolResultContentBlock $contentBlock, + public ContentBlock $contentBlock, ) {} public static function from(array $payload): self { - // dump($payload); - $contentBlock = match ($payload['content_block']['type']) { - 'text' => TextContentBlock::from($payload['content_block']), - 'thinking' => ThinkingContentBlock::from($payload['content_block']), - 'redacted_thinking' => RedactedThinkingContentBlock::from($payload['content_block']), - 'tool_use' => ToolUseContentBlock::from($payload['content_block']), - 'server_tool_use' => ServerToolUseContentBlock::from($payload['content_block']), - 'web_search_tool_result' => WebSearchToolResultContentBlock::from($payload['content_block']), - default => throw new InvalidArgumentException('Invalid content type: ' . $payload['content_block']['type']), - }; - return new self( index: $payload['index'], - contentBlock: $contentBlock, + contentBlock: Message::mapContentBlock($payload['content_block']), )->setRaw($payload); } } diff --git a/src/SDK/Anthropic/Requests/CreateMessage.php b/src/SDK/Anthropic/Requests/CreateMessage.php index 78301d9..c5707d3 100644 --- a/src/SDK/Anthropic/Requests/CreateMessage.php +++ b/src/SDK/Anthropic/Requests/CreateMessage.php @@ -16,6 +16,9 @@ class CreateMessage extends Request implements HasBody { use HasJsonBody; + /** + * @param array $parameters + */ public function __construct( protected array $parameters, ) {} @@ -27,6 +30,9 @@ public function resolveEndpoint(): string return '/messages'; } + /** + * @return array + */ protected function defaultBody(): array { return array_filter( @@ -36,23 +42,25 @@ protected function defaultBody(): array ); } + /** + * @return array + */ protected function defaultHeaders(): array { + $headers = []; $betas = $this->parameters['betas'] ?? []; - if (empty($betas)) { - return []; + if ($betas !== []) { + $headers['anthropic-beta'] = implode(',', $betas); } - return [ - 'anthropic-beta' => implode(',', $betas), - ]; + return $headers; } public function createDtoFromResponse(Response $response): Message|MessageStream { return $this->parameters['stream'] ?? false - ? new MessageStream($response) + ? MessageStream::fromResponse($response) : Message::fromResponse($response); } } diff --git a/src/SDK/Anthropic/Resources/Messages.php b/src/SDK/Anthropic/Resources/Messages.php index a4f7326..7370d40 100644 --- a/src/SDK/Anthropic/Resources/Messages.php +++ b/src/SDK/Anthropic/Resources/Messages.php @@ -11,11 +11,21 @@ class Messages extends BaseResource { + /** + * Create a message or a streamed message. + * + * @param array $parameters + */ public function create(array $parameters): Message|MessageStream { return $this->connector->send(new CreateMessage($parameters))->dtoOrFail(); } + /** + * Create a streamed message. + * + * @param array $parameters + */ public function stream(array $parameters): MessageStream { return $this->create([ diff --git a/tests/Pest.php b/tests/Pest.php index faf346e..2a8fc07 100644 --- a/tests/Pest.php +++ b/tests/Pest.php @@ -10,6 +10,10 @@ Config::preventStrayRequests(); MockConfig::setFixturePath('tests/fixtures/sdk'); +if (env('CI')) { + MockConfig::throwOnMissingFixtures(); +} + uses(TestCase::class) ->in('Unit') ->beforeEach(fn() => MockClient::destroyGlobal()); diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index ff3b750..8ef3919 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -65,7 +65,7 @@ return $carry . ($chunk->message->content ?? ''); }, ''); - expect($output)->toBe('Hello! I\'m doing well, thank you for asking. How are you doing today? Is there anything I can help you with?'); + expect($output)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?"); }); test('it can use tools', function (): void { diff --git a/tests/Unit/SDK/Anthropic/AnthropicTest.php b/tests/Unit/SDK/Anthropic/AnthropicTest.php index 6c3e329..7c587c9 100644 --- a/tests/Unit/SDK/Anthropic/AnthropicTest.php +++ b/tests/Unit/SDK/Anthropic/AnthropicTest.php @@ -13,23 +13,23 @@ use Cortex\SDK\Anthropic\Data\Messages\Message; use Cortex\SDK\Anthropic\Requests\CreateMessage; use Cortex\SDK\Anthropic\Data\Messages\MessageStream; -use Cortex\SDK\Anthropic\Data\Messages\TextContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ToolUseContentBlock; -use Cortex\SDK\Anthropic\Data\Messages\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\SignatureDelta; -use Cortex\SDK\Anthropic\Data\Messages\ServerToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStart; -use Cortex\SDK\Anthropic\Data\Messages\WebSearchToolResultContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\TextContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStop; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStart; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ServerToolUseContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\WebSearchToolResultContentBlock; describe('Non-streaming messages', function (): void { test('it can create a message', function (): void { - MockClient::global([ + $mockClient = MockClient::global([ CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), ]); @@ -51,10 +51,12 @@ ->and($message->content[0])->toBeInstanceOf(TextContentBlock::class) ->and($message->content[0]->text)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?") ->and($message->usage)->toBeInstanceOf(Usage::class); + + $mockClient->assertSentCount(1, CreateMessage::class); }); test('it can create a message with thinking', function (): void { - MockClient::global([ + $mockClient = MockClient::global([ CreateMessage::class => MockResponse::fixture('anthropic/messages/thinking'), ]); @@ -83,6 +85,8 @@ ->and($message->content[1])->toBeInstanceOf(TextContentBlock::class) ->and($message->content[1]->text)->toBeString() ->and($message->usage)->toBeInstanceOf(Usage::class); + + $mockClient->assertSentCount(1, CreateMessage::class); }); test('it can create a message with redacted thinking', function (): void { diff --git a/tests/fixtures/anthropic/chat-stream-json.txt b/tests/fixtures/anthropic/chat-stream-json.txt deleted file mode 100644 index b47a7ba..0000000 --- a/tests/fixtures/anthropic/chat-stream-json.txt +++ /dev/null @@ -1,32 +0,0 @@ -event: message_start -data: {"type": "message_start", "message": {"id": "msg_test", "type": "message", "role": "assistant", "content": [], "model": "claude-3-5-sonnet-20241022", "stop_reason": null, "stop_sequence": null, "usage": {"input_tokens": 25, "output_tokens": 1}}} - -event: content_block_start -data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "{"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "\"setup\":"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "\"Why did the dog sit in the shade?\""}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": ",\"punchline\":"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "\"Because he didn't want to be a hot dog!\""}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "}"}} - -event: content_block_stop -data: {"type": "content_block_stop", "index": 0} - -event: message_delta -data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence":null}, "usage": {"output_tokens": 30}} - -event: message_stop -data: {"type": "message_stop"} diff --git a/tests/fixtures/anthropic/chat-stream-tool-calls.txt b/tests/fixtures/anthropic/chat-stream-tool-calls.txt deleted file mode 100644 index fa623ba..0000000 --- a/tests/fixtures/anthropic/chat-stream-tool-calls.txt +++ /dev/null @@ -1,29 +0,0 @@ -event: message_start -data: {"type": "message_start", "message": {"id": "msg_test_tool", "type": "message", "role": "assistant", "content": [], "model": "claude-3-5-sonnet-20241022", "stop_reason": null, "stop_sequence": null, "usage": {"input_tokens": 25, "output_tokens": 1}}} - -event: content_block_start -data: {"type": "content_block_start", "index": 0, "content_block": {"type": "tool_use", "id": "call_multiply_123", "name": "multiply", "input": {}}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "{\"x\":"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "3"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": ",\"y\":"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "4"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "input_json_delta", "partial_json": "}"}} - -event: content_block_stop -data: {"type": "content_block_stop", "index": 0} - -event: message_delta -data: {"type": "message_delta", "delta": {"stop_reason": "tool_use", "stop_sequence":null}, "usage": {"output_tokens": 25}} - -event: message_stop -data: {"type": "message_stop"} diff --git a/tests/fixtures/anthropic/chat-stream.txt b/tests/fixtures/anthropic/chat-stream.txt deleted file mode 100644 index e5fb8be..0000000 --- a/tests/fixtures/anthropic/chat-stream.txt +++ /dev/null @@ -1,23 +0,0 @@ -event: message_start -data: {"type": "message_start", "message": {"id": "msg_1nZdL29xx5MUA1yADyHTEsnR8uuvGzszyY", "type": "message", "role": "assistant", "content": [], "model": "claude-3-opus-20240229", "stop_reason": null, "stop_sequence": null, "usage": {"input_tokens": 25, "output_tokens": 1}}} - -event: content_block_start -data: {"type": "content_block_start", "index": 0, "content_block": {"type": "text", "text": ""}} - -event: ping -data: {"type": "ping"} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "Hello"}} - -event: content_block_delta -data: {"type": "content_block_delta", "index": 0, "delta": {"type": "text_delta", "text": "!"}} - -event: content_block_stop -data: {"type": "content_block_stop", "index": 0} - -event: message_delta -data: {"type": "message_delta", "delta": {"stop_reason": "end_turn", "stop_sequence":null}, "usage": {"output_tokens": 15}} - -event: message_stop -data: {"type": "message_stop"} From 9882179cf4174489cd41b4c5ab154cc766672c38 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 29 Dec 2025 11:12:21 +0000 Subject: [PATCH 06/54] fix --- .github/workflows/static-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 9c795d8..3c83f94 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -70,7 +70,7 @@ jobs: ecs-cache- - name: Run Rector - run: ./vendor/bin/rector process --dry-run --no-progress-bar --no-diffs --output-format=checkstyle + run: ./vendor/bin/rector process --dry-run --no-progress-bar --no-diffs --output-format=github - name: Run ECS run: ./vendor/bin/ecs check --no-progress-bar --no-diffs --output-format=checkstyle From 4d9a484a815248c40c740ce358f5fbf6cdbe6ada Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sat, 3 Jan 2026 01:01:15 +0000 Subject: [PATCH 07/54] stan --- .cursor/commands/fix-stan.md | 2 +- phpstan.dist.neon | 1 - src/LLM/Drivers/Anthropic/AnthropicChat.php | 3 +++ .../Anthropic/Concerns/MapStreamResponse.php | 2 +- .../Drivers/Anthropic/Concerns/MapsResponse.php | 16 ++++++++++++---- src/SDK/Anthropic/Contracts/StreamEvent.php | 6 ++++++ .../ContentBlocks/ServerToolUseContentBlock.php | 3 +++ .../ContentBlocks/ToolUseContentBlock.php | 3 +++ .../WebSearchToolResultContentBlock.php | 3 +++ src/SDK/Anthropic/Data/Messages/Message.php | 3 +++ .../Anthropic/Data/Messages/MessageStream.php | 3 +++ .../Data/Messages/Streaming/CitationsDelta.php | 5 ++++- .../Streaming/Events/AbstractStreamEvent.php | 9 +++++++++ .../Streaming/Events/ContentBlockDelta.php | 3 +++ .../Streaming/Events/ContentBlockStart.php | 3 +++ .../Streaming/Events/ContentBlockStop.php | 3 +++ .../Messages/Streaming/Events/MessageDelta.php | 3 +++ .../Messages/Streaming/Events/MessageStart.php | 3 +++ .../Messages/Streaming/Events/MessageStop.php | 3 +++ .../Data/Messages/Streaming/Events/Ping.php | 3 +++ .../Data/Messages/Streaming/Events/Unknown.php | 6 ++++++ src/SDK/Anthropic/Data/Messages/Usage.php | 7 +++++++ 22 files changed, 85 insertions(+), 8 deletions(-) diff --git a/.cursor/commands/fix-stan.md b/.cursor/commands/fix-stan.md index 78b3d84..7b4b4ae 100644 --- a/.cursor/commands/fix-stan.md +++ b/.cursor/commands/fix-stan.md @@ -1 +1 @@ -Use `composer stan --no-progress` to see the PHPStan failures, and then fix them. +Use `composer stan --no-progress` to see the PHPStan failures, and then fix them. Do not ignore failures unless they are truly impossible to fix. diff --git a/phpstan.dist.neon b/phpstan.dist.neon index 3a4ef42..ae93ac0 100644 --- a/phpstan.dist.neon +++ b/phpstan.dist.neon @@ -3,6 +3,5 @@ parameters: paths: - src excludePaths: - - src/LLM/Drivers/Anthropic/AnthropicChat.php - src/LLM/Streaming tmpDir: .phpstan-cache diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 260f0d2..08471ee 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -207,6 +207,9 @@ protected function buildParams(array $additionalParameters): array ]; } + /** + * @param array $responses + */ public static function fake( array $responses, ?string $apiKey = null, diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 987d8f8..85b50b7 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -92,7 +92,7 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult usage: $usage, ), ), - createdAt: $meta?->createdAt ?? new DateTimeImmutable(), + createdAt: $meta->createdAt ?? new DateTimeImmutable(), finishReason: $finishReason, usage: $usage, contentSoFar: $contentSoFar, diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php index ca5d054..413f547 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php @@ -31,8 +31,11 @@ protected function mapResponse(MessageResponse $message): ChatResult $finishReason = $this->mapFinishReason($message->stopReason); $meta = $message->getMeta(); - $toolCalls = collect($message->content) - ->filter(fn(object $content): bool => $content instanceof ToolUseContentBlock) + /** @var \Illuminate\Support\Collection $contentCollection */ + $contentCollection = collect($message->content); + /** @var \Illuminate\Support\Collection $toolUseBlocks */ + $toolUseBlocks = $contentCollection->filter(fn(object $content): bool => $content instanceof ToolUseContentBlock); + $toolCalls = $toolUseBlocks ->map(function (ToolUseContentBlock $content): ToolCall { return new ToolCall( $content->id, @@ -56,11 +59,11 @@ protected function mapResponse(MessageResponse $message): ChatResult finishReason: $finishReason, usage: $usage, processingTime: $meta?->processingTime, - providerMetadata: $meta?->raw ?? [], + providerMetadata: $meta->raw ?? [], ), id: $message->id, ), - createdAt: $meta?->createdAt ?? new DateTimeImmutable(), + createdAt: $meta->createdAt ?? new DateTimeImmutable(), finishReason: $finishReason, ); @@ -78,6 +81,11 @@ protected function mapResponse(MessageResponse $message): ChatResult ); } + /** + * @param array $content + * + * @return array + */ protected function mapContent(array $content): array { return collect($content) diff --git a/src/SDK/Anthropic/Contracts/StreamEvent.php b/src/SDK/Anthropic/Contracts/StreamEvent.php index ddff85b..a07a828 100644 --- a/src/SDK/Anthropic/Contracts/StreamEvent.php +++ b/src/SDK/Anthropic/Contracts/StreamEvent.php @@ -8,8 +8,14 @@ interface StreamEvent { + /** + * @param array $payload + */ public static function from(array $payload): self; + /** + * @return array + */ public function raw(): array; public function meta(): ?Meta; diff --git a/src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php index 0380991..70406ec 100644 --- a/src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ServerToolUseContentBlock.php @@ -10,6 +10,9 @@ final class ServerToolUseContentBlock implements ContentBlock { public string $type = 'server_tool_use'; + /** + * @param array $input + */ public function __construct( public string $id, public string $name, diff --git a/src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php index c6653ec..6277b1d 100644 --- a/src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/ToolUseContentBlock.php @@ -10,6 +10,9 @@ final class ToolUseContentBlock implements ContentBlock { public string $type = 'tool_use'; + /** + * @param array $input + */ public function __construct( public string $id, public string $name, diff --git a/src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php b/src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php index 5703205..a886a42 100644 --- a/src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php +++ b/src/SDK/Anthropic/Data/Messages/ContentBlocks/WebSearchToolResultContentBlock.php @@ -10,6 +10,9 @@ final class WebSearchToolResultContentBlock implements ContentBlock { public string $type = 'web_search_tool_result'; + /** + * @param array $content + */ public function __construct( public string $toolUseId, public array $content, diff --git a/src/SDK/Anthropic/Data/Messages/Message.php b/src/SDK/Anthropic/Data/Messages/Message.php index 45a2dc8..57c8423 100644 --- a/src/SDK/Anthropic/Data/Messages/Message.php +++ b/src/SDK/Anthropic/Data/Messages/Message.php @@ -34,6 +34,9 @@ public function __construct( public ?Usage $usage = null, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( diff --git a/src/SDK/Anthropic/Data/Messages/MessageStream.php b/src/SDK/Anthropic/Data/Messages/MessageStream.php index ceb7cd1..7c4dfd7 100644 --- a/src/SDK/Anthropic/Data/Messages/MessageStream.php +++ b/src/SDK/Anthropic/Data/Messages/MessageStream.php @@ -17,6 +17,9 @@ use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStart; +/** + * @implements IteratorAggregate + */ final readonly class MessageStream implements IteratorAggregate { public function __construct( diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/CitationsDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/CitationsDelta.php index c05772a..1535c29 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/CitationsDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/CitationsDelta.php @@ -6,12 +6,15 @@ final class CitationsDelta { + /** + * @param array $citation + */ public function __construct( public array $citation, ) {} /** - * @param array{citations: array} $payload + * @param array{citation: array} $payload */ public static function from(array $payload): self { diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/AbstractStreamEvent.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/AbstractStreamEvent.php index f023002..d098eb6 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/AbstractStreamEvent.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/AbstractStreamEvent.php @@ -8,10 +8,16 @@ abstract class AbstractStreamEvent { + /** + * @var array + */ protected array $raw = []; protected ?Meta $meta = null; + /** + * @return array + */ public function raw(): array { return $this->raw; @@ -22,6 +28,9 @@ public function meta(): ?Meta return $this->meta; } + /** + * @param array $payload + */ public function setRaw(array $payload): static { $this->raw = $payload; diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php index 153cb7d..01a84f2 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockDelta.php @@ -22,6 +22,9 @@ public function __construct( public TextDelta|InputJsonDelta|ThinkingDelta|SignatureDelta|CitationsDelta $delta, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { $delta = match ($payload['delta']['type']) { diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php index 1806093..e00801a 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStart.php @@ -18,6 +18,9 @@ public function __construct( public ContentBlock $contentBlock, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php index 01dde03..37ad6d0 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/ContentBlockStop.php @@ -15,6 +15,9 @@ public function __construct( public int $index, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php index ff4bba9..ad6ef07 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageDelta.php @@ -15,6 +15,9 @@ public function __construct( public ?string $stopSequence = null, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php index 3a77239..a00032f 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStart.php @@ -13,6 +13,9 @@ public function __construct( public Message $message, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php index 244647a..e382de9 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/MessageStop.php @@ -8,6 +8,9 @@ final class MessageStop extends AbstractStreamEvent implements StreamEvent { + /** + * @param array $payload + */ public static function from(array $payload): self { return new self()->setRaw($payload); diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php index 58d9e04..600240c 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Ping.php @@ -8,6 +8,9 @@ final class Ping extends AbstractStreamEvent implements StreamEvent { + /** + * @param array $payload + */ public static function from(array $payload): self { return new self()->setRaw($payload); diff --git a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php index 99f21f1..dd73dcf 100644 --- a/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php +++ b/src/SDK/Anthropic/Data/Messages/Streaming/Events/Unknown.php @@ -8,11 +8,17 @@ final class Unknown extends AbstractStreamEvent implements StreamEvent { + /** + * @param array $payload + */ public function __construct( public string $type, public array $payload, ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( diff --git a/src/SDK/Anthropic/Data/Messages/Usage.php b/src/SDK/Anthropic/Data/Messages/Usage.php index a45bb6f..f879e8e 100644 --- a/src/SDK/Anthropic/Data/Messages/Usage.php +++ b/src/SDK/Anthropic/Data/Messages/Usage.php @@ -6,6 +6,10 @@ final class Usage { + /** + * @param array|null $cacheCreation + * @param array $serverToolUse + */ public function __construct( public int $inputTokens, public int $outputTokens, @@ -16,6 +20,9 @@ public function __construct( public array $serverToolUse = [], ) {} + /** + * @param array $payload + */ public static function from(array $payload): self { return new self( From a610c6048ed11a4f6db71efe4d17991a965228cd Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sun, 4 Jan 2026 00:12:16 +0000 Subject: [PATCH 08/54] wip --- src/Console/ChatPrompt.php | 14 ++- src/LLM/AbstractLLM.php | 7 ++ src/LLM/Contracts/LLM.php | 13 ++ src/LLM/Data/ChatGenerationChunk.php | 30 ++++- src/LLM/Data/ChatStreamResult.php | 3 +- .../Messages/Content/ReasoningContent.php | 7 ++ src/LLM/Data/Messages/Content/TextContent.php | 11 ++ .../Anthropic/Concerns/MapStreamResponse.php | 69 +++++++++-- .../Chat/Concerns/MapsStreamResponse.php | 32 ++++- .../Responses/Concerns/MapsStreamResponse.php | 57 ++++++++- src/OutputParsers/EnumOutputParser.php | 2 +- src/OutputParsers/JsonOutputParser.php | 2 +- src/OutputParsers/XmlOutputParser.php | 4 +- src/OutputParsers/XmlTagOutputParser.php | 10 +- tests/Unit/Agents/AgentTest.php | 6 +- .../Drivers/Anthropic/AnthropicChatTest.php | 116 +++++++++++++++++- .../LLM/Drivers/OpenAI/OpenAIChatTest.php | 56 ++++++++- tests/Unit/LLM/StreamBufferTest.php | 6 +- 18 files changed, 399 insertions(+), 46 deletions(-) diff --git a/src/Console/ChatPrompt.php b/src/Console/ChatPrompt.php index 4ed4be8..d936332 100644 --- a/src/Console/ChatPrompt.php +++ b/src/Console/ChatPrompt.php @@ -407,6 +407,8 @@ protected function processAgentResponse(string $userInput): void // Stream response in real-time foreach ($result as $chunk) { + $textSoFar = $chunk->textSoFar(); + // Handle tool calls in debug mode if ($this->debug) { match ($chunk->type) { @@ -417,9 +419,9 @@ protected function processAgentResponse(string $userInput): void }; } - if ($chunk->type === ChunkType::TextDelta && $chunk->contentSoFar !== '') { - $fullResponse = $chunk->contentSoFar; - $this->streamingContent = $chunk->contentSoFar; + if ($chunk->type === ChunkType::TextDelta && $textSoFar !== null) { + $fullResponse = $textSoFar; + $this->streamingContent = $textSoFar; // Auto-scroll to bottom during streaming if enabled if ($this->autoScroll) { @@ -430,9 +432,9 @@ protected function processAgentResponse(string $userInput): void $this->throttledRender(); } - if ($chunk->isFinal && $chunk->contentSoFar !== '') { - $fullResponse = $chunk->contentSoFar; - $this->streamingContent = $chunk->contentSoFar; + if ($chunk->isFinal && $textSoFar !== null) { + $fullResponse = $textSoFar; + $this->streamingContent = $textSoFar; // Force render for final chunk (not throttled) $this->render(); } diff --git a/src/LLM/AbstractLLM.php b/src/LLM/AbstractLLM.php index c9c4a36..2f40674 100644 --- a/src/LLM/AbstractLLM.php +++ b/src/LLM/AbstractLLM.php @@ -103,6 +103,13 @@ public function __construct( } } + public function stream( + MessageCollection|Message|array|string $messages, + array $additionalParameters = [], + ): ChatStreamResult { + return $this->withStreaming(true)->invoke($messages, $additionalParameters); + } + public function handlePipeable(mixed $payload, RuntimeConfig $config, Closure $next): mixed { $this->shouldParseOutput($config->context->shouldParseOutput()); diff --git a/src/LLM/Contracts/LLM.php b/src/LLM/Contracts/LLM.php index 20c02f8..b35f1dc 100644 --- a/src/LLM/Contracts/LLM.php +++ b/src/LLM/Contracts/LLM.php @@ -31,6 +31,19 @@ public function invoke( array $additionalParameters = [], ): ChatResult|ChatStreamResult; + /** + * Convenience method to stream the LLM response. + * + * @param \Cortex\LLM\Data\Messages\MessageCollection|\Cortex\LLM\Contracts\Message|array|string $messages + * @param array $additionalParameters + * + * @return \Cortex\LLM\Data\ChatStreamResult<\Cortex\LLM\Data\ChatGenerationChunk> + */ + public function stream( + MessageCollection|Message|array|string $messages, + array $additionalParameters = [], + ): ChatStreamResult; + /** * Specify the tools to use for the LLM. * diff --git a/src/LLM/Data/ChatGenerationChunk.php b/src/LLM/Data/ChatGenerationChunk.php index 936bdd0..dd2cb8f 100644 --- a/src/LLM/Data/ChatGenerationChunk.php +++ b/src/LLM/Data/ChatGenerationChunk.php @@ -8,10 +8,13 @@ use DateTimeImmutable; use DateTimeInterface; use Cortex\LLM\Enums\ChunkType; +use Cortex\LLM\Contracts\Content; use Cortex\LLM\Enums\FinishReason; use Cortex\LLM\Data\Messages\ToolMessage; use Illuminate\Contracts\Support\Arrayable; use Cortex\LLM\Data\Messages\AssistantMessage; +use Cortex\LLM\Data\Messages\Content\TextContent; +use Cortex\LLM\Data\Messages\Content\ReasoningContent; /** * @implements Arrayable @@ -19,6 +22,7 @@ readonly class ChatGenerationChunk implements Arrayable { /** + * @param array<\Cortex\LLM\Contracts\Content> $contentSoFar * @param array|null $rawChunk * @param array $metadata */ @@ -29,7 +33,7 @@ public function __construct( public DateTimeInterface $createdAt = new DateTimeImmutable(), public ?FinishReason $finishReason = null, public ?Usage $usage = null, - public string $contentSoFar = '', + public array $contentSoFar = [], public bool $isFinal = false, public mixed $parsedOutput = null, public ?string $outputParserError = null, @@ -48,6 +52,30 @@ public function text(): ?string return $this->message->text(); } + /** + * Get the text content that has been streamed so far. + */ + public function textSoFar(): ?string + { + /** @var \Cortex\LLM\Data\Messages\Content\TextContent|null $textContent */ + $textContent = collect($this->contentSoFar) + ->first(fn(Content $content): bool => $content instanceof TextContent); + + return $textContent?->text; + } + + /** + * Get the reasoning content that has been streamed so far. + */ + public function reasoningSoFar(): ?string + { + /** @var \Cortex\LLM\Data\Messages\Content\ReasoningContent|null $reasoningContent */ + $reasoningContent = collect($this->contentSoFar) + ->first(fn(Content $content): bool => $content instanceof ReasoningContent); + + return $reasoningContent?->reasoning; + } + public function cloneWithParsedOutput(mixed $parsedOutput): self { return new self( diff --git a/src/LLM/Data/ChatStreamResult.php b/src/LLM/Data/ChatStreamResult.php index 313fa1b..9078f6d 100644 --- a/src/LLM/Data/ChatStreamResult.php +++ b/src/LLM/Data/ChatStreamResult.php @@ -12,6 +12,7 @@ use Cortex\Events\RuntimeConfigStreamChunk; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Data\Concerns\HasStreamResponses; +use Cortex\LLM\Data\Messages\Content\TextContent; /** * @extends LazyCollection @@ -93,7 +94,7 @@ public static function fake(?string $string = null, ?ToolCallCollection $toolCal completionTokens: $index, totalTokens: $index, ), - contentSoFar: $contentSoFar, + contentSoFar: [new TextContent($contentSoFar)], isFinal: $isFinal, ); diff --git a/src/LLM/Data/Messages/Content/ReasoningContent.php b/src/LLM/Data/Messages/Content/ReasoningContent.php index b5b49e3..6c1b046 100644 --- a/src/LLM/Data/Messages/Content/ReasoningContent.php +++ b/src/LLM/Data/Messages/Content/ReasoningContent.php @@ -10,4 +10,11 @@ public function __construct( public string $id, public string $reasoning, ) {} + + public function append(string $reasoning): self + { + $this->reasoning .= $reasoning; + + return $this; + } } diff --git a/src/LLM/Data/Messages/Content/TextContent.php b/src/LLM/Data/Messages/Content/TextContent.php index 4611bf9..c0d77e8 100644 --- a/src/LLM/Data/Messages/Content/TextContent.php +++ b/src/LLM/Data/Messages/Content/TextContent.php @@ -27,4 +27,15 @@ public function replaceVariables(array $variables): static return new self($this->getCompiler()->compile($this->text, $variables)); } + + public function append(string $text): self + { + if ($this->text === null) { + return new self($text); + } + + $this->text .= $text; + + return $this; + } } diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 85b50b7..26c1b24 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -12,10 +12,13 @@ use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\SDK\Anthropic\Contracts\StreamEvent; +use Cortex\LLM\Data\Messages\Content\TextContent; use Cortex\SDK\Anthropic\Data\Messages\MessageStream; +use Cortex\LLM\Data\Messages\Content\ReasoningContent; use Cortex\SDK\Anthropic\Data\Messages\Streaming\TextDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\InputJsonDelta; +use Cortex\SDK\Anthropic\Data\Messages\Streaming\SignatureDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStart; @@ -40,7 +43,9 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult return new ChatStreamResult(function () use ($response): Generator { yield from $this->streamBuffer?->drain() ?? []; - $contentSoFar = ''; + $contentSoFar = []; + $currentTextContent = null; + $currentReasoningContent = null; /** @var \Cortex\SDK\Anthropic\Contracts\StreamEvent $event */ foreach ($response as $event) { @@ -58,13 +63,59 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult ? $this->mapFinishReason($event->stopReason) : null; - $contentSoFar .= $event instanceof ContentBlockDelta - ? ($event->delta instanceof TextDelta - ? $event->delta->text - : ($event->delta instanceof InputJsonDelta - ? $event->delta->partialJson - : $event->delta->thinking)) - : ''; + // Handle ContentBlockStart - initialize new content objects + if ($event instanceof ContentBlockStart) { + if ($event->contentBlock instanceof TextContentBlock) { + $currentTextContent = new TextContent($event->contentBlock->text); + } elseif ($event->contentBlock instanceof ThinkingContentBlock) { + $currentReasoningContent = new ReasoningContent( + $event->contentBlock->signature, + $event->contentBlock->thinking, + ); + } elseif ($event->contentBlock instanceof RedactedThinkingContentBlock) { + // For redacted thinking, we might use the data field as id + $currentReasoningContent = new ReasoningContent( + $event->contentBlock->data, + $event->contentBlock->text, + ); + } + } + + // Handle ContentBlockDelta - append incremental content + if ($event instanceof ContentBlockDelta) { + if ($event->delta instanceof TextDelta && $currentTextContent !== null) { + $currentTextContent = $currentTextContent->append($event->delta->text); + } elseif ($event->delta instanceof ThinkingDelta && $currentReasoningContent !== null) { + $currentReasoningContent = $currentReasoningContent->append($event->delta->thinking); + } elseif ($event->delta instanceof SignatureDelta && $currentReasoningContent !== null) { + // Update the ID if signature delta comes after content block start + $currentReasoningContent = new ReasoningContent( + $event->delta->signature, + $currentReasoningContent->reasoning, + ); + } + } + + // Handle ContentBlockStop - finalize and add to contentSoFar + if ($event instanceof ContentBlockStop) { + if ($currentTextContent !== null) { + $contentSoFar[] = $currentTextContent; + $currentTextContent = null; + } elseif ($currentReasoningContent !== null) { + $contentSoFar[] = $currentReasoningContent; + $currentReasoningContent = null; + } + } + + // Build current contentSoFar array including partial content being built + // Clone content objects to avoid mutating references in previous chunks + $currentContentSoFar = [...$contentSoFar]; + + if ($currentTextContent !== null) { + $currentContentSoFar[] = new TextContent($currentTextContent->text); + } elseif ($currentReasoningContent !== null) { + $currentContentSoFar[] = new ReasoningContent($currentReasoningContent->id, $currentReasoningContent->reasoning); + } $id = $event instanceof MessageStart ? $event->message->id @@ -95,7 +146,7 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult createdAt: $meta->createdAt ?? new DateTimeImmutable(), finishReason: $finishReason, usage: $usage, - contentSoFar: $contentSoFar, + contentSoFar: $currentContentSoFar, isFinal: $usage !== null, rawChunk: $this->includeRaw ? $event->raw() : null, ); diff --git a/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php b/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php index 42d2267..d3e23ae 100644 --- a/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php +++ b/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php @@ -21,6 +21,7 @@ use Cortex\OutputParsers\JsonOutputParser; use Cortex\Exceptions\OutputParserException; use Cortex\LLM\Data\Messages\AssistantMessage; +use Cortex\LLM\Data\Messages\Content\TextContent; use OpenAI\Responses\Chat\CreateStreamedResponseChoice; /** @mixin \Cortex\LLM\Drivers\OpenAI\Chat\OpenAIChat */ @@ -36,7 +37,8 @@ trait MapsStreamResponse protected function mapStreamResponse(StreamResponse $response): ChatStreamResult { return new ChatStreamResult(function () use ($response): Generator { - $contentSoFar = ''; + $contentSoFar = []; + $currentTextContent = null; $toolCallsSoFar = []; $isActiveText = false; $finishReason = null; @@ -68,8 +70,14 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult $usage, ); - // Now update content and tool call tracking - $contentSoFar .= $choice->delta->content; + // Handle text content - initialize or append + if ($choice->delta->content !== null) { + if ($currentTextContent === null) { + $currentTextContent = new TextContent($choice->delta->content); + } else { + $currentTextContent = $currentTextContent->append($choice->delta->content); + } + } // Track tool calls across chunks foreach ($choice->delta->toolCalls as $toolCall) { @@ -158,11 +166,25 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult } } + // Finalize text content if this is the end + if ($usage !== null && $currentTextContent !== null) { + $contentSoFar[] = $currentTextContent; + $currentTextContent = null; + } + + // Build current contentSoFar array including partial content being built + // Clone TextContent to avoid mutating references in previous chunks + $currentContentSoFar = [...$contentSoFar]; + + if ($currentTextContent !== null) { + $currentContentSoFar[] = new TextContent($currentTextContent->text); + } + $chatGenerationChunk = new ChatGenerationChunk( type: $chunkType, id: $chunk->id, message: new AssistantMessage( - content: $choice->delta->content ?? null, + content: $chunk->choices !== [] ? ($chunk->choices[0]->delta->content ?? null) : null, toolCalls: $accumulatedToolCallsSoFar ?? null, metadata: new ResponseMetadata( id: $chunk->id, @@ -175,7 +197,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult createdAt: DateTimeImmutable::createFromFormat('U', (string) $chunk->created), finishReason: $usage !== null ? $finishReason : null, usage: $usage, - contentSoFar: $contentSoFar, + contentSoFar: $currentContentSoFar, isFinal: $usage !== null, rawChunk: $this->includeRaw ? $chunk->toArray() : null, ); diff --git a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php index 4cfd444..8ca9194 100644 --- a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php +++ b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php @@ -20,8 +20,10 @@ use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Data\Messages\AssistantMessage; use OpenAI\Responses\Responses\CreateResponse; +use Cortex\LLM\Data\Messages\Content\TextContent; use OpenAI\Responses\Responses\Output\OutputMessage; use OpenAI\Responses\Responses\Streaming\OutputItem; +use Cortex\LLM\Data\Messages\Content\ReasoningContent; use OpenAI\Responses\Responses\Output\OutputReasoning; use OpenAI\Responses\Responses\Streaming\OutputTextDelta; use OpenAI\Responses\Responses\Streaming\ReasoningTextDelta; @@ -42,7 +44,9 @@ trait MapsStreamResponse protected function mapStreamResponse(StreamResponse $response): ChatStreamResult { return new ChatStreamResult(function () use ($response): Generator { - $contentSoFar = ''; + $contentSoFar = []; + $currentTextContent = null; + $currentReasoningContent = []; $toolCallsSoFar = []; $reasoningSoFar = []; $reasoningTextSoFar = []; @@ -127,6 +131,11 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult 'id' => $item->id, 'summary' => '', ]; + + // Initialize ReasoningContent object + if (! isset($currentReasoningContent[$item->id])) { + $currentReasoningContent[$item->id] = new ReasoningContent($item->id, ''); + } } } @@ -135,7 +144,12 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult if ($data instanceof OutputTextDelta) { $currentDelta = $data->delta; - $contentSoFar .= $currentDelta; + + if ($currentTextContent === null) { + $currentTextContent = new TextContent($currentDelta); + } else { + $currentTextContent = $currentTextContent->append($currentDelta); + } } // Handle function call arguments deltas @@ -165,6 +179,14 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult } $reasoningTextSoFar[$itemId] .= $data->delta; + + // Update or create ReasoningContent object + if (! isset($currentReasoningContent[$itemId])) { + $reasoningId = $reasoningSoFar[$itemId]['id'] ?? $itemId; + $currentReasoningContent[$itemId] = new ReasoningContent($reasoningId, $reasoningTextSoFar[$itemId]); + } else { + $currentReasoningContent[$itemId] = $currentReasoningContent[$itemId]->append($data->delta); + } } // Build accumulated tool calls @@ -201,12 +223,39 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult 'response.incomplete', ], true); + // Build current contentSoFar array including partial content being built + // Clone content objects to avoid mutating references in previous chunks + $currentContentSoFar = [...$contentSoFar]; + + if ($currentTextContent !== null) { + $currentContentSoFar[] = new TextContent($currentTextContent->text); + } + + foreach ($currentReasoningContent as $reasoningContent) { + $currentContentSoFar[] = new ReasoningContent($reasoningContent->id, $reasoningContent->reasoning); + } + + // Finalize content when response is complete + if ($isFinal) { + if ($currentTextContent !== null) { + $contentSoFar[] = $currentTextContent; + $currentTextContent = null; + } + + foreach ($currentReasoningContent as $reasoningContent) { + $contentSoFar[] = $reasoningContent; + } + + $currentReasoningContent = []; + } + // Determine chunk type // For output_item.added events, check if it's a new tool call to determine chunk type + $contentSoFarString = $currentTextContent?->text ?? ''; // @phpstan-ignore nullsafe.neverNull $chunkType = $this->resolveResponsesChunkType( $event, $currentDelta, - $contentSoFar, + $contentSoFarString, $finishReason, $event === 'response.output_item.added' && $isNewToolCall && $data instanceof OutputItem && $data->item instanceof OutputFunctionToolCall, ); @@ -240,7 +289,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult : new DateTimeImmutable(), finishReason: $finishReason, usage: $responseUsage, - contentSoFar: $contentSoFar, + contentSoFar: $currentContentSoFar, isFinal: $isFinal, rawChunk: $rawChunk, ); diff --git a/src/OutputParsers/EnumOutputParser.php b/src/OutputParsers/EnumOutputParser.php index 4bd6861..45d8665 100644 --- a/src/OutputParsers/EnumOutputParser.php +++ b/src/OutputParsers/EnumOutputParser.php @@ -42,7 +42,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): Backed } $textOutput = match (true) { - $output instanceof ChatGenerationChunk => $output->contentSoFar, + $output instanceof ChatGenerationChunk => $output->textSoFar(), $output instanceof ChatGeneration => $output->message->text() ?? '', default => $output, }; diff --git a/src/OutputParsers/JsonOutputParser.php b/src/OutputParsers/JsonOutputParser.php index f83fe6c..8d03a48 100644 --- a/src/OutputParsers/JsonOutputParser.php +++ b/src/OutputParsers/JsonOutputParser.php @@ -26,7 +26,7 @@ class JsonOutputParser extends AbstractOutputParser public function parse(ChatGeneration|ChatGenerationChunk|string $output): array { $output = match (true) { - $output instanceof ChatGenerationChunk => $output->contentSoFar, + $output instanceof ChatGenerationChunk => $output->textSoFar() ?? '', $output instanceof ChatGeneration => $output->message->text() ?? '', default => $output, }; diff --git a/src/OutputParsers/XmlOutputParser.php b/src/OutputParsers/XmlOutputParser.php index d5e7dda..960b2d5 100644 --- a/src/OutputParsers/XmlOutputParser.php +++ b/src/OutputParsers/XmlOutputParser.php @@ -26,7 +26,7 @@ class XmlOutputParser extends AbstractOutputParser public function parse(ChatGeneration|ChatGenerationChunk|string $output): array { $output = match (true) { - $output instanceof ChatGenerationChunk => $output->contentSoFar, + $output instanceof ChatGenerationChunk => $output->textSoFar(), $output instanceof ChatGeneration => $output->message->text() ?? '', default => $output, }; @@ -35,7 +35,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): array return $this->parseXml($output); } catch (Exception) { foreach (self::PATTERNS as $pattern) { - preg_match($pattern, $output, $matches); + preg_match($pattern, (string) $output, $matches); if (isset($matches[1])) { return $this->parseXml($matches[1]); diff --git a/src/OutputParsers/XmlTagOutputParser.php b/src/OutputParsers/XmlTagOutputParser.php index 3614a15..bd468e3 100644 --- a/src/OutputParsers/XmlTagOutputParser.php +++ b/src/OutputParsers/XmlTagOutputParser.php @@ -34,7 +34,7 @@ public function __construct( public function parse(ChatGeneration|ChatGenerationChunk|string $output): string { $output = match (true) { - $output instanceof ChatGenerationChunk => $output->contentSoFar, + $output instanceof ChatGenerationChunk => $output->textSoFar(), $output instanceof ChatGeneration => $output->message->text() ?? '', default => $output, }; @@ -44,7 +44,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): string // Try closed tag first $pattern = '/<' . preg_quote($this->tagName, '/') . '>((?:(?!<' . preg_quote($this->tagName, '/') . ').)*?)<\/' . preg_quote($this->tagName, '/') . '>/s'; - if (preg_match($pattern, $output, $matches)) { + if (preg_match($pattern, (string) $output, $matches)) { return trim($matches[1]); } @@ -52,7 +52,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): string if ($this->allowUnclosedTags) { $pattern = '/<' . preg_quote($this->tagName, '/') . '>([^<]*?)(?:<\/' . preg_quote($this->tagName, '/') . '>|\s*$)/s'; - if (preg_match($pattern, $output, $matches)) { + if (preg_match($pattern, (string) $output, $matches)) { return trim($matches[1]); } } @@ -65,7 +65,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): string // Try closed tags first foreach (self::CLOSED_TAG_PATTERNS as $pattern) { - if (preg_match($pattern, $output, $matches)) { + if (preg_match($pattern, (string) $output, $matches)) { return trim($matches[2]); } } @@ -73,7 +73,7 @@ public function parse(ChatGeneration|ChatGenerationChunk|string $output): string // If unclosed tags are allowed, try those patterns if ($this->allowUnclosedTags) { foreach (self::UNCLOSED_TAG_PATTERNS as $pattern) { - if (preg_match($pattern, $output, $matches)) { + if (preg_match($pattern, (string) $output, $matches)) { return trim($matches[2]); } } diff --git a/tests/Unit/Agents/AgentTest.php b/tests/Unit/Agents/AgentTest.php index 7438359..dd10b96 100644 --- a/tests/Unit/Agents/AgentTest.php +++ b/tests/Unit/Agents/AgentTest.php @@ -642,9 +642,9 @@ function (int $x, int $y): int { $finalChunk = array_find($llmChunks, fn($chunk): bool => $chunk instanceof ChatGenerationChunk && $chunk->isFinal); expect($finalChunk)->not->toBeNull('Should have a final chunk') - ->and($finalChunk->contentSoFar)->toContain('Hello!') - ->and($finalChunk->contentSoFar)->toContain('program') - ->and($finalChunk->contentSoFar)->toContain('assist you today'); + ->and($finalChunk->textSoFar())->toContain('Hello!') + ->and($finalChunk->textSoFar())->toContain('program') + ->and($finalChunk->textSoFar())->toContain('assist you today'); }); test('it dispatches AgentStart and AgentEnd events only once when streaming', function (): void { diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index 8ef3919..01a36be 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -24,6 +24,7 @@ use Cortex\LLM\Drivers\Anthropic\AnthropicChat; use Cortex\SDK\Anthropic\Requests\CreateMessage; use Cortex\LLM\Data\Messages\Content\TextContent; +use Cortex\LLM\Data\Messages\Content\ReasoningContent; use Anthropic\Responses\Messages\CreateStreamedResponse as ChatCreateStreamedResponse; test('it responds to messages', function (): void { @@ -56,16 +57,125 @@ expect($chunks)->toBeInstanceOf(ChatStreamResult::class); - // dd($chunks->toArray()); + $allChunks = []; + $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) use (&$allChunks) { + $allChunks[] = $chunk; - $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) { expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) - ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); + ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class) + ->and($chunk->contentSoFar)->toBeArray(); + + // Verify contentSoFar contains content objects + foreach ($chunk->contentSoFar as $content) { + expect($content instanceof TextContent || $content instanceof ReasoningContent)->toBeTrue(); + } return $carry . ($chunk->message->content ?? ''); }, ''); expect($output)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?"); + + // Verify final chunk has complete content in contentSoFar + $finalChunk = null; + foreach ($allChunks as $chunk) { + if ($chunk->isFinal) { + /** @var \Cortex\LLM\Data\ChatGenerationChunk $finalChunk */ + $finalChunk = $chunk; + } + } + + // If still no chunks, something is wrong + expect(count($allChunks))->toBeGreaterThan(0, 'Expected at least one chunk to be yielded') + ->and($finalChunk)->not->toBeNull() + ->and($finalChunk->contentSoFar)->toBeArray() + ->and($finalChunk->contentSoFar)->toHaveCount(1) + ->and($finalChunk->contentSoFar[0])->toBeInstanceOf(TextContent::class) + ->and($finalChunk->contentSoFar[0]->text)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?"); +}); + +test('it can stream with reasoning content', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/thinking-streamed'), + ]); + + $llm->withStreaming(); + + $chunks = $llm->includeRaw()->stream([ + new UserMessage('What is the mass of the Moon?'), + ]); + + expect($chunks)->toBeInstanceOf(ChatStreamResult::class); + + $hasReasoningContent = false; + $hasTextContent = false; + $finalChunk = null; + + foreach ($chunks as $chunk) { + expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) + ->and($chunk->contentSoFar)->toBeArray(); + + // Verify contentSoFar contains content objects + foreach ($chunk->contentSoFar as $content) { + expect($content instanceof TextContent || $content instanceof ReasoningContent)->toBeTrue(); + + if ($content instanceof ReasoningContent) { + $hasReasoningContent = true; + expect($content->id)->toBeString() + ->and($content->reasoning)->toBeString(); + } + + if ($content instanceof TextContent) { + $hasTextContent = true; + } + } + + if ($chunk->isFinal) { + $finalChunk = $chunk; + } + } + + expect($hasReasoningContent)->toBeTrue('Expected at least one chunk to have reasoning content') + ->and($hasTextContent)->toBeTrue('Expected at least one chunk to have text content') + ->and($finalChunk)->not->toBeNull() + ->and($finalChunk->contentSoFar)->toBeArray() + ->and($finalChunk->contentSoFar)->toHaveCount(2) // Should have both reasoning and text content + ->and($finalChunk->contentSoFar[0])->toBeInstanceOf(ReasoningContent::class) + ->and($finalChunk->contentSoFar[1])->toBeInstanceOf(TextContent::class); +}); + +test('it tracks contentSoFar incrementally during streaming', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple-streamed'), + ]); + + $llm->withStreaming(); + + $chunks = $llm->invoke([ + new UserMessage('Hello, how are you?'), + ]); + + $contentSoFarLengths = []; + $hasPartialContent = false; + + foreach ($chunks as $chunk) { + expect($chunk->contentSoFar)->toBeArray(); + + $contentSoFarLengths[] = count($chunk->contentSoFar); + + // During streaming, contentSoFar should include partial content being built + if ($chunk->contentSoFar !== []) { + $hasPartialContent = true; + // Verify the last item is a content object + $contentArray = $chunk->contentSoFar; + $lastContent = $contentArray[count($contentArray) - 1]; + expect($lastContent instanceof TextContent || $lastContent instanceof ReasoningContent)->toBeTrue(); + } + } + + // Verify that contentSoFar grows during streaming + expect($hasPartialContent)->toBeTrue('Expected contentSoFar to include partial content during streaming') + ->and($contentSoFarLengths)->toContain(1) // Should have at least one content item at some point + ->and(max($contentSoFarLengths))->toBeGreaterThanOrEqual(1); // Should reach at least 1 item }); test('it can use tools', function (): void { diff --git a/tests/Unit/LLM/Drivers/OpenAI/OpenAIChatTest.php b/tests/Unit/LLM/Drivers/OpenAI/OpenAIChatTest.php index 5020ecd..67e4374 100644 --- a/tests/Unit/LLM/Drivers/OpenAI/OpenAIChatTest.php +++ b/tests/Unit/LLM/Drivers/OpenAI/OpenAIChatTest.php @@ -29,6 +29,7 @@ use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Drivers\OpenAI\Chat\OpenAIChat; use Cortex\LLM\Data\Messages\MessageCollection; +use Cortex\LLM\Data\Messages\Content\TextContent; use OpenAI\Responses\Chat\CreateResponse as ChatCreateResponse; use OpenAI\Responses\Chat\CreateStreamedResponse as ChatCreateStreamedResponse; @@ -71,12 +72,21 @@ $expectedOutput = 'Hello! I’m just a program, so I don’t have feelings, but I’m here and ready to help you with whatever you need. How can I assist you today?'; $chunkTypes = []; - $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) use (&$chunkTypes, $expectedOutput) { + $allChunks = []; + $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) use (&$chunkTypes, &$allChunks, $expectedOutput) { $chunkTypes[] = $chunk->type; + $allChunks[] = $chunk; + expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class) + ->and($chunk->contentSoFar)->toBeArray() ->and($expectedOutput)->toContain($chunk->message->content); + // Verify contentSoFar contains TextContent objects + foreach ($chunk->contentSoFar as $content) { + expect($content)->toBeInstanceOf(TextContent::class); + } + return $carry . $chunk->message->content; }, ''); @@ -87,7 +97,49 @@ ->and($chunkTypes[0])->toBe(ChunkType::TextStart) // First text content ->and($chunkTypes[1])->toBe(ChunkType::TextDelta) // Subsequent text ->and($chunkTypes[37])->toBe(ChunkType::TextDelta) // Last text content - ->and($chunkTypes[38])->toBe(ChunkType::TextEnd); // Text end in flush + ->and($chunkTypes[38])->toBe(ChunkType::TextEnd); + $finalChunk = array_find($allChunks, fn($chunk) => $chunk->isFinal); + + expect($finalChunk)->not->toBeNull() + ->and($finalChunk->contentSoFar)->toBeArray() + ->and($finalChunk->contentSoFar)->toHaveCount(1) + ->and($finalChunk->contentSoFar[0])->toBeInstanceOf(TextContent::class) + ->and($finalChunk->contentSoFar[0]->text)->toBe($expectedOutput); +}); + +test('it tracks contentSoFar incrementally during streaming', function (): void { + $llm = OpenAIChat::fake([ + ChatCreateStreamedResponse::fake(fopen(__DIR__ . '/../../../../fixtures/openai/chat-stream.txt', 'r')), + ]); + + $llm->withStreaming(); + + $chunks = $llm->invoke([ + new UserMessage('Hello, how are you?'), + ]); + + $contentSoFarLengths = []; + $hasPartialContent = false; + + foreach ($chunks as $chunk) { + expect($chunk->contentSoFar)->toBeArray(); + + $contentSoFarLengths[] = count($chunk->contentSoFar); + + // During streaming, contentSoFar should include partial content being built + if ($chunk->contentSoFar !== []) { + $hasPartialContent = true; + // Verify the last item is a TextContent object + $contentArray = $chunk->contentSoFar; + $lastContent = $contentArray[count($contentArray) - 1]; + expect($lastContent instanceof TextContent)->toBeTrue(); + } + } + + // Verify that contentSoFar grows during streaming + expect($hasPartialContent)->toBeTrue('Expected contentSoFar to include partial content during streaming') + ->and($contentSoFarLengths)->toContain(1) // Should have at least one content item at some point + ->and(max($contentSoFarLengths))->toBeGreaterThanOrEqual(1); // Should reach at least 1 item }); test('it can use tools', function (): void { diff --git a/tests/Unit/LLM/StreamBufferTest.php b/tests/Unit/LLM/StreamBufferTest.php index c5d771f..525da41 100644 --- a/tests/Unit/LLM/StreamBufferTest.php +++ b/tests/Unit/LLM/StreamBufferTest.php @@ -33,7 +33,7 @@ $next = function (mixed $payload, RuntimeConfig $config): mixed { if ($payload instanceof ChatGenerationChunk) { // Push item during stream - $config->stream->push('mid-' . $payload->contentSoFar); + $config->stream->push('mid-' . $payload->textSoFar()); } return $payload; @@ -53,12 +53,12 @@ expect($items[0])->toBe('start'); expect($items[1])->toBeInstanceOf(ChatGenerationChunk::class); - expect($items[1]->contentSoFar)->toBe('A'); // First chunk has contentSoFar = 'A' + expect($items[1]->textSoFar())->toBe('A'); // First chunk has contentSoFar = 'A' expect($items[2])->toBe('mid-A'); expect($items[3])->toBeInstanceOf(ChatGenerationChunk::class); - expect($items[3]->contentSoFar)->toBe('AB'); // Second chunk accumulates: 'A' + 'B' = 'AB' + expect($items[3]->textSoFar())->toBe('AB'); // Second chunk accumulates: 'A' + 'B' = 'AB' expect($items[4])->toBe('mid-AB'); // Buffer item uses contentSoFar from the chunk ('AB') }); From 426b6afa32a9f7bf7f2db0dbf601d4694268fd58 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sun, 4 Jan 2026 00:44:51 +0000 Subject: [PATCH 09/54] update tests --- .../Anthropic/Concerns/MapStreamResponse.php | 41 +- .../Drivers/Anthropic/AnthropicChatTest.php | 368 +++++++----------- 2 files changed, 172 insertions(+), 237 deletions(-) diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 26c1b24..28405c1 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -7,6 +7,8 @@ use Generator; use DateTimeImmutable; use Cortex\LLM\Enums\ChunkType; +use Cortex\Events\ChatModelStream; +use Cortex\Events\ChatModelStreamEnd; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ResponseMetadata; use Cortex\LLM\Data\ChatGenerationChunk; @@ -28,7 +30,9 @@ use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ThinkingContentBlock; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\ContentBlockStart; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\ServerToolUseContentBlock; use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\RedactedThinkingContentBlock; +use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\WebSearchToolResultContentBlock; /** @mixin \Cortex\LLM\Drivers\Anthropic\AnthropicChat */ trait MapStreamResponse @@ -46,12 +50,15 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult $contentSoFar = []; $currentTextContent = null; $currentReasoningContent = null; + $chatGenerationChunk = null; + /** @var array> $contentBlocksByIndex */ + $contentBlocksByIndex = []; /** @var \Cortex\SDK\Anthropic\Contracts\StreamEvent $event */ foreach ($response as $event) { yield from $this->streamBuffer?->drain() ?? []; - $chunkType = $this->mapChunkType($event); + $chunkType = $this->mapChunkType($event, $contentBlocksByIndex); if ($chunkType === null) { continue; @@ -65,6 +72,8 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult // Handle ContentBlockStart - initialize new content objects if ($event instanceof ContentBlockStart) { + $contentBlocksByIndex[$event->index] = $event->contentBlock::class; + if ($event->contentBlock instanceof TextContentBlock) { $currentTextContent = new TextContent($event->contentBlock->text); } elseif ($event->contentBlock instanceof ThinkingContentBlock) { @@ -151,14 +160,25 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult rawChunk: $this->includeRaw ? $event->raw() : null, ); + $chatGenerationChunk = $this->applyOutputParserIfApplicable($chatGenerationChunk); + + $this->dispatchEvent(new ChatModelStream($this, $chatGenerationChunk)); + yield $chatGenerationChunk; } yield from $this->streamBuffer?->drain() ?? []; + + if ($chatGenerationChunk !== null) { + $this->dispatchEvent(new ChatModelStreamEnd($this, $chatGenerationChunk)); + } }); } - protected function mapChunkType(StreamEvent $event): ?ChunkType + /** + * @param array> $contentBlocksByIndex + */ + protected function mapChunkType(StreamEvent $event, array $contentBlocksByIndex = []): ?ChunkType { if ($event instanceof MessageStart) { return ChunkType::MessageStart; @@ -188,14 +208,15 @@ protected function mapChunkType(StreamEvent $event): ?ChunkType } if ($event instanceof ContentBlockStop) { - - return ChunkType::TextEnd; - // return match (get_class($event->contentBlock)) { - // TextContentBlock::class => ChunkType::TextEnd, - // ThinkingContentBlock::class => ChunkType::ReasoningEnd, - // RedactedThinkingContentBlock::class => ChunkType::ReasoningEnd, - // ToolUseContentBlock::class => ChunkType::ToolInputEnd, - // }; + $contentBlockClass = $contentBlocksByIndex[$event->index] ?? null; + + return match ($contentBlockClass) { + TextContentBlock::class => ChunkType::TextEnd, + ThinkingContentBlock::class, RedactedThinkingContentBlock::class => ChunkType::ReasoningEnd, + ToolUseContentBlock::class => ChunkType::ToolInputEnd, + WebSearchToolResultContentBlock::class, ServerToolUseContentBlock::class => ChunkType::ToolOutputEnd, + default => ChunkType::TextEnd, + }; } return ChunkType::Custom; diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index 01a36be..d56f4ff 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -4,14 +4,14 @@ namespace Cortex\Tests\Unit\LLM\Drivers\Anthropic; -use Cortex\Cortex; use Cortex\LLM\Data\Usage; use Cortex\Attributes\Tool; -use Cortex\Tools\SchemaTool; use Cortex\JsonSchema\Schema; use Cortex\LLM\Data\ToolCall; use Cortex\LLM\Data\ChatResult; +use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Data\FunctionCall; +use Saloon\Http\Faking\MockClient; use Cortex\LLM\Data\ChatGeneration; use Saloon\Http\Faking\MockResponse; use Cortex\LLM\Data\ChatStreamResult; @@ -19,13 +19,11 @@ use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\ModelInfo\Enums\ModelFeature; use Cortex\LLM\Data\Messages\UserMessage; -use Cortex\LLM\Enums\StructuredOutputMode; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Drivers\Anthropic\AnthropicChat; use Cortex\SDK\Anthropic\Requests\CreateMessage; use Cortex\LLM\Data\Messages\Content\TextContent; use Cortex\LLM\Data\Messages\Content\ReasoningContent; -use Anthropic\Responses\Messages\CreateStreamedResponse as ChatCreateStreamedResponse; test('it responds to messages', function (): void { $llm = AnthropicChat::fake([ @@ -143,6 +141,73 @@ ->and($finalChunk->contentSoFar[1])->toBeInstanceOf(TextContent::class); }); +test('it emits correct chunk types for content block end events', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/thinking-streamed'), + ]); + + $llm->withStreaming(); + + $chunks = $llm->stream([ + new UserMessage('What is the mass of the Moon?'), + ]); + + expect($chunks)->toBeInstanceOf(ChatStreamResult::class); + + $chunkTypes = []; + + foreach ($chunks as $chunk) { + $chunkTypes[] = $chunk->type; + } + + // Verify we have the correct sequence of chunk types + // The thinking-streamed fixture has: thinking block (index 0) then text block (index 1) + expect($chunkTypes)->toContain(ChunkType::MessageStart) + ->and($chunkTypes)->toContain(ChunkType::ReasoningStart) + ->and($chunkTypes)->toContain(ChunkType::ReasoningDelta) + ->and($chunkTypes)->toContain(ChunkType::ReasoningEnd) + ->and($chunkTypes)->toContain(ChunkType::TextStart) + ->and($chunkTypes)->toContain(ChunkType::TextDelta) + ->and($chunkTypes)->toContain(ChunkType::TextEnd); + + // Verify the order: ReasoningEnd should come before TextStart + $reasoningEndIndex = array_search(ChunkType::ReasoningEnd, $chunkTypes, true); + $textStartIndex = array_search(ChunkType::TextStart, $chunkTypes, true); + + expect($reasoningEndIndex)->toBeLessThan($textStartIndex); +}); + +test('it emits TextEnd for text-only streaming', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple-streamed'), + ]); + + $llm->withStreaming(); + + $chunks = $llm->stream([ + new UserMessage('Hello, how are you?'), + ]); + + expect($chunks)->toBeInstanceOf(ChatStreamResult::class); + + $chunkTypes = []; + + foreach ($chunks as $chunk) { + $chunkTypes[] = $chunk->type; + } + + // Verify we have the correct chunk types for text-only streaming + expect($chunkTypes)->toContain(ChunkType::MessageStart) + ->and($chunkTypes)->toContain(ChunkType::TextStart) + ->and($chunkTypes)->toContain(ChunkType::TextDelta) + ->and($chunkTypes)->toContain(ChunkType::TextEnd); + + // Verify we don't have any reasoning chunk types + expect($chunkTypes)->not->toContain(ChunkType::ReasoningStart) + ->and($chunkTypes)->not->toContain(ChunkType::ReasoningDelta) + ->and($chunkTypes)->not->toContain(ChunkType::ReasoningEnd); +}); + test('it tracks contentSoFar incrementally during streaming', function (): void { $llm = AnthropicChat::fake([ CreateMessage::class => MockResponse::fixture('anthropic/messages/simple-streamed'), @@ -247,64 +312,27 @@ ]); }); -test('it can use structured output using the schema tool', function (): void { - $llm = AnthropicChat::fake([ - CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output-tool-use'), - ]); - - $llm->addFeature(ModelFeature::ToolCalling); - - $schema = Schema::object('Person')->properties( - Schema::string('name'), - Schema::integer('age'), - ); - - $llm->withStructuredOutput($schema, outputMode: StructuredOutputMode::Tool); - - $result = $llm->withMaxTokens(1024)->invoke([ - new UserMessage('Tell me about a person'), - ]); - - expect($result->parsedOutput) - ->toBe([ - 'name' => 'John Smith', - 'age' => 35, - ]); - - expect($result->generation->message->content) - ->toBeArray() - ->toContainOnlyInstancesOf(TextContent::class); - - expect($result->generation->message->toolCalls) - ->toBeInstanceOf(ToolCallCollection::class) - ->and($result->generation->message->toolCalls) - ->toHaveCount(1) - ->and($result->generation->message->toolCalls[0]) - ->toBeInstanceOf(ToolCall::class) - ->and($result->generation->message->toolCalls[0]->function) - ->toBeInstanceOf(FunctionCall::class) - ->and($result->generation->message->toolCalls[0]->function->name) - ->toBe(SchemaTool::NAME); -})->skip(); - test('it can stream with structured output', function (): void { $llm = AnthropicChat::fake([ - ChatCreateStreamedResponse::fake(fopen(__DIR__ . '/../../../../fixtures/anthropic/chat-stream-json.txt', 'r')), - ], 'claude-3-5-sonnet-20241022'); + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output-streamed'), + ]); $llm->addFeature(ModelFeature::StructuredOutput); $llm->withStreaming(); - class AnthropicJoke - { - public function __construct( - public ?string $setup = null, - public ?string $punchline = null, - ) {} - } + $llm->withStructuredOutput( + Schema::object() + ->properties( + Schema::string('name')->required(), + Schema::string('email')->required(), + Schema::string('plan_interest')->required(), + Schema::boolean('demo_requested')->required(), + ) + ->additionalProperties(false), + ); - $result = $llm->withStructuredOutput(AnthropicJoke::class)->invoke([ - new UserMessage('Tell me a joke about dogs'), + $result = $llm->invoke([ + new UserMessage('Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.'), ]); expect($result)->toBeInstanceOf(ChatStreamResult::class); @@ -316,227 +344,113 @@ public function __construct( // Check if this chunk has the parsed output we expect if ($chunk->isFinal) { - expect($chunk->parsedOutput->setup)->toBe('Why did the dog sit in the shade?') - ->and($chunk->parsedOutput->punchline)->toBe("Because he didn't want to be a hot dog!"); + expect($chunk->parsedOutput)->toBe([ + 'name' => 'John Smith', + 'email' => 'john@example.com', + 'plan_interest' => 'Enterprise', + 'demo_requested' => true, + ]); $hasValidParsedOutput = true; } }); expect($hasValidParsedOutput)->toBeTrue('Expected at least one chunk to have valid parsed output'); - - /** @var \Anthropic\Testing\ClientFake $client */ - $client = $llm->getClient(); - - $client->messages()->assertSent(function (string $method, array $parameters): bool { - return $parameters['model'] === 'claude-3-5-sonnet-20241022' - && $parameters['messages'][0]['role'] === 'user' - && $parameters['messages'][0]['content'] === 'Tell me a joke about dogs'; - }); -})->skip(); +}); test('it can stream with tool calls', function (): void { $llm = AnthropicChat::fake([ - ChatCreateStreamedResponse::fake(fopen(__DIR__ . '/../../../../fixtures/anthropic/chat-stream-tool-calls.txt', 'r')), - ], 'claude-3-5-sonnet-20241022'); + CreateMessage::class => MockResponse::fixture('anthropic/messages/tool-use-streamed'), + ]); $llm->addFeature(ModelFeature::ToolCalling); $llm->withStreaming(); $llm->withTools([ - #[Tool(name: 'multiply', description: 'Multiply two numbers')] - fn(int $x, int $y): int => $x * $y, + #[Tool(name: 'get_weather', description: 'Get the current weather in a given location')] + fn(string $location): string => sprintf('The weather in %s is raining', $location), ]); $result = $llm->invoke([ - new UserMessage('What is 3 times 4?'), + new UserMessage('What is the weather in Manchester?'), ]); expect($result)->toBeInstanceOf(ChatStreamResult::class); - $hasToolCalls = false; - $toolCallsAccumulated = false; + $hasToolInputChunks = false; + $finalChunk = null; - $result->each(function (ChatGenerationChunk $chunk) use (&$hasToolCalls, &$toolCallsAccumulated): void { + $result->each(function (ChatGenerationChunk $chunk) use (&$hasToolInputChunks, &$finalChunk): void { expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); - // Check if we have tool calls in any chunk - if ($chunk->message->toolCalls?->isNotEmpty()) { - $hasToolCalls = true; - - // Check if tool calls are properly accumulated - if ($chunk->isFinal) { - expect($chunk->message->toolCalls) - ->toBeInstanceOf(ToolCallCollection::class) - ->and($chunk->message->toolCalls) - ->toHaveCount(1) - ->and($chunk->message->toolCalls[0]) - ->toBeInstanceOf(ToolCall::class) - ->and($chunk->message->toolCalls[0]->id) - ->toBe('call_multiply_123') - ->and($chunk->message->toolCalls[0]->function) - ->toBeInstanceOf(FunctionCall::class) - ->and($chunk->message->toolCalls[0]->function->name) - ->toBe('multiply') - ->and($chunk->message->toolCalls[0]->function->arguments) - ->toBe([ - 'x' => 3, - 'y' => 4, - ]); - - $toolCallsAccumulated = true; - } + if ($chunk->type === ChunkType::ToolInputStart || $chunk->type === ChunkType::ToolInputDelta) { + $hasToolInputChunks = true; } - }); - - expect($hasToolCalls)->toBeTrue('Expected at least one chunk to have tool calls') - ->and($toolCallsAccumulated)->toBeTrue('Expected final chunk to have complete tool call data'); - /** @var \Anthropic\Testing\ClientFake $client */ - $client = $llm->getClient(); - - $client->messages()->assertSent(function (string $method, array $parameters): bool { - return $parameters['model'] === 'claude-3-5-sonnet-20241022' - && $parameters['messages'][0]['role'] === 'user' - && $parameters['messages'][0]['content'] === 'What is 3 times 4?' - && $parameters['tools'][0]['type'] === 'custom' - && $parameters['tools'][0]['name'] === 'multiply'; + if ($chunk->isFinal) { + $finalChunk = $chunk; + } }); -})->skip(); -test('it can use structured output with an enum', function (): void { + expect($hasToolInputChunks)->toBeTrue('Expected at least one chunk to have tool input') + ->and($finalChunk)->not->toBeNull('Expected a final chunk'); +}); + +test('it can use structured output with a class', function (): void { + // structured-output.json returns: + // {"name":"John Smith","email":"john@example.com","plan_interest":"Enterprise","demo_requested":true} $llm = AnthropicChat::fake([ - createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'text', - 'text' => '{"AnthropicSentiment":"positive"}', - ], - ], - ]), - createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'text', - 'text' => '{"AnthropicSentiment":"neutral"}', - ], - ], - ]), - createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'text', - 'text' => '{"AnthropicSentiment":"negative"}', - ], - ], - ]), - createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'text', - 'text' => '{"AnthropicSentiment":"neutral"}', - ], - ], - ]), - ], 'claude-3-5-sonnet-20241022'); + CreateMessage::class => MockResponse::fixture('anthropic/messages/structured-output'), + ]); $llm->addFeature(ModelFeature::StructuredOutput); - enum AnthropicSentiment: string + class AnthropicProspect { - case Positive = 'positive'; - case Negative = 'negative'; - case Neutral = 'neutral'; + public function __construct( + public string $name, + public string $email, + public string $plan_interest, + public bool $demo_requested, + ) {} } - $llm->withStructuredOutput(AnthropicSentiment::class); - - expect($llm->invoke('Analyze the sentiment of this text: This pizza is awesome')->parsedOutput) - ->toBe(AnthropicSentiment::Positive); - - expect($llm->invoke('Analyze the sentiment of this text: This pizza is okay')->parsedOutput) - ->toBe(AnthropicSentiment::Neutral); + $llm->withStructuredOutput(AnthropicProspect::class); - expect($llm->invoke('Analyze the sentiment of this text: This pizza is terrible')->parsedOutput) - ->toBe(AnthropicSentiment::Negative); - - $getSentiment = Cortex::prompt('Analyze the sentiment of this text: {input}')->llm($llm); - - $result = $getSentiment->invoke('This pizza is average'); + $result = $llm->invoke('Extract the key information from this email'); expect($result)->toBeInstanceOf(ChatResult::class) - ->and($result->parsedOutput)->toBe(AnthropicSentiment::Neutral); -})->skip(); - -test('it can force json output', function (): void { - $response = createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'text', - 'text' => '{"setup":"Why did the scarecrow win an award?","punchline":"Because he was outstanding in his field!"}', - ], - ], - ]); - - $llm = AnthropicChat::fake([ - $response, - ], 'claude-3-5-sonnet-20241022'); - - $llm->addFeature(ModelFeature::JsonOutput); - $llm->forceJsonOutput(); - - $result = $llm->invoke([ - new UserMessage('Tell me a joke'), - ]); - - expect($result->generation->message->content) - ->toBeArray() - ->toContainOnlyInstancesOf(TextContent::class) - ->and($result->generation->message->content[0]->text) - ->toBe('{"setup":"Why did the scarecrow win an award?","punchline":"Because he was outstanding in his field!"}'); -})->skip(); + ->and($result->parsedOutput)->toBeInstanceOf(AnthropicProspect::class) + ->and($result->parsedOutput->name)->toBe('John Smith') + ->and($result->parsedOutput->email)->toBe('john@example.com') + ->and($result->parsedOutput->plan_interest)->toBe('Enterprise') + ->and($result->parsedOutput->demo_requested)->toBeTrue(); +}); test('it can set temperature and max tokens', function (): void { $llm = AnthropicChat::fake([ - createAnthropicResponse([ - 'content' => [ - [ - 'type' => 'text', - 'text' => 'Hello!', - ], - ], - ]), + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), ]); - $llm->withTemperature(0.7)->withMaxTokens(100)->invoke([ + $result = $llm->withTemperature(0.7)->withMaxTokens(100)->invoke([ new UserMessage('Hello'), ]); - /** @var \Anthropic\Testing\ClientFake $client */ - $client = $llm->getClient(); + // Verify the request completes successfully with the configured parameters + expect($result)->toBeInstanceOf(ChatResult::class) + ->and($result->generation->message->content)->toBeArray() + ->toContainOnlyInstancesOf(TextContent::class); - $client->messages()->assertSent(function (string $method, array $parameters): bool { - return $parameters['temperature'] === 0.7 && $parameters['max_tokens'] === 100; + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + return $request->body()->get('temperature') === 0.7 && $request->body()->get('max_tokens') === 100; }); -})->skip(); +}); test('it tracks token usage', function (): void { - $response = createAnthropicResponse([ - 'usage' => [ - 'input_tokens' => 10, - 'output_tokens' => 20, - ], - 'content' => [ - [ - 'type' => 'text', - 'text' => 'Hello!', - ], - ], - ]); - + // The simple.json fixture includes usage: input_tokens: 13, output_tokens: 30 $llm = AnthropicChat::fake([ - $response, + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), ]); $result = $llm->invoke([ @@ -545,6 +459,6 @@ enum AnthropicSentiment: string expect($result->usage) ->toBeInstanceOf(Usage::class) - ->and($result->usage->promptTokens)->toBe(10) - ->and($result->usage->completionTokens)->toBe(20); -})->skip(); + ->and($result->usage->promptTokens)->toBe(13) + ->and($result->usage->completionTokens)->toBe(30); +}); From 7b308340eecda898112b0aca5d64ded715233f93 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sun, 4 Jan 2026 00:47:03 +0000 Subject: [PATCH 10/54] stan --- src/Tools/Prebuilt/OpenMeteoWeatherTool.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Tools/Prebuilt/OpenMeteoWeatherTool.php b/src/Tools/Prebuilt/OpenMeteoWeatherTool.php index 6033ccc..97c2a07 100644 --- a/src/Tools/Prebuilt/OpenMeteoWeatherTool.php +++ b/src/Tools/Prebuilt/OpenMeteoWeatherTool.php @@ -41,7 +41,6 @@ public function invoke(ToolCall|array $toolCall = [], ?RuntimeConfig $config = n $location = $arguments['location']; /** @var \Illuminate\Http\Client\Response $geocodeResponse */ - // @phpstan-ignore staticMethod.void $geocodeResponse = Http::get('https://geocoding-api.open-meteo.com/v1/search', [ 'name' => $location, 'count' => 1, @@ -59,7 +58,6 @@ public function invoke(ToolCall|array $toolCall = [], ?RuntimeConfig $config = n $windSpeedUnit = $config?->context?->get('wind_speed_unit') ?? 'mph'; /** @var \Illuminate\Http\Client\Response $weatherResponse */ - // @phpstan-ignore staticMethod.void $weatherResponse = Http::get('https://api.open-meteo.com/v1/forecast', [ 'latitude' => $latitude, 'longitude' => $longitude, From 44ccc3cec54aa33e347a50ebfd5597e6e35a2bbe Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sun, 4 Jan 2026 14:02:05 +0000 Subject: [PATCH 11/54] wip --- src/Agents/Prebuilt/WeatherAgent.php | 3 +- src/LLM/Drivers/Anthropic/AnthropicChat.php | 92 +--- .../Anthropic/Concerns/MapStreamResponse.php | 421 ++++++++++++------ .../Anthropic/Concerns/MapsMessages.php | 199 +++++++++ src/SDK/Anthropic/Resources/Messages.php | 4 +- .../Drivers/Anthropic/AnthropicChatTest.php | 281 +++++++++++- .../app/Providers/CortexServiceProvider.php | 48 +- 7 files changed, 811 insertions(+), 237 deletions(-) create mode 100644 src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php diff --git a/src/Agents/Prebuilt/WeatherAgent.php b/src/Agents/Prebuilt/WeatherAgent.php index 64858e7..a9a3727 100644 --- a/src/Agents/Prebuilt/WeatherAgent.php +++ b/src/Agents/Prebuilt/WeatherAgent.php @@ -46,7 +46,8 @@ public function llm(): LLM|string|null { // return Cortex::llm('ollama', 'gpt-oss:20b')->ignoreFeatures(); // return Cortex::llm('openai', 'gpt-4.1-mini')->ignoreFeatures(); - return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); + // return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); + return Cortex::llm('anthropic', 'claude-sonnet-4-5-20250929')->ignoreFeatures(); } #[Override] diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 08471ee..84eb760 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -6,25 +6,21 @@ use Throwable; use Cortex\LLM\AbstractLLM; -use Illuminate\Support\Arr; use Cortex\LLM\Contracts\Tool; use Cortex\LLM\Data\ChatResult; use Cortex\LLM\Enums\ToolChoice; use Cortex\Events\ChatModelError; use Cortex\Events\ChatModelStart; use Cortex\LLM\Contracts\Message; -use Cortex\LLM\Enums\MessageRole; use Cortex\Exceptions\LLMException; use Cortex\SDK\Anthropic\Anthropic; use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\Messages\ToolMessage; use Cortex\ModelInfo\Enums\ModelProvider; use Cortex\LLM\Enums\StructuredOutputMode; use Cortex\LLM\Data\Messages\SystemMessage; -use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Data\Messages\MessageCollection; -use Cortex\LLM\Data\Messages\Content\TextContent; use Cortex\LLM\Drivers\Anthropic\Concerns\MapsUsage; +use Cortex\LLM\Drivers\Anthropic\Concerns\MapsMessages; use Cortex\LLM\Drivers\Anthropic\Concerns\MapsResponse; use Cortex\LLM\Drivers\Anthropic\Concerns\MapsFinishReason; use Cortex\LLM\Drivers\Anthropic\Concerns\MapStreamResponse; @@ -32,6 +28,7 @@ class AnthropicChat extends AbstractLLM { use MapsUsage; + use MapsMessages; use MapsResponse; use MapsFinishReason; use MapStreamResponse; @@ -65,7 +62,7 @@ public function invoke( $params = $this->buildParams([ ...$additionalParameters, - 'messages' => static::mapMessagesForInput($nonSystemMessages), + 'messages' => $this->mapMessagesForInput($nonSystemMessages), ]); /** @var \Cortex\LLM\Data\Messages\SystemMessage|null $systemMessage */ @@ -88,73 +85,6 @@ public function invoke( } } - /** - * Take the given messages and format them for the OpenAI API. - * - * @return array> - */ - protected static function mapMessagesForInput(MessageCollection $messages): array - { - return $messages - ->map(function (Message $message) { - if ($message instanceof ToolMessage) { - return [ - 'role' => MessageRole::User->value, - 'content' => [ - [ - 'type' => 'tool_use', - 'tool_use_id' => $message->id, - 'content' => $message->content, - ], - ], - ]; - } - - if ($message instanceof AssistantMessage && $message->toolCalls?->isNotEmpty()) { - $formattedMessage = $message->toArray(); - - // Ensure the function arguments are encoded as a string - foreach ($message->toolCalls as $index => $toolCall) { - Arr::set( - $formattedMessage, - 'tool_calls.' . $index . '.function.arguments', - json_encode($toolCall->function->arguments), - ); - } - - return $formattedMessage; - } - - $formattedMessage = $message->toArray(); - - if (isset($formattedMessage['content']) && is_array($formattedMessage['content'])) { - $formattedMessage['content'] = array_map(function (mixed $content) { - return match (true) { - $content instanceof TextContent => [ - 'type' => 'text', - 'text' => $content->text, - ], - // $content instanceof ImageContent => [ - // 'type' => 'image_url', - // 'image_url' => [ - // 'url' => $content->urlOrBase64, - // ], - // ], - // $content instanceof DocumentContent => [ - // 'type' => 'document', - // 'document' => $content->data, - // ], - default => $content, - }; - }, $formattedMessage['content']); - } - - return $formattedMessage; - }) - ->values() - ->toArray(); - } - /** * @param array $additionalParameters * @@ -168,6 +98,14 @@ protected function buildParams(array $additionalParameters): array if ($this->structuredOutputConfig !== null) { $this->structuredOutputMode = StructuredOutputMode::Tool; + $params['betas'] ??= []; + $params['betas'][] = 'structured-outputs-2025-11-13'; + + $params['output_format'] = [ + 'type' => 'json_schema', + 'schema' => $this->structuredOutputConfig->schema->additionalProperties(false)->toArray(), + ]; + } elseif ($this->forceJsonOutput) { $this->structuredOutputMode = StructuredOutputMode::Json; } @@ -200,11 +138,17 @@ protected function buildParams(array $additionalParameters): array ->toArray(); } - return [ + $finalParams = [ ...$params, ...$this->parameters, ...$additionalParameters, ]; + + if (! isset($finalParams['max_tokens'])) { + throw new LLMException('`max_tokens` parameter is required for Anthropic.'); + } + + return $finalParams; } /** diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 28405c1..fba2da8 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -6,12 +6,17 @@ use Generator; use DateTimeImmutable; +use Cortex\LLM\Data\ToolCall; use Cortex\LLM\Enums\ChunkType; +use Cortex\LLM\Data\FunctionCall; use Cortex\Events\ChatModelStream; use Cortex\Events\ChatModelStreamEnd; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ResponseMetadata; +use Cortex\LLM\Data\ToolCallCollection; use Cortex\LLM\Data\ChatGenerationChunk; +use Cortex\OutputParsers\JsonOutputParser; +use Cortex\Exceptions\OutputParserException; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\SDK\Anthropic\Contracts\StreamEvent; use Cortex\LLM\Data\Messages\Content\TextContent; @@ -37,6 +42,32 @@ /** @mixin \Cortex\LLM\Drivers\Anthropic\AnthropicChat */ trait MapStreamResponse { + /** + * @var array + */ + private array $completedContent = []; + + private ?TextContent $pendingTextContent = null; + + private ?ReasoningContent $pendingReasoningContent = null; + + /** + * @var array> + */ + private array $contentBlockTypes = []; + + /** + * @var array + */ + private array $completedToolCalls = []; + + /** + * @var array{id: string, name: string, partialJson: string}|null + */ + private ?array $pendingToolCall = null; + + private ?int $pendingToolCallIndex = null; + /** * Map a streaming response to a ChatStreamResult. * @@ -45,121 +76,27 @@ trait MapStreamResponse protected function mapStreamResponse(MessageStream $response): ChatStreamResult { return new ChatStreamResult(function () use ($response): Generator { + $this->resetStreamState(); + yield from $this->streamBuffer?->drain() ?? []; - $contentSoFar = []; - $currentTextContent = null; - $currentReasoningContent = null; $chatGenerationChunk = null; - /** @var array> $contentBlocksByIndex */ - $contentBlocksByIndex = []; + $messageId = null; - /** @var \Cortex\SDK\Anthropic\Contracts\StreamEvent $event */ foreach ($response as $event) { yield from $this->streamBuffer?->drain() ?? []; - $chunkType = $this->mapChunkType($event, $contentBlocksByIndex); + $chunkType = $this->mapChunkType($event, $this->contentBlockTypes); if ($chunkType === null) { continue; } - $meta = $event->meta(); - - $finishReason = $event instanceof MessageDelta - ? $this->mapFinishReason($event->stopReason) - : null; - - // Handle ContentBlockStart - initialize new content objects - if ($event instanceof ContentBlockStart) { - $contentBlocksByIndex[$event->index] = $event->contentBlock::class; - - if ($event->contentBlock instanceof TextContentBlock) { - $currentTextContent = new TextContent($event->contentBlock->text); - } elseif ($event->contentBlock instanceof ThinkingContentBlock) { - $currentReasoningContent = new ReasoningContent( - $event->contentBlock->signature, - $event->contentBlock->thinking, - ); - } elseif ($event->contentBlock instanceof RedactedThinkingContentBlock) { - // For redacted thinking, we might use the data field as id - $currentReasoningContent = new ReasoningContent( - $event->contentBlock->data, - $event->contentBlock->text, - ); - } - } + $messageId = $this->extractMessageId($event, $messageId); - // Handle ContentBlockDelta - append incremental content - if ($event instanceof ContentBlockDelta) { - if ($event->delta instanceof TextDelta && $currentTextContent !== null) { - $currentTextContent = $currentTextContent->append($event->delta->text); - } elseif ($event->delta instanceof ThinkingDelta && $currentReasoningContent !== null) { - $currentReasoningContent = $currentReasoningContent->append($event->delta->thinking); - } elseif ($event->delta instanceof SignatureDelta && $currentReasoningContent !== null) { - // Update the ID if signature delta comes after content block start - $currentReasoningContent = new ReasoningContent( - $event->delta->signature, - $currentReasoningContent->reasoning, - ); - } - } - - // Handle ContentBlockStop - finalize and add to contentSoFar - if ($event instanceof ContentBlockStop) { - if ($currentTextContent !== null) { - $contentSoFar[] = $currentTextContent; - $currentTextContent = null; - } elseif ($currentReasoningContent !== null) { - $contentSoFar[] = $currentReasoningContent; - $currentReasoningContent = null; - } - } - - // Build current contentSoFar array including partial content being built - // Clone content objects to avoid mutating references in previous chunks - $currentContentSoFar = [...$contentSoFar]; - - if ($currentTextContent !== null) { - $currentContentSoFar[] = new TextContent($currentTextContent->text); - } elseif ($currentReasoningContent !== null) { - $currentContentSoFar[] = new ReasoningContent($currentReasoningContent->id, $currentReasoningContent->reasoning); - } - - $id = $event instanceof MessageStart - ? $event->message->id - : ($id ?? null); - - $usage = $event instanceof MessageDelta - ? $this->mapUsage($event->cumulativeUsage) - : null; - - $chatGenerationChunk = new ChatGenerationChunk( - type: $chunkType, - id: $id, - message: new AssistantMessage( - content: $event instanceof ContentBlockDelta - ? ($event->delta instanceof TextDelta - ? $event->delta->text - : null) - : null, - // toolCalls: $accumulatedToolCallsSoFar ?? null, - metadata: new ResponseMetadata( - id: $id, - model: $this->model, - provider: $this->modelProvider, - finishReason: $finishReason, - usage: $usage, - ), - ), - createdAt: $meta->createdAt ?? new DateTimeImmutable(), - finishReason: $finishReason, - usage: $usage, - contentSoFar: $currentContentSoFar, - isFinal: $usage !== null, - rawChunk: $this->includeRaw ? $event->raw() : null, - ); + $this->processStreamEvent($event); + $chatGenerationChunk = $this->buildChunk($event, $chunkType, $messageId); $chatGenerationChunk = $this->applyOutputParserIfApplicable($chatGenerationChunk); $this->dispatchEvent(new ChatModelStream($this, $chatGenerationChunk)); @@ -175,50 +112,270 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult }); } + private function resetStreamState(): void + { + $this->completedContent = []; + $this->pendingTextContent = null; + $this->pendingReasoningContent = null; + $this->contentBlockTypes = []; + $this->completedToolCalls = []; + $this->pendingToolCall = null; + $this->pendingToolCallIndex = null; + } + + private function processStreamEvent(StreamEvent $event): void + { + match (true) { + $event instanceof ContentBlockStart => $this->handleContentBlockStart($event), + $event instanceof ContentBlockDelta => $this->handleContentBlockDelta($event), + $event instanceof ContentBlockStop => $this->handleContentBlockStop($event), + default => null, + }; + } + + private function handleContentBlockStart(ContentBlockStart $event): void + { + $this->contentBlockTypes[$event->index] = $event->contentBlock::class; + + match (true) { + $event->contentBlock instanceof TextContentBlock => $this->pendingTextContent = new TextContent($event->contentBlock->text), + $event->contentBlock instanceof ThinkingContentBlock => $this->pendingReasoningContent = new ReasoningContent( + $event->contentBlock->signature, + $event->contentBlock->thinking, + ), + $event->contentBlock instanceof RedactedThinkingContentBlock => $this->pendingReasoningContent = new ReasoningContent( + $event->contentBlock->data, + $event->contentBlock->text, + ), + $event->contentBlock instanceof ToolUseContentBlock => $this->startToolCall($event), + default => null, + }; + } + + private function startToolCall(ContentBlockStart $event): void + { + /** @var ToolUseContentBlock $contentBlock */ + $contentBlock = $event->contentBlock; + + $this->pendingToolCall = [ + 'id' => $contentBlock->id, + 'name' => $contentBlock->name, + 'partialJson' => '', + ]; + $this->pendingToolCallIndex = $event->index; + } + + private function handleContentBlockDelta(ContentBlockDelta $event): void + { + match (true) { + $event->delta instanceof TextDelta && $this->pendingTextContent !== null => $this->pendingTextContent = $this->pendingTextContent->append($event->delta->text), + $event->delta instanceof ThinkingDelta && $this->pendingReasoningContent !== null => $this->pendingReasoningContent = $this->pendingReasoningContent->append($event->delta->thinking), + $event->delta instanceof SignatureDelta && $this->pendingReasoningContent !== null => $this->pendingReasoningContent = new ReasoningContent( + $event->delta->signature, + $this->pendingReasoningContent->reasoning, + ), + $event->delta instanceof InputJsonDelta && $this->pendingToolCall !== null => $this->pendingToolCall['partialJson'] .= $event->delta->partialJson, + default => null, + }; + } + + private function handleContentBlockStop(ContentBlockStop $event): void + { + if ($this->pendingTextContent !== null) { + $this->completedContent[] = $this->pendingTextContent; + $this->pendingTextContent = null; + + return; + } + + if ($this->pendingReasoningContent !== null) { + $this->completedContent[] = $this->pendingReasoningContent; + $this->pendingReasoningContent = null; + + return; + } + + if ($this->pendingToolCall !== null && $this->pendingToolCallIndex === $event->index) { + $this->finalizeToolCall(); + } + } + + private function finalizeToolCall(): void + { + if ($this->pendingToolCall === null) { + return; + } + + $this->completedToolCalls[] = new ToolCall( + $this->pendingToolCall['id'], + new FunctionCall( + $this->pendingToolCall['name'], + $this->parseJsonSafely($this->pendingToolCall['partialJson']), + ), + ); + + $this->pendingToolCall = null; + $this->pendingToolCallIndex = null; + } + /** - * @param array> $contentBlocksByIndex + * @return array */ - protected function mapChunkType(StreamEvent $event, array $contentBlocksByIndex = []): ?ChunkType + private function parseJsonSafely(string $json): array { - if ($event instanceof MessageStart) { - return ChunkType::MessageStart; + if ($json === '') { + return []; } - if ($event instanceof MessageStop) { - return null; + try { + return new JsonOutputParser()->parse($json); + } catch (OutputParserException) { + return []; } + } + + private function extractMessageId(StreamEvent $event, ?string $currentId): ?string + { + return $event instanceof MessageStart + ? $event->message->id + : $currentId; + } - if ($event instanceof ContentBlockStart) { - return match ($event->contentBlock::class) { - TextContentBlock::class => ChunkType::TextStart, - ThinkingContentBlock::class => ChunkType::ReasoningStart, - RedactedThinkingContentBlock::class => ChunkType::ReasoningStart, - ToolUseContentBlock::class => ChunkType::ToolInputStart, - default => null, - }; + /** + * @return array + */ + private function buildContentSnapshot(): array + { + $snapshot = [...$this->completedContent]; + + if ($this->pendingTextContent !== null) { + $snapshot[] = new TextContent($this->pendingTextContent->text); + } elseif ($this->pendingReasoningContent !== null) { + $snapshot[] = new ReasoningContent( + $this->pendingReasoningContent->id, + $this->pendingReasoningContent->reasoning, + ); } - if ($event instanceof ContentBlockDelta) { - return match ($event->delta::class) { - TextDelta::class => ChunkType::TextDelta, - ThinkingDelta::class => ChunkType::ReasoningDelta, - InputJsonDelta::class => ChunkType::ToolInputDelta, - default => null, - }; + return $snapshot; + } + + private function buildToolCallCollection(): ?ToolCallCollection + { + $toolCalls = [...$this->completedToolCalls]; + + if ($this->pendingToolCall !== null) { + $toolCalls[] = new ToolCall( + $this->pendingToolCall['id'], + new FunctionCall( + $this->pendingToolCall['name'], + $this->parseJsonSafely($this->pendingToolCall['partialJson']), + ), + ); } - if ($event instanceof ContentBlockStop) { - $contentBlockClass = $contentBlocksByIndex[$event->index] ?? null; + return $toolCalls !== [] ? new ToolCallCollection($toolCalls) : null; + } - return match ($contentBlockClass) { - TextContentBlock::class => ChunkType::TextEnd, - ThinkingContentBlock::class, RedactedThinkingContentBlock::class => ChunkType::ReasoningEnd, - ToolUseContentBlock::class => ChunkType::ToolInputEnd, - WebSearchToolResultContentBlock::class, ServerToolUseContentBlock::class => ChunkType::ToolOutputEnd, - default => ChunkType::TextEnd, - }; + private function extractTextDelta(StreamEvent $event): ?string + { + if (! $event instanceof ContentBlockDelta) { + return null; } - return ChunkType::Custom; + return $event->delta instanceof TextDelta + ? $event->delta->text + : null; + } + + private function buildChunk( + StreamEvent $event, + ChunkType $chunkType, + ?string $messageId, + ): ChatGenerationChunk { + $finishReason = $event instanceof MessageDelta + ? $this->mapFinishReason($event->stopReason) + : null; + + $usage = $event instanceof MessageDelta + ? $this->mapUsage($event->cumulativeUsage) + : null; + + $meta = $event->meta(); + + return new ChatGenerationChunk( + type: $chunkType, + id: $messageId, + message: new AssistantMessage( + content: $this->extractTextDelta($event), + toolCalls: $this->buildToolCallCollection(), + metadata: new ResponseMetadata( + id: $messageId, + model: $this->model, + provider: $this->modelProvider, + finishReason: $finishReason, + usage: $usage, + processingTime: $meta?->processingTime, + providerMetadata: $meta?->raw ?? [], + ), + ), + createdAt: $meta?->createdAt ?? new DateTimeImmutable(), + finishReason: $finishReason, + usage: $usage, + contentSoFar: $this->buildContentSnapshot(), + isFinal: $usage !== null, + rawChunk: $this->includeRaw ? $event->raw() : null, + ); + } + + /** + * @param array> $contentBlocksByIndex + */ + protected function mapChunkType(StreamEvent $event, array $contentBlocksByIndex = []): ?ChunkType + { + return match (true) { + $event instanceof MessageStart => ChunkType::MessageStart, + $event instanceof MessageStop => null, + $event instanceof ContentBlockStart => $this->mapContentBlockStartType($event), + $event instanceof ContentBlockDelta => $this->mapContentBlockDeltaType($event), + $event instanceof ContentBlockStop => $this->mapContentBlockStopType($event, $contentBlocksByIndex), + default => ChunkType::Custom, + }; + } + + private function mapContentBlockStartType(ContentBlockStart $event): ?ChunkType + { + return match ($event->contentBlock::class) { + TextContentBlock::class => ChunkType::TextStart, + ThinkingContentBlock::class, RedactedThinkingContentBlock::class => ChunkType::ReasoningStart, + ToolUseContentBlock::class => ChunkType::ToolInputStart, + default => null, + }; + } + + private function mapContentBlockDeltaType(ContentBlockDelta $event): ?ChunkType + { + return match ($event->delta::class) { + TextDelta::class => ChunkType::TextDelta, + ThinkingDelta::class => ChunkType::ReasoningDelta, + InputJsonDelta::class => ChunkType::ToolInputDelta, + default => null, + }; + } + + /** + * @param array> $contentBlocksByIndex + */ + private function mapContentBlockStopType(ContentBlockStop $event, array $contentBlocksByIndex): ChunkType + { + $contentBlockClass = $contentBlocksByIndex[$event->index] ?? null; + + return match ($contentBlockClass) { + TextContentBlock::class => ChunkType::TextEnd, + ThinkingContentBlock::class, RedactedThinkingContentBlock::class => ChunkType::ReasoningEnd, + ToolUseContentBlock::class => ChunkType::ToolInputEnd, + WebSearchToolResultContentBlock::class, ServerToolUseContentBlock::class => ChunkType::ToolOutputEnd, + default => ChunkType::TextEnd, + }; } } diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php b/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php new file mode 100644 index 0000000..9f103ca --- /dev/null +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php @@ -0,0 +1,199 @@ +> + */ + protected function mapMessagesForInput(MessageCollection $messages): array + { + return $messages + ->map(fn (Message $message): array => $this->mapMessage($message)) + ->values() + ->toArray(); + } + + /** + * @return array + */ + private function mapMessage(Message $message): array + { + return match (true) { + $message instanceof ToolMessage => $this->mapToolMessage($message), + $message instanceof AssistantMessage => $this->mapAssistantMessage($message), + default => $this->mapGenericMessage($message), + }; + } + + /** + * Map a tool message (tool result) to Anthropic format. + * + * Tool results must be sent as a user message with tool_result content blocks. + * + * @return array + */ + private function mapToolMessage(ToolMessage $message): array + { + return [ + 'role' => MessageRole::User->value, + 'content' => [ + [ + 'type' => 'tool_result', + 'tool_use_id' => $message->id, + 'content' => $message->text() ?? '', + ], + ], + ]; + } + + /** + * Map an assistant message to Anthropic format. + * + * Tool calls are represented as tool_use content blocks in Anthropic. + * + * @return array + */ + private function mapAssistantMessage(AssistantMessage $message): array + { + $content = $this->mapMessageContent($message->content); + + if ($message->toolCalls?->isNotEmpty()) { + foreach ($message->toolCalls as $toolCall) { + $content[] = [ + 'type' => 'tool_use', + 'id' => $toolCall->id, + 'name' => $toolCall->function->name, + 'input' => $toolCall->function->arguments, + ]; + } + } + + return [ + 'role' => MessageRole::Assistant->value, + 'content' => $content, + ]; + } + + /** + * Map a generic message (user, etc.) to Anthropic format. + * + * @return array + */ + private function mapGenericMessage(Message $message): array + { + $formattedMessage = $message->toArray(); + + if (isset($formattedMessage['content']) && is_array($formattedMessage['content'])) { + $formattedMessage['content'] = $this->mapMessageContent($formattedMessage['content']); + } + + return $formattedMessage; + } + + /** + * Map message content to Anthropic content blocks. + * + * @param string|array|null $content + * + * @return array> + */ + private function mapMessageContent(string|array|null $content): array + { + if ($content === null) { + return []; + } + + if (is_string($content)) { + return [ + [ + 'type' => 'text', + 'text' => $content, + ], + ]; + } + + return array_values(array_filter( + array_map(fn (mixed $item): ?array => $this->mapContentBlock($item), $content), + )); + } + + /** + * Map a single content block to Anthropic format. + * + * @return array|null + */ + private function mapContentBlock(mixed $content): ?array + { + return match (true) { + $content instanceof TextContent => $this->mapTextContent($content), + $content instanceof ImageContent => $this->mapImageContent($content), + is_array($content) => $content, + default => null, + }; + } + + /** + * @return array + */ + private function mapTextContent(TextContent $content): array + { + return [ + 'type' => 'text', + 'text' => $content->text, + ]; + } + + /** + * Map image content to Anthropic's image format. + * + * @return array + */ + private function mapImageContent(ImageContent $content): array + { + $this->supportsFeatureOrFail(ModelFeature::Vision); + + if ($this->isDataUrl($content->url)) { + $dataUrl = DataUrl::parse($content->url); + + return [ + 'type' => 'image', + 'source' => [ + 'type' => 'base64', + 'media_type' => $dataUrl->mediaType, + 'data' => $dataUrl->data, + ], + ]; + } + + return [ + 'type' => 'image', + 'source' => [ + 'type' => 'url', + 'url' => $content->url, + ], + ]; + } + + private function isDataUrl(string $url): bool + { + return str_starts_with($url, 'data:'); + } +} + diff --git a/src/SDK/Anthropic/Resources/Messages.php b/src/SDK/Anthropic/Resources/Messages.php index 7370d40..ee70c12 100644 --- a/src/SDK/Anthropic/Resources/Messages.php +++ b/src/SDK/Anthropic/Resources/Messages.php @@ -18,7 +18,9 @@ class Messages extends BaseResource */ public function create(array $parameters): Message|MessageStream { - return $this->connector->send(new CreateMessage($parameters))->dtoOrFail(); + $response = $this->connector->send(new CreateMessage($parameters)); + + return $response->throw()->dtoOrFail(); } /** diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index d56f4ff..c8d561e 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -18,11 +18,13 @@ use Cortex\LLM\Data\ToolCallCollection; use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\ModelInfo\Enums\ModelFeature; +use Cortex\LLM\Data\Messages\ToolMessage; use Cortex\LLM\Data\Messages\UserMessage; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Drivers\Anthropic\AnthropicChat; use Cortex\SDK\Anthropic\Requests\CreateMessage; use Cortex\LLM\Data\Messages\Content\TextContent; +use Cortex\LLM\Data\Messages\Content\ImageContent; use Cortex\LLM\Data\Messages\Content\ReasoningContent; test('it responds to messages', function (): void { @@ -377,24 +379,48 @@ expect($result)->toBeInstanceOf(ChatStreamResult::class); - $hasToolInputChunks = false; - $finalChunk = null; + $chunkTypes = []; - $result->each(function (ChatGenerationChunk $chunk) use (&$hasToolInputChunks, &$finalChunk): void { + /** @var \Cortex\LLM\Data\ChatGenerationChunk|null $toolInputEndChunk */ + $toolInputEndChunk = null; + $toolCallsAccumulated = false; + + $result->each(function (ChatGenerationChunk $chunk) use (&$chunkTypes, &$toolInputEndChunk, &$toolCallsAccumulated): void { expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) ->and($chunk->message)->toBeInstanceOf(AssistantMessage::class); - if ($chunk->type === ChunkType::ToolInputStart || $chunk->type === ChunkType::ToolInputDelta) { - $hasToolInputChunks = true; + $chunkTypes[] = $chunk->type; + + // Check if tool calls are being accumulated + if ($chunk->message->toolCalls !== null && $chunk->message->toolCalls->isNotEmpty()) { + $toolCallsAccumulated = true; } - if ($chunk->isFinal) { - $finalChunk = $chunk; + if ($chunk->type === ChunkType::ToolInputEnd) { + $toolInputEndChunk = $chunk; } }); - expect($hasToolInputChunks)->toBeTrue('Expected at least one chunk to have tool input') - ->and($finalChunk)->not->toBeNull('Expected a final chunk'); + // Verify we have the correct chunk types for tool call streaming + expect($chunkTypes)->toContain(ChunkType::MessageStart) + ->and($chunkTypes)->toContain(ChunkType::ToolInputStart) + ->and($chunkTypes)->toContain(ChunkType::ToolInputDelta) + ->and($chunkTypes)->toContain(ChunkType::ToolInputEnd); + + // Verify tool calls were accumulated during streaming + expect($toolCallsAccumulated)->toBeTrue('Expected tool calls to be accumulated during streaming'); + + // Verify the ToolInputEnd chunk has the complete tool call + expect($toolInputEndChunk)->not->toBeNull('Expected a ToolInputEnd chunk') + ->and($toolInputEndChunk->message->toolCalls)->toBeInstanceOf(ToolCallCollection::class) + ->and($toolInputEndChunk->message->toolCalls)->toHaveCount(1) + ->and($toolInputEndChunk->message->toolCalls[0])->toBeInstanceOf(ToolCall::class) + ->and($toolInputEndChunk->message->toolCalls[0]->id)->toBe('toolu_017cgnjBthRJC2WTaB6As9FB') + ->and($toolInputEndChunk->message->toolCalls[0]->function)->toBeInstanceOf(FunctionCall::class) + ->and($toolInputEndChunk->message->toolCalls[0]->function->name)->toBe('get_weather') + ->and($toolInputEndChunk->message->toolCalls[0]->function->arguments)->toBe([ + 'location' => 'Manchester, UK', + ]); }); test('it can use structured output with a class', function (): void { @@ -462,3 +488,240 @@ public function __construct( ->and($result->usage->promptTokens)->toBe(13) ->and($result->usage->completionTokens)->toBe(30); }); + +test('it formats tool messages as tool_result content blocks', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('What is the weather?'), + new AssistantMessage( + toolCalls: new ToolCallCollection([ + new ToolCall('tool_123', new FunctionCall('get_weather', ['location' => 'London'])), + ]), + ), + new ToolMessage('The weather in London is sunny and 22°C', 'tool_123'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + + // The third message should be the tool result + $toolResultMessage = $messages[2]; + + return $toolResultMessage['role'] === 'user' + && $toolResultMessage['content'][0]['type'] === 'tool_result' + && $toolResultMessage['content'][0]['tool_use_id'] === 'tool_123' + && $toolResultMessage['content'][0]['content'] === 'The weather in London is sunny and 22°C'; + }); +}); + +test('it formats assistant messages with tool calls as tool_use content blocks', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('What is the weather?'), + new AssistantMessage( + content: 'Let me check the weather for you.', + toolCalls: new ToolCallCollection([ + new ToolCall('tool_abc', new FunctionCall('get_weather', ['location' => 'Paris'])), + ]), + ), + new ToolMessage('Rainy, 15°C', 'tool_abc'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + + // The second message should be the assistant message with tool call + $assistantMessage = $messages[1]; + + // Should have text content and tool_use content + $hasTextContent = false; + $hasToolUse = false; + + foreach ($assistantMessage['content'] as $block) { + if ($block['type'] === 'text' && $block['text'] === 'Let me check the weather for you.') { + $hasTextContent = true; + } + + if ($block['type'] === 'tool_use' + && $block['id'] === 'tool_abc' + && $block['name'] === 'get_weather' + && $block['input'] === ['location' => 'Paris']) { + $hasToolUse = true; + } + } + + return $assistantMessage['role'] === 'assistant' && $hasTextContent && $hasToolUse; + }); +}); + +test('it formats assistant messages with multiple tool calls', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('Compare weather in London and Paris'), + new AssistantMessage( + toolCalls: new ToolCallCollection([ + new ToolCall('tool_1', new FunctionCall('get_weather', ['location' => 'London'])), + new ToolCall('tool_2', new FunctionCall('get_weather', ['location' => 'Paris'])), + ]), + ), + new ToolMessage('Sunny, 20°C', 'tool_1'), + new ToolMessage('Rainy, 15°C', 'tool_2'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + + $assistantMessage = $messages[1]; + + // Should have two tool_use blocks + $toolUseBlocks = array_filter( + $assistantMessage['content'], + fn ($block) => $block['type'] === 'tool_use', + ); + + return count($toolUseBlocks) === 2; + }); +}); + +test('it formats user messages with text content', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage([ + new TextContent('Hello, how are you?'), + ]), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + $userMessage = $messages[0]; + + return $userMessage['role'] === 'user' + && $userMessage['content'][0]['type'] === 'text' + && $userMessage['content'][0]['text'] === 'Hello, how are you?'; + }); +}); + +test('it formats image content with URL', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->addFeature(ModelFeature::Vision); + + $llm->invoke([ + new UserMessage([ + new TextContent('What is in this image?'), + new ImageContent('https://example.com/image.jpg'), + ]), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + $userMessage = $messages[0]; + + $hasText = false; + $hasImage = false; + + foreach ($userMessage['content'] as $block) { + if ($block['type'] === 'text' && $block['text'] === 'What is in this image?') { + $hasText = true; + } + + if ($block['type'] === 'image' + && $block['source']['type'] === 'url' + && $block['source']['url'] === 'https://example.com/image.jpg') { + $hasImage = true; + } + } + + return $hasText && $hasImage; + }); +}); + +test('it formats image content with base64 data URL', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->addFeature(ModelFeature::Vision); + + $base64Data = base64_encode('fake-image-data'); + $dataUrl = "data:image/png;base64,{$base64Data}"; + + $llm->invoke([ + new UserMessage([ + new TextContent('Describe this image'), + new ImageContent($dataUrl), + ]), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request) use ($base64Data): bool { + $messages = $request->body()->get('messages'); + $userMessage = $messages[0]; + + $hasImage = false; + + foreach ($userMessage['content'] as $block) { + if ($block['type'] === 'image' + && $block['source']['type'] === 'base64' + && $block['source']['media_type'] === 'image/png' + && $block['source']['data'] === $base64Data) { + $hasImage = true; + } + } + + return $hasImage; + }); +}); + +test('it handles string content in user messages', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('Simple string message'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + $userMessage = $messages[0]; + + return $userMessage['role'] === 'user' + && $userMessage['content'] === 'Simple string message'; + }); +}); + +test('it handles assistant messages without tool calls', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('Hello'), + new AssistantMessage('Hi there!'), + new UserMessage('How are you?'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + $assistantMessage = $messages[1]; + + // Should have text content block + return $assistantMessage['role'] === 'assistant' + && $assistantMessage['content'][0]['type'] === 'text' + && $assistantMessage['content'][0]['text'] === 'Hi there!'; + }); +}); diff --git a/workbench/app/Providers/CortexServiceProvider.php b/workbench/app/Providers/CortexServiceProvider.php index d71beeb..f761b48 100644 --- a/workbench/app/Providers/CortexServiceProvider.php +++ b/workbench/app/Providers/CortexServiceProvider.php @@ -54,26 +54,28 @@ public function boot(): void // ], // )); - // Cortex::registerAgent(new Agent( - // name: 'comedian', - // prompt: Cortex::prompt() - // ->builder() - // ->messages([ - // new SystemMessage('You are a comedian.'), - // new UserMessage('Tell me a joke about {topic}.'), - // ]) - // ->metadata( - // provider: 'lmstudio', - // model: 'gpt-oss:20b', - // parameters: [ - // 'temperature' => 1.5, - // ], - // structuredOutput: Schema::object()->properties( - // Schema::string('setup')->required(), - // Schema::string('punchline')->required(), - // ), - // ), - // )); + Cortex::registerAgent(new Agent( + name: 'comedian', + prompt: Cortex::prompt() + ->builder() + ->messages([ + new SystemMessage('You are a comedian.'), + new UserMessage('Tell me a joke about {topic}.'), + ]) + ->metadata( + // provider: 'lmstudio', + // model: 'gpt-oss:20b', + provider: 'anthropic', + model: 'claude-haiku-4-5-20251001', + parameters: [ + 'max_tokens' => 100, + ], + structuredOutput: Schema::object()->properties( + Schema::string('setup')->required(), + Schema::string('punchline')->required(), + ), + ), + )); Cortex::registerAgent(new Agent( name: 'generic', @@ -81,6 +83,12 @@ public function boot(): void llm: 'lmstudio/openai/gpt-oss-20b' )); + Cortex::registerAgent(new Agent( + name: 'generic_anthropic', + prompt: 'You are a helpful assistant.', + llm: 'anthropic/claude-3-7-sonnet-20250219' + )); + Cortex::registerAgent(new Agent( name: 'code_generator', prompt: Cortex::prompt()->factory('blade')->make('example'), From 9713edc6110c93f9383e2d001bdee7fa77b0f667 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sun, 4 Jan 2026 15:08:56 +0000 Subject: [PATCH 12/54] fix and refactor --- src/Agents/Stages/HandleToolCalls.php | 6 +- src/LLM/Drivers/Anthropic/AnthropicChat.php | 14 +- .../Anthropic/Concerns/MapStreamResponse.php | 4 +- .../Anthropic/Concerns/MapsMessages.php | 80 +++++++---- .../Drivers/Anthropic/AnthropicChatTest.php | 126 +++++++++++++++++- 5 files changed, 191 insertions(+), 39 deletions(-) diff --git a/src/Agents/Stages/HandleToolCalls.php b/src/Agents/Stages/HandleToolCalls.php index 528cd3e..52e943e 100644 --- a/src/Agents/Stages/HandleToolCalls.php +++ b/src/Agents/Stages/HandleToolCalls.php @@ -39,7 +39,9 @@ public function __construct( public function handlePipeable(mixed $payload, RuntimeConfig $config, Closure $next): mixed { return match (true) { - $payload instanceof ChatGenerationChunk && $payload->type === ChunkType::ToolInputEnd => $this->handleStreamingChunk($payload, $config, $next), + // We trigger on isFinal (not ToolInputEnd) so that AddMessageToMemoryMiddleware + // has already added the assistant message before we process tool calls. + $payload instanceof ChatGenerationChunk && $payload->isFinal && $payload->message->hasToolCalls() => $this->handleStreamingChunk($payload, $config, $next), $payload instanceof ChatStreamResult => $this->handleStreamingResult($payload), default => $this->handleNonStreaming($payload, $config, $next), }; @@ -158,7 +160,7 @@ protected function getGeneration(mixed $payload): ChatGeneration|ChatGenerationC { return match (true) { $payload instanceof ChatGeneration => $payload, - $payload instanceof ChatGenerationChunk && $payload->type === ChunkType::ToolInputEnd => $payload, + $payload instanceof ChatGenerationChunk && $payload->isFinal && $payload->message->hasToolCalls() => $payload, $payload instanceof ChatResult => $payload->generation, default => null, }; diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 84eb760..dda93dc 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -98,8 +98,6 @@ protected function buildParams(array $additionalParameters): array if ($this->structuredOutputConfig !== null) { $this->structuredOutputMode = StructuredOutputMode::Tool; - $params['betas'] ??= []; - $params['betas'][] = 'structured-outputs-2025-11-13'; $params['output_format'] = [ 'type' => 'json_schema', @@ -148,6 +146,11 @@ protected function buildParams(array $additionalParameters): array throw new LLMException('`max_tokens` parameter is required for Anthropic.'); } + if ($this->structuredOutputConfig !== null) { + $finalParams['betas'] ??= []; + $finalParams['betas'][] = 'structured-outputs-2025-11-13'; + } + return $finalParams; } @@ -162,10 +165,15 @@ public static function fake( ): self { $client = Anthropic::fake($responses, $apiKey); - return new self( + $instance = new self( $client, $model ?? 'claude-4-5-sonnet-20250926', $modelProvider ?? ModelProvider::Anthropic, ); + + // Set a default max_tokens for testing + $instance->withMaxTokens(8096); + + return $instance; } } diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index fba2da8..5d3713e 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -316,10 +316,10 @@ private function buildChunk( finishReason: $finishReason, usage: $usage, processingTime: $meta?->processingTime, - providerMetadata: $meta?->raw ?? [], + providerMetadata: $meta->raw ?? [], ), ), - createdAt: $meta?->createdAt ?? new DateTimeImmutable(), + createdAt: $meta->createdAt ?? new DateTimeImmutable(), finishReason: $finishReason, usage: $usage, contentSoFar: $this->buildContentSnapshot(), diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php b/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php index 9f103ca..265c4e7 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php @@ -6,8 +6,8 @@ use Cortex\Support\DataUrl; use Cortex\LLM\Contracts\Message; -use Cortex\ModelInfo\Enums\ModelFeature; use Cortex\LLM\Enums\MessageRole; +use Cortex\ModelInfo\Enums\ModelFeature; use Cortex\LLM\Data\Messages\ToolMessage; use Cortex\LLM\Data\Messages\AssistantMessage; use Cortex\LLM\Data\Messages\MessageCollection; @@ -20,49 +20,80 @@ trait MapsMessages /** * Map the given messages to the Anthropic API format. * + * Anthropic requires consecutive tool results to be merged into a single + * user message, as it doesn't allow consecutive messages with the same role. + * * @return array> */ protected function mapMessagesForInput(MessageCollection $messages): array { - return $messages - ->map(fn (Message $message): array => $this->mapMessage($message)) - ->values() - ->toArray(); + $mapped = []; + $pendingToolResults = []; + + foreach ($messages as $message) { + if ($message instanceof ToolMessage) { + $pendingToolResults[] = $this->mapToolResultBlock($message); + + continue; + } + + // Flush any pending tool results before adding the next message + if ($pendingToolResults !== []) { + $mapped[] = $this->createToolResultsMessage($pendingToolResults); + $pendingToolResults = []; + } + + $mapped[] = $this->mapMessage($message); + } + + // Flush any remaining tool results at the end + if ($pendingToolResults !== []) { + $mapped[] = $this->createToolResultsMessage($pendingToolResults); + } + + return $mapped; } /** + * Create a single user message containing all tool results. + * + * @param array> $toolResults + * * @return array */ - private function mapMessage(Message $message): array + private function createToolResultsMessage(array $toolResults): array { - return match (true) { - $message instanceof ToolMessage => $this->mapToolMessage($message), - $message instanceof AssistantMessage => $this->mapAssistantMessage($message), - default => $this->mapGenericMessage($message), - }; + return [ + 'role' => MessageRole::User->value, + 'content' => $toolResults, + ]; } /** - * Map a tool message (tool result) to Anthropic format. - * - * Tool results must be sent as a user message with tool_result content blocks. + * Map a tool message to a tool_result content block. * * @return array */ - private function mapToolMessage(ToolMessage $message): array + private function mapToolResultBlock(ToolMessage $message): array { return [ - 'role' => MessageRole::User->value, - 'content' => [ - [ - 'type' => 'tool_result', - 'tool_use_id' => $message->id, - 'content' => $message->text() ?? '', - ], - ], + 'type' => 'tool_result', + 'tool_use_id' => $message->id, + 'content' => $message->text() ?? '', ]; } + /** + * @return array + */ + private function mapMessage(Message $message): array + { + return match (true) { + $message instanceof AssistantMessage => $this->mapAssistantMessage($message), + default => $this->mapGenericMessage($message), + }; + } + /** * Map an assistant message to Anthropic format. * @@ -130,7 +161,7 @@ private function mapMessageContent(string|array|null $content): array } return array_values(array_filter( - array_map(fn (mixed $item): ?array => $this->mapContentBlock($item), $content), + array_map(fn(mixed $item): ?array => $this->mapContentBlock($item), $content), )); } @@ -196,4 +227,3 @@ private function isDataUrl(string $url): bool return str_starts_with($url, 'data:'); } } - diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index c8d561e..c581492 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -498,7 +498,9 @@ public function __construct( new UserMessage('What is the weather?'), new AssistantMessage( toolCalls: new ToolCallCollection([ - new ToolCall('tool_123', new FunctionCall('get_weather', ['location' => 'London'])), + new ToolCall('tool_123', new FunctionCall('get_weather', [ + 'location' => 'London', + ])), ]), ), new ToolMessage('The weather in London is sunny and 22°C', 'tool_123'), @@ -527,7 +529,9 @@ public function __construct( new AssistantMessage( content: 'Let me check the weather for you.', toolCalls: new ToolCallCollection([ - new ToolCall('tool_abc', new FunctionCall('get_weather', ['location' => 'Paris'])), + new ToolCall('tool_abc', new FunctionCall('get_weather', [ + 'location' => 'Paris', + ])), ]), ), new ToolMessage('Rainy, 15°C', 'tool_abc'), @@ -551,7 +555,9 @@ public function __construct( if ($block['type'] === 'tool_use' && $block['id'] === 'tool_abc' && $block['name'] === 'get_weather' - && $block['input'] === ['location' => 'Paris']) { + && $block['input'] === [ + 'location' => 'Paris', + ]) { $hasToolUse = true; } } @@ -569,8 +575,12 @@ public function __construct( new UserMessage('Compare weather in London and Paris'), new AssistantMessage( toolCalls: new ToolCallCollection([ - new ToolCall('tool_1', new FunctionCall('get_weather', ['location' => 'London'])), - new ToolCall('tool_2', new FunctionCall('get_weather', ['location' => 'Paris'])), + new ToolCall('tool_1', new FunctionCall('get_weather', [ + 'location' => 'London', + ])), + new ToolCall('tool_2', new FunctionCall('get_weather', [ + 'location' => 'Paris', + ])), ]), ), new ToolMessage('Sunny, 20°C', 'tool_1'), @@ -585,7 +595,7 @@ public function __construct( // Should have two tool_use blocks $toolUseBlocks = array_filter( $assistantMessage['content'], - fn ($block) => $block['type'] === 'tool_use', + fn(array $block): bool => $block['type'] === 'tool_use', ); return count($toolUseBlocks) === 2; @@ -658,7 +668,7 @@ public function __construct( $llm->addFeature(ModelFeature::Vision); $base64Data = base64_encode('fake-image-data'); - $dataUrl = "data:image/png;base64,{$base64Data}"; + $dataUrl = 'data:image/png;base64,' . $base64Data; $llm->invoke([ new UserMessage([ @@ -725,3 +735,105 @@ public function __construct( && $assistantMessage['content'][0]['text'] === 'Hi there!'; }); }); + +test('it merges consecutive tool messages into a single user message', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('Compare weather in London and Paris'), + new AssistantMessage( + toolCalls: new ToolCallCollection([ + new ToolCall('tool_1', new FunctionCall('get_weather', [ + 'location' => 'London', + ])), + new ToolCall('tool_2', new FunctionCall('get_weather', [ + 'location' => 'Paris', + ])), + ]), + ), + new ToolMessage('Sunny, 20°C', 'tool_1'), + new ToolMessage('Rainy, 15°C', 'tool_2'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + + // Should have exactly 3 messages: user, assistant, user (merged tool results) + if (count($messages) !== 3) { + return false; + } + + // Third message should be a single user message with both tool results + $toolResultsMessage = $messages[2]; + + if ($toolResultsMessage['role'] !== 'user') { + return false; + } + + // Should have 2 tool_result content blocks + if (count($toolResultsMessage['content']) !== 2) { + return false; + } + + $firstResult = $toolResultsMessage['content'][0]; + $secondResult = $toolResultsMessage['content'][1]; + + return $firstResult['type'] === 'tool_result' + && $firstResult['tool_use_id'] === 'tool_1' + && $firstResult['content'] === 'Sunny, 20°C' + && $secondResult['type'] === 'tool_result' + && $secondResult['tool_use_id'] === 'tool_2' + && $secondResult['content'] === 'Rainy, 15°C'; + }); +}); + +test('it does not merge non-consecutive tool messages', function (): void { + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple'), + ]); + + $llm->invoke([ + new UserMessage('What is the weather?'), + new AssistantMessage( + toolCalls: new ToolCallCollection([ + new ToolCall('tool_1', new FunctionCall('get_weather', [ + 'location' => 'London', + ])), + ]), + ), + new ToolMessage('Sunny', 'tool_1'), + new AssistantMessage('The weather in London is sunny. Want to check another city?'), + new UserMessage('Yes, check Paris'), + new AssistantMessage( + toolCalls: new ToolCallCollection([ + new ToolCall('tool_2', new FunctionCall('get_weather', [ + 'location' => 'Paris', + ])), + ]), + ), + new ToolMessage('Rainy', 'tool_2'), + ]); + + MockClient::getGlobal()->assertSent(function (CreateMessage $request): bool { + $messages = $request->body()->get('messages'); + + // Should have 7 messages (tool messages not merged because they're not consecutive) + if (count($messages) !== 7) { + return false; + } + + // First tool result at index 2 + $firstToolResult = $messages[2]; + // Second tool result at index 6 + $secondToolResult = $messages[6]; + + return $firstToolResult['role'] === 'user' + && count($firstToolResult['content']) === 1 + && $firstToolResult['content'][0]['tool_use_id'] === 'tool_1' + && $secondToolResult['role'] === 'user' + && count($secondToolResult['content']) === 1 + && $secondToolResult['content'][0]['tool_use_id'] === 'tool_2'; + }); +}); From 6067b83b14e062339f3fad1545047434877be0ba Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 5 Jan 2026 00:25:07 +0000 Subject: [PATCH 13/54] add caching support --- composer.json | 1 + config/cortex.php | 6 +++ src/Agents/Prebuilt/WeatherAgent.php | 4 +- src/Http/Controllers/AgentsController.php | 8 ++- src/LLM/Data/ChatResult.php | 3 ++ src/LLM/Drivers/Anthropic/AnthropicChat.php | 15 +++++- .../Anthropic/Concerns/MapStreamResponse.php | 10 ++-- .../Anthropic/Concerns/MapsResponse.php | 5 +- src/LLM/LLMManager.php | 33 ++++++------ src/SDK/Anthropic/Anthropic.php | 9 +++- src/SDK/Anthropic/Requests/CreateMessage.php | 50 ++++++++++++++++++- src/SDK/Anthropic/Resources/Messages.php | 35 ++++++++++--- tests/Unit/SDK/Anthropic/AnthropicTest.php | 20 ++++---- 13 files changed, 153 insertions(+), 46 deletions(-) diff --git a/composer.json b/composer.json index 6549e89..43dec53 100644 --- a/composer.json +++ b/composer.json @@ -27,6 +27,7 @@ "psr-discovery/cache-implementations": "^1.2", "psr-discovery/event-dispatcher-implementations": "^1.1", "react/async": "^4.3", + "saloonphp/cache-plugin": "^3.0", "saloonphp/saloon": "^3.14", "spatie/laravel-package-tools": "^1.17" }, diff --git a/config/cortex.php b/config/cortex.php index 6f38310..26d1f2b 100644 --- a/config/cortex.php +++ b/config/cortex.php @@ -23,6 +23,12 @@ 'llm' => [ 'default' => env('CORTEX_DEFAULT_LLM', 'openai'), + 'cache' => [ + 'enabled' => env('CORTEX_LLM_CACHE_ENABLED', false), + 'store' => env('CORTEX_LLM_CACHE_STORE'), + 'ttl' => env('CORTEX_LLM_CACHE_TTL', 3600), + ], + 'openai' => [ 'driver' => LLMDriver::OpenAIChat, 'options' => [ diff --git a/src/Agents/Prebuilt/WeatherAgent.php b/src/Agents/Prebuilt/WeatherAgent.php index a9a3727..323157e 100644 --- a/src/Agents/Prebuilt/WeatherAgent.php +++ b/src/Agents/Prebuilt/WeatherAgent.php @@ -46,8 +46,8 @@ public function llm(): LLM|string|null { // return Cortex::llm('ollama', 'gpt-oss:20b')->ignoreFeatures(); // return Cortex::llm('openai', 'gpt-4.1-mini')->ignoreFeatures(); - // return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); - return Cortex::llm('anthropic', 'claude-sonnet-4-5-20250929')->ignoreFeatures(); + return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); + // return Cortex::llm('anthropic', 'claude-sonnet-4-5-20250929')->ignoreFeatures(); } #[Override] diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index 99ff7ee..a880f17 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -46,7 +46,13 @@ public function invoke(string $agent, Request $request): JsonResponse // dump($event->exception->getMessage()); // dump($event->exception->getTraceAsString()); }); - $result = $agent->invoke(input: $request->all()); + + $result = $agent->invoke( + messages: $request->has('message') ? [ + new UserMessage($request->input('message')), + ] : [], + input: $request->all(), + ); } catch (Throwable $e) { return response()->json([ 'error' => $e->getMessage(), diff --git a/src/LLM/Data/ChatResult.php b/src/LLM/Data/ChatResult.php index 9e01ade..dcdc07c 100644 --- a/src/LLM/Data/ChatResult.php +++ b/src/LLM/Data/ChatResult.php @@ -15,11 +15,13 @@ /** * @param array|null $rawResponse + * @param array $metadata */ public function __construct( public ChatGeneration $generation, public Usage $usage, public ?array $rawResponse = null, + public array $metadata = [], ) { $this->parsedOutput = $this->generation->parsedOutput; } @@ -50,6 +52,7 @@ public function toArray(): array 'generation' => $this->generation, 'usage' => $this->usage, 'raw_response' => $this->rawResponse, + 'metadata' => $this->metadata, ]; } } diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index dda93dc..4a4315e 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -74,10 +74,21 @@ public function invoke( $this->dispatchEvent(new ChatModelStart($this, $messages, $params)); + if ($this->streaming) { + $params['stream'] = true; + } + try { + $response = $this->client->messages()->create( + parameters: $params, + cacheEnabled: $this->useCache, + ); + + $isCached = $response->isCached(); + return $this->streaming - ? $this->mapStreamResponse($this->client->messages()->stream($params)) - : $this->mapResponse($this->client->messages()->create($params)); + ? $this->mapStreamResponse($response->dtoOrFail(), $isCached) + : $this->mapResponse($response->dtoOrFail(), $isCached); } catch (Throwable $e) { $this->dispatchEvent(new ChatModelError($this, $params, $e)); diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 5d3713e..a7439ad 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -73,9 +73,9 @@ trait MapStreamResponse * * @param \Cortex\SDK\Anthropic\Data\Messages\MessageStream<\Cortex\SDK\Anthropic\Contracts\StreamEvent> $response */ - protected function mapStreamResponse(MessageStream $response): ChatStreamResult + protected function mapStreamResponse(MessageStream $response, bool $isCached = false): ChatStreamResult { - return new ChatStreamResult(function () use ($response): Generator { + return new ChatStreamResult(function () use ($response, $isCached): Generator { $this->resetStreamState(); yield from $this->streamBuffer?->drain() ?? []; @@ -96,7 +96,7 @@ protected function mapStreamResponse(MessageStream $response): ChatStreamResult $this->processStreamEvent($event); - $chatGenerationChunk = $this->buildChunk($event, $chunkType, $messageId); + $chatGenerationChunk = $this->buildChunk($event, $chunkType, $messageId, $isCached); $chatGenerationChunk = $this->applyOutputParserIfApplicable($chatGenerationChunk); $this->dispatchEvent(new ChatModelStream($this, $chatGenerationChunk)); @@ -292,6 +292,7 @@ private function buildChunk( StreamEvent $event, ChunkType $chunkType, ?string $messageId, + bool $isCached = false, ): ChatGenerationChunk { $finishReason = $event instanceof MessageDelta ? $this->mapFinishReason($event->stopReason) @@ -325,6 +326,9 @@ private function buildChunk( contentSoFar: $this->buildContentSnapshot(), isFinal: $usage !== null, rawChunk: $this->includeRaw ? $event->raw() : null, + metadata: [ + 'is_cached' => $isCached, + ], ); } diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php index 413f547..48efb28 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php @@ -25,7 +25,7 @@ trait MapsResponse /** * Map a standard (non-streaming) response to a ChatResult. */ - protected function mapResponse(MessageResponse $message): ChatResult + protected function mapResponse(MessageResponse $message, bool $isCached = false): ChatResult { $usage = $this->mapUsage($message->usage); $finishReason = $this->mapFinishReason($message->stopReason); @@ -78,6 +78,9 @@ protected function mapResponse(MessageResponse $message): ChatResult $generation, $usage, $rawResponse, // @phpstan-ignore argument.type + metadata: [ + 'is_cached' => $isCached, + ], ); } diff --git a/src/LLM/LLMManager.php b/src/LLM/LLMManager.php index b0a79e7..b6100ab 100644 --- a/src/LLM/LLMManager.php +++ b/src/LLM/LLMManager.php @@ -71,6 +71,10 @@ protected function createDriver($driver): LLM // @pest-ignore-type ? $driver->value : $driver; + if ($driver === 'cache') { + throw new InvalidArgumentException('Invalid driver - cache keyword is reserved for internal use.'); + } + if (isset($this->customCreators[$driver])) { return $this->callCustomCreator($config); } @@ -131,26 +135,17 @@ public function createOpenAIResponsesDriver(array $config, string $name): OpenAI */ public function createAnthropicDriver(array $config, string $name): AnthropicChat { - // $client = Anthropic::factory() - // ->withApiKey(Arr::get($config, 'options.api_key') ?? '') - // ->withHttpHeader('anthropic-version', Arr::get($config, 'options.version', '2023-06-01')); - - // foreach (Arr::get($config, 'options.headers', []) as $key => $value) { - // $client->withHttpHeader($key, $value); - // } - - // foreach (Arr::get($config, 'options.query_params', []) as $key => $value) { - // $client->withQueryParam($key, $value); - // } - - // if ($baseUri = Arr::get($config, 'options.base_uri')) { - // $client->withBaseUri($baseUri); - // } + $cacheEnabled = $this->config->get('cortex.llm.cache.enabled', false); + $cacheStore = $cacheEnabled + ? $this->container->make('cache')->store($this->config->get('cortex.llm.cache.store')) + : null; $client = new Anthropic( - Arr::get($config, 'options.api_key') ?? '', - Arr::get($config, 'options.base_uri'), - Arr::get($config, 'options.version'), + apiKey: Arr::get($config, 'options.api_key') ?? '', + baseUri: Arr::get($config, 'options.base_uri'), + version: Arr::get($config, 'options.version'), + cacheStore: $cacheStore, + cacheExpiryInSeconds: $this->config->get('cortex.llm.cache.ttl'), ); if (! isset($config['default_model'])) { @@ -169,6 +164,8 @@ public function createAnthropicDriver(array $config, string $name): AnthropicCha ); $driver->setEventDispatcher(new IlluminateEventDispatcherBridge($this->container->make('events'))); + $driver->withCaching($cacheEnabled); + return $driver; } diff --git a/src/SDK/Anthropic/Anthropic.php b/src/SDK/Anthropic/Anthropic.php index 319a86d..7b0828a 100644 --- a/src/SDK/Anthropic/Anthropic.php +++ b/src/SDK/Anthropic/Anthropic.php @@ -8,6 +8,7 @@ use Saloon\Http\Connector; use Saloon\Http\Faking\MockClient; use Saloon\Http\Auth\HeaderAuthenticator; +use Illuminate\Contracts\Cache\Repository; use Cortex\SDK\Anthropic\Resources\Messages; class Anthropic extends Connector @@ -17,11 +18,17 @@ public function __construct( protected string $apiKey, protected ?string $baseUri = null, protected ?string $version = null, + protected ?Repository $cacheStore = null, + protected ?int $cacheExpiryInSeconds = null, ) {} public function messages(): Messages { - return new Messages($this); + return new Messages( + $this, + $this->cacheStore, + $this->cacheExpiryInSeconds, + ); } public function resolveBaseUrl(): string diff --git a/src/SDK/Anthropic/Requests/CreateMessage.php b/src/SDK/Anthropic/Requests/CreateMessage.php index c5707d3..c041b1b 100644 --- a/src/SDK/Anthropic/Requests/CreateMessage.php +++ b/src/SDK/Anthropic/Requests/CreateMessage.php @@ -7,13 +7,21 @@ use Saloon\Enums\Method; use Saloon\Http\Request; use Saloon\Http\Response; +use Saloon\Http\PendingRequest; use Saloon\Contracts\Body\HasBody; use Saloon\Traits\Body\HasJsonBody; +use Illuminate\Support\Facades\Cache; +use Saloon\CachePlugin\Contracts\Driver; +use Saloon\CachePlugin\Traits\HasCaching; +use Illuminate\Contracts\Cache\Repository; +use Saloon\CachePlugin\Contracts\Cacheable; use Cortex\SDK\Anthropic\Data\Messages\Message; +use Saloon\CachePlugin\Drivers\LaravelCacheDriver; use Cortex\SDK\Anthropic\Data\Messages\MessageStream; -class CreateMessage extends Request implements HasBody +class CreateMessage extends Request implements HasBody, Cacheable { + use HasCaching; use HasJsonBody; /** @@ -21,6 +29,8 @@ class CreateMessage extends Request implements HasBody */ public function __construct( protected array $parameters, + protected ?Repository $cacheStore = null, + protected ?int $cacheExpiryInSeconds = null, ) {} protected Method $method = Method::POST; @@ -63,4 +73,42 @@ public function createDtoFromResponse(Response $response): Message|MessageStream ? MessageStream::fromResponse($response) : Message::fromResponse($response); } + + public function resolveCacheDriver(): Driver + { + return new LaravelCacheDriver($this->cacheStore ?? Cache::store()); + } + + public function cacheExpiryInSeconds(): int + { + return $this->cacheExpiryInSeconds ?? 3600; + } + + /** + * @return array<\Saloon\Enums\Method> + */ + protected function getCacheableMethods(): array + { + return [$this->method]; + } + + protected function cacheKey(PendingRequest $pendingRequest): ?string + { + $className = $pendingRequest->getRequest()::class; + $requestUrl = $pendingRequest->getUrl(); + $query = $pendingRequest->query()->all(); + $headers = $pendingRequest->headers()->all(); + $body = $pendingRequest->body()->all(); + + return hash( + 'sha256', + json_encode([ + 'className' => $className, + 'requestUrl' => $requestUrl, + 'query' => $query, + 'headers' => $headers, + 'body' => $body, + ], JSON_THROW_ON_ERROR), + ); + } } diff --git a/src/SDK/Anthropic/Resources/Messages.php b/src/SDK/Anthropic/Resources/Messages.php index ee70c12..57bb2af 100644 --- a/src/SDK/Anthropic/Resources/Messages.php +++ b/src/SDK/Anthropic/Resources/Messages.php @@ -4,23 +4,44 @@ namespace Cortex\SDK\Anthropic\Resources; +use Saloon\Http\Response; +use Saloon\Http\Connector; use Saloon\Http\BaseResource; -use Cortex\SDK\Anthropic\Data\Messages\Message; +use Illuminate\Contracts\Cache\Repository; use Cortex\SDK\Anthropic\Requests\CreateMessage; -use Cortex\SDK\Anthropic\Data\Messages\MessageStream; class Messages extends BaseResource { + public function __construct( + Connector $connector, + protected ?Repository $cacheStore = null, + protected ?int $cacheExpiryInSeconds = null, + ) { + parent::__construct($connector); + } + /** * Create a message or a streamed message. * * @param array $parameters */ - public function create(array $parameters): Message|MessageStream + public function create(array $parameters, bool $cacheEnabled = false): Response { - $response = $this->connector->send(new CreateMessage($parameters)); + $request = new CreateMessage( + $parameters, + $this->cacheStore, + $this->cacheExpiryInSeconds, + ); + + if ($cacheEnabled) { + $request->enableCaching(); + } else { + $request->disableCaching(); + } + + $response = $this->connector->send($request); - return $response->throw()->dtoOrFail(); + return $response->throw(); } /** @@ -28,11 +49,11 @@ public function create(array $parameters): Message|MessageStream * * @param array $parameters */ - public function stream(array $parameters): MessageStream + public function stream(array $parameters, bool $cacheEnabled = false): Response { return $this->create([ ...$parameters, 'stream' => true, - ]); + ], $cacheEnabled); } } diff --git a/tests/Unit/SDK/Anthropic/AnthropicTest.php b/tests/Unit/SDK/Anthropic/AnthropicTest.php index 7c587c9..0e0e4d3 100644 --- a/tests/Unit/SDK/Anthropic/AnthropicTest.php +++ b/tests/Unit/SDK/Anthropic/AnthropicTest.php @@ -44,7 +44,7 @@ 'content' => 'Hello, how are you?', ], ], - ]); + ])->dtoOrFail(); expect($message)->toBeInstanceOf(Message::class) ->and($message->content)->toHaveCount(1) @@ -75,7 +75,7 @@ 'content' => 'What is the weight of the moon?', ], ], - ]); + ])->dtoOrFail(); expect($message)->toBeInstanceOf(Message::class) ->and($message->content)->toHaveCount(2) @@ -159,7 +159,7 @@ 'content' => 'What is the weather in Manchester?', ], ], - ]); + ])->dtoOrFail(); expect($message)->toBeInstanceOf(Message::class) ->and($message->content)->toHaveCount(1) @@ -194,7 +194,7 @@ 'content' => 'What is a recent positive news story? Current date is ' . date('Y-m-d'), ], ], - ]); + ])->dtoOrFail(); expect($response)->toBeInstanceOf(Message::class) ->and($response->content)->toHaveCount(12) @@ -236,7 +236,7 @@ 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.', ], ], - ]); + ])->dtoOrFail(); expect($message)->toBeInstanceOf(Message::class) ->and($message->content)->toHaveCount(1) @@ -263,7 +263,7 @@ 'content' => 'Hello, how are you?', ], ], - ]); + ])->dtoOrFail(); expect($response)->toBeInstanceOf(MessageStream::class) ->and($response->getIterator())->toBeInstanceOf(Generator::class); @@ -299,7 +299,7 @@ 'content' => 'What is the weight of the moon?', ], ], - ]); + ])->dtoOrFail(); expect($response)->toBeInstanceOf(MessageStream::class) ->and($response->getIterator())->toBeInstanceOf(Generator::class); @@ -366,7 +366,7 @@ 'content' => 'What is the weather in Manchester?', ], ], - ]); + ])->dtoOrFail(); expect($response)->toBeInstanceOf(MessageStream::class) ->and($response->getIterator())->toBeInstanceOf(Generator::class); @@ -405,7 +405,7 @@ 'content' => 'What is a recent positive news story?', ], ], - ]); + ])->dtoOrFail(); expect($response)->toBeInstanceOf(MessageStream::class) ->and($response->getIterator())->toBeInstanceOf(Generator::class); @@ -501,7 +501,7 @@ 'content' => 'Extract the key information from this email: John Smith (john@example.com) is interested in our Enterprise plan and wants to schedule a demo for next Tuesday at 2pm.', ], ], - ]); + ])->dtoOrFail(); expect($response)->toBeInstanceOf(MessageStream::class) ->and($response->getIterator())->toBeInstanceOf(Generator::class); From bfef1bc2652efafa92e5902eca9186735f54cb34 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 5 Jan 2026 23:05:59 +0000 Subject: [PATCH 14/54] wip --- src/Agents/Prebuilt/WeatherAgent.php | 4 +-- src/Http/Controllers/AgentsController.php | 1 + src/LLM/AbstractLLM.php | 3 +-- .../Anthropic/Concerns/MapStreamResponse.php | 4 +-- src/LLM/Enums/ChunkType.php | 8 ++---- .../app/Providers/CortexServiceProvider.php | 27 +++++++++---------- 6 files changed, 21 insertions(+), 26 deletions(-) diff --git a/src/Agents/Prebuilt/WeatherAgent.php b/src/Agents/Prebuilt/WeatherAgent.php index 323157e..fe8ba07 100644 --- a/src/Agents/Prebuilt/WeatherAgent.php +++ b/src/Agents/Prebuilt/WeatherAgent.php @@ -46,8 +46,8 @@ public function llm(): LLM|string|null { // return Cortex::llm('ollama', 'gpt-oss:20b')->ignoreFeatures(); // return Cortex::llm('openai', 'gpt-4.1-mini')->ignoreFeatures(); - return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); - // return Cortex::llm('anthropic', 'claude-sonnet-4-5-20250929')->ignoreFeatures(); + // return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); + return Cortex::llm('anthropic', 'claude-haiku-4-5')->ignoreFeatures(); } #[Override] diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index a880f17..fa9836f 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -117,6 +117,7 @@ public function stream(string $agent, Request $request): void// : StreamedRespon try { foreach ($result as $chunk) { dump(sprintf('%s: %s', $chunk->type->value, $chunk->message->content())); + // dump($chunk->toArray()); } // return $result->streamResponse(); diff --git a/src/LLM/AbstractLLM.php b/src/LLM/AbstractLLM.php index 2f40674..cc08074 100644 --- a/src/LLM/AbstractLLM.php +++ b/src/LLM/AbstractLLM.php @@ -16,6 +16,7 @@ use Cortex\LLM\Contracts\Tool; use Cortex\Events\ChatModelEnd; use Cortex\LLM\Data\ToolConfig; +use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Enums\ToolChoice; use Cortex\Events\ChatModelError; use Cortex\Events\ChatModelStart; @@ -529,10 +530,8 @@ protected function applyOutputParserIfApplicable( ): ChatGeneration|ChatGenerationChunk { if ($this->shouldParseOutput && $this->outputParser !== null) { try { - // $this->streamBuffer?->push(new ChatGenerationChunk(type: ChunkType::OutputParserStart)); $this->dispatchEvent(new OutputParserStart($this->outputParser, $generationOrChunk)); $parsedOutput = $this->outputParser->parse($generationOrChunk); - // $this->streamBuffer?->push(new ChatGenerationChunk(type: ChunkType::OutputParserEnd)); $this->dispatchEvent(new OutputParserEnd($this->outputParser, $parsedOutput)); $generationOrChunk = $generationOrChunk->cloneWithParsedOutput($parsedOutput); diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index a7439ad..f6f7064 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -339,11 +339,11 @@ protected function mapChunkType(StreamEvent $event, array $contentBlocksByIndex { return match (true) { $event instanceof MessageStart => ChunkType::MessageStart, - $event instanceof MessageStop => null, + $event instanceof MessageDelta => ChunkType::MessageEnd, $event instanceof ContentBlockStart => $this->mapContentBlockStartType($event), $event instanceof ContentBlockDelta => $this->mapContentBlockDeltaType($event), $event instanceof ContentBlockStop => $this->mapContentBlockStopType($event, $contentBlocksByIndex), - default => ChunkType::Custom, + default => null, }; } diff --git a/src/LLM/Enums/ChunkType.php b/src/LLM/Enums/ChunkType.php index c3ea25c..ccce0b8 100644 --- a/src/LLM/Enums/ChunkType.php +++ b/src/LLM/Enums/ChunkType.php @@ -68,10 +68,6 @@ enum ChunkType: string case Custom = 'custom'; - case OutputParserStart = 'output_parser_start'; - - case OutputParserEnd = 'output_parser_end'; - case ChatModelStart = 'chat_model_start'; case ChatModelEnd = 'chat_model_end'; @@ -93,6 +89,8 @@ public function isOperational(): bool self::RunEnd, self::StepStart, self::StepEnd, + self::MessageStart, + self::MessageEnd, ], true); } @@ -105,7 +103,6 @@ public function isStart(): bool self::ToolInputStart, self::RunStart, self::ChatModelStart, - self::OutputParserStart, self::StepStart => true, default => false, }; @@ -121,7 +118,6 @@ public function isEnd(): bool self::ToolOutputEnd, self::RunEnd, self::ChatModelEnd, - self::OutputParserEnd, self::StepEnd => true, default => false, }; diff --git a/workbench/app/Providers/CortexServiceProvider.php b/workbench/app/Providers/CortexServiceProvider.php index f761b48..feb3797 100644 --- a/workbench/app/Providers/CortexServiceProvider.php +++ b/workbench/app/Providers/CortexServiceProvider.php @@ -42,17 +42,18 @@ public function boot(): void // ], // )); - // Cortex::registerAgent(new Agent( - // name: 'quote_of_the_day', - // prompt: 'Generate a quote of the day about {topic}.', - // llm: 'lmstudio/openai/gpt-oss-20b', - // output: [ - // Schema::string('quote') - // ->description('Do not include the author in the quote. Just a single sentence.') - // ->required(), - // Schema::string('author')->required(), - // ], - // )); + Cortex::registerAgent(new Agent( + name: 'quote_of_the_day', + prompt: 'Generate a quote of the day about {topic}.', + llm: 'lmstudio/openai/gpt-oss-20b', + output: [ + Schema::string('quote') + ->description('Do not include the author in the quote. Just a single sentence.') + ->required(), + Schema::string('author') + ->required(), + ], + )); Cortex::registerAgent(new Agent( name: 'comedian', @@ -63,10 +64,8 @@ public function boot(): void new UserMessage('Tell me a joke about {topic}.'), ]) ->metadata( - // provider: 'lmstudio', - // model: 'gpt-oss:20b', provider: 'anthropic', - model: 'claude-haiku-4-5-20251001', + model: 'claude-haiku-4-5', parameters: [ 'max_tokens' => 100, ], From a29b75a2c3c04a1e53bdb411507d42f7d1793631 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Tue, 6 Jan 2026 22:18:20 +0000 Subject: [PATCH 15/54] wip --- src/AGUI/Contracts/Event.php | 24 ++ src/AGUI/Events/AbstractEvent.php | 43 ++- src/AGUI/Events/ActivityDelta.php | 30 +- src/AGUI/Events/ActivitySnapshot.php | 27 +- src/AGUI/Events/Custom.php | 24 +- src/AGUI/Events/MessagesSnapshot.php | 24 +- src/AGUI/Events/Raw.php | 25 +- src/AGUI/Events/ReasoningEnd.php | 24 +- src/AGUI/Events/ReasoningMessageContent.php | 25 +- src/AGUI/Events/ReasoningMessageEnd.php | 24 +- src/AGUI/Events/ReasoningMessageStart.php | 23 +- src/AGUI/Events/ReasoningStart.php | 25 +- src/AGUI/Events/RunError.php | 25 +- src/AGUI/Events/RunFinished.php | 26 +- src/AGUI/Events/RunStarted.php | 27 +- src/AGUI/Events/StateDelta.php | 24 +- src/AGUI/Events/StateSnapshot.php | 24 +- src/AGUI/Events/StepFinished.php | 24 +- src/AGUI/Events/StepStarted.php | 24 +- src/AGUI/Events/TextMessageChunk.php | 26 +- src/AGUI/Events/TextMessageContent.php | 25 +- src/AGUI/Events/TextMessageEnd.php | 24 +- src/AGUI/Events/TextMessageStart.php | 25 +- src/AGUI/Events/ToolCallArgs.php | 25 +- src/AGUI/Events/ToolCallChunk.php | 27 +- src/AGUI/Events/ToolCallEnd.php | 24 +- src/AGUI/Events/ToolCallResult.php | 27 +- src/AGUI/Events/ToolCallStart.php | 26 +- src/Agents/Prebuilt/WeatherAgent.php | 4 +- src/Agents/Stages/TrackAgentStart.php | 8 +- src/Http/Controllers/AgentsController.php | 12 +- ...otocol.php => StreamingProtocolDriver.php} | 9 +- src/LLM/Data/Concerns/HasStreamResponses.php | 39 +- src/LLM/Enums/StreamingProtocol.php | 24 ++ src/LLM/Streaming/AgUiDataStream.php | 360 ++++++------------ src/LLM/Streaming/DefaultDataStream.php | 4 +- src/LLM/Streaming/VercelDataStream.php | 4 +- src/LLM/Streaming/VercelTextStream.php | 4 +- .../Unit/LLM/Streaming/AgUiDataStreamTest.php | 300 --------------- .../app/Providers/CortexServiceProvider.php | 34 +- .../resources/views/playground.blade.php | 0 41 files changed, 728 insertions(+), 795 deletions(-) rename src/LLM/Contracts/{StreamingProtocol.php => StreamingProtocolDriver.php} (53%) create mode 100644 src/LLM/Enums/StreamingProtocol.php create mode 100644 workbench/resources/views/playground.blade.php diff --git a/src/AGUI/Contracts/Event.php b/src/AGUI/Contracts/Event.php index 5c0a25d..41eb0e0 100644 --- a/src/AGUI/Contracts/Event.php +++ b/src/AGUI/Contracts/Event.php @@ -9,9 +9,33 @@ interface Event { + /** + * The type of the event. + */ public EventType $type { get; } + /** + * The timestamp of the event. + */ public ?DateTimeImmutable $timestamp { get; } + /** + * The raw event data. + */ public mixed $rawEvent { get; } + + /** + * Set the timestamp for the event. + */ + public function withTimestamp(DateTimeImmutable $timestamp): static; + + /** + * Set the raw event for the event. + */ + public function withRawEvent(mixed $rawEvent): static; + + /** + * Convert the event to an array. + */ + public function toArray(): array; } diff --git a/src/AGUI/Events/AbstractEvent.php b/src/AGUI/Events/AbstractEvent.php index 1394ca1..93f0018 100644 --- a/src/AGUI/Events/AbstractEvent.php +++ b/src/AGUI/Events/AbstractEvent.php @@ -5,15 +5,48 @@ namespace Cortex\AGUI\Events; use DateTimeImmutable; +use DateTimeInterface; use Cortex\AGUI\Contracts\Event; use Cortex\AGUI\Enums\EventType; abstract class AbstractEvent implements Event { - public EventType $type; + public protected(set) EventType $type; - public function __construct( - public ?DateTimeImmutable $timestamp = null, - public mixed $rawEvent = null, - ) {} + public protected(set) ?DateTimeImmutable $timestamp; + + public protected(set) mixed $rawEvent; + + public function withTimestamp(DateTimeImmutable $timestamp): static + { + $this->timestamp = $timestamp; + + return $this; + } + + public function withRawEvent(mixed $rawEvent): static + { + $this->rawEvent = $rawEvent; + + return $this; + } + + protected function formattedTimestamp(): ?string + { + return $this->timestamp?->format(DateTimeInterface::ATOM); + } + + protected function buildArray(array $additional = []): array + { + $payload = [ + 'type' => $this->type->value, + ...$additional, + ]; + + if ($this->timestamp !== null) { + $payload['timestamp'] = $this->formattedTimestamp(); + } + + return $payload; + } } diff --git a/src/AGUI/Events/ActivityDelta.php b/src/AGUI/Events/ActivityDelta.php index ccbf378..a814669 100644 --- a/src/AGUI/Events/ActivityDelta.php +++ b/src/AGUI/Events/ActivityDelta.php @@ -4,22 +4,36 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ActivityDelta extends AbstractEvent +/** + * @implements Arrayable + */ +final class ActivityDelta extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ActivityDelta; + /** - * @param array $patch + * @param string $messageId Identifier for the target activity message + * @param string $activityType Activity discriminator (mirrors the value from the most recent snapshot) + * @param array $patch Array of RFC 6902 JSON Patch operations to apply to the activity data */ public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $activityType = '', public array $patch = [], - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ActivityDelta; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'activityType' => $this->activityType, + 'patch' => $this->patch, + ]); } } diff --git a/src/AGUI/Events/ActivitySnapshot.php b/src/AGUI/Events/ActivitySnapshot.php index 5ab8e1c..a3fafda 100644 --- a/src/AGUI/Events/ActivitySnapshot.php +++ b/src/AGUI/Events/ActivitySnapshot.php @@ -4,23 +4,36 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ActivitySnapshot extends AbstractEvent +/** + * @implements Arrayable + */ +final class ActivitySnapshot extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ActivitySnapshot; + /** * @param array $content */ public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $activityType = '', public array $content = [], public bool $replace = true, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ActivitySnapshot; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'activityType' => $this->activityType, + 'content' => $this->content, + 'replace' => $this->replace, + ]); } } diff --git a/src/AGUI/Events/Custom.php b/src/AGUI/Events/Custom.php index a8c74aa..fe31056 100644 --- a/src/AGUI/Events/Custom.php +++ b/src/AGUI/Events/Custom.php @@ -6,16 +6,28 @@ use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class Custom extends AbstractEvent +/** + * @implements Arrayable + */ +final class Custom extends AbstractEvent implements Arrayable { + public EventType $type = EventType::Custom; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $name = '', public mixed $value = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::Custom; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'name' => $this->name, + 'value' => $this->value, + ]); } } diff --git a/src/AGUI/Events/MessagesSnapshot.php b/src/AGUI/Events/MessagesSnapshot.php index 788de82..2a00c3b 100644 --- a/src/AGUI/Events/MessagesSnapshot.php +++ b/src/AGUI/Events/MessagesSnapshot.php @@ -4,20 +4,30 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class MessagesSnapshot extends AbstractEvent +/** + * @implements Arrayable + */ +final class MessagesSnapshot extends AbstractEvent implements Arrayable { + public EventType $type = EventType::MessagesSnapshot; + /** * @param array $messages */ public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public array $messages = [], - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::MessagesSnapshot; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messages' => $this->messages, + ]); } } diff --git a/src/AGUI/Events/Raw.php b/src/AGUI/Events/Raw.php index f79c473..485500c 100644 --- a/src/AGUI/Events/Raw.php +++ b/src/AGUI/Events/Raw.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class Raw extends AbstractEvent +/** + * @implements Arrayable + */ +final class Raw extends AbstractEvent implements Arrayable { + public EventType $type = EventType::Raw; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public mixed $event = null, public ?string $source = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::Raw; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'event' => $this->event, + 'source' => $this->source, + ]); } } diff --git a/src/AGUI/Events/ReasoningEnd.php b/src/AGUI/Events/ReasoningEnd.php index 8afdfe2..fa89bb4 100644 --- a/src/AGUI/Events/ReasoningEnd.php +++ b/src/AGUI/Events/ReasoningEnd.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ReasoningEnd extends AbstractEvent +/** + * @implements Arrayable + */ +final class ReasoningEnd extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ReasoningEnd; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ReasoningEnd; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + ]); } } diff --git a/src/AGUI/Events/ReasoningMessageContent.php b/src/AGUI/Events/ReasoningMessageContent.php index 375d495..ef2d94c 100644 --- a/src/AGUI/Events/ReasoningMessageContent.php +++ b/src/AGUI/Events/ReasoningMessageContent.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ReasoningMessageContent extends AbstractEvent +/** + * @implements Arrayable + */ +final class ReasoningMessageContent extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ReasoningMessageContent; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $delta = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ReasoningMessageContent; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'delta' => $this->delta, + ]); } } diff --git a/src/AGUI/Events/ReasoningMessageEnd.php b/src/AGUI/Events/ReasoningMessageEnd.php index 5b4ac06..a01ca0b 100644 --- a/src/AGUI/Events/ReasoningMessageEnd.php +++ b/src/AGUI/Events/ReasoningMessageEnd.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ReasoningMessageEnd extends AbstractEvent +/** + * @implements Arrayable + */ +final class ReasoningMessageEnd extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ReasoningMessageEnd; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ReasoningMessageEnd; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + ]); } } diff --git a/src/AGUI/Events/ReasoningMessageStart.php b/src/AGUI/Events/ReasoningMessageStart.php index 9f8d84a..55835d0 100644 --- a/src/AGUI/Events/ReasoningMessageStart.php +++ b/src/AGUI/Events/ReasoningMessageStart.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; +/** + * @implements Arrayable + */ final class ReasoningMessageStart extends AbstractEvent { + public EventType $type = EventType::ReasoningMessageStart; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $role = 'assistant', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ReasoningMessageStart; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'role' => $this->role, + ]); } } diff --git a/src/AGUI/Events/ReasoningStart.php b/src/AGUI/Events/ReasoningStart.php index 6936cdf..483354e 100644 --- a/src/AGUI/Events/ReasoningStart.php +++ b/src/AGUI/Events/ReasoningStart.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ReasoningStart extends AbstractEvent +/** + * @implements Arrayable + */ +final class ReasoningStart extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ReasoningStart; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public ?string $encryptedContent = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ReasoningStart; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'encryptedContent' => $this->encryptedContent, + ]); } } diff --git a/src/AGUI/Events/RunError.php b/src/AGUI/Events/RunError.php index adb58c2..dd41f11 100644 --- a/src/AGUI/Events/RunError.php +++ b/src/AGUI/Events/RunError.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class RunError extends AbstractEvent +/** + * @implements Arrayable + */ +final class RunError extends AbstractEvent implements Arrayable { + public EventType $type = EventType::RunError; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $message = '', public ?string $code = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::RunError; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'message' => $this->message, + 'code' => $this->code, + ]); } } diff --git a/src/AGUI/Events/RunFinished.php b/src/AGUI/Events/RunFinished.php index 4c2f6ed..dfb5bb8 100644 --- a/src/AGUI/Events/RunFinished.php +++ b/src/AGUI/Events/RunFinished.php @@ -4,19 +4,31 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class RunFinished extends AbstractEvent +/** + * @implements Arrayable + */ +final class RunFinished extends AbstractEvent implements Arrayable { + public EventType $type = EventType::RunFinished; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $threadId = '', public string $runId = '', public mixed $result = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::RunFinished; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'threadId' => $this->threadId, + 'runId' => $this->runId, + 'result' => $this->result, + ]); } } diff --git a/src/AGUI/Events/RunStarted.php b/src/AGUI/Events/RunStarted.php index 006cf05..ea2c2fa 100644 --- a/src/AGUI/Events/RunStarted.php +++ b/src/AGUI/Events/RunStarted.php @@ -4,20 +4,33 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class RunStarted extends AbstractEvent +/** + * @implements Arrayable + */ +final class RunStarted extends AbstractEvent implements Arrayable { + public EventType $type = EventType::RunStarted; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $threadId = '', public string $runId = '', public ?string $parentRunId = null, public mixed $input = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::RunStarted; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'threadId' => $this->threadId, + 'runId' => $this->runId, + 'parentRunId' => $this->parentRunId, + 'input' => $this->input, + ]); } } diff --git a/src/AGUI/Events/StateDelta.php b/src/AGUI/Events/StateDelta.php index 8f9f50b..7d62899 100644 --- a/src/AGUI/Events/StateDelta.php +++ b/src/AGUI/Events/StateDelta.php @@ -4,20 +4,30 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class StateDelta extends AbstractEvent +/** + * @implements Arrayable + */ +final class StateDelta extends AbstractEvent implements Arrayable { + public EventType $type = EventType::StateDelta; + /** * @param array $delta */ public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public array $delta = [], - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::StateDelta; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'delta' => $this->delta, + ]); } } diff --git a/src/AGUI/Events/StateSnapshot.php b/src/AGUI/Events/StateSnapshot.php index ad17898..81894c8 100644 --- a/src/AGUI/Events/StateSnapshot.php +++ b/src/AGUI/Events/StateSnapshot.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class StateSnapshot extends AbstractEvent +/** + * @implements Arrayable + */ +final class StateSnapshot extends AbstractEvent implements Arrayable { + public EventType $type = EventType::StateSnapshot; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public mixed $snapshot = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::StateSnapshot; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'snapshot' => $this->snapshot, + ]); } } diff --git a/src/AGUI/Events/StepFinished.php b/src/AGUI/Events/StepFinished.php index dbfea42..d5d1a70 100644 --- a/src/AGUI/Events/StepFinished.php +++ b/src/AGUI/Events/StepFinished.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class StepFinished extends AbstractEvent +/** + * @implements Arrayable + */ +final class StepFinished extends AbstractEvent implements Arrayable { + public EventType $type = EventType::StepFinished; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $stepName = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::StepFinished; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'stepName' => $this->stepName, + ]); } } diff --git a/src/AGUI/Events/StepStarted.php b/src/AGUI/Events/StepStarted.php index c13d7cd..1ac5b3d 100644 --- a/src/AGUI/Events/StepStarted.php +++ b/src/AGUI/Events/StepStarted.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class StepStarted extends AbstractEvent +/** + * @implements Arrayable + */ +final class StepStarted extends AbstractEvent implements Arrayable { + public EventType $type = EventType::StepStarted; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $stepName = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::StepStarted; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'stepName' => $this->stepName, + ]); } } diff --git a/src/AGUI/Events/TextMessageChunk.php b/src/AGUI/Events/TextMessageChunk.php index ca0848a..8ff37ee 100644 --- a/src/AGUI/Events/TextMessageChunk.php +++ b/src/AGUI/Events/TextMessageChunk.php @@ -4,19 +4,31 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class TextMessageChunk extends AbstractEvent +/** + * @implements Arrayable + */ +final class TextMessageChunk extends AbstractEvent implements Arrayable { + public EventType $type = EventType::TextMessageChunk; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public ?string $messageId = null, public ?string $role = null, public ?string $delta = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::TextMessageChunk; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'role' => $this->role, + 'delta' => $this->delta, + ]); } } diff --git a/src/AGUI/Events/TextMessageContent.php b/src/AGUI/Events/TextMessageContent.php index 9fae383..cc6407b 100644 --- a/src/AGUI/Events/TextMessageContent.php +++ b/src/AGUI/Events/TextMessageContent.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class TextMessageContent extends AbstractEvent +/** + * @implements Arrayable + */ +final class TextMessageContent extends AbstractEvent implements Arrayable { + public EventType $type = EventType::TextMessageContent; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $delta = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::TextMessageContent; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'delta' => $this->delta, + ]); } } diff --git a/src/AGUI/Events/TextMessageEnd.php b/src/AGUI/Events/TextMessageEnd.php index 4dc018c..434ea83 100644 --- a/src/AGUI/Events/TextMessageEnd.php +++ b/src/AGUI/Events/TextMessageEnd.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class TextMessageEnd extends AbstractEvent +/** + * @implements Arrayable + */ +final class TextMessageEnd extends AbstractEvent implements Arrayable { + public EventType $type = EventType::TextMessageEnd; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::TextMessageEnd; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + ]); } } diff --git a/src/AGUI/Events/TextMessageStart.php b/src/AGUI/Events/TextMessageStart.php index 65075b1..8f5e5db 100644 --- a/src/AGUI/Events/TextMessageStart.php +++ b/src/AGUI/Events/TextMessageStart.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class TextMessageStart extends AbstractEvent +/** + * @implements Arrayable + */ +final class TextMessageStart extends AbstractEvent implements Arrayable { + public EventType $type = EventType::TextMessageStart; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $role = 'assistant', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::TextMessageStart; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'role' => $this->role, + ]); } } diff --git a/src/AGUI/Events/ToolCallArgs.php b/src/AGUI/Events/ToolCallArgs.php index 241bfa5..1e2dc65 100644 --- a/src/AGUI/Events/ToolCallArgs.php +++ b/src/AGUI/Events/ToolCallArgs.php @@ -4,18 +4,29 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ToolCallArgs extends AbstractEvent +/** + * @implements Arrayable + */ +final class ToolCallArgs extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ToolCallArgs; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $toolCallId = '', public string $delta = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ToolCallArgs; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'toolCallId' => $this->toolCallId, + 'delta' => $this->delta, + ]); } } diff --git a/src/AGUI/Events/ToolCallChunk.php b/src/AGUI/Events/ToolCallChunk.php index 41844fa..119c271 100644 --- a/src/AGUI/Events/ToolCallChunk.php +++ b/src/AGUI/Events/ToolCallChunk.php @@ -4,20 +4,33 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ToolCallChunk extends AbstractEvent +/** + * @implements Arrayable + */ +final class ToolCallChunk extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ToolCallChunk; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public ?string $toolCallId = null, public ?string $toolCallName = null, public ?string $parentMessageId = null, public ?string $delta = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ToolCallChunk; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'toolCallId' => $this->toolCallId, + 'toolCallName' => $this->toolCallName, + 'parentMessageId' => $this->parentMessageId, + 'delta' => $this->delta, + ]); } } diff --git a/src/AGUI/Events/ToolCallEnd.php b/src/AGUI/Events/ToolCallEnd.php index cd8f7c3..f210c4d 100644 --- a/src/AGUI/Events/ToolCallEnd.php +++ b/src/AGUI/Events/ToolCallEnd.php @@ -4,17 +4,27 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ToolCallEnd extends AbstractEvent +/** + * @implements Arrayable + */ +final class ToolCallEnd extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ToolCallEnd; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $toolCallId = '', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ToolCallEnd; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'toolCallId' => $this->toolCallId, + ]); } } diff --git a/src/AGUI/Events/ToolCallResult.php b/src/AGUI/Events/ToolCallResult.php index bf66d2d..b25aa4f 100644 --- a/src/AGUI/Events/ToolCallResult.php +++ b/src/AGUI/Events/ToolCallResult.php @@ -4,20 +4,33 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ToolCallResult extends AbstractEvent +/** + * @implements Arrayable + */ +final class ToolCallResult extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ToolCallResult; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $messageId = '', public string $toolCallId = '', public string $content = '', public ?string $role = 'tool', - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ToolCallResult; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'messageId' => $this->messageId, + 'toolCallId' => $this->toolCallId, + 'content' => $this->content, + 'role' => $this->role, + ]); } } diff --git a/src/AGUI/Events/ToolCallStart.php b/src/AGUI/Events/ToolCallStart.php index 491ed38..4808dba 100644 --- a/src/AGUI/Events/ToolCallStart.php +++ b/src/AGUI/Events/ToolCallStart.php @@ -4,19 +4,31 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; +use Illuminate\Contracts\Support\Arrayable; -final class ToolCallStart extends AbstractEvent +/** + * @implements Arrayable + */ +final class ToolCallStart extends AbstractEvent implements Arrayable { + public EventType $type = EventType::ToolCallStart; + public function __construct( - ?DateTimeImmutable $timestamp = null, - mixed $rawEvent = null, public string $toolCallId = '', public string $toolCallName = '', public ?string $parentMessageId = null, - ) { - parent::__construct($timestamp, $rawEvent); - $this->type = EventType::ToolCallStart; + ) {} + + /** + * @return array + */ + public function toArray(): array + { + return $this->buildArray([ + 'toolCallId' => $this->toolCallId, + 'toolCallName' => $this->toolCallName, + 'parentMessageId' => $this->parentMessageId, + ]); } } diff --git a/src/Agents/Prebuilt/WeatherAgent.php b/src/Agents/Prebuilt/WeatherAgent.php index fe8ba07..57599c3 100644 --- a/src/Agents/Prebuilt/WeatherAgent.php +++ b/src/Agents/Prebuilt/WeatherAgent.php @@ -46,8 +46,8 @@ public function llm(): LLM|string|null { // return Cortex::llm('ollama', 'gpt-oss:20b')->ignoreFeatures(); // return Cortex::llm('openai', 'gpt-4.1-mini')->ignoreFeatures(); - // return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); - return Cortex::llm('anthropic', 'claude-haiku-4-5')->ignoreFeatures(); + return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); + // return Cortex::llm('anthropic', 'claude-haiku-4-5')->ignoreFeatures(); } #[Override] diff --git a/src/Agents/Stages/TrackAgentStart.php b/src/Agents/Stages/TrackAgentStart.php index 8b34e3c..6007a64 100644 --- a/src/Agents/Stages/TrackAgentStart.php +++ b/src/Agents/Stages/TrackAgentStart.php @@ -24,7 +24,13 @@ public function __construct( public function handlePipeable(mixed $payload, RuntimeConfig $config, Closure $next): mixed { $config->pushChunkWhenStreaming( - new ChatGenerationChunk(ChunkType::RunStart), + new ChatGenerationChunk( + ChunkType::RunStart, + metadata: [ + 'run_id' => $config->runId, + 'thread_id' => $config->threadId, + ], + ), fn() => $this->agent->dispatchEvent(new AgentStart($this->agent, $config)), ); diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index fa9836f..d9bf9f3 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -77,7 +77,7 @@ public function invoke(string $agent, Request $request): JsonResponse ]); } - public function stream(string $agent, Request $request): void// : StreamedResponse + public function stream(string $agent, Request $request)//: void// : StreamedResponse { $agent = Cortex::agent($agent); @@ -115,12 +115,12 @@ public function stream(string $agent, Request $request): void// : StreamedRespon ); try { - foreach ($result as $chunk) { - dump(sprintf('%s: %s', $chunk->type->value, $chunk->message->content())); - // dump($chunk->toArray()); - } + // foreach ($result as $chunk) { + // dump(sprintf('%s: %s', $chunk->type->value, $chunk->message->content())); + // // dump($chunk->toArray()); + // } - // return $result->streamResponse(); + return $result->agUiStreamResponse(); } catch (Throwable $e) { dd($e); } diff --git a/src/LLM/Contracts/StreamingProtocol.php b/src/LLM/Contracts/StreamingProtocolDriver.php similarity index 53% rename from src/LLM/Contracts/StreamingProtocol.php rename to src/LLM/Contracts/StreamingProtocolDriver.php index 1d20116..f46d67d 100644 --- a/src/LLM/Contracts/StreamingProtocol.php +++ b/src/LLM/Contracts/StreamingProtocolDriver.php @@ -6,14 +6,11 @@ use Closure; use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\ChatGenerationChunk; -interface StreamingProtocol +interface StreamingProtocolDriver { - public function streamResponse(ChatStreamResult $result): Closure; - /** - * @return array + * Stream the response from the LLM. */ - public function mapChunkToPayload(ChatGenerationChunk $chunk): array; + public function streamResponse(ChatStreamResult $result): Closure; } diff --git a/src/LLM/Data/Concerns/HasStreamResponses.php b/src/LLM/Data/Concerns/HasStreamResponses.php index 317e2b4..cc3bcfa 100644 --- a/src/LLM/Data/Concerns/HasStreamResponses.php +++ b/src/LLM/Data/Concerns/HasStreamResponses.php @@ -4,11 +4,8 @@ namespace Cortex\LLM\Data\Concerns; -use Cortex\LLM\Streaming\AgUiDataStream; -use Cortex\LLM\Streaming\VercelDataStream; -use Cortex\LLM\Streaming\VercelTextStream; -use Cortex\LLM\Contracts\StreamingProtocol; -use Cortex\LLM\Streaming\DefaultDataStream; +use Cortex\LLM\Enums\StreamingProtocol; +use Cortex\LLM\Contracts\StreamingProtocolDriver; use Symfony\Component\HttpFoundation\StreamedResponse; /** @mixin \Cortex\LLM\Data\ChatStreamResult */ @@ -17,41 +14,15 @@ trait HasStreamResponses /** * Create a streaming response using the Vercel AI SDK protocol. */ - public function streamResponse(): StreamedResponse + public function streamResponse(StreamingProtocol $protocol): StreamedResponse { - return $this->toStreamedResponse(new DefaultDataStream()); - } - - /** - * Create a plain text streaming response (Vercel AI SDK text format). - * Streams only the text content without any JSON encoding or metadata. - * - * @see https://sdk.vercel.ai/docs/ai-sdk-core/generating-text - */ - public function vercelTextStreamResponse(): StreamedResponse - { - return $this->toStreamedResponse(new VercelTextStream()); - } - - public function vercelDataStreamResponse(): StreamedResponse - { - return $this->toStreamedResponse(new VercelDataStream()); - } - - /** - * Create a streaming response using the AG-UI protocol. - * - * @see https://docs.ag-ui.com/concepts/events.md - */ - public function agUiStreamResponse(): StreamedResponse - { - return $this->toStreamedResponse(new AgUiDataStream()); + return $this->toStreamedResponse($protocol->driver()); } /** * Create a streaming response using a custom streaming protocol. */ - public function toStreamedResponse(StreamingProtocol $protocol): StreamedResponse + public function toStreamedResponse(StreamingProtocolDriver $protocol): StreamedResponse { /** @var \Illuminate\Routing\ResponseFactory $responseFactory */ $responseFactory = response(); diff --git a/src/LLM/Enums/StreamingProtocol.php b/src/LLM/Enums/StreamingProtocol.php new file mode 100644 index 0000000..c989db0 --- /dev/null +++ b/src/LLM/Enums/StreamingProtocol.php @@ -0,0 +1,24 @@ + new AgUiDataStream(), + self::Vercel => new VercelDataStream(), + }; + } +} diff --git a/src/LLM/Streaming/AgUiDataStream.php b/src/LLM/Streaming/AgUiDataStream.php index 875c783..a27231d 100644 --- a/src/LLM/Streaming/AgUiDataStream.php +++ b/src/LLM/Streaming/AgUiDataStream.php @@ -5,23 +5,38 @@ namespace Cortex\LLM\Streaming; use Closure; +use DateTimeImmutable; use Illuminate\Support\Js; +use Cortex\AGUI\Events\Custom; use Cortex\LLM\Enums\ChunkType; +use Cortex\AGUI\Events\RunError; +use Cortex\LLM\Enums\MessageRole; +use Cortex\AGUI\Events\RunStarted; +use Cortex\AGUI\Events\RunFinished; +use Cortex\AGUI\Events\StepStarted; +use Cortex\AGUI\Events\ToolCallEnd; +use Cortex\AGUI\Events\ReasoningEnd; +use Cortex\AGUI\Events\StepFinished; +use Cortex\AGUI\Events\ToolCallArgs; +use Cortex\AGUI\Events\ToolCallStart; use Cortex\LLM\Data\ChatStreamResult; +use Cortex\AGUI\Events\ReasoningStart; +use Cortex\AGUI\Events\TextMessageEnd; +use Cortex\AGUI\Events\ToolCallResult; +use Cortex\AGUI\Events\TextMessageStart; use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Contracts\StreamingProtocol; +use Cortex\AGUI\Events\TextMessageContent; +use Cortex\LLM\Contracts\StreamingProtocolDriver; +use Cortex\AGUI\Contracts\Event as AgUiEvent; +use Cortex\AGUI\Events\ReasoningMessageContent; /** * AG-UI (Agent User Interaction Protocol) streaming implementation. * * @see https://docs.ag-ui.com/concepts/events.md */ -class AgUiDataStream implements StreamingProtocol +class AgUiDataStream implements StreamingProtocolDriver { - private bool $messageStarted = false; - - private bool $runStarted = false; - private ?string $currentMessageId = null; private ?string $runId = null; @@ -36,38 +51,12 @@ public function streamResponse(ChatStreamResult $result): Closure break; } - $events = $this->mapChunkToEvents($chunk); - - foreach ($events as $event) { - $payload = Js::encode($event); - - echo 'event: message' . "\n"; - echo 'data: ' . $payload . "\n\n"; - - if (ob_get_level() > 0) { - ob_flush(); - } - - flush(); - } - } - - // Send final events if needed - if ($this->messageStarted) { - $this->sendEvent([ - 'type' => 'TextMessageEnd', - 'messageId' => $this->currentMessageId, - 'timestamp' => now()->toIso8601String(), - ]); - } + $event = $this->mapChunkToEvent($chunk); + $timestamp = DateTimeImmutable::createFromInterface($chunk->createdAt); - if ($this->runStarted) { - $this->sendEvent([ - 'type' => 'RunFinished', - 'runId' => $this->runId, - 'threadId' => $this->threadId, - 'timestamp' => now()->toIso8601String(), - ]); + $this->sendEvent( + $event->withTimestamp($timestamp)->withRawEvent($chunk->toArray()), + ); } if (ob_get_level() > 0) { @@ -78,224 +67,109 @@ public function streamResponse(ChatStreamResult $result): Closure }; } - public function mapChunkToPayload(ChatGenerationChunk $chunk): array - { - // For compatibility with the interface, return the first event - $events = $this->mapChunkToEvents($chunk); - - return $events[0] ?? []; - } - /** - * Map a ChatGenerationChunk to one or more AG-UI events. - * - * @return array> + * Map a ChatGenerationChunk to an AG-UI event. */ - protected function mapChunkToEvents(ChatGenerationChunk $chunk): array + protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent { - $events = []; - $timestamp = $chunk->createdAt->format('c'); - - // Handle lifecycle events - if ($chunk->type === ChunkType::MessageStart) { - // Start run if not started - if (! $this->runStarted) { - // Use chunk ID as basis for run and thread IDs - $this->runId = 'run_' . $chunk->id; - $this->threadId = 'thread_' . $chunk->id; - - $events[] = [ - 'type' => 'RunStarted', - 'runId' => $this->runId, - 'threadId' => $this->threadId, - 'timestamp' => $timestamp, - ]; - $this->runStarted = true; - } - - // Start text message - $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id; - $events[] = [ - 'type' => 'TextMessageStart', - 'messageId' => $this->currentMessageId, - 'role' => 'assistant', - 'timestamp' => $timestamp, - ]; - $this->messageStarted = true; - } - - // Handle text content - if ($chunk->type === ChunkType::TextDelta && $chunk->message->content !== null && $chunk->message->content !== '') { - $events[] = [ - 'type' => 'TextMessageContent', - 'messageId' => $this->currentMessageId ?? ($chunk->message->metadata?->id ?? $chunk->id), - 'delta' => $chunk->message->content, - 'timestamp' => $timestamp, - ]; - } - - // Handle text end - if ($chunk->type === ChunkType::TextEnd) { - $events[] = [ - 'type' => 'TextMessageEnd', - 'messageId' => $this->currentMessageId ?? ($chunk->message->metadata?->id ?? $chunk->id), - 'timestamp' => $timestamp, - ]; - $this->messageStarted = false; - } - - // Handle reasoning content (using draft reasoning events) - if ($chunk->type === ChunkType::ReasoningStart) { - $reasoningId = $chunk->message->metadata?->id ?? $chunk->id; - $events[] = [ - 'type' => 'ReasoningStart', - 'messageId' => $reasoningId, - 'timestamp' => $timestamp, - ]; - } - - if ($chunk->type === ChunkType::ReasoningDelta && $chunk->message->content !== null && $chunk->message->content !== '') { - $events[] = [ - 'type' => 'ReasoningMessageContent', - 'messageId' => $chunk->message->metadata?->id ?? $chunk->id, - 'delta' => $chunk->message->content, - 'timestamp' => $timestamp, - ]; - } - - if ($chunk->type === ChunkType::ReasoningEnd) { - $events[] = [ - 'type' => 'ReasoningEnd', - 'messageId' => $chunk->message->metadata?->id ?? $chunk->id, - 'timestamp' => $timestamp, - ]; - } - - // Handle tool calls - if ($chunk->type === ChunkType::ToolInputStart && $chunk->message->toolCalls !== null) { - foreach ($chunk->message->toolCalls as $toolCall) { - $events[] = [ - 'type' => 'ToolCallStart', - 'toolCallId' => $toolCall->id, - 'toolName' => $toolCall->function->name, - 'timestamp' => $timestamp, - ]; - } - } - - if ($chunk->type === ChunkType::ToolInputDelta && $chunk->message->toolCalls !== null) { - foreach ($chunk->message->toolCalls as $toolCall) { - $events[] = [ - 'type' => 'ToolCallContent', - 'toolCallId' => $toolCall->id, - 'delta' => json_encode($toolCall->function->arguments), - 'timestamp' => $timestamp, - ]; - } - } - - if ($chunk->type === ChunkType::ToolInputEnd && $chunk->message->toolCalls !== null) { - foreach ($chunk->message->toolCalls as $toolCall) { - $events[] = [ - 'type' => 'ToolCallEnd', - 'toolCallId' => $toolCall->id, - 'timestamp' => $timestamp, - ]; - } - } - - // Handle tool output - if ($chunk->type === ChunkType::ToolOutputEnd && $chunk->message->toolCalls !== null) { - foreach ($chunk->message->toolCalls as $toolCall) { - $events[] = [ - 'type' => 'ToolCallResult', - 'toolCallId' => $toolCall->id, - 'result' => $toolCall->result ?? null, - 'timestamp' => $timestamp, - ]; - } - } - - // Handle step events - if ($chunk->type === ChunkType::StepStart) { - $events[] = [ - 'type' => 'StepStarted', - 'stepName' => 'step_' . $chunk->id, - 'timestamp' => $timestamp, - ]; - } - - if ($chunk->type === ChunkType::StepEnd) { - $events[] = [ - 'type' => 'StepFinished', - 'stepName' => 'step_' . $chunk->id, - 'timestamp' => $timestamp, - ]; - } - - // Handle errors - if ($chunk->type === ChunkType::Error) { - $events[] = [ - 'type' => 'RunError', - 'message' => $chunk->message->content ?? 'An error occurred', - 'timestamp' => $timestamp, - ]; - $this->runStarted = false; - $this->messageStarted = false; - } - - // Handle final chunk - if ($chunk->isFinal && $chunk->type === ChunkType::MessageEnd) { - // End message if it was started - if ($this->messageStarted) { - $events[] = [ - 'type' => 'TextMessageEnd', - 'messageId' => $this->currentMessageId ?? ($chunk->message->metadata?->id ?? $chunk->id), - 'timestamp' => $timestamp, - ]; - $this->messageStarted = false; - } - - // End run - if ($this->runStarted) { - $event = [ - 'type' => 'RunFinished', - 'runId' => $this->runId, - 'threadId' => $this->threadId, - 'timestamp' => $timestamp, - ]; - - // Add usage as result if available - if ($chunk->usage !== null) { - $event['result'] = [ - 'usage' => $chunk->usage->toArray(), - ]; - } - - $events[] = $event; - $this->runStarted = false; - } - } - - return $events; + $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? ''; + + return match ($chunk->type) { + ChunkType::RunStart => new RunStarted( + runId: $this->runId = $chunk->metadata['run_id'], + threadId: $this->threadId = $chunk->metadata['thread_id'], + ), + + ChunkType::RunEnd => new RunFinished( + runId: $this->runId, + threadId: $this->threadId, + ), + + ChunkType::TextStart => new TextMessageStart( + messageId: $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? '', + ), + + ChunkType::TextDelta => new TextMessageContent( + messageId: $this->currentMessageId, + delta: $chunk->message->text() ?? '', + ), + + ChunkType::TextEnd => new TextMessageEnd( + messageId: $this->currentMessageId, + ), + + ChunkType::ReasoningStart => new ReasoningStart( + messageId: $this->currentMessageId, + ), + + ChunkType::ReasoningDelta => ($chunk->message->content !== null && $chunk->message->content !== '') + ? new ReasoningMessageContent( + messageId: $this->currentMessageId, + delta: $chunk->message->text() ?? '', + ) + : null, + + ChunkType::ReasoningEnd => new ReasoningEnd( + messageId: $this->currentMessageId, + ), + + ChunkType::ToolInputStart => $chunk->message->toolCalls !== null + ? new ToolCallStart( + toolCallId: $chunk->message->toolCalls->first()->id, + toolCallName: $chunk->message->toolCalls->first()->function->name, + parentMessageId: $this->currentMessageId, + ) + : null, + + ChunkType::ToolInputDelta => $chunk->message->toolCalls !== null + ? new ToolCallArgs( + toolCallId: $chunk->message->toolCalls->first()->id, + delta: json_encode($chunk->message->toolCalls->first()->function->arguments), + ) + : null, + + ChunkType::ToolInputEnd => $chunk->message->toolCalls !== null + ? new ToolCallEnd( + toolCallId: $chunk->message->toolCalls->first()->id, + ) + : null, + + ChunkType::ToolOutputEnd => $chunk->message->role === MessageRole::Tool + ? new ToolCallResult( + messageId: $this->currentMessageId, + toolCallId: $chunk->message->id, + content: $chunk->message->text() ?? '', + ) + : null, + + ChunkType::StepStart => new StepStarted( + stepName: 'step_' . ($chunk->id ?? ''), + ), + + ChunkType::StepEnd => new StepFinished( + stepName: 'step_' . ($chunk->id ?? ''), + ), + + ChunkType::Error => new RunError( + message: $chunk->exception?->getMessage() ?? 'An error occurred', + ), + + default => new Custom( + name: $chunk->type->value, + ), + }; } /** * Send a single event to the output stream. * - * @param array $event + * @param \Cortex\AGUI\Contracts\Event $event */ - protected function sendEvent(array $event): void + protected function sendEvent(AgUiEvent $event): void { - $payload = Js::encode($event); + $payload = Js::encode($event->toArray()); echo 'event: message' . "\n"; echo 'data: ' . $payload . "\n\n"; - - if (ob_get_level() > 0) { - ob_flush(); - } - - flush(); } } diff --git a/src/LLM/Streaming/DefaultDataStream.php b/src/LLM/Streaming/DefaultDataStream.php index 754da47..196f1ed 100644 --- a/src/LLM/Streaming/DefaultDataStream.php +++ b/src/LLM/Streaming/DefaultDataStream.php @@ -8,9 +8,9 @@ use Illuminate\Support\Js; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Contracts\StreamingProtocol; +use Cortex\LLM\Contracts\StreamingProtocolDriver; -class DefaultDataStream implements StreamingProtocol +class DefaultDataStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { diff --git a/src/LLM/Streaming/VercelDataStream.php b/src/LLM/Streaming/VercelDataStream.php index 109ee1c..6d7e97c 100644 --- a/src/LLM/Streaming/VercelDataStream.php +++ b/src/LLM/Streaming/VercelDataStream.php @@ -9,9 +9,9 @@ use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Contracts\StreamingProtocol; +use Cortex\LLM\Contracts\StreamingProtocolDriver; -class VercelDataStream implements StreamingProtocol +class VercelDataStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { diff --git a/src/LLM/Streaming/VercelTextStream.php b/src/LLM/Streaming/VercelTextStream.php index aa5fd80..dd81f1c 100644 --- a/src/LLM/Streaming/VercelTextStream.php +++ b/src/LLM/Streaming/VercelTextStream.php @@ -8,7 +8,7 @@ use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Contracts\StreamingProtocol; +use Cortex\LLM\Contracts\StreamingProtocolDriver; /** * Vercel AI SDK Text Stream implementation. @@ -19,7 +19,7 @@ * * @see https://sdk.vercel.ai/docs/ai-sdk-core/generating-text */ -class VercelTextStream implements StreamingProtocol +class VercelTextStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { diff --git a/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php b/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php index 49a605e..09c105e 100644 --- a/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php +++ b/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php @@ -4,303 +4,3 @@ namespace Tests\Unit\LLM\Streaming; -use Closure; -use ArrayIterator; -use ReflectionClass; -use DateTimeImmutable; -use Cortex\LLM\Data\Usage; -use Cortex\LLM\Enums\ChunkType; -use Cortex\LLM\Enums\FinishReason; -use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Streaming\AgUiDataStream; -use Cortex\LLM\Data\Messages\AssistantMessage; - -beforeEach(function (): void { - $this->stream = new AgUiDataStream(); -}); - -it('maps MessageStart chunk to RunStarted and TextMessageStart events', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'RunStarted') - ->and($payload)->toHaveKey('runId') - ->and($payload)->toHaveKey('threadId') - ->and($payload)->toHaveKey('timestamp'); -}); - -it('maps TextDelta chunk to TextMessageContent event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello, '), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'TextMessageContent') - ->and($events[0])->toHaveKey('delta', 'Hello, ') - ->and($events[0])->toHaveKey('messageId') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps TextEnd chunk to TextMessageEnd event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'TextMessageEnd') - ->and($events[0])->toHaveKey('messageId') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps ReasoningStart chunk to ReasoningStart event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'ReasoningStart') - ->and($events[0])->toHaveKey('messageId') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps ReasoningDelta chunk to ReasoningMessageContent event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Thinking...'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'ReasoningMessageContent') - ->and($events[0])->toHaveKey('delta', 'Thinking...') - ->and($events[0])->toHaveKey('messageId') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps ReasoningEnd chunk to ReasoningEnd event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'ReasoningEnd') - ->and($events[0])->toHaveKey('messageId') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps StepStart chunk to StepStarted event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::StepStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'StepStarted') - ->and($events[0])->toHaveKey('stepName', 'step_msg_123') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps StepEnd chunk to StepFinished event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::StepEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'StepFinished') - ->and($events[0])->toHaveKey('stepName', 'step_msg_123') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps Error chunk to RunError event', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::Error, - id: 'msg_123', - message: new AssistantMessage(content: 'Something went wrong'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(1) - ->and($events[0])->toHaveKey('type', 'RunError') - ->and($events[0])->toHaveKey('message', 'Something went wrong') - ->and($events[0])->toHaveKey('timestamp'); -}); - -it('maps MessageEnd final chunk to TextMessageEnd and RunFinished events', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::Stop, - isFinal: true, - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - // Simulate that message was started - $messageStartedProperty = $reflection->getProperty('messageStarted'); - $messageStartedProperty->setValue($this->stream, true); - - $runStartedProperty = $reflection->getProperty('runStarted'); - $runStartedProperty->setValue($this->stream, true); - - $currentMessageIdProperty = $reflection->getProperty('currentMessageId'); - $currentMessageIdProperty->setValue($this->stream, 'msg_123'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toHaveCount(2) - ->and($events[0])->toHaveKey('type', 'TextMessageEnd') - ->and($events[1])->toHaveKey('type', 'RunFinished') - ->and($events[1])->toHaveKey('runId') - ->and($events[1])->toHaveKey('threadId'); -}); - -it('includes usage information in RunFinished result when available', function (): void { - $usage = new Usage( - promptTokens: 10, - completionTokens: 20, - ); - - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::Stop, - usage: $usage, - isFinal: true, - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - // Simulate that run was started - $runStartedProperty = $reflection->getProperty('runStarted'); - $runStartedProperty->setValue($this->stream, true); - - $events = $method->invoke($this->stream, $chunk); - - $runFinishedEvent = collect($events)->firstWhere('type', 'RunFinished'); - - expect($runFinishedEvent)->toHaveKey('result') - ->and($runFinishedEvent['result'])->toHaveKey('usage'); -}); - -it('does not emit text content events for empty deltas', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('mapChunkToEvents'); - - $events = $method->invoke($this->stream, $chunk); - - expect($events)->toBeEmpty(); -}); - -it('returns streamResponse closure that can be invoked', function (): void { - $chunks = [ - new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - ]; - - $result = new ChatStreamResult( - new ArrayIterator($chunks), - ); - - $closure = $this->stream->streamResponse($result); - - expect($closure)->toBeInstanceOf(Closure::class); - - // Note: We cannot reliably test the actual output here because flush() - // bypasses output buffering. The closure invocation is sufficient to - // verify it works without errors. - ob_start(); - - try { - $closure(); - } finally { - ob_end_clean(); - } -})->group('stream'); diff --git a/workbench/app/Providers/CortexServiceProvider.php b/workbench/app/Providers/CortexServiceProvider.php index feb3797..0b154bd 100644 --- a/workbench/app/Providers/CortexServiceProvider.php +++ b/workbench/app/Providers/CortexServiceProvider.php @@ -6,10 +6,10 @@ use Cortex\Agents\Agent; use Cortex\JsonSchema\Schema; use Illuminate\Support\ServiceProvider; -use Cortex\ModelInfo\Enums\ModelFeature; use Cortex\LLM\Data\Messages\UserMessage; use Cortex\LLM\Data\Messages\SystemMessage; +use function Cortex\Support\tool; use function Orchestra\Testbench\package_path; class CortexServiceProvider extends ServiceProvider @@ -32,15 +32,15 @@ public function boot(): void package_path('workbench/resources/views/prompts'), ); - // Cortex::registerAgent(new Agent( - // name: 'holiday_generator', - // prompt: 'Invent a new holiday and describe its traditions. Max 3 sentences.', - // llm: Cortex::llm('lmstudio/openai/gpt-oss-20b')->withTemperature(1.5), - // output: [ - // Schema::string('name')->required(), - // Schema::string('description')->required(), - // ], - // )); + Cortex::registerAgent(new Agent( + name: 'holiday_generator', + prompt: 'Invent a new holiday and describe its traditions. Max 3 sentences.', + llm: Cortex::llm('lmstudio/openai/gpt-oss-20b')->withTemperature(1.5), + output: [ + Schema::string('name')->required(), + Schema::string('description')->required(), + ], + )); Cortex::registerAgent(new Agent( name: 'quote_of_the_day', @@ -88,10 +88,22 @@ public function boot(): void llm: 'anthropic/claude-3-7-sonnet-20250219' )); + Cortex::registerAgent(new Agent( + name: 'function_gemma', + llm: 'lmstudio/functiongemma-270m-it', + prompt: 'You are a helpful assistant.', + tools: [ + tool( + 'toggle_light', + 'Toggle the light in the room', + fn(bool $onOrOff): string => 'Light toggled ' . ($onOrOff ? 'on' : 'off') . '.', + ), + ], + )); + Cortex::registerAgent(new Agent( name: 'code_generator', prompt: Cortex::prompt()->factory('blade')->make('example'), - // prompt: 'blade/example', )); } } diff --git a/workbench/resources/views/playground.blade.php b/workbench/resources/views/playground.blade.php new file mode 100644 index 0000000..e69de29 From 97054328a5000bd257c414625b1c4db0330459a6 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Tue, 6 Jan 2026 22:26:06 +0000 Subject: [PATCH 16/54] wip --- src/AGUI/Contracts/Event.php | 2 ++ src/AGUI/Events/AbstractEvent.php | 9 +++++++-- src/AGUI/Events/Custom.php | 1 - src/AGUI/Events/ReasoningMessageStart.php | 2 +- src/Http/Controllers/AgentsController.php | 18 ++++++++++-------- src/LLM/AbstractLLM.php | 1 - .../Anthropic/Concerns/MapStreamResponse.php | 1 - src/LLM/Enums/ChunkType.php | 6 ++++++ src/LLM/Streaming/AgUiDataStream.php | 8 +++----- .../Unit/LLM/Streaming/AgUiDataStreamTest.php | 1 - 10 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/AGUI/Contracts/Event.php b/src/AGUI/Contracts/Event.php index 41eb0e0..ea78265 100644 --- a/src/AGUI/Contracts/Event.php +++ b/src/AGUI/Contracts/Event.php @@ -36,6 +36,8 @@ public function withRawEvent(mixed $rawEvent): static; /** * Convert the event to an array. + * + * @return array */ public function toArray(): array; } diff --git a/src/AGUI/Events/AbstractEvent.php b/src/AGUI/Events/AbstractEvent.php index 93f0018..127ad9b 100644 --- a/src/AGUI/Events/AbstractEvent.php +++ b/src/AGUI/Events/AbstractEvent.php @@ -13,7 +13,7 @@ abstract class AbstractEvent implements Event { public protected(set) EventType $type; - public protected(set) ?DateTimeImmutable $timestamp; + public protected(set) ?DateTimeImmutable $timestamp = null; public protected(set) mixed $rawEvent; @@ -36,6 +36,11 @@ protected function formattedTimestamp(): ?string return $this->timestamp?->format(DateTimeInterface::ATOM); } + /** + * @param array $additional + * + * @return array + */ protected function buildArray(array $additional = []): array { $payload = [ @@ -47,6 +52,6 @@ protected function buildArray(array $additional = []): array $payload['timestamp'] = $this->formattedTimestamp(); } - return $payload; + return array_filter($payload, fn(mixed $value): bool => $value !== null); } } diff --git a/src/AGUI/Events/Custom.php b/src/AGUI/Events/Custom.php index fe31056..667a5c9 100644 --- a/src/AGUI/Events/Custom.php +++ b/src/AGUI/Events/Custom.php @@ -4,7 +4,6 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use Cortex\AGUI\Enums\EventType; use Illuminate\Contracts\Support\Arrayable; diff --git a/src/AGUI/Events/ReasoningMessageStart.php b/src/AGUI/Events/ReasoningMessageStart.php index 55835d0..eedb193 100644 --- a/src/AGUI/Events/ReasoningMessageStart.php +++ b/src/AGUI/Events/ReasoningMessageStart.php @@ -10,7 +10,7 @@ /** * @implements Arrayable */ -final class ReasoningMessageStart extends AbstractEvent +final class ReasoningMessageStart extends AbstractEvent implements Arrayable { public EventType $type = EventType::ReasoningMessageStart; diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index d9bf9f3..4465cca 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -14,7 +14,9 @@ use Cortex\Events\AgentStepStart; use Illuminate\Http\JsonResponse; use Illuminate\Routing\Controller; +use Cortex\LLM\Enums\StreamingProtocol; use Cortex\LLM\Data\Messages\UserMessage; +use Symfony\Component\HttpFoundation\StreamedResponse; class AgentsController extends Controller { @@ -77,7 +79,7 @@ public function invoke(string $agent, Request $request): JsonResponse ]); } - public function stream(string $agent, Request $request)//: void// : StreamedResponse + public function stream(string $agent, Request $request): StreamedResponse { $agent = Cortex::agent($agent); @@ -120,16 +122,16 @@ public function stream(string $agent, Request $request)//: void// : StreamedResp // // dump($chunk->toArray()); // } - return $result->agUiStreamResponse(); + return $result->streamResponse(StreamingProtocol::AGUI); } catch (Throwable $e) { dd($e); } - dd([ - 'total_usage' => $agent->getTotalUsage()->toArray(), - 'steps' => $agent->getSteps()->toArray(), - 'parsed_output' => $agent->getParsedOutput(), - 'memory' => $agent->getMemory()->getMessages()->toArray(), - ]); + // dd([ + // 'total_usage' => $agent->getTotalUsage()->toArray(), + // 'steps' => $agent->getSteps()->toArray(), + // 'parsed_output' => $agent->getParsedOutput(), + // 'memory' => $agent->getMemory()->getMessages()->toArray(), + // ]); } } diff --git a/src/LLM/AbstractLLM.php b/src/LLM/AbstractLLM.php index cc08074..97a4fe9 100644 --- a/src/LLM/AbstractLLM.php +++ b/src/LLM/AbstractLLM.php @@ -16,7 +16,6 @@ use Cortex\LLM\Contracts\Tool; use Cortex\Events\ChatModelEnd; use Cortex\LLM\Data\ToolConfig; -use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Enums\ToolChoice; use Cortex\Events\ChatModelError; use Cortex\Events\ChatModelStart; diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index f6f7064..c085391 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -26,7 +26,6 @@ use Cortex\SDK\Anthropic\Data\Messages\Streaming\ThinkingDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\InputJsonDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\SignatureDelta; -use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStop; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageDelta; use Cortex\SDK\Anthropic\Data\Messages\Streaming\Events\MessageStart; use Cortex\SDK\Anthropic\Data\Messages\ContentBlocks\TextContentBlock; diff --git a/src/LLM/Enums/ChunkType.php b/src/LLM/Enums/ChunkType.php index ccce0b8..63a4156 100644 --- a/src/LLM/Enums/ChunkType.php +++ b/src/LLM/Enums/ChunkType.php @@ -72,6 +72,12 @@ enum ChunkType: string case ChatModelEnd = 'chat_model_end'; + case OutputParserStart = 'output_parser_start'; + + case OutputParserEnd = 'output_parser_end'; + + case OutputParserError = 'output_parser_error'; + public function isText(): bool { return match ($this) { diff --git a/src/LLM/Streaming/AgUiDataStream.php b/src/LLM/Streaming/AgUiDataStream.php index a27231d..109e2c5 100644 --- a/src/LLM/Streaming/AgUiDataStream.php +++ b/src/LLM/Streaming/AgUiDataStream.php @@ -26,9 +26,9 @@ use Cortex\AGUI\Events\TextMessageStart; use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\AGUI\Events\TextMessageContent; -use Cortex\LLM\Contracts\StreamingProtocolDriver; use Cortex\AGUI\Contracts\Event as AgUiEvent; use Cortex\AGUI\Events\ReasoningMessageContent; +use Cortex\LLM\Contracts\StreamingProtocolDriver; /** * AG-UI (Agent User Interaction Protocol) streaming implementation. @@ -76,13 +76,13 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent return match ($chunk->type) { ChunkType::RunStart => new RunStarted( - runId: $this->runId = $chunk->metadata['run_id'], threadId: $this->threadId = $chunk->metadata['thread_id'], + runId: $this->runId = $chunk->metadata['run_id'], ), ChunkType::RunEnd => new RunFinished( - runId: $this->runId, threadId: $this->threadId, + runId: $this->runId, ), ChunkType::TextStart => new TextMessageStart( @@ -162,8 +162,6 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent /** * Send a single event to the output stream. - * - * @param \Cortex\AGUI\Contracts\Event $event */ protected function sendEvent(AgUiEvent $event): void { diff --git a/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php b/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php index 09c105e..a9d6c55 100644 --- a/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php +++ b/tests/Unit/LLM/Streaming/AgUiDataStreamTest.php @@ -3,4 +3,3 @@ declare(strict_types=1); namespace Tests\Unit\LLM\Streaming; - From 5e01a3ad29d00a6ad710bd0956c28879882c0a4a Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Thu, 8 Jan 2026 14:29:18 +0000 Subject: [PATCH 17/54] vercel --- composer.json | 1 + routes/api.php | 15 +- src/AGUI/Events/AbstractEvent.php | 4 +- src/Http/Controllers/AGUIController.php | 48 +++++ src/Http/Controllers/AgentsController.php | 32 ++- src/LLM/AbstractLLM.php | 8 +- src/LLM/Contracts/StreamingProtocolDriver.php | 5 + src/LLM/Data/ChatGenerationChunk.php | 15 ++ src/LLM/Data/ChatStreamResult.php | 19 +- src/LLM/Data/Concerns/HasStreamResponses.php | 5 +- src/LLM/Data/FunctionCall.php | 2 + src/LLM/Data/Messages/AssistantMessage.php | 7 +- .../Messages/Content/ReasoningContent.php | 2 +- src/LLM/Data/Messages/UserMessage.php | 7 +- src/LLM/Drivers/Anthropic/AnthropicChat.php | 8 + .../Anthropic/Concerns/MapStreamResponse.php | 32 +-- .../Anthropic/Concerns/MapsResponse.php | 4 +- src/LLM/Drivers/OpenAI/Chat/OpenAIChat.php | 8 + .../Responses/Concerns/MapsResponse.php | 10 +- .../Responses/Concerns/MapsStreamResponse.php | 14 +- .../OpenAI/Responses/OpenAIResponses.php | 8 + src/LLM/Enums/ChunkType.php | 23 ++- src/LLM/Streaming/AgUiDataStream.php | 73 +++++-- src/LLM/Streaming/VercelDataStream.php | 194 ++++++++++-------- .../app/Providers/CortexServiceProvider.php | 13 ++ 25 files changed, 403 insertions(+), 154 deletions(-) create mode 100644 src/Http/Controllers/AGUIController.php diff --git a/composer.json b/composer.json index 43dec53..2285a9d 100644 --- a/composer.json +++ b/composer.json @@ -32,6 +32,7 @@ "spatie/laravel-package-tools": "^1.17" }, "require-dev": { + "beyondcode/laravel-dump-server": "^2.1", "guzzlehttp/guzzle": "^7.9", "hkulekci/qdrant": "^0.5.8", "league/event": "^3.0", diff --git a/routes/api.php b/routes/api.php index d1a10e0..68fb37e 100644 --- a/routes/api.php +++ b/routes/api.php @@ -1,11 +1,22 @@ ['/cortex/*']]); +config(['cors.allowed_origins' => ['*']]); +config(['cors.allowed_methods' => ['*']]); +config(['cors.allowed_headers' => ['*']]); +config(['cors.exposed_headers' => ['*']]); +config(['cors.supports_credentials' => true]); + Route::prefix('cortex')->name('cortex.')->group(function () { Route::prefix('agents')->name('agents.')->group(function () { - Route::get('/{agent}/invoke', [AgentsController::class, 'invoke'])->name('invoke'); - Route::get('/{agent}/stream', [AgentsController::class, 'stream'])->name('stream'); + Route::any('/{agent}/invoke', [AgentsController::class, 'invoke'])->name('invoke'); + Route::any('/{agent}/stream', [AgentsController::class, 'stream'])->name('stream'); }); + + Route::post('/agui', AGUIController::class)->name('agui.invoke'); }); diff --git a/src/AGUI/Events/AbstractEvent.php b/src/AGUI/Events/AbstractEvent.php index 127ad9b..869bae1 100644 --- a/src/AGUI/Events/AbstractEvent.php +++ b/src/AGUI/Events/AbstractEvent.php @@ -31,9 +31,9 @@ public function withRawEvent(mixed $rawEvent): static return $this; } - protected function formattedTimestamp(): ?string + protected function formattedTimestamp(): ?int { - return $this->timestamp?->format(DateTimeInterface::ATOM); + return $this->timestamp?->getTimestamp(); } /** diff --git a/src/Http/Controllers/AGUIController.php b/src/Http/Controllers/AGUIController.php new file mode 100644 index 0000000..04f54be --- /dev/null +++ b/src/Http/Controllers/AGUIController.php @@ -0,0 +1,48 @@ +all()); + + $messages = $request->collect('messages') + ->map(function ($message) { + return new UserMessage($message['content']); + }); + + try { + return Cortex::agent('generic_thinking') + // ->onChunk(function (AgentStreamChunk $event): void { + // dump($event->chunk->toArray()); + // // dump($event->chunk->type->value . ': ' . $event->chunk->content()); + // }) + ->stream( + messages: $messages->all(), + input: $request->all(), + config: new RuntimeConfig( + threadId: $request->input('thread_id'), + runId: $request->input('run_id'), + ), + ) + ->withoutReasoning() + ->streamResponse(StreamingProtocol::AGUI); + } catch (\Throwable $e) { + dd($e); + } + } +} diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index 4465cca..62099a8 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -7,6 +7,7 @@ use Throwable; use Cortex\Cortex; use Cortex\Events\AgentEnd; +use Illuminate\Support\Arr; use Illuminate\Http\Request; use Cortex\Events\AgentStart; use Cortex\Events\AgentStepEnd; @@ -109,20 +110,31 @@ public function stream(string $agent, Request $request): StreamedResponse // } // }); - $result = $agent->stream( - messages: $request->has('message') ? [ + if ($request->isMethod('post')) { + $messages = [$request->collect('messages') + ->map(function ($message) { + return new UserMessage( + content: Arr::get($message, 'parts.0.text', ''), + // id: $message['id'] ?? null, + ); + }) + ->last() + ]; + } else { + $messages = $request->has('message') ? [ new UserMessage($request->input('message')), - ] : [], - input: $request->all(), - ); + ] : []; + } try { - // foreach ($result as $chunk) { - // dump(sprintf('%s: %s', $chunk->type->value, $chunk->message->content())); - // // dump($chunk->toArray()); - // } + $result = $agent->stream( + messages: $messages, + input: $request->input('input', []), + ); - return $result->streamResponse(StreamingProtocol::AGUI); + return $result->streamResponse( + $request->enum('protocol', StreamingProtocol::class, StreamingProtocol::Vercel) + ); } catch (Throwable $e) { dd($e); } diff --git a/src/LLM/AbstractLLM.php b/src/LLM/AbstractLLM.php index 97a4fe9..7b8fcd4 100644 --- a/src/LLM/AbstractLLM.php +++ b/src/LLM/AbstractLLM.php @@ -62,6 +62,10 @@ abstract class AbstractLLM implements LLM */ protected array $parameters = []; + protected ?int $maxTokens = null; + + protected ?float $temperature = null; + protected ?ToolConfig $toolConfig = null; protected ?StructuredOutputConfig $structuredOutputConfig = null; @@ -310,14 +314,14 @@ public function withModel(string $model): static public function withTemperature(?float $temperature): static { - $this->parameters['temperature'] = $temperature; + $this->temperature = $temperature; return $this; } public function withMaxTokens(?int $maxTokens): static { - $this->parameters['max_tokens'] = $maxTokens; + $this->maxTokens = $maxTokens; return $this; } diff --git a/src/LLM/Contracts/StreamingProtocolDriver.php b/src/LLM/Contracts/StreamingProtocolDriver.php index f46d67d..1dd2b6a 100644 --- a/src/LLM/Contracts/StreamingProtocolDriver.php +++ b/src/LLM/Contracts/StreamingProtocolDriver.php @@ -13,4 +13,9 @@ interface StreamingProtocolDriver * Stream the response from the LLM. */ public function streamResponse(ChatStreamResult $result): Closure; + + /** + * Get any additional headers for the streaming response. + */ + public function headers(): array; } diff --git a/src/LLM/Data/ChatGenerationChunk.php b/src/LLM/Data/ChatGenerationChunk.php index dd2cb8f..45158e4 100644 --- a/src/LLM/Data/ChatGenerationChunk.php +++ b/src/LLM/Data/ChatGenerationChunk.php @@ -52,6 +52,21 @@ public function text(): ?string return $this->message->text(); } + public function reasoning(): ?string + { + return $this->message->reasoning(); + } + + public function isTextEmpty(): bool + { + return $this->message->isTextEmpty(); + } + + public function isReasoningEmpty(): bool + { + return $this->message->isReasoningEmpty(); + } + /** * Get the text content that has been streamed so far. */ diff --git a/src/LLM/Data/ChatStreamResult.php b/src/LLM/Data/ChatStreamResult.php index 9078f6d..92c8029 100644 --- a/src/LLM/Data/ChatStreamResult.php +++ b/src/LLM/Data/ChatStreamResult.php @@ -32,11 +32,26 @@ public function text(): self /** * Stream only chunks where message content is not empty. */ - public function withoutEmpty(): self + public function withoutEmptyTextOrReasoningDeltas(): self { - return $this->reject(fn(ChatGenerationChunk $chunk): bool => $chunk->type->isText() && empty($chunk->content())); + return $this->reject(fn(ChatGenerationChunk $chunk): bool => match (true) { + $chunk->type === ChunkType::TextDelta => $chunk->isTextEmpty(), + $chunk->type === ChunkType::ReasoningDelta => $chunk->isReasoningEmpty(), + default => false, + }); + } + + /** + * Stream without reasoning chunks. + */ + public function withoutReasoning(): self + { + return $this->reject(fn(ChatGenerationChunk $chunk): bool => $chunk->type->isReasoning()); } + /** + * Append the stream buffer to the result. + */ public function appendStreamBuffer(RuntimeConfig $config): self { return new self(function () use ($config): Generator { diff --git a/src/LLM/Data/Concerns/HasStreamResponses.php b/src/LLM/Data/Concerns/HasStreamResponses.php index cc3bcfa..d885e32 100644 --- a/src/LLM/Data/Concerns/HasStreamResponses.php +++ b/src/LLM/Data/Concerns/HasStreamResponses.php @@ -22,15 +22,16 @@ public function streamResponse(StreamingProtocol $protocol): StreamedResponse /** * Create a streaming response using a custom streaming protocol. */ - public function toStreamedResponse(StreamingProtocolDriver $protocol): StreamedResponse + public function toStreamedResponse(StreamingProtocolDriver $driver): StreamedResponse { /** @var \Illuminate\Routing\ResponseFactory $responseFactory */ $responseFactory = response(); - return $responseFactory->stream($protocol->streamResponse($this), headers: [ + return $responseFactory->stream($driver->streamResponse($this), headers: [ 'Content-Type' => 'text/event-stream', 'Cache-Control' => 'no-cache', 'X-Accel-Buffering' => 'no', + ...$driver->headers(), ]); } } diff --git a/src/LLM/Data/FunctionCall.php b/src/LLM/Data/FunctionCall.php index d838c4e..d43c6b5 100644 --- a/src/LLM/Data/FunctionCall.php +++ b/src/LLM/Data/FunctionCall.php @@ -17,6 +17,7 @@ public function __construct( public string $name, public array $arguments, + public ?string $delta = null, ) {} public function toArray(): array @@ -24,6 +25,7 @@ public function toArray(): array return [ 'name' => $this->name, 'arguments' => $this->arguments, + 'delta' => $this->delta, ]; } } diff --git a/src/LLM/Data/Messages/AssistantMessage.php b/src/LLM/Data/Messages/AssistantMessage.php index f8e2b51..c164b81 100644 --- a/src/LLM/Data/Messages/AssistantMessage.php +++ b/src/LLM/Data/Messages/AssistantMessage.php @@ -57,9 +57,12 @@ public function text(): ?string public function isTextEmpty(): bool { - $text = $this->text(); + return empty($this->text()); + } - return $text === null || $text === ''; + public function isReasoningEmpty(): bool + { + return empty($this->reasoning()); } /** diff --git a/src/LLM/Data/Messages/Content/ReasoningContent.php b/src/LLM/Data/Messages/Content/ReasoningContent.php index 6c1b046..65f4855 100644 --- a/src/LLM/Data/Messages/Content/ReasoningContent.php +++ b/src/LLM/Data/Messages/Content/ReasoningContent.php @@ -7,8 +7,8 @@ final class ReasoningContent extends AbstractContent { public function __construct( - public string $id, public string $reasoning, + public array $metadata = [], ) {} public function append(string $reasoning): self diff --git a/src/LLM/Data/Messages/UserMessage.php b/src/LLM/Data/Messages/UserMessage.php index 0bff5ed..d678e5e 100644 --- a/src/LLM/Data/Messages/UserMessage.php +++ b/src/LLM/Data/Messages/UserMessage.php @@ -21,6 +21,7 @@ */ public function __construct( public string|array $content, + public ?string $id = null, public ?string $name = null, ) { $this->role = MessageRole::User; @@ -61,6 +62,10 @@ public function toArray(): array 'content' => $this->content, ]; + if ($this->id !== null) { + $data['id'] = $this->id; + } + if ($this->name !== null) { $data['name'] = $this->name; } @@ -70,7 +75,7 @@ public function toArray(): array public function cloneWithContent(mixed $content): self { - return new self($content, $this->name); + return new self($content, $this->id, $this->name); } /** diff --git a/src/LLM/Drivers/Anthropic/AnthropicChat.php b/src/LLM/Drivers/Anthropic/AnthropicChat.php index 4a4315e..2b831fc 100644 --- a/src/LLM/Drivers/Anthropic/AnthropicChat.php +++ b/src/LLM/Drivers/Anthropic/AnthropicChat.php @@ -107,6 +107,14 @@ protected function buildParams(array $additionalParameters): array 'model' => $this->model, ]; + if ($this->maxTokens !== null) { + $params['max_tokens'] = $this->maxTokens; + } + + if ($this->temperature !== null) { + $params['temperature'] = $this->temperature; + } + if ($this->structuredOutputConfig !== null) { $this->structuredOutputMode = StructuredOutputMode::Tool; diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index c085391..3a93e00 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -139,12 +139,13 @@ private function handleContentBlockStart(ContentBlockStart $event): void match (true) { $event->contentBlock instanceof TextContentBlock => $this->pendingTextContent = new TextContent($event->contentBlock->text), $event->contentBlock instanceof ThinkingContentBlock => $this->pendingReasoningContent = new ReasoningContent( - $event->contentBlock->signature, - $event->contentBlock->thinking, + reasoning: $event->contentBlock->thinking, + metadata: [ + 'signature' => $event->contentBlock->signature, + ], ), $event->contentBlock instanceof RedactedThinkingContentBlock => $this->pendingReasoningContent = new ReasoningContent( $event->contentBlock->data, - $event->contentBlock->text, ), $event->contentBlock instanceof ToolUseContentBlock => $this->startToolCall($event), default => null, @@ -170,8 +171,10 @@ private function handleContentBlockDelta(ContentBlockDelta $event): void $event->delta instanceof TextDelta && $this->pendingTextContent !== null => $this->pendingTextContent = $this->pendingTextContent->append($event->delta->text), $event->delta instanceof ThinkingDelta && $this->pendingReasoningContent !== null => $this->pendingReasoningContent = $this->pendingReasoningContent->append($event->delta->thinking), $event->delta instanceof SignatureDelta && $this->pendingReasoningContent !== null => $this->pendingReasoningContent = new ReasoningContent( - $event->delta->signature, - $this->pendingReasoningContent->reasoning, + reasoning: $this->pendingReasoningContent->reasoning, + metadata: [ + 'signature' => $event->delta->signature, + ], ), $event->delta instanceof InputJsonDelta && $this->pendingToolCall !== null => $this->pendingToolCall['partialJson'] .= $event->delta->partialJson, default => null, @@ -210,6 +213,7 @@ private function finalizeToolCall(): void new FunctionCall( $this->pendingToolCall['name'], $this->parseJsonSafely($this->pendingToolCall['partialJson']), + $this->pendingToolCall['partialJson'], ), ); @@ -251,8 +255,8 @@ private function buildContentSnapshot(): array $snapshot[] = new TextContent($this->pendingTextContent->text); } elseif ($this->pendingReasoningContent !== null) { $snapshot[] = new ReasoningContent( - $this->pendingReasoningContent->id, - $this->pendingReasoningContent->reasoning, + reasoning: $this->pendingReasoningContent->reasoning, + metadata: $this->pendingReasoningContent->metadata, ); } @@ -269,6 +273,7 @@ private function buildToolCallCollection(): ?ToolCallCollection new FunctionCall( $this->pendingToolCall['name'], $this->parseJsonSafely($this->pendingToolCall['partialJson']), + $this->pendingToolCall['partialJson'], ), ); } @@ -276,15 +281,17 @@ private function buildToolCallCollection(): ?ToolCallCollection return $toolCalls !== [] ? new ToolCallCollection($toolCalls) : null; } - private function extractTextDelta(StreamEvent $event): ?string + private function extractContentDelta(StreamEvent $event): TextContent|ReasoningContent|null { if (! $event instanceof ContentBlockDelta) { return null; } - return $event->delta instanceof TextDelta - ? $event->delta->text - : null; + return match (true) { + $event->delta instanceof TextDelta => new TextContent($event->delta->text), + $event->delta instanceof ThinkingDelta => new ReasoningContent($event->delta->thinking), + default => null, + }; } private function buildChunk( @@ -307,7 +314,7 @@ private function buildChunk( type: $chunkType, id: $messageId, message: new AssistantMessage( - content: $this->extractTextDelta($event), + content: [$this->extractContentDelta($event)], toolCalls: $this->buildToolCallCollection(), metadata: new ResponseMetadata( id: $messageId, @@ -318,6 +325,7 @@ private function buildChunk( processingTime: $meta?->processingTime, providerMetadata: $meta->raw ?? [], ), + id: $messageId, ), createdAt: $meta->createdAt ?? new DateTimeImmutable(), finishReason: $finishReason, diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php index 48efb28..217d2cb 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsResponse.php @@ -96,7 +96,9 @@ protected function mapContent(array $content): array ->map(function (object $content): mixed { return match (true) { $content instanceof TextContentBlock => new TextContent($content->text), - $content instanceof ThinkingContentBlock => new ReasoningContent($content->signature, $content->thinking), + $content instanceof ThinkingContentBlock => new ReasoningContent($content->thinking, metadata: [ + 'signature' => $content->signature, + ]), // 'redacted_thinking' => new RedactedThinkingContent($content['redacted_thinking']), default => null, }; diff --git a/src/LLM/Drivers/OpenAI/Chat/OpenAIChat.php b/src/LLM/Drivers/OpenAI/Chat/OpenAIChat.php index 7c1e072..eda7749 100644 --- a/src/LLM/Drivers/OpenAI/Chat/OpenAIChat.php +++ b/src/LLM/Drivers/OpenAI/Chat/OpenAIChat.php @@ -85,6 +85,14 @@ protected function buildParams(array $additionalParameters): array 'model' => $this->model, ]; + if ($this->maxTokens !== null) { + $params['max_tokens'] = $this->maxTokens; + } + + if ($this->temperature !== null) { + $params['temperature'] = $this->temperature; + } + if ($this->structuredOutputConfig !== null) { $this->supportsFeatureOrFail(ModelFeature::StructuredOutput); diff --git a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsResponse.php b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsResponse.php index a381a91..e9938ec 100644 --- a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsResponse.php +++ b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsResponse.php @@ -67,8 +67,14 @@ protected function mapResponse(CreateResponse $response): ChatResult $reasoningContent = $output ->filter(fn(ResponseContract $item): bool => $item instanceof OutputReasoning) ->map(fn(OutputReasoning $reasoning): ReasoningContent => new ReasoningContent( - $reasoning->id, - Arr::first($reasoning->summary)->text ?? '', + reasoning: Arr::first($reasoning->summary)->text ?? '', + metadata: [ + 'id' => $reasoning->id, + 'status' => $reasoning->status, + 'type' => $reasoning->type, + 'encrypted_content' => $reasoning->encryptedContent, + 'summary' => $reasoning->summary, + ], )) ->all(); diff --git a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php index 8ca9194..1a8df6d 100644 --- a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php +++ b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php @@ -134,7 +134,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult // Initialize ReasoningContent object if (! isset($currentReasoningContent[$item->id])) { - $currentReasoningContent[$item->id] = new ReasoningContent($item->id, ''); + $currentReasoningContent[$item->id] = new ReasoningContent(''); } } } @@ -183,7 +183,12 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult // Update or create ReasoningContent object if (! isset($currentReasoningContent[$itemId])) { $reasoningId = $reasoningSoFar[$itemId]['id'] ?? $itemId; - $currentReasoningContent[$itemId] = new ReasoningContent($reasoningId, $reasoningTextSoFar[$itemId]); + $currentReasoningContent[$itemId] = new ReasoningContent( + reasoning: $reasoningTextSoFar[$itemId], + metadata: [ + 'id' => $reasoningId, + ], + ); } else { $currentReasoningContent[$itemId] = $currentReasoningContent[$itemId]->append($data->delta); } @@ -232,7 +237,10 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult } foreach ($currentReasoningContent as $reasoningContent) { - $currentContentSoFar[] = new ReasoningContent($reasoningContent->id, $reasoningContent->reasoning); + $currentContentSoFar[] = new ReasoningContent( + reasoning: $reasoningContent->reasoning, + metadata: $reasoningContent->metadata, + ); } // Finalize content when response is complete diff --git a/src/LLM/Drivers/OpenAI/Responses/OpenAIResponses.php b/src/LLM/Drivers/OpenAI/Responses/OpenAIResponses.php index 4f6a7cb..c469fbf 100644 --- a/src/LLM/Drivers/OpenAI/Responses/OpenAIResponses.php +++ b/src/LLM/Drivers/OpenAI/Responses/OpenAIResponses.php @@ -83,6 +83,14 @@ protected function buildParams(array $additionalParameters): array 'model' => $this->model, ]; + if ($this->maxTokens !== null) { + $params['max_tokens'] = $this->maxTokens; + } + + if ($this->temperature !== null) { + $params['temperature'] = $this->temperature; + } + if ($this->structuredOutputConfig !== null) { $this->supportsFeatureOrFail(ModelFeature::StructuredOutput); diff --git a/src/LLM/Enums/ChunkType.php b/src/LLM/Enums/ChunkType.php index 63a4156..f7f5734 100644 --- a/src/LLM/Enums/ChunkType.php +++ b/src/LLM/Enums/ChunkType.php @@ -81,9 +81,15 @@ enum ChunkType: string public function isText(): bool { return match ($this) { - self::TextStart, - self::TextDelta => true, - self::TextEnd => true, + self::TextStart, self::TextDelta, self::TextEnd => true, + default => false, + }; + } + + public function isReasoning(): bool + { + return match ($this) { + self::ReasoningStart, self::ReasoningDelta, self::ReasoningEnd => true, default => false, }; } @@ -100,6 +106,17 @@ public function isOperational(): bool ], true); } + public function isToolCall(): bool + { + return match ($this) { + self::ToolInputStart, + self::ToolInputDelta, + self::ToolInputEnd, + self::ToolOutputEnd => true, + default => false, + }; + } + public function isStart(): bool { return match ($this) { diff --git a/src/LLM/Streaming/AgUiDataStream.php b/src/LLM/Streaming/AgUiDataStream.php index 109e2c5..0db4663 100644 --- a/src/LLM/Streaming/AgUiDataStream.php +++ b/src/LLM/Streaming/AgUiDataStream.php @@ -5,7 +5,6 @@ namespace Cortex\LLM\Streaming; use Closure; -use DateTimeImmutable; use Illuminate\Support\Js; use Cortex\AGUI\Events\Custom; use Cortex\LLM\Enums\ChunkType; @@ -26,7 +25,9 @@ use Cortex\AGUI\Events\TextMessageStart; use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\AGUI\Events\TextMessageContent; +use Cortex\AGUI\Events\ReasoningMessageEnd; use Cortex\AGUI\Contracts\Event as AgUiEvent; +use Cortex\AGUI\Events\ReasoningMessageStart; use Cortex\AGUI\Events\ReasoningMessageContent; use Cortex\LLM\Contracts\StreamingProtocolDriver; @@ -52,11 +53,24 @@ public function streamResponse(ChatStreamResult $result): Closure } $event = $this->mapChunkToEvent($chunk); - $timestamp = DateTimeImmutable::createFromInterface($chunk->createdAt); - $this->sendEvent( - $event->withTimestamp($timestamp)->withRawEvent($chunk->toArray()), - ); + if ($event === null) { + continue; + } + + // $timestamp = DateTimeImmutable::createFromInterface($chunk->createdAt); + + if (is_array($event)) { + foreach ($event as $e) { + $this->sendEvent( + $e->withRawEvent($chunk->toArray()), + ); + } + } else { + $this->sendEvent( + $event->withRawEvent($chunk->toArray()), + ); + } } if (ob_get_level() > 0) { @@ -67,10 +81,15 @@ public function streamResponse(ChatStreamResult $result): Closure }; } + public function headers(): array + { + return []; + } + /** * Map a ChatGenerationChunk to an AG-UI event. */ - protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent + protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent|array|null { $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? ''; @@ -89,29 +108,41 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent messageId: $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? '', ), - ChunkType::TextDelta => new TextMessageContent( - messageId: $this->currentMessageId, - delta: $chunk->message->text() ?? '', - ), + ChunkType::TextDelta => ! empty($chunk->message->text()) + ? new TextMessageContent( + messageId: $this->currentMessageId, + delta: $chunk->message->text(), + ) + : null, ChunkType::TextEnd => new TextMessageEnd( messageId: $this->currentMessageId, ), - ChunkType::ReasoningStart => new ReasoningStart( - messageId: $this->currentMessageId, - ), + ChunkType::ReasoningStart => [ + new ReasoningStart( + messageId: $this->currentMessageId, + ), + new ReasoningMessageStart( + messageId: $this->currentMessageId, + ), + ], - ChunkType::ReasoningDelta => ($chunk->message->content !== null && $chunk->message->content !== '') + ChunkType::ReasoningDelta => ! empty($chunk->message->reasoning()) ? new ReasoningMessageContent( messageId: $this->currentMessageId, - delta: $chunk->message->text() ?? '', + delta: $chunk->message->reasoning(), ) : null, - ChunkType::ReasoningEnd => new ReasoningEnd( - messageId: $this->currentMessageId, - ), + ChunkType::ReasoningEnd => [ + new ReasoningMessageEnd( + messageId: $this->currentMessageId, + ), + new ReasoningEnd( + messageId: $this->currentMessageId, + ), + ], ChunkType::ToolInputStart => $chunk->message->toolCalls !== null ? new ToolCallStart( @@ -169,5 +200,11 @@ protected function sendEvent(AgUiEvent $event): void echo 'event: message' . "\n"; echo 'data: ' . $payload . "\n\n"; + + if (ob_get_level() > 0) { + ob_flush(); + } + + flush(); } } diff --git a/src/LLM/Streaming/VercelDataStream.php b/src/LLM/Streaming/VercelDataStream.php index 6d7e97c..7504484 100644 --- a/src/LLM/Streaming/VercelDataStream.php +++ b/src/LLM/Streaming/VercelDataStream.php @@ -5,7 +5,7 @@ namespace Cortex\LLM\Streaming; use Closure; -use Illuminate\Support\Js; +use Generator; use Cortex\LLM\Enums\ChunkType; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ChatGenerationChunk; @@ -13,116 +13,128 @@ class VercelDataStream implements StreamingProtocolDriver { + protected ?string $currentMessageId = null; + + protected ?string $finishReason = null; + public function streamResponse(ChatStreamResult $result): Closure { - return function () use ($result): void { - foreach ($result as $chunk) { + return function () use ($result): Generator { + foreach ($result->withoutEmptyTextOrReasoningDeltas() as $chunk) { if (connection_aborted() !== 0) { break; } - $payload = Js::encode($this->mapChunkToPayload($chunk)); + $event = $this->mapChunkToEvent($chunk); - echo 'data: ' . $payload; - echo "\n\n"; - - if (ob_get_level() > 0) { - ob_flush(); + if ($event !== null) { + yield 'data: ' . json_encode($event) . "\n\n"; } - - flush(); - } - - echo '[DONE]'; - - if (ob_get_level() > 0) { - ob_flush(); } - flush(); + yield 'data: [DONE]' . "\n\n"; }; } - public function mapChunkToPayload(ChatGenerationChunk $chunk): array + public function headers(): array { - $payload = [ - 'type' => $this->mapChunkTypeToVercelType($chunk->type), + return [ + 'x-vercel-ai-ui-message-stream' => 'v1', ]; - - // Add messageId for message start events (per Vercel protocol) - if ($chunk->type === ChunkType::MessageStart) { - $payload['messageId'] = $chunk->id; - } - - // Add unique ID for text/reasoning blocks - if (in_array($chunk->type, [ - ChunkType::TextStart, - ChunkType::TextDelta, - ChunkType::TextEnd, - ChunkType::ReasoningStart, - ChunkType::ReasoningDelta, - ChunkType::ReasoningEnd, - ], true)) { - // Use message ID as the block identifier - $payload['id'] = $chunk->message->metadata?->id ?? $chunk->id; - } - - // Add delta content for incremental updates - if (in_array($chunk->type, [ChunkType::TextDelta, ChunkType::ReasoningDelta], true)) { - $payload['delta'] = $chunk->message->content; - } - - // Add full message content for other types - if (! isset($payload['delta']) && $chunk->message->content !== null) { - $payload['content'] = $chunk->message->content; - } - - // Add tool calls if present - if ($chunk->message->toolCalls !== null && $chunk->message->toolCalls->isNotEmpty()) { - $payload['toolCalls'] = $chunk->message->toolCalls->toArray(); - } - - // Add usage information if available - if ($chunk->usage !== null) { - $payload['usage'] = $chunk->usage->toArray(); - } - - // Add finish reason if final chunk - if ($chunk->isFinal && $chunk->finishReason !== null) { - $payload['finishReason'] = $chunk->finishReason->value; - } - - return $payload; } - protected function mapChunkTypeToVercelType(ChunkType $type): string + /** + * Map a ChatGenerationChunk to an AG-UI event. + */ + protected function mapChunkToEvent(ChatGenerationChunk $chunk): ?array { - return match ($type) { - ChunkType::MessageStart => 'start', - ChunkType::MessageEnd => 'finish', - - ChunkType::TextStart => 'text-start', - ChunkType::TextDelta => 'text-delta', - ChunkType::TextEnd => 'text-end', - - ChunkType::ReasoningStart => 'reasoning-start', - ChunkType::ReasoningDelta => 'reasoning-delta', - ChunkType::ReasoningEnd => 'reasoning-end', - - ChunkType::SourceDocument => 'source-document', - ChunkType::File => 'file', + $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? ''; - ChunkType::ToolInputStart => 'tool-input-start', - ChunkType::ToolInputDelta => 'tool-input-delta', - ChunkType::ToolInputEnd => 'tool-input-available', - ChunkType::ToolOutputEnd => 'tool-output-available', - - ChunkType::StepStart => 'start-step', - ChunkType::StepEnd => 'finish-step', - - ChunkType::Error => 'error', + if ($chunk->finishReason !== null) { + $this->finishReason = $chunk->finishReason->value; + } - default => $type->value, + return match ($chunk->type) { + ChunkType::RunStart => [ + 'type' => 'start', + ], + + ChunkType::RunEnd => array_filter([ + 'type' => 'finish', + 'finishReason' => $this->finishReason, + ]), + + ChunkType::StepStart => [ + 'type' => 'start-step', + ], + + ChunkType::StepEnd => [ + 'type' => 'finish-step', + ], + + ChunkType::TextStart => [ + 'type' => 'text-start', + 'id' => $this->currentMessageId, + ], + + ChunkType::TextDelta => [ + 'type' => 'text-delta', + 'id' => $this->currentMessageId, + 'delta' => $chunk->message->text(), + ], + + ChunkType::TextEnd => [ + 'type' => 'text-end', + 'id' => $this->currentMessageId, + ], + + ChunkType::ReasoningStart => [ + 'type' => 'reasoning-start', + 'id' => $this->currentMessageId, + ], + + ChunkType::ReasoningDelta => [ + 'type' => 'reasoning-delta', + 'id' => $this->currentMessageId, + 'delta' => $chunk->message->reasoning(), + ], + + ChunkType::ReasoningEnd => [ + 'type' => 'reasoning-end', + 'id' => $this->currentMessageId, + ], + + ChunkType::ToolInputStart => [ + 'type' => 'tool-input-start', + 'toolCallId' => $chunk->message->toolCalls->first()?->id, + 'toolName' => $chunk->message->toolCalls->first()?->function->name, + ], + + ChunkType::ToolInputDelta => [ + 'type' => 'tool-input-delta', + 'toolCallId' => $chunk->message->toolCalls->first()?->id, + 'inputTextDelta' => $chunk->message->toolCalls->first()?->function->delta ?? '', + ], + + ChunkType::ToolInputEnd => [ + 'type' => 'tool-input-available', + 'toolCallId' => $chunk->message->toolCalls->first()?->id, + 'toolName' => $chunk->message->toolCalls->first()?->function->name, + 'input' => $chunk->message->toolCalls->first()?->function->arguments, + ], + + ChunkType::ToolOutputEnd => [ + 'type' => 'tool-output-available', + 'toolCallId' => $chunk->message->id, + 'output' => $chunk->message->text() ?? '', + ], + + ChunkType::Error => [ + 'type' => 'error', + 'errorText' => $chunk->exception?->getMessage() ?? 'An error occurred', + ], + + default => null, }; } } diff --git a/workbench/app/Providers/CortexServiceProvider.php b/workbench/app/Providers/CortexServiceProvider.php index 0b154bd..9680d3c 100644 --- a/workbench/app/Providers/CortexServiceProvider.php +++ b/workbench/app/Providers/CortexServiceProvider.php @@ -82,6 +82,19 @@ public function boot(): void llm: 'lmstudio/openai/gpt-oss-20b' )); + Cortex::registerAgent(new Agent( + name: 'generic_thinking', + prompt: 'You are a helpful assistant.', + llm: Cortex::llm('anthropic/claude-3-7-sonnet-20250219') + ->withMaxTokens(2048) + ->withParameters([ + 'thinking' => [ + 'type' => 'enabled', + 'budget_tokens' => 1024, + ], + ]) + )); + Cortex::registerAgent(new Agent( name: 'generic_anthropic', prompt: 'You are a helpful assistant.', From 89ccc17c681980e7dd88c592bcccb8221f72bf51 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Fri, 9 Jan 2026 00:03:57 +0000 Subject: [PATCH 18/54] improvements --- src/AGUI/Contracts/Event.php | 6 +- src/AGUI/Events/AbstractEvent.php | 7 +-- src/Http/Controllers/AGUIController.php | 7 +-- src/Http/Controllers/AgentsController.php | 4 +- src/LLM/Data/Messages/AssistantMessage.php | 4 +- src/LLM/Streaming/AgUiDataStream.php | 67 ++++++++-------------- src/LLM/Streaming/VercelDataStream.php | 2 +- 7 files changed, 37 insertions(+), 60 deletions(-) diff --git a/src/AGUI/Contracts/Event.php b/src/AGUI/Contracts/Event.php index ea78265..530f204 100644 --- a/src/AGUI/Contracts/Event.php +++ b/src/AGUI/Contracts/Event.php @@ -4,7 +4,7 @@ namespace Cortex\AGUI\Contracts; -use DateTimeImmutable; +use DateTimeInterface; use Cortex\AGUI\Enums\EventType; interface Event @@ -17,7 +17,7 @@ interface Event /** * The timestamp of the event. */ - public ?DateTimeImmutable $timestamp { get; } + public ?DateTimeInterface $timestamp { get; } /** * The raw event data. @@ -27,7 +27,7 @@ interface Event /** * Set the timestamp for the event. */ - public function withTimestamp(DateTimeImmutable $timestamp): static; + public function withTimestamp(DateTimeInterface $timestamp): static; /** * Set the raw event for the event. diff --git a/src/AGUI/Events/AbstractEvent.php b/src/AGUI/Events/AbstractEvent.php index 869bae1..eb8cf57 100644 --- a/src/AGUI/Events/AbstractEvent.php +++ b/src/AGUI/Events/AbstractEvent.php @@ -4,7 +4,6 @@ namespace Cortex\AGUI\Events; -use DateTimeImmutable; use DateTimeInterface; use Cortex\AGUI\Contracts\Event; use Cortex\AGUI\Enums\EventType; @@ -13,11 +12,11 @@ abstract class AbstractEvent implements Event { public protected(set) EventType $type; - public protected(set) ?DateTimeImmutable $timestamp = null; + public protected(set) ?DateTimeInterface $timestamp = null; public protected(set) mixed $rawEvent; - public function withTimestamp(DateTimeImmutable $timestamp): static + public function withTimestamp(DateTimeInterface $timestamp): static { $this->timestamp = $timestamp; @@ -49,7 +48,7 @@ protected function buildArray(array $additional = []): array ]; if ($this->timestamp !== null) { - $payload['timestamp'] = $this->formattedTimestamp(); + $payload['timestamp'] = $this->timestamp->getTimestamp(); } return array_filter($payload, fn(mixed $value): bool => $value !== null); diff --git a/src/Http/Controllers/AGUIController.php b/src/Http/Controllers/AGUIController.php index 04f54be..ad665f0 100644 --- a/src/Http/Controllers/AGUIController.php +++ b/src/Http/Controllers/AGUIController.php @@ -4,12 +4,11 @@ namespace Cortex\Http\Controllers; +use Throwable; use Cortex\Cortex; use Illuminate\Http\Request; -use Illuminate\Http\JsonResponse; use Cortex\Pipeline\RuntimeConfig; use Illuminate\Routing\Controller; -use Cortex\Events\AgentStreamChunk; use Cortex\LLM\Enums\StreamingProtocol; use Cortex\LLM\Data\Messages\UserMessage; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -21,7 +20,7 @@ public function __invoke(Request $request): StreamedResponse // dump('AGUI request', $request->all()); $messages = $request->collect('messages') - ->map(function ($message) { + ->map(function (array $message): UserMessage { return new UserMessage($message['content']); }); @@ -41,7 +40,7 @@ public function __invoke(Request $request): StreamedResponse ) ->withoutReasoning() ->streamResponse(StreamingProtocol::AGUI); - } catch (\Throwable $e) { + } catch (Throwable $e) { dd($e); } } diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index 62099a8..c9d69d8 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -118,7 +118,7 @@ public function stream(string $agent, Request $request): StreamedResponse // id: $message['id'] ?? null, ); }) - ->last() + ->last(), ]; } else { $messages = $request->has('message') ? [ @@ -133,7 +133,7 @@ public function stream(string $agent, Request $request): StreamedResponse ); return $result->streamResponse( - $request->enum('protocol', StreamingProtocol::class, StreamingProtocol::Vercel) + $request->enum('protocol', StreamingProtocol::class, StreamingProtocol::Vercel), ); } catch (Throwable $e) { dd($e); diff --git a/src/LLM/Data/Messages/AssistantMessage.php b/src/LLM/Data/Messages/AssistantMessage.php index c164b81..a9fcbcb 100644 --- a/src/LLM/Data/Messages/AssistantMessage.php +++ b/src/LLM/Data/Messages/AssistantMessage.php @@ -57,12 +57,12 @@ public function text(): ?string public function isTextEmpty(): bool { - return empty($this->text()); + return in_array($this->text(), [null, ''], true); } public function isReasoningEmpty(): bool { - return empty($this->reasoning()); + return in_array($this->reasoning(), [null, ''], true); } /** diff --git a/src/LLM/Streaming/AgUiDataStream.php b/src/LLM/Streaming/AgUiDataStream.php index 0db4663..aa2b238 100644 --- a/src/LLM/Streaming/AgUiDataStream.php +++ b/src/LLM/Streaming/AgUiDataStream.php @@ -5,7 +5,8 @@ namespace Cortex\LLM\Streaming; use Closure; -use Illuminate\Support\Js; +use Generator; +use DateTimeImmutable; use Cortex\AGUI\Events\Custom; use Cortex\LLM\Enums\ChunkType; use Cortex\AGUI\Events\RunError; @@ -46,7 +47,7 @@ class AgUiDataStream implements StreamingProtocolDriver public function streamResponse(ChatStreamResult $result): Closure { - return function () use ($result): void { + return function () use ($result): Generator { foreach ($result as $chunk) { if (connection_aborted() !== 0) { break; @@ -54,30 +55,16 @@ public function streamResponse(ChatStreamResult $result): Closure $event = $this->mapChunkToEvent($chunk); - if ($event === null) { - continue; - } - - // $timestamp = DateTimeImmutable::createFromInterface($chunk->createdAt); - - if (is_array($event)) { - foreach ($event as $e) { - $this->sendEvent( - $e->withRawEvent($chunk->toArray()), - ); + if ($event !== null) { + if (is_array($event)) { + foreach ($event as $e) { + yield $this->getEventLine($e, $chunk); + } + } else { + yield $this->getEventLine($event, $chunk); } - } else { - $this->sendEvent( - $event->withRawEvent($chunk->toArray()), - ); } } - - if (ob_get_level() > 0) { - ob_flush(); - } - - flush(); }; } @@ -108,12 +95,12 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent|array| messageId: $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? '', ), - ChunkType::TextDelta => ! empty($chunk->message->text()) - ? new TextMessageContent( + ChunkType::TextDelta => in_array($chunk->message->text(), [null, ''], true) + ? null + : new TextMessageContent( messageId: $this->currentMessageId, delta: $chunk->message->text(), - ) - : null, + ), ChunkType::TextEnd => new TextMessageEnd( messageId: $this->currentMessageId, @@ -128,12 +115,12 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent|array| ), ], - ChunkType::ReasoningDelta => ! empty($chunk->message->reasoning()) - ? new ReasoningMessageContent( + ChunkType::ReasoningDelta => in_array($chunk->message->reasoning(), [null, ''], true) + ? null + : new ReasoningMessageContent( messageId: $this->currentMessageId, delta: $chunk->message->reasoning(), - ) - : null, + ), ChunkType::ReasoningEnd => [ new ReasoningMessageEnd( @@ -191,20 +178,12 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): AgUiEvent|array| }; } - /** - * Send a single event to the output stream. - */ - protected function sendEvent(AgUiEvent $event): void + protected function getEventLine(AgUiEvent $event, ChatGenerationChunk $chunk): string { - $payload = Js::encode($event->toArray()); - - echo 'event: message' . "\n"; - echo 'data: ' . $payload . "\n\n"; - - if (ob_get_level() > 0) { - ob_flush(); - } + $payload = $event->withRawEvent($chunk->toArray()) + ->withTimestamp($chunk->createdAt) + ->toArray(); - flush(); + return 'event: message' . "\n" . 'data: ' . json_encode($payload, JSON_THROW_ON_ERROR) . "\n\n"; } } diff --git a/src/LLM/Streaming/VercelDataStream.php b/src/LLM/Streaming/VercelDataStream.php index 7504484..26b58cc 100644 --- a/src/LLM/Streaming/VercelDataStream.php +++ b/src/LLM/Streaming/VercelDataStream.php @@ -28,7 +28,7 @@ public function streamResponse(ChatStreamResult $result): Closure $event = $this->mapChunkToEvent($chunk); if ($event !== null) { - yield 'data: ' . json_encode($event) . "\n\n"; + yield 'data: ' . json_encode($event, JSON_THROW_ON_ERROR) . "\n\n"; } } From 50c80002c2ebff14ff8f71c9cb4ef4d94868621c Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Sun, 11 Jan 2026 13:04:51 +0000 Subject: [PATCH 19/54] streaming protocols --- src/AGUI/Enums/EventType.php | 12 +- src/Agents/Agent.php | 4 + src/Agents/Prebuilt/WeatherAgent.php | 2 + src/Http/Controllers/AGUIController.php | 8 +- src/Http/Controllers/AgentsController.php | 46 +- src/LLM/Contracts/StreamingProtocolDriver.php | 2 + src/LLM/Data/ChatGenerationChunk.php | 5 + src/LLM/Data/ChatStreamResult.php | 11 +- src/LLM/Data/Messages/AssistantMessage.php | 27 +- .../Messages/Content/ReasoningContent.php | 3 + .../Anthropic/Concerns/MapsMessages.php | 3 + .../Chat/Concerns/MapsStreamResponse.php | 1 + .../Responses/Concerns/MapsStreamResponse.php | 7 +- src/LLM/Enums/ChunkType.php | 3 - src/LLM/Enums/StreamingProtocol.php | 7 +- src/LLM/Streaming/AgUiDataStream.php | 1 - src/LLM/Streaming/DefaultDataStream.php | 48 +- src/LLM/Streaming/VercelDataStream.php | 13 +- src/LLM/Streaming/VercelTextStream.php | 47 +- src/Pipeline/RuntimeConfig.php | 3 + src/Pipeline/State.php | 12 + src/Tools/FrontendTool.php | 54 ++ src/Tools/Prebuilt/OpenMeteoWeatherTool.php | 19 +- .../Drivers/Anthropic/AnthropicChatTest.php | 7 +- .../Drivers/OpenAI/OpenAIResponsesTest.php | 6 +- .../LLM/Streaming/VercelDataStreamTest.php | 574 ------------------ .../LLM/Streaming/VercelTextStreamTest.php | 423 ------------- 27 files changed, 215 insertions(+), 1133 deletions(-) create mode 100644 src/Pipeline/State.php create mode 100644 src/Tools/FrontendTool.php diff --git a/src/AGUI/Enums/EventType.php b/src/AGUI/Enums/EventType.php index 6498102..ba0534e 100644 --- a/src/AGUI/Enums/EventType.php +++ b/src/AGUI/Enums/EventType.php @@ -49,22 +49,22 @@ enum EventType: string case ToolCallResult = 'TOOL_CALL_RESULT'; /** Marks the start of reasoning. */ - case ReasoningStart = 'REASONING_START'; + case ReasoningStart = 'THINKING_START'; /** Marks the end of reasoning. */ - case ReasoningEnd = 'REASONING_END'; + case ReasoningEnd = 'THINKING_END'; /** Signals the start of a reasoning message. */ - case ReasoningMessageStart = 'REASONING_MESSAGE_START'; + case ReasoningMessageStart = 'THINKING_TEXT_MESSAGE_START'; /** Represents a chunk of content in a streaming reasoning message. */ - case ReasoningMessageContent = 'REASONING_MESSAGE_CONTENT'; + case ReasoningMessageContent = 'THINKING_TEXT_MESSAGE_CONTENT'; /** Signals the end of a reasoning message. */ - case ReasoningMessageEnd = 'REASONING_MESSAGE_END'; + case ReasoningMessageEnd = 'THINKING_TEXT_MESSAGE_END'; /** A convenience event to auto start/close reasoning messages. */ - case ReasoningMessageChunk = 'REASONING_MESSAGE_CHUNK'; + case ReasoningMessageChunk = 'THINKING_TEXT_MESSAGE_CHUNK'; /** Provides a complete snapshot of an agent’s state. */ case StateSnapshot = 'STATE_SNAPSHOT'; diff --git a/src/Agents/Agent.php b/src/Agents/Agent.php index d19b6c8..d0d83e1 100644 --- a/src/Agents/Agent.php +++ b/src/Agents/Agent.php @@ -405,6 +405,10 @@ protected function invokePipeline( $config ??= $this->runtimeConfig ?? new RuntimeConfig(); $this->withRuntimeConfig($config); + foreach ($config->tools as $tool) { + $this->llm->addTool($tool); + } + if ($streaming) { $config->onStreamChunk(function (RuntimeConfigStreamChunk $event): void { $this->withRuntimeConfig($event->config); diff --git a/src/Agents/Prebuilt/WeatherAgent.php b/src/Agents/Prebuilt/WeatherAgent.php index 57599c3..d989ec3 100644 --- a/src/Agents/Prebuilt/WeatherAgent.php +++ b/src/Agents/Prebuilt/WeatherAgent.php @@ -37,6 +37,8 @@ public function prompt(): ChatPromptTemplate|ChatPromptBuilder|string - If the user asks for activities, respond in the format they request. Use the get_weather tool to fetch current weather data. + + Only answer in sentences, you don't need to show the weather data, since it's handled with the tool output. INSTRUCTIONS, ), ]); diff --git a/src/Http/Controllers/AGUIController.php b/src/Http/Controllers/AGUIController.php index ad665f0..b188f8f 100644 --- a/src/Http/Controllers/AGUIController.php +++ b/src/Http/Controllers/AGUIController.php @@ -6,6 +6,7 @@ use Throwable; use Cortex\Cortex; +use Cortex\Pipeline\State; use Illuminate\Http\Request; use Cortex\Pipeline\RuntimeConfig; use Illuminate\Routing\Controller; @@ -21,7 +22,10 @@ public function __invoke(Request $request): StreamedResponse $messages = $request->collect('messages') ->map(function (array $message): UserMessage { - return new UserMessage($message['content']); + return new UserMessage( + content: $message['content'], + id: $message['id'] ?? null, + ); }); try { @@ -34,11 +38,11 @@ public function __invoke(Request $request): StreamedResponse messages: $messages->all(), input: $request->all(), config: new RuntimeConfig( + state: new State($request->input('state', [])), threadId: $request->input('thread_id'), runId: $request->input('run_id'), ), ) - ->withoutReasoning() ->streamResponse(StreamingProtocol::AGUI); } catch (Throwable $e) { dd($e); diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index c9d69d8..3f60967 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -10,10 +10,12 @@ use Illuminate\Support\Arr; use Illuminate\Http\Request; use Cortex\Events\AgentStart; +use Cortex\Tools\FrontendTool; use Cortex\Events\AgentStepEnd; use Cortex\Events\AgentStepError; use Cortex\Events\AgentStepStart; use Illuminate\Http\JsonResponse; +use Cortex\Pipeline\RuntimeConfig; use Illuminate\Routing\Controller; use Cortex\LLM\Enums\StreamingProtocol; use Cortex\LLM\Data\Messages\UserMessage; @@ -84,32 +86,6 @@ public function stream(string $agent, Request $request): StreamedResponse { $agent = Cortex::agent($agent); - // $agent->onStart(function (AgentStart $event): void { - // dump('---- AGENT START ----'); - // }); - // $agent->onEnd(function (AgentEnd $event): void { - // dump('---- AGENT END ----'); - // }); - // $agent->onStepStart(function (AgentStepStart $event): void { - // dump('-- STEP START --'); - // }); - // $agent->onStepEnd(function (AgentStepEnd $event): void { - // dump('-- STEP END --'); - // }); - // $agent->onStepError(function (AgentStepError $event): void { - // dump('-- STEP ERROR --'); - // }); - // $agent->onChunk(function (AgentStreamChunk $event): void { - // dump($event->chunk->type->value); - // $toolCalls = $event->chunk->message->toolCalls; - - // if ($toolCalls !== null) { - // dump(sprintf('chunk: %s', $event->chunk->message->toolCalls?->toJson())); - // } else { - // dump(sprintf('chunk: %s', $event->chunk->message->content)); - // } - // }); - if ($request->isMethod('post')) { $messages = [$request->collect('messages') ->map(function ($message) { @@ -126,10 +102,28 @@ public function stream(string $agent, Request $request): StreamedResponse ] : []; } + /** @var array> $toolsInput */ + $toolsInput = $request->input('tools', []); + $tools = collect($toolsInput) + ->filter(function ($tool): bool { + return $tool !== []; + }) + ->map(function (array $tool, $toolName): FrontendTool { + return new FrontendTool( + $toolName, + $tool['description'] ?? null, + $tool['parameters'] ?? [], + ); + }) + ->values(); + try { $result = $agent->stream( messages: $messages, input: $request->input('input', []), + config: new RuntimeConfig( + tools: $tools->all(), + ), ); return $result->streamResponse( diff --git a/src/LLM/Contracts/StreamingProtocolDriver.php b/src/LLM/Contracts/StreamingProtocolDriver.php index 1dd2b6a..39226b6 100644 --- a/src/LLM/Contracts/StreamingProtocolDriver.php +++ b/src/LLM/Contracts/StreamingProtocolDriver.php @@ -16,6 +16,8 @@ public function streamResponse(ChatStreamResult $result): Closure; /** * Get any additional headers for the streaming response. + * + * @return array */ public function headers(): array; } diff --git a/src/LLM/Data/ChatGenerationChunk.php b/src/LLM/Data/ChatGenerationChunk.php index 45158e4..ac6cc2b 100644 --- a/src/LLM/Data/ChatGenerationChunk.php +++ b/src/LLM/Data/ChatGenerationChunk.php @@ -67,6 +67,11 @@ public function isReasoningEmpty(): bool return $this->message->isReasoningEmpty(); } + public function isToolInputEmpty(): bool + { + return $this->message->isToolInputEmpty(); + } + /** * Get the text content that has been streamed so far. */ diff --git a/src/LLM/Data/ChatStreamResult.php b/src/LLM/Data/ChatStreamResult.php index 92c8029..6fb6a8e 100644 --- a/src/LLM/Data/ChatStreamResult.php +++ b/src/LLM/Data/ChatStreamResult.php @@ -30,13 +30,14 @@ public function text(): self } /** - * Stream only chunks where message content is not empty. + * Stream only chunks where delta content is not empty. */ - public function withoutEmptyTextOrReasoningDeltas(): self + public function withoutEmptyDeltas(): self { - return $this->reject(fn(ChatGenerationChunk $chunk): bool => match (true) { - $chunk->type === ChunkType::TextDelta => $chunk->isTextEmpty(), - $chunk->type === ChunkType::ReasoningDelta => $chunk->isReasoningEmpty(), + return $this->reject(fn(ChatGenerationChunk $chunk): bool => match ($chunk->type) { + ChunkType::TextDelta => $chunk->isTextEmpty(), + ChunkType::ReasoningDelta => $chunk->isReasoningEmpty(), + ChunkType::ToolInputDelta => $chunk->isToolInputEmpty(), default => false, }); } diff --git a/src/LLM/Data/Messages/AssistantMessage.php b/src/LLM/Data/Messages/AssistantMessage.php index a9fcbcb..df80cf2 100644 --- a/src/LLM/Data/Messages/AssistantMessage.php +++ b/src/LLM/Data/Messages/AssistantMessage.php @@ -55,16 +55,6 @@ public function text(): ?string : null; } - public function isTextEmpty(): bool - { - return in_array($this->text(), [null, ''], true); - } - - public function isReasoningEmpty(): bool - { - return in_array($this->reasoning(), [null, ''], true); - } - /** * Get the reasoning content of the message. */ @@ -81,6 +71,23 @@ public function reasoning(): ?string return null; } + public function isTextEmpty(): bool + { + return in_array($this->text(), [null, ''], true); + } + + public function isReasoningEmpty(): bool + { + return in_array($this->reasoning(), [null, ''], true); + } + + public function isToolInputEmpty(): bool + { + $toolCall = $this->toolCalls?->first(); + + return in_array($toolCall->function->arguments ?? null, [null, ''], true); + } + /** * Determine if the message has tool calls. */ diff --git a/src/LLM/Data/Messages/Content/ReasoningContent.php b/src/LLM/Data/Messages/Content/ReasoningContent.php index 65f4855..87a1016 100644 --- a/src/LLM/Data/Messages/Content/ReasoningContent.php +++ b/src/LLM/Data/Messages/Content/ReasoningContent.php @@ -6,6 +6,9 @@ final class ReasoningContent extends AbstractContent { + /** + * @param array $metadata + */ public function __construct( public string $reasoning, public array $metadata = [], diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php b/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php index 265c4e7..f9ca95c 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapsMessages.php @@ -135,6 +135,9 @@ private function mapGenericMessage(Message $message): array $formattedMessage['content'] = $this->mapMessageContent($formattedMessage['content']); } + // Anthropic does not support message IDs, so we remove it. + unset($formattedMessage['id']); + return $formattedMessage; } diff --git a/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php b/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php index d3e23ae..70a8fa3 100644 --- a/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php +++ b/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php @@ -133,6 +133,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult new FunctionCall( $toolCall['function']['name'], $arguments, + $toolCall['function']['arguments'], ), ); }) diff --git a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php index 1a8df6d..f44c2f7 100644 --- a/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php +++ b/src/LLM/Drivers/OpenAI/Responses/Concerns/MapsStreamResponse.php @@ -212,6 +212,7 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult new FunctionCall( $toolCall['function']['name'], $arguments, + $toolCall['function']['arguments'], ), ); }) @@ -341,7 +342,7 @@ protected function resolveResponsesChunkType( if ($finishReason !== null) { return match ($finishReason) { FinishReason::ToolCalls => ChunkType::ToolInputEnd, - default => ChunkType::Done, + default => ChunkType::MessageEnd, }; } @@ -350,7 +351,7 @@ protected function resolveResponsesChunkType( // Response lifecycle events 'response.created' => ChunkType::MessageStart, 'response.in_progress' => ChunkType::MessageStart, - 'response.completed', 'response.failed', 'response.incomplete' => ChunkType::Done, + 'response.completed', 'response.failed', 'response.incomplete' => ChunkType::MessageEnd, // Output item events // When a function call is added, it's the start of tool input @@ -382,7 +383,7 @@ protected function resolveResponsesChunkType( // Refusal events 'response.refusal.delta' => ChunkType::TextDelta, - 'response.refusal.done' => ChunkType::Done, + 'response.refusal.done' => ChunkType::MessageEnd, // Tool-specific events (file search, web search, code interpreter, etc.) // These are treated as tool calls, map to appropriate chunk types diff --git a/src/LLM/Enums/ChunkType.php b/src/LLM/Enums/ChunkType.php index f7f5734..35f995a 100644 --- a/src/LLM/Enums/ChunkType.php +++ b/src/LLM/Enums/ChunkType.php @@ -60,9 +60,6 @@ enum ChunkType: string /** A part indicating that a step (i.e., one LLM API call) has been completed. */ case StepEnd = 'step_end'; - /** Indicates that the streaming has completed. */ - case Done = 'done'; - /** Indicates that an error occurred during streaming. */ case Error = 'error'; diff --git a/src/LLM/Enums/StreamingProtocol.php b/src/LLM/Enums/StreamingProtocol.php index c989db0..5548a3b 100644 --- a/src/LLM/Enums/StreamingProtocol.php +++ b/src/LLM/Enums/StreamingProtocol.php @@ -6,19 +6,24 @@ use Cortex\LLM\Streaming\AgUiDataStream; use Cortex\LLM\Streaming\VercelDataStream; +use Cortex\LLM\Streaming\VercelTextStream; +use Cortex\LLM\Streaming\DefaultDataStream; use Cortex\LLM\Contracts\StreamingProtocolDriver; enum StreamingProtocol: string { + case Default = 'default'; case AGUI = 'agui'; - case Vercel = 'vercel'; + case VercelText = 'vercel_text'; public function driver(): StreamingProtocolDriver { return match ($this) { + self::Default => new DefaultDataStream(), self::AGUI => new AgUiDataStream(), self::Vercel => new VercelDataStream(), + self::VercelText => new VercelTextStream(), }; } } diff --git a/src/LLM/Streaming/AgUiDataStream.php b/src/LLM/Streaming/AgUiDataStream.php index aa2b238..454dc59 100644 --- a/src/LLM/Streaming/AgUiDataStream.php +++ b/src/LLM/Streaming/AgUiDataStream.php @@ -6,7 +6,6 @@ use Closure; use Generator; -use DateTimeImmutable; use Cortex\AGUI\Events\Custom; use Cortex\LLM\Enums\ChunkType; use Cortex\AGUI\Events\RunError; diff --git a/src/LLM/Streaming/DefaultDataStream.php b/src/LLM/Streaming/DefaultDataStream.php index 196f1ed..dc55de0 100644 --- a/src/LLM/Streaming/DefaultDataStream.php +++ b/src/LLM/Streaming/DefaultDataStream.php @@ -5,7 +5,7 @@ namespace Cortex\LLM\Streaming; use Closure; -use Illuminate\Support\Js; +use Generator; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Contracts\StreamingProtocolDriver; @@ -14,42 +14,40 @@ class DefaultDataStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { - return function () use ($result): void { - foreach ($result as $chunk) { + return function () use ($result): Generator { + foreach ($result->withoutEmptyDeltas() as $chunk) { if (connection_aborted() !== 0) { break; } - $payload = Js::encode($this->mapChunkToPayload($chunk)); + $event = $this->mapChunkToEvent($chunk); - echo 'data: ' . $payload; - echo "\n\n"; - - if (ob_get_level() > 0) { - ob_flush(); - } - - flush(); - } - - echo '[DONE]'; - - if (ob_get_level() > 0) { - ob_flush(); + yield 'data: ' . json_encode($event, JSON_THROW_ON_ERROR) . "\n\n"; } - flush(); + yield 'data: [DONE]' . "\n\n"; }; } - public function mapChunkToPayload(ChatGenerationChunk $chunk): array + public function headers(): array { - return [ - 'id' => $chunk->id, + return []; + } + + public function mapChunkToEvent(ChatGenerationChunk $chunk): array + { + $event = [ 'type' => $chunk->type->value, - 'content' => $chunk->message->content, - 'finish_reason' => $chunk->finishReason?->value, - 'created_at' => $chunk->createdAt->getTimestamp(), ]; + + if ($chunk->id !== null) { + $event['id'] = $chunk->id; + } + + if ($chunk->message->content() !== null) { + $event['content'] = $chunk->message->content(); + } + + return $event; } } diff --git a/src/LLM/Streaming/VercelDataStream.php b/src/LLM/Streaming/VercelDataStream.php index 26b58cc..0374801 100644 --- a/src/LLM/Streaming/VercelDataStream.php +++ b/src/LLM/Streaming/VercelDataStream.php @@ -7,6 +7,7 @@ use Closure; use Generator; use Cortex\LLM\Enums\ChunkType; +use Cortex\LLM\Enums\FinishReason; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Contracts\StreamingProtocolDriver; @@ -20,7 +21,7 @@ class VercelDataStream implements StreamingProtocolDriver public function streamResponse(ChatStreamResult $result): Closure { return function () use ($result): Generator { - foreach ($result->withoutEmptyTextOrReasoningDeltas() as $chunk) { + foreach ($result->withoutEmptyDeltas() as $chunk) { if (connection_aborted() !== 0) { break; } @@ -51,7 +52,7 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): ?array $this->currentMessageId = $chunk->message->metadata?->id ?? $chunk->id ?? ''; if ($chunk->finishReason !== null) { - $this->finishReason = $chunk->finishReason->value; + $this->finishReason = $this->mapFinishReason($chunk->finishReason); } return match ($chunk->type) { @@ -137,4 +138,12 @@ protected function mapChunkToEvent(ChatGenerationChunk $chunk): ?array default => null, }; } + + protected function mapFinishReason(FinishReason $finishReason): string + { + return match ($finishReason) { + FinishReason::ToolCalls => 'tool-calls', + default => $finishReason->value, + }; + } } diff --git a/src/LLM/Streaming/VercelTextStream.php b/src/LLM/Streaming/VercelTextStream.php index dd81f1c..0c59b16 100644 --- a/src/LLM/Streaming/VercelTextStream.php +++ b/src/LLM/Streaming/VercelTextStream.php @@ -5,62 +5,27 @@ namespace Cortex\LLM\Streaming; use Closure; -use Cortex\LLM\Enums\ChunkType; +use Generator; use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Contracts\StreamingProtocolDriver; -/** - * Vercel AI SDK Text Stream implementation. - * - * This is the simplest streaming format - plain text chunks streamed directly - * without any JSON encoding or event structure. Similar to Vercel's - * toTextStreamResponse() method. - * - * @see https://sdk.vercel.ai/docs/ai-sdk-core/generating-text - */ class VercelTextStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { - return function () use ($result): void { - foreach ($result as $chunk) { + return function () use ($result): Generator { + foreach ($result->text()->withoutEmptyDeltas() as $chunk) { if (connection_aborted() !== 0) { break; } - // Only output text content, ignore metadata and other chunk types - if ($this->shouldOutputChunk($chunk) && $chunk->message->content !== null && $chunk->message->content !== '') { - echo $chunk->message->content; - - if (ob_get_level() > 0) { - ob_flush(); - } - - flush(); - } + yield $chunk->text(); } }; } - public function mapChunkToPayload(ChatGenerationChunk $chunk): array - { - // For text stream, we don't use JSON payloads - // Return minimal structure for interface compatibility - return [ - 'content' => $chunk->message->content ?? '', - ]; - } - - /** - * Determine if this chunk should be output to the stream. - * Only text and reasoning deltas are output. - */ - protected function shouldOutputChunk(ChatGenerationChunk $chunk): bool + public function headers(): array { - return in_array($chunk->type, [ - ChunkType::TextDelta, - ChunkType::ReasoningDelta, - ], true); + return []; } } diff --git a/src/Pipeline/RuntimeConfig.php b/src/Pipeline/RuntimeConfig.php index 2b6a4a6..4a12aae 100644 --- a/src/Pipeline/RuntimeConfig.php +++ b/src/Pipeline/RuntimeConfig.php @@ -44,11 +44,14 @@ class RuntimeConfig implements Arrayable /** * @param Closure(string $threadId): Store|null $storeFactory Factory to create stores for the given threadId + * @param array $tools */ public function __construct( public Context $context = new Context(), public Metadata $metadata = new Metadata(), + public State $state = new State(), public StreamBuffer $stream = new StreamBuffer(), + public array $tools = [], public ?Throwable $exception = null, ?string $threadId = null, ?string $runId = null, diff --git a/src/Pipeline/State.php b/src/Pipeline/State.php new file mode 100644 index 0000000..8c3a92c --- /dev/null +++ b/src/Pipeline/State.php @@ -0,0 +1,12 @@ + + */ +class State extends Fluent {} diff --git a/src/Tools/FrontendTool.php b/src/Tools/FrontendTool.php new file mode 100644 index 0000000..4a4268a --- /dev/null +++ b/src/Tools/FrontendTool.php @@ -0,0 +1,54 @@ + $schema + */ + public function __construct( + protected string $name, + protected ?string $description = null, + protected array $schema = [], + ) {} + + public function name(): string + { + return $this->name; + } + + public function description(): string + { + return $this->description ?? ''; + } + + public function schema(): ObjectSchema + { + $schema = Schema::from($this->schema); + + if (! $schema instanceof ObjectSchema) { + throw new GenericException(sprintf('Schema for tool [%s] is not an object', $this->name)); + } + + return $schema; + } + + /** + * @param ToolCall|array $toolCall + */ + public function invoke(ToolCall|array $toolCall = [], ?RuntimeConfig $config = null): mixed + { + throw new GenericException( + 'The Frontend tool does not support invocation. It is only used for frontend output.', + ); + } +} diff --git a/src/Tools/Prebuilt/OpenMeteoWeatherTool.php b/src/Tools/Prebuilt/OpenMeteoWeatherTool.php index 97c2a07..57b4704 100644 --- a/src/Tools/Prebuilt/OpenMeteoWeatherTool.php +++ b/src/Tools/Prebuilt/OpenMeteoWeatherTool.php @@ -8,6 +8,7 @@ use Cortex\LLM\Data\ToolCall; use Cortex\Tools\AbstractTool; use Cortex\Pipeline\RuntimeConfig; +use Illuminate\Support\Facades\Date; use Illuminate\Support\Facades\Http; use Cortex\JsonSchema\Types\ObjectSchema; @@ -48,33 +49,43 @@ public function invoke(ToolCall|array $toolCall = [], ?RuntimeConfig $config = n 'format' => 'json', ]); + $name = $geocodeResponse->json('results.0.name'); $latitude = $geocodeResponse->json('results.0.latitude'); $longitude = $geocodeResponse->json('results.0.longitude'); + $timezone = $geocodeResponse->json('results.0.timezone'); if (! $latitude || ! $longitude) { return 'Could not find location for: ' . $location; } $windSpeedUnit = $config?->context?->get('wind_speed_unit') ?? 'mph'; + $temperatureUnit = $config?->context?->get('temperature_unit') ?? 'celsius'; /** @var \Illuminate\Http\Client\Response $weatherResponse */ $weatherResponse = Http::get('https://api.open-meteo.com/v1/forecast', [ 'latitude' => $latitude, 'longitude' => $longitude, 'current' => 'temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,wind_gusts_10m,weather_code', - 'wind_speed_unit' => $config?->context?->get('wind_speed_unit') ?? 'mph', + 'wind_speed_unit' => $windSpeedUnit, + 'temperature_unit' => $temperatureUnit, ]); $data = $weatherResponse->collect('current'); + $time = $data->get('time'); + $time = Date::parse($time, 'UTC')->setTimezone($timezone)->toDateTimeString(); return [ + 'time' => $time, 'temperature' => $data->get('temperature_2m'), 'feels_like' => $data->get('apparent_temperature'), + 'temperature_unit' => $temperatureUnit, 'humidity' => $data->get('relative_humidity_2m'), - 'wind_speed' => $data->get('wind_speed_10m') . $windSpeedUnit, - 'wind_gusts' => $data->get('wind_gusts_10m') . $windSpeedUnit, + 'wind_speed' => $data->get('wind_speed_10m'), + 'wind_gusts' => $data->get('wind_gusts_10m'), + 'wind_speed_unit' => $windSpeedUnit, 'conditions' => $this->getWeatherConditions($data->get('weather_code')), - 'location' => $location, + 'conditions_code' => $data->get('weather_code'), + 'location' => $name, ]; } diff --git a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php index c581492..4013030 100644 --- a/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php +++ b/tests/Unit/LLM/Drivers/Anthropic/AnthropicChatTest.php @@ -58,7 +58,7 @@ expect($chunks)->toBeInstanceOf(ChatStreamResult::class); $allChunks = []; - $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) use (&$allChunks) { + $output = $chunks->reduce(function (string $carry, ChatGenerationChunk $chunk) use (&$allChunks): string { $allChunks[] = $chunk; expect($chunk)->toBeInstanceOf(ChatGenerationChunk::class) @@ -70,7 +70,7 @@ expect($content instanceof TextContent || $content instanceof ReasoningContent)->toBeTrue(); } - return $carry . ($chunk->message->content ?? ''); + return $carry . ($chunk->message->text() ?? ''); }, ''); expect($output)->toBe("Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?"); @@ -120,8 +120,7 @@ if ($content instanceof ReasoningContent) { $hasReasoningContent = true; - expect($content->id)->toBeString() - ->and($content->reasoning)->toBeString(); + expect($content->reasoning)->toBeString(); } if ($content instanceof TextContent) { diff --git a/tests/Unit/LLM/Drivers/OpenAI/OpenAIResponsesTest.php b/tests/Unit/LLM/Drivers/OpenAI/OpenAIResponsesTest.php index d2acb27..967370e 100644 --- a/tests/Unit/LLM/Drivers/OpenAI/OpenAIResponsesTest.php +++ b/tests/Unit/LLM/Drivers/OpenAI/OpenAIResponsesTest.php @@ -527,7 +527,7 @@ // Verify chunk types are correctly mapped expect($chunkTypes)->toContain(ChunkType::MessageStart) ->and($chunkTypes)->toContain(ChunkType::TextDelta) - ->and($chunkTypes)->toContain(ChunkType::Done); + ->and($chunkTypes)->toContain(ChunkType::MessageEnd); // Verify that stream end event is the final event expect($eventOrder)->not->toBeEmpty() @@ -716,7 +716,7 @@ expect($chunkTypes)->toContain(ChunkType::ToolInputStart) // When output_item.added with function_call ->and($chunkTypes)->toContain(ChunkType::ToolInputDelta) // function_call_arguments.delta ->and($chunkTypes)->toContain(ChunkType::ToolInputEnd) // function_call_arguments.done - ->and($chunkTypes)->toContain(ChunkType::Done); // response.completed + ->and($chunkTypes)->toContain(ChunkType::MessageEnd); // response.completed }); test('it correctly maps chunk types for streaming with reasoning', function (): void { @@ -740,5 +740,5 @@ ->and($chunkTypes)->toContain(ChunkType::ReasoningDelta) // reasoning_summary_text.delta ->and($chunkTypes)->toContain(ChunkType::ReasoningEnd) // reasoning_summary_text.done ->and($chunkTypes)->toContain(ChunkType::TextDelta) // output_text.delta - ->and($chunkTypes)->toContain(ChunkType::Done); // response.completed + ->and($chunkTypes)->toContain(ChunkType::MessageEnd); // response.completed }); diff --git a/tests/Unit/LLM/Streaming/VercelDataStreamTest.php b/tests/Unit/LLM/Streaming/VercelDataStreamTest.php index ea02bbd..a9d6c55 100644 --- a/tests/Unit/LLM/Streaming/VercelDataStreamTest.php +++ b/tests/Unit/LLM/Streaming/VercelDataStreamTest.php @@ -3,577 +3,3 @@ declare(strict_types=1); namespace Tests\Unit\LLM\Streaming; - -use Closure; -use ArrayIterator; -use DateTimeImmutable; -use Cortex\LLM\Data\Usage; -use Cortex\LLM\Data\ToolCall; -use Cortex\LLM\Enums\ChunkType; -use Cortex\LLM\Data\FunctionCall; -use Cortex\LLM\Enums\FinishReason; -use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\ResponseMetadata; -use Cortex\LLM\Data\ToolCallCollection; -use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\ModelInfo\Enums\ModelProvider; -use Cortex\LLM\Streaming\VercelDataStream; -use Cortex\LLM\Data\Messages\AssistantMessage; - -beforeEach(function (): void { - $this->stream = new VercelDataStream(); -}); - -it('maps MessageStart chunk to start type with messageId', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'start') - ->and($payload)->toHaveKey('messageId', 'msg_123'); -}); - -it('maps MessageEnd chunk to finish type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'finish'); -}); - -it('maps TextStart chunk to text-start type with id', function (): void { - $metadata = new ResponseMetadata( - id: 'resp_456', - model: 'gpt-4', - provider: ModelProvider::OpenAI, - ); - $chunk = new ChatGenerationChunk( - type: ChunkType::TextStart, - id: 'msg_123', - message: new AssistantMessage(content: '', metadata: $metadata), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'text-start') - ->and($payload)->toHaveKey('id', 'resp_456'); -}); - -it('maps TextDelta chunk to text-delta type with delta and id', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello, '), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'text-delta') - ->and($payload)->toHaveKey('delta', 'Hello, ') - ->and($payload)->toHaveKey('id', 'msg_123') - ->and($payload)->not->toHaveKey('content'); -}); - -it('maps TextEnd chunk to text-end type with id', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'text-end') - ->and($payload)->toHaveKey('id', 'msg_123'); -}); - -it('maps ReasoningStart chunk to reasoning-start type with id', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'reasoning-start') - ->and($payload)->toHaveKey('id', 'msg_123'); -}); - -it('maps ReasoningDelta chunk to reasoning-delta type with delta', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Thinking step 1...'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'reasoning-delta') - ->and($payload)->toHaveKey('delta', 'Thinking step 1...') - ->and($payload)->toHaveKey('id', 'msg_123') - ->and($payload)->not->toHaveKey('content'); -}); - -it('maps ReasoningEnd chunk to reasoning-end type with id', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'reasoning-end') - ->and($payload)->toHaveKey('id', 'msg_123'); -}); - -it('maps ToolInputStart chunk to tool-input-start type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolInputStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'tool-input-start'); -}); - -it('maps ToolInputDelta chunk to tool-input-delta type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolInputDelta, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'tool-input-delta'); -}); - -it('maps ToolInputEnd chunk to tool-input-available type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolInputEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'tool-input-available'); -}); - -it('maps ToolOutputEnd chunk to tool-output-available type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolOutputEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'tool-output-available'); -}); - -it('maps StepStart chunk to start-step type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::StepStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'start-step'); -}); - -it('maps StepEnd chunk to finish-step type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::StepEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'finish-step'); -}); - -it('maps Error chunk to error type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::Error, - id: 'msg_123', - message: new AssistantMessage(content: 'An error occurred'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'error') - ->and($payload)->toHaveKey('content', 'An error occurred'); -}); - -it('maps SourceDocument chunk to source-document type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::SourceDocument, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'source-document'); -}); - -it('maps File chunk to file type', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::File, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'file'); -}); - -it('includes tool calls in payload when present', function (): void { - $toolCall = new ToolCall( - id: 'call_123', - function: new FunctionCall( - name: 'get_weather', - arguments: [ - 'city' => 'San Francisco', - ], - ), - ); - - $toolCalls = new ToolCallCollection([$toolCall]); - - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolInputEnd, - id: 'msg_123', - message: new AssistantMessage(content: '', toolCalls: $toolCalls), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('toolCalls') - ->and($payload['toolCalls'])->toBeArray() - ->and($payload['toolCalls'])->toHaveCount(1) - ->and($payload['toolCalls'][0])->toHaveKey('id', 'call_123') - ->and($payload['toolCalls'][0])->toHaveKey('function'); -}); - -it('includes usage information when available', function (): void { - $usage = new Usage( - promptTokens: 10, - completionTokens: 20, - ); - - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - usage: $usage, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('usage') - ->and($payload['usage'])->toHaveKey('prompt_tokens', 10) - ->and($payload['usage'])->toHaveKey('completion_tokens', 20) - ->and($payload['usage'])->toHaveKey('total_tokens', 30); -}); - -it('includes finish reason for final chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::Stop, - isFinal: true, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('finishReason', 'stop'); -}); - -it('does not include finish reason for non-final chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: null, - isFinal: false, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->not->toHaveKey('finishReason'); -}); - -it('uses metadata id over chunk id for text blocks when available', function (): void { - $metadata = new ResponseMetadata( - id: 'resp_meta_id', - model: 'gpt-4', - provider: ModelProvider::OpenAI, - ); - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_chunk_id', - message: new AssistantMessage(content: 'test', metadata: $metadata), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('id', 'resp_meta_id'); -}); - -it('falls back to chunk id when metadata id is not available', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_chunk_id', - message: new AssistantMessage(content: 'test'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('id', 'msg_chunk_id'); -}); - -it('does not add content key when delta is present', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('delta', 'Hello') - ->and($payload)->not->toHaveKey('content'); -}); - -it('adds content key when delta is not present and content is available', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: 'Full message'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('content', 'Full message') - ->and($payload)->not->toHaveKey('delta'); -}); - -it('does not add content or delta when content is null', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->not->toHaveKey('content') - ->and($payload)->not->toHaveKey('delta'); -}); - -it('returns streamResponse closure that can be invoked', function (): void { - $chunks = [ - new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - ]; - - $result = new ChatStreamResult( - new ArrayIterator($chunks), - ); - - $closure = $this->stream->streamResponse($result); - - expect($closure)->toBeInstanceOf(Closure::class); - - // Note: We cannot reliably test the actual output here because flush() - // bypasses output buffering. The closure invocation is sufficient to - // verify it works without errors. - ob_start(); - - try { - $closure(); - } finally { - ob_end_clean(); - } -})->group('stream'); - -it('handles multiple tool calls in a single chunk', function (): void { - $toolCall1 = new ToolCall( - id: 'call_1', - function: new FunctionCall('tool_one', [ - 'arg' => 'value1', - ]), - ); - $toolCall2 = new ToolCall( - id: 'call_2', - function: new FunctionCall('tool_two', [ - 'arg' => 'value2', - ]), - ); - - $toolCalls = new ToolCallCollection([$toolCall1, $toolCall2]); - - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolInputEnd, - id: 'msg_123', - message: new AssistantMessage(content: '', toolCalls: $toolCalls), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('toolCalls') - ->and($payload['toolCalls'])->toHaveCount(2) - ->and($payload['toolCalls'][0]['id'])->toBe('call_1') - ->and($payload['toolCalls'][1]['id'])->toBe('call_2'); -}); - -it('includes finish reason stop correctly', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::Stop, - isFinal: true, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('finishReason', 'stop'); -}); - -it('includes finish reason length correctly', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::Length, - isFinal: true, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('finishReason', 'length'); -}); - -it('includes finish reason tool_calls correctly', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::ToolCalls, - isFinal: true, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('finishReason', 'tool_calls'); -}); - -it('includes finish reason content_filter correctly', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::ContentFilter, - isFinal: true, - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('finishReason', 'content_filter'); -}); - -it('handles unknown chunk types by using the enum value', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::Done, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('type', 'done'); -}); - -it('only includes messageId for MessageStart events', function (): void { - $startChunk = new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $deltaChunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'text'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $startPayload = $this->stream->mapChunkToPayload($startChunk); - $deltaPayload = $this->stream->mapChunkToPayload($deltaChunk); - - expect($startPayload)->toHaveKey('messageId', 'msg_123') - ->and($deltaPayload)->not->toHaveKey('messageId'); -}); diff --git a/tests/Unit/LLM/Streaming/VercelTextStreamTest.php b/tests/Unit/LLM/Streaming/VercelTextStreamTest.php index 97c76e3..a9d6c55 100644 --- a/tests/Unit/LLM/Streaming/VercelTextStreamTest.php +++ b/tests/Unit/LLM/Streaming/VercelTextStreamTest.php @@ -3,426 +3,3 @@ declare(strict_types=1); namespace Tests\Unit\LLM\Streaming; - -use Closure; -use ArrayIterator; -use ReflectionClass; -use DateTimeImmutable; -use Cortex\LLM\Data\Usage; -use Cortex\LLM\Enums\ChunkType; -use Cortex\LLM\Enums\FinishReason; -use Cortex\LLM\Data\ChatStreamResult; -use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Streaming\VercelTextStream; -use Cortex\LLM\Data\Messages\AssistantMessage; - -beforeEach(function (): void { - $this->stream = new VercelTextStream(); -}); - -it('outputs text content for TextDelta chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello, '), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeTrue(); -}); - -it('outputs text content for ReasoningDelta chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Thinking...'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeTrue(); -}); - -it('does not output MessageStart chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output MessageEnd chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output TextStart chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output TextEnd chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output ReasoningStart chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output ReasoningEnd chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ReasoningEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output ToolInputStart chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::ToolInputStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('does not output Error chunks', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::Error, - id: 'msg_123', - message: new AssistantMessage(content: 'Error occurred'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeFalse(); -}); - -it('mapChunkToPayload returns content', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello, world!'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('content', 'Hello, world!'); -}); - -it('mapChunkToPayload returns empty string for null content', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $payload = $this->stream->mapChunkToPayload($chunk); - - expect($payload)->toHaveKey('content', ''); -}); - -it('returns streamResponse closure that can be invoked', function (): void { - $chunks = [ - new ChatGenerationChunk( - type: ChunkType::MessageStart, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: ', world!'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::MessageEnd, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - ]; - - $result = new ChatStreamResult( - new ArrayIterator($chunks), - ); - - $closure = $this->stream->streamResponse($result); - - expect($closure)->toBeInstanceOf(Closure::class); - - // Note: We cannot reliably test the actual output here because flush() - // bypasses output buffering. The closure invocation is sufficient to - // verify it works without errors. - ob_start(); - - try { - $closure(); - } finally { - ob_end_clean(); - } -})->group('stream'); - -it('streams only text content without metadata or JSON', function (): void { - $chunks = [ - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Part 1'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: ' Part 2'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - ]; - - $result = new ChatStreamResult( - new ArrayIterator($chunks), - ); - - $closure = $this->stream->streamResponse($result); - - // Verify closure is created - expect($closure)->toBeInstanceOf(Closure::class); -}); - -it('ignores chunks with null content', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - // Should not output (returns false for empty content in streamResponse logic) - expect($method->invoke($this->stream, $chunk))->toBeTrue(); // Type is correct - expect($chunk->message->content)->toBeNull(); // But content is null, so won't output -}); - -it('ignores chunks with empty string content', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: ''), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - // Type check passes, but empty content won't be output in streamResponse - expect($method->invoke($this->stream, $chunk))->toBeTrue(); - expect($chunk->message->content)->toBe(''); -}); - -it('handles whitespace content correctly', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: ' '), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - // Whitespace is valid content and should be output - expect($method->invoke($this->stream, $chunk))->toBeTrue(); - expect($chunk->message->content)->toBe(' '); -}); - -it('handles special characters in content', function (): void { - $specialContent = "Line 1\nLine 2\tTabbed\r\nWindows line"; - - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: $specialContent), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeTrue(); - expect($chunk->message->content)->toBe($specialContent); -}); - -it('handles unicode characters in content', function (): void { - $unicodeContent = 'Hello 👋 世界 🌍'; - - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: $unicodeContent), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeTrue(); - expect($chunk->message->content)->toBe($unicodeContent); -}); - -it('handles very long content strings', function (): void { - $longContent = str_repeat('A', 10000); - - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: $longContent), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - expect($method->invoke($this->stream, $chunk))->toBeTrue(); - expect(strlen($chunk->message->content))->toBe(10000); -}); - -it('ignores usage metadata when streaming text', function (): void { - $usage = new Usage( - promptTokens: 10, - completionTokens: 20, - ); - - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Hello'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - usage: $usage, - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - // Should still output, usage is just ignored - expect($method->invoke($this->stream, $chunk))->toBeTrue(); -}); - -it('ignores finish reason when streaming text', function (): void { - $chunk = new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Final text'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - finishReason: FinishReason::Stop, - ); - - $reflection = new ReflectionClass($this->stream); - $method = $reflection->getMethod('shouldOutputChunk'); - - // Should still output, finish reason is ignored - expect($method->invoke($this->stream, $chunk))->toBeTrue(); -}); - -it('streams mixed text and reasoning deltas', function (): void { - $chunks = [ - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Text part'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:00'), - ), - new ChatGenerationChunk( - type: ChunkType::ReasoningDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'Reasoning part'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:01'), - ), - new ChatGenerationChunk( - type: ChunkType::TextDelta, - id: 'msg_123', - message: new AssistantMessage(content: 'More text'), - createdAt: new DateTimeImmutable('2024-01-01 12:00:02'), - ), - ]; - - $result = new ChatStreamResult( - new ArrayIterator($chunks), - ); - - $closure = $this->stream->streamResponse($result); - - expect($closure)->toBeInstanceOf(Closure::class); -}); From dfe90c35b0428b7d4f53ab7a342287af4501da33 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 12 Jan 2026 23:29:34 +0000 Subject: [PATCH 20/54] wip --- config/cortex.php | 13 ++ scratchpad.php | 4 +- src/Agents/Prebuilt/WeatherAgent.php | 11 +- src/Http/Controllers/AGUIController.php | 6 +- src/Http/Controllers/AgentsController.php | 45 ++----- .../Anthropic/Concerns/MapStreamResponse.php | 5 +- .../Chat/Concerns/MapsStreamResponse.php | 25 ++++ src/LLM/Enums/StreamingProtocol.php | 12 +- ...efaultDataStream.php => RawDataStream.php} | 4 +- .../{VercelTextStream.php => TextStream.php} | 2 +- ...therTool.php => GetCurrentWeatherTool.php} | 2 +- tests/Unit/Agents/AgentOldTest.php | 4 +- tests/Unit/LLM/StreamBufferTest.php | 125 +++++++++++------- .../Factories/BladePromptFactoryTest.php | 4 +- tests/fixtures/prompts/test-tools.blade.php | 4 +- 15 files changed, 154 insertions(+), 112 deletions(-) rename src/LLM/Streaming/{DefaultDataStream.php => RawDataStream.php} (90%) rename src/LLM/Streaming/{VercelTextStream.php => TextStream.php} (91%) rename src/Tools/Prebuilt/{OpenMeteoWeatherTool.php => GetCurrentWeatherTool.php} (98%) diff --git a/config/cortex.php b/config/cortex.php index 26d1f2b..f5a8be5 100644 --- a/config/cortex.php +++ b/config/cortex.php @@ -3,6 +3,7 @@ declare(strict_types=1); use Cortex\LLM\Enums\LLMDriver; +use Cortex\LLM\Enums\StreamingProtocol; use Cortex\Agents\Prebuilt\WeatherAgent; use Cortex\ModelInfo\Enums\ModelProvider; use Cortex\ModelInfo\Providers\OllamaModelInfoProvider; @@ -371,4 +372,16 @@ 'agents' => [ WeatherAgent::class, ], + + /* + |-------------------------------------------------------------------------- + | Default Streaming Protocol + |-------------------------------------------------------------------------- + | + | The default streaming protocol to use for streaming responses. + | + | Supported protocols: "raw", "agui", "vercel", "text" + | + */ + 'default_streaming_protocol' => StreamingProtocol::Vercel, ]; diff --git a/scratchpad.php b/scratchpad.php index 1910990..d05b2c8 100644 --- a/scratchpad.php +++ b/scratchpad.php @@ -10,7 +10,7 @@ use Cortex\Agents\Prebuilt\WeatherAgent; use Cortex\LLM\Data\Messages\UserMessage; use Cortex\LLM\Data\Messages\SystemMessage; -use Cortex\Tools\Prebuilt\OpenMeteoWeatherTool; +use Cortex\Tools\Prebuilt\GetCurrentWeatherTool; $prompt = Cortex::prompt([ new SystemMessage('You are an expert at geography.'), @@ -121,7 +121,7 @@ ->withPrompt('You are a weather agent. You tell the weather in {location}.') ->withLLM('lmstudio/openai/gpt-oss-20b') ->withTools([ - OpenMeteoWeatherTool::class, + GetCurrentWeatherTool::class, ]) ->withOutput([ Schema::string('location')->required(), diff --git a/src/Agents/Prebuilt/WeatherAgent.php b/src/Agents/Prebuilt/WeatherAgent.php index d989ec3..b33eb93 100644 --- a/src/Agents/Prebuilt/WeatherAgent.php +++ b/src/Agents/Prebuilt/WeatherAgent.php @@ -11,8 +11,8 @@ use Cortex\Agents\AbstractAgentBuilder; use Cortex\LLM\Data\Messages\SystemMessage; use Cortex\Prompts\Builders\ChatPromptBuilder; -use Cortex\Tools\Prebuilt\OpenMeteoWeatherTool; use Cortex\Prompts\Templates\ChatPromptTemplate; +use Cortex\Tools\Prebuilt\GetCurrentWeatherTool; class WeatherAgent extends AbstractAgentBuilder { @@ -35,10 +35,9 @@ public function prompt(): ChatPromptTemplate|ChatPromptBuilder|string - Keep responses concise but informative - If the user asks for activities and provides the weather forecast, suggest activities based on the weather forecast. - If the user asks for activities, respond in the format they request. + - Respond in sentences, you don't need to show the weather data, since it's handled with the tool output. - Use the get_weather tool to fetch current weather data. - - Only answer in sentences, you don't need to show the weather data, since it's handled with the tool output. + Use the `get_weather` tool to fetch current weather data. INSTRUCTIONS, ), ]); @@ -46,8 +45,6 @@ public function prompt(): ChatPromptTemplate|ChatPromptBuilder|string public function llm(): LLM|string|null { - // return Cortex::llm('ollama', 'gpt-oss:20b')->ignoreFeatures(); - // return Cortex::llm('openai', 'gpt-4.1-mini')->ignoreFeatures(); return Cortex::llm('lmstudio', 'openai/gpt-oss-20b')->ignoreFeatures(); // return Cortex::llm('anthropic', 'claude-haiku-4-5')->ignoreFeatures(); } @@ -56,7 +53,7 @@ public function llm(): LLM|string|null public function tools(): array|ToolKit { return [ - OpenMeteoWeatherTool::class, + GetCurrentWeatherTool::class, ]; } } diff --git a/src/Http/Controllers/AGUIController.php b/src/Http/Controllers/AGUIController.php index b188f8f..7097845 100644 --- a/src/Http/Controllers/AGUIController.php +++ b/src/Http/Controllers/AGUIController.php @@ -29,11 +29,7 @@ public function __invoke(Request $request): StreamedResponse }); try { - return Cortex::agent('generic_thinking') - // ->onChunk(function (AgentStreamChunk $event): void { - // dump($event->chunk->toArray()); - // // dump($event->chunk->type->value . ': ' . $event->chunk->content()); - // }) + return Cortex::agent('weather') ->stream( messages: $messages->all(), input: $request->all(), diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index 3f60967..3f1bc3e 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -6,14 +6,10 @@ use Throwable; use Cortex\Cortex; -use Cortex\Events\AgentEnd; use Illuminate\Support\Arr; use Illuminate\Http\Request; -use Cortex\Events\AgentStart; +use Cortex\Pipeline\Metadata; use Cortex\Tools\FrontendTool; -use Cortex\Events\AgentStepEnd; -use Cortex\Events\AgentStepError; -use Cortex\Events\AgentStepStart; use Illuminate\Http\JsonResponse; use Cortex\Pipeline\RuntimeConfig; use Illuminate\Routing\Controller; @@ -27,36 +23,17 @@ public function invoke(string $agent, Request $request): JsonResponse { try { $agent = Cortex::agent($agent); - $agent->onStart(function (AgentStart $event): void { - // dump('-- agent start'); - }); - $agent->onEnd(function (AgentEnd $event): void { - // dump('-- agent end'); - }); - - $agent->onStepStart(function (AgentStepStart $event): void { - // dump( - // sprintf('---- step %d start', $event->config?->context?->getCurrentStepNumber()), - // // $event->config?->context->toArray(), - // ); - }); - $agent->onStepEnd(function (AgentStepEnd $event): void { - // dump( - // sprintf('---- step %d end', $event->config?->context?->getCurrentStepNumber()), - // // $event->config?->toArray(), - // ); - }); - $agent->onStepError(function (AgentStepError $event): void { - // dump(sprintf('step error: %d', $event->config?->context?->getCurrentStepNumber())); - // dump($event->exception->getMessage()); - // dump($event->exception->getTraceAsString()); - }); $result = $agent->invoke( messages: $request->has('message') ? [ new UserMessage($request->input('message')), ] : [], input: $request->all(), + config: new RuntimeConfig( + metadata: new Metadata($request->input('metadata', [])), + threadId: $request->input('id'), + runId: $request->input('run_id'), + ), ); } catch (Throwable $e) { return response()->json([ @@ -88,10 +65,10 @@ public function stream(string $agent, Request $request): StreamedResponse if ($request->isMethod('post')) { $messages = [$request->collect('messages') - ->map(function ($message) { + ->map(function (array $message) { return new UserMessage( content: Arr::get($message, 'parts.0.text', ''), - // id: $message['id'] ?? null, + id: $message['id'] ?? null, ); }) ->last(), @@ -105,10 +82,10 @@ public function stream(string $agent, Request $request): StreamedResponse /** @var array> $toolsInput */ $toolsInput = $request->input('tools', []); $tools = collect($toolsInput) - ->filter(function ($tool): bool { + ->filter(function (array $tool): bool { return $tool !== []; }) - ->map(function (array $tool, $toolName): FrontendTool { + ->map(function (array $tool, string $toolName): FrontendTool { return new FrontendTool( $toolName, $tool['description'] ?? null, @@ -127,7 +104,7 @@ public function stream(string $agent, Request $request): StreamedResponse ); return $result->streamResponse( - $request->enum('protocol', StreamingProtocol::class, StreamingProtocol::Vercel), + $request->enum('protocol', StreamingProtocol::class, config('cortex.default_streaming_protocol')), ); } catch (Throwable $e) { dd($e); diff --git a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php index 3a93e00..67928a0 100644 --- a/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php +++ b/src/LLM/Drivers/Anthropic/Concerns/MapStreamResponse.php @@ -95,8 +95,9 @@ protected function mapStreamResponse(MessageStream $response, bool $isCached = f $this->processStreamEvent($event); - $chatGenerationChunk = $this->buildChunk($event, $chunkType, $messageId, $isCached); - $chatGenerationChunk = $this->applyOutputParserIfApplicable($chatGenerationChunk); + $chatGenerationChunk = $this->applyOutputParserIfApplicable( + $this->buildChunk($event, $chunkType, $messageId, $isCached), + ); $this->dispatchEvent(new ChatModelStream($this, $chatGenerationChunk)); diff --git a/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php b/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php index 70a8fa3..7dd3f55 100644 --- a/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php +++ b/src/LLM/Drivers/OpenAI/Chat/Concerns/MapsStreamResponse.php @@ -70,6 +70,31 @@ protected function mapStreamResponse(StreamResponse $response): ChatStreamResult $usage, ); + // Hack to push the delta from TextStart to TextDelta + if ($chunkType === ChunkType::TextStart) { + $this->streamBuffer?->push(new ChatGenerationChunk( + type: ChunkType::TextDelta, + id: $chunk->id, + message: new AssistantMessage( + content: $choice->delta->content, + metadata: new ResponseMetadata( + id: $chunk->id, + model: $this->model, + provider: $this->modelProvider, + finishReason: $finishReason, + usage: $usage, + ), + id: $chunk->id, + ), + createdAt: DateTimeImmutable::createFromFormat('U', (string) $chunk->created), + finishReason: $finishReason, + usage: $usage, + contentSoFar: [], + isFinal: false, + rawChunk: $this->includeRaw ? $chunk->toArray() : null, + )); + } + // Handle text content - initialize or append if ($choice->delta->content !== null) { if ($currentTextContent === null) { diff --git a/src/LLM/Enums/StreamingProtocol.php b/src/LLM/Enums/StreamingProtocol.php index 5548a3b..cf84fb1 100644 --- a/src/LLM/Enums/StreamingProtocol.php +++ b/src/LLM/Enums/StreamingProtocol.php @@ -4,26 +4,26 @@ namespace Cortex\LLM\Enums; +use Cortex\LLM\Streaming\TextStream; +use Cortex\LLM\Streaming\RawDataStream; use Cortex\LLM\Streaming\AgUiDataStream; use Cortex\LLM\Streaming\VercelDataStream; -use Cortex\LLM\Streaming\VercelTextStream; -use Cortex\LLM\Streaming\DefaultDataStream; use Cortex\LLM\Contracts\StreamingProtocolDriver; enum StreamingProtocol: string { - case Default = 'default'; + case Raw = 'raw'; case AGUI = 'agui'; case Vercel = 'vercel'; - case VercelText = 'vercel_text'; + case Text = 'vercel_text'; public function driver(): StreamingProtocolDriver { return match ($this) { - self::Default => new DefaultDataStream(), + self::Raw => new RawDataStream(), self::AGUI => new AgUiDataStream(), self::Vercel => new VercelDataStream(), - self::VercelText => new VercelTextStream(), + self::Text => new TextStream(), }; } } diff --git a/src/LLM/Streaming/DefaultDataStream.php b/src/LLM/Streaming/RawDataStream.php similarity index 90% rename from src/LLM/Streaming/DefaultDataStream.php rename to src/LLM/Streaming/RawDataStream.php index dc55de0..267704a 100644 --- a/src/LLM/Streaming/DefaultDataStream.php +++ b/src/LLM/Streaming/RawDataStream.php @@ -10,7 +10,7 @@ use Cortex\LLM\Data\ChatGenerationChunk; use Cortex\LLM\Contracts\StreamingProtocolDriver; -class DefaultDataStream implements StreamingProtocolDriver +class RawDataStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { @@ -41,7 +41,7 @@ public function mapChunkToEvent(ChatGenerationChunk $chunk): array ]; if ($chunk->id !== null) { - $event['id'] = $chunk->id; + $event['id'] = $chunk->id ?? $chunk->message->id; } if ($chunk->message->content() !== null) { diff --git a/src/LLM/Streaming/VercelTextStream.php b/src/LLM/Streaming/TextStream.php similarity index 91% rename from src/LLM/Streaming/VercelTextStream.php rename to src/LLM/Streaming/TextStream.php index 0c59b16..a84fe1a 100644 --- a/src/LLM/Streaming/VercelTextStream.php +++ b/src/LLM/Streaming/TextStream.php @@ -9,7 +9,7 @@ use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Contracts\StreamingProtocolDriver; -class VercelTextStream implements StreamingProtocolDriver +class TextStream implements StreamingProtocolDriver { public function streamResponse(ChatStreamResult $result): Closure { diff --git a/src/Tools/Prebuilt/OpenMeteoWeatherTool.php b/src/Tools/Prebuilt/GetCurrentWeatherTool.php similarity index 98% rename from src/Tools/Prebuilt/OpenMeteoWeatherTool.php rename to src/Tools/Prebuilt/GetCurrentWeatherTool.php index 57b4704..599dd5c 100644 --- a/src/Tools/Prebuilt/OpenMeteoWeatherTool.php +++ b/src/Tools/Prebuilt/GetCurrentWeatherTool.php @@ -12,7 +12,7 @@ use Illuminate\Support\Facades\Http; use Cortex\JsonSchema\Types\ObjectSchema; -class OpenMeteoWeatherTool extends AbstractTool +class GetCurrentWeatherTool extends AbstractTool { public function name(): string { diff --git a/tests/Unit/Agents/AgentOldTest.php b/tests/Unit/Agents/AgentOldTest.php index d04bbee..025b21a 100644 --- a/tests/Unit/Agents/AgentOldTest.php +++ b/tests/Unit/Agents/AgentOldTest.php @@ -16,7 +16,7 @@ use Cortex\LLM\Data\Messages\UserMessage; use Cortex\OutputParsers\JsonOutputParser; use Cortex\LLM\Data\Messages\SystemMessage; -use Cortex\Tools\Prebuilt\OpenMeteoWeatherTool; +use Cortex\Tools\Prebuilt\GetCurrentWeatherTool; use function Cortex\Support\llm; use function Cortex\Support\tool; @@ -177,7 +177,7 @@ function (): string { // llm: 'lmstudio/gpt-oss:20b', llm: 'openai/gpt-4.1-mini', tools: [ - OpenMeteoWeatherTool::class, + GetCurrentWeatherTool::class, ], output: [ Schema::string('summary')->required(), diff --git a/tests/Unit/LLM/StreamBufferTest.php b/tests/Unit/LLM/StreamBufferTest.php index 525da41..62bd765 100644 --- a/tests/Unit/LLM/StreamBufferTest.php +++ b/tests/Unit/LLM/StreamBufferTest.php @@ -3,9 +3,10 @@ declare(strict_types=1); use Cortex\Pipeline\RuntimeConfig; +use Saloon\Http\Faking\MockResponse; use Cortex\LLM\Data\ChatGenerationChunk; -use Cortex\LLM\Drivers\OpenAI\Chat\OpenAIChat; -use OpenAI\Responses\Chat\CreateStreamedResponse; +use Cortex\LLM\Drivers\Anthropic\AnthropicChat; +use Cortex\SDK\Anthropic\Requests\CreateMessage; it('interleaves stream buffer items with chat stream result', function (): void { // Setup @@ -14,26 +15,16 @@ // Push initial item $config->stream->push('start'); - // Create a minimal stream response file for our test chunks - $streamContent = "data: {\"id\":\"chatcmpl-1\",\"object\":\"chat.completion.chunk\",\"created\":1234567890,\"model\":\"gpt-4\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"A\"},\"finish_reason\":null}]}\n\n"; - $streamContent .= "data: {\"id\":\"chatcmpl-1\",\"object\":\"chat.completion.chunk\",\"created\":1234567890,\"model\":\"gpt-4\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"B\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":10,\"completion_tokens\":2,\"total_tokens\":12}}\n\n"; - $streamContent .= "data: [DONE]\n\n"; - - $streamHandle = fopen('php://memory', 'r+'); - fwrite($streamHandle, $streamContent); - rewind($streamHandle); - - // Use real OpenAIChat with MapsStreamResponse - it will handle buffer draining - $llm = OpenAIChat::fake([ - CreateStreamedResponse::fake($streamHandle), - ]); - $llm->withStreaming(); + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple-streamed'), + ])->withStreaming(); // Next closure (simulating next stage in pipeline that pushes to buffer) $next = function (mixed $payload, RuntimeConfig $config): mixed { if ($payload instanceof ChatGenerationChunk) { // Push item during stream - $config->stream->push('mid-' . $payload->textSoFar()); + $textSoFar = $payload->textSoFar() ?? ''; + $config->stream->push('mid-' . $textSoFar); } return $payload; @@ -42,43 +33,68 @@ // Execute $result = $llm->handlePipeable('input', $config, $next); - // Collect results - $items = []; - foreach ($result as $item) { - $items[] = $item; - } + $items = iterator_to_array($result, false); - expect($items)->toHaveCount(5); + // Expected sequence based on fixture: + // 1. Buffer drain: 'start' + // 2. MessageStart chunk (no text) + // 3. Buffer drain: 'mid-' (empty string from MessageStart) + // 4. TextStart chunk (no text) + // 5. Buffer drain: 'mid-' (empty string from TextStart) + // 6. TextDelta chunk: "Hello! I'm doing well, thank" + // 7. Buffer drain: 'mid-Hello! I'm doing well, thank' + // 8. TextDelta chunk: "Hello! I'm doing well, thank you for asking. How" + // 9. Buffer drain: 'mid-Hello! I'm doing well, thank you for asking. How' + // 10. TextDelta chunk: "Hello! I'm doing well, thank you for asking. How are you doing today? Is" + // 11. Buffer drain: 'mid-Hello! I'm doing well, thank you for asking. How are you doing today? Is' + // 12. TextDelta chunk: "Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?" + // 13. Buffer drain: 'mid-Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?' + // 14. TextEnd chunk (full text) + // 15. Buffer drain: 'mid-Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?' + // 16. MessageEnd chunk (full text) + // 17. Buffer drain: 'mid-Hello! I'm doing well, thank you for asking. How are you doing today? Is there anything I can help you with?' + // 18. Final buffer drain (empty) + + expect($items)->toHaveCount(17); expect($items[0])->toBe('start'); + // First chunk is MessageStart (no text) expect($items[1])->toBeInstanceOf(ChatGenerationChunk::class); - expect($items[1]->textSoFar())->toBe('A'); // First chunk has contentSoFar = 'A' + expect($items[1]->textSoFar())->toBeNull(); - expect($items[2])->toBe('mid-A'); + // Buffer item from MessageStart (empty text) + expect($items[2])->toBe('mid-'); + // Second chunk is TextStart (no text yet) expect($items[3])->toBeInstanceOf(ChatGenerationChunk::class); - expect($items[3]->textSoFar())->toBe('AB'); // Second chunk accumulates: 'A' + 'B' = 'AB' + expect($items[3]->textSoFar())->toBe(''); + + // Buffer item from TextStart (empty text) + expect($items[4])->toBe('mid-'); + + // Third chunk is first TextDelta + expect($items[5])->toBeInstanceOf(ChatGenerationChunk::class); + expect($items[5]->textSoFar())->toBe("Hello! I'm doing well, thank"); + + // Buffer item from first TextDelta + expect($items[6])->toBe("mid-Hello! I'm doing well, thank"); - expect($items[4])->toBe('mid-AB'); // Buffer item uses contentSoFar from the chunk ('AB') + // Fourth chunk is second TextDelta + expect($items[7])->toBeInstanceOf(ChatGenerationChunk::class); + expect($items[7]->textSoFar())->toBe("Hello! I'm doing well, thank you for asking. How"); + + // Buffer item from second TextDelta + expect($items[8])->toBe("mid-Hello! I'm doing well, thank you for asking. How"); }); it('handles buffer items pushed after stream completion', function (): void { $config = new RuntimeConfig(); - // Create a stream response with one chunk - $streamContent = "data: {\"id\":\"chatcmpl-1\",\"object\":\"chat.completion.chunk\",\"created\":1234567890,\"model\":\"gpt-4\",\"choices\":[{\"index\":0,\"delta\":{\"content\":\"Hello\"},\"finish_reason\":\"stop\"}],\"usage\":{\"prompt_tokens\":10,\"completion_tokens\":1,\"total_tokens\":11}}\n\n"; - $streamContent .= "data: [DONE]\n\n"; - - $streamHandle = fopen('php://memory', 'r+'); - fwrite($streamHandle, $streamContent); - rewind($streamHandle); - - // Use real OpenAIChat with MapsStreamResponse - it will handle buffer draining - $llm = OpenAIChat::fake([ - CreateStreamedResponse::fake($streamHandle), - ]); - $llm->withStreaming(); + // Use AnthropicChat with MapStreamResponse - it will handle buffer draining + $llm = AnthropicChat::fake([ + CreateMessage::class => MockResponse::fixture('anthropic/messages/simple-streamed'), + ])->withStreaming(); // Push item to buffer AFTER the stream completes (during iteration, after last chunk) $next = function (mixed $payload, RuntimeConfig $config): mixed { @@ -94,10 +110,27 @@ $items = iterator_to_array($result, false); - // MapsStreamResponse drains buffer after stream completes (line 179-181) - // So items pushed after the final chunk should be drained and yielded - expect($items)->toHaveCount(2); - expect($items[0])->toBeInstanceOf(ChatGenerationChunk::class); - expect($items[0]->isFinal)->toBeTrue(); // Final chunk - expect($items[1])->toBe('after-completion'); // Buffer item drained after stream completion + // MapStreamResponse drains buffer: + // 1. At the start (empty, doesn't yield anything) + // 2. Yields chunks from the stream + // 3. After stream completes, drains buffer (yields items pushed during chunk processing) + // The final chunk has isFinal=true when it contains usage information (MessageEnd chunk) + // The buffer item 'after-completion' is pushed when processing the final chunk, + // then drained after the stream completes + + // Find the final chunk and buffer item + $finalChunk = null; + $bufferItem = null; + + foreach ($items as $item) { + if ($item instanceof ChatGenerationChunk && $item->isFinal) { + $finalChunk = $item; + } elseif ($item === 'after-completion') { + $bufferItem = $item; + } + } + + expect($finalChunk)->not->toBeNull('Expected to find final chunk'); + expect($finalChunk->isFinal)->toBeTrue(); + expect($bufferItem)->toBe('after-completion'); }); diff --git a/tests/Unit/Prompts/Factories/BladePromptFactoryTest.php b/tests/Unit/Prompts/Factories/BladePromptFactoryTest.php index 0673088..103f135 100644 --- a/tests/Unit/Prompts/Factories/BladePromptFactoryTest.php +++ b/tests/Unit/Prompts/Factories/BladePromptFactoryTest.php @@ -9,9 +9,9 @@ use Cortex\Prompts\Compilers\BladeCompiler; use Cortex\LLM\Drivers\OpenAI\Chat\OpenAIChat; use Cortex\LLM\Data\Messages\MessageCollection; -use Cortex\Tools\Prebuilt\OpenMeteoWeatherTool; use Cortex\Prompts\Factories\BladePromptFactory; use Cortex\Prompts\Templates\ChatPromptTemplate; +use Cortex\Tools\Prebuilt\GetCurrentWeatherTool; use Illuminate\Foundation\Testing\Concerns\InteractsWithViews; use OpenAI\Responses\Chat\CreateResponse as ChatCreateResponse; @@ -247,5 +247,5 @@ expect($template->metadata)->not->toBeNull(); expect($template->metadata->tools)->toHaveCount(2); - expect($template->metadata->tools)->toContain(OpenMeteoWeatherTool::class); + expect($template->metadata->tools)->toContain(GetCurrentWeatherTool::class); }); diff --git a/tests/fixtures/prompts/test-tools.blade.php b/tests/fixtures/prompts/test-tools.blade.php index fe1f41c..933dded 100644 --- a/tests/fixtures/prompts/test-tools.blade.php +++ b/tests/fixtures/prompts/test-tools.blade.php @@ -2,7 +2,7 @@ use Cortex\Attributes\Tool; use Cortex\JsonSchema\Schema; -use Cortex\Tools\Prebuilt\OpenMeteoWeatherTool; +use Cortex\Tools\Prebuilt\GetCurrentWeatherTool; use function Cortex\Prompts\llm; use function Cortex\Prompts\tools; @@ -15,7 +15,7 @@ llm('openai', 'gpt-4'); tools([ - OpenMeteoWeatherTool::class, + GetCurrentWeatherTool::class, #[Tool(name: 'get_weather', description: 'Get the weather for a given location')] fn(string $query): string => 'The weather in ' . $query . ' is sunny.', ]); From 224f1c0a7703f732034d64cd1bfc65c4c71c81d4 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Mon, 12 Jan 2026 23:42:19 +0000 Subject: [PATCH 21/54] pass through thread id --- src/Agents/Agent.php | 12 +- src/Contracts/ChatMemory.php | 5 + src/Memory/ChatMemory.php | 7 + src/Memory/Contracts/Store.php | 5 + src/Memory/Stores/CacheStore.php | 5 + src/Memory/Stores/InMemoryStore.php | 5 + tests/Unit/Agents/AgentOldTest.php | 214 ---------------------------- tests/Unit/Agents/AgentTest.php | 142 ++++++++++++++++++ 8 files changed, 177 insertions(+), 218 deletions(-) delete mode 100644 tests/Unit/Agents/AgentOldTest.php diff --git a/src/Agents/Agent.php b/src/Agents/Agent.php index d0d83e1..4bccfd0 100644 --- a/src/Agents/Agent.php +++ b/src/Agents/Agent.php @@ -101,7 +101,7 @@ public function __construct( protected array $middleware = [], ) { $this->prompt = self::buildPromptTemplate($prompt, $strict, $initialPromptVariables); - $this->memory = self::buildMemory($this->prompt, $this->memoryStore); + $this->memory = self::buildMemory($this->prompt, $runtimeConfig?->threadId, $this->memoryStore); $this->middleware = [...$this->defaultMiddleware(), ...$this->middleware]; // Reset the prompt to only the message placeholders, since the initial @@ -432,6 +432,7 @@ protected function invokePipeline( $messages = Utils::toMessageCollection($messages); $this->memory + ->setThreadId($config->threadId) ->setMessages($this->memory->getMessages()->merge($messages)) ->setVariables([ ...$this->initialPromptVariables, @@ -502,9 +503,12 @@ protected static function buildPromptTemplate( /** * Build the memory instance for the agent. */ - protected static function buildMemory(ChatPromptTemplate $prompt, ?Store $memoryStore = null): ChatMemoryContract - { - $memoryStore ??= new InMemoryStore(Str::uuid7()->toString()); + protected static function buildMemory( + ChatPromptTemplate $prompt, + ?string $threadId = null, + ?Store $memoryStore = null, + ): ChatMemoryContract { + $memoryStore ??= new InMemoryStore($threadId ?? Str::uuid7()->toString()); $memoryStore->setMessages($prompt->messages->withoutPlaceholders()); return new ChatMemory($memoryStore); diff --git a/src/Contracts/ChatMemory.php b/src/Contracts/ChatMemory.php index db96b22..0efd8ec 100644 --- a/src/Contracts/ChatMemory.php +++ b/src/Contracts/ChatMemory.php @@ -50,4 +50,9 @@ public function getVariables(): array; * Delegates to the underlying store - the store is the source of truth for threadId. */ public function getThreadId(): string; + + /** + * Set the thread ID for this memory instance. + */ + public function setThreadId(string $threadId): static; } diff --git a/src/Memory/ChatMemory.php b/src/Memory/ChatMemory.php index 138855b..dd62ccf 100644 --- a/src/Memory/ChatMemory.php +++ b/src/Memory/ChatMemory.php @@ -99,4 +99,11 @@ public function getThreadId(): string { return $this->store->getThreadId(); } + + public function setThreadId(string $threadId): static + { + $this->store->setThreadId($threadId); + + return $this; + } } diff --git a/src/Memory/Contracts/Store.php b/src/Memory/Contracts/Store.php index 5b77698..aed65ea 100644 --- a/src/Memory/Contracts/Store.php +++ b/src/Memory/Contracts/Store.php @@ -40,4 +40,9 @@ public function reset(): void; * Get the thread ID for this store. */ public function getThreadId(): string; + + /** + * Set the thread ID for this store. + */ + public function setThreadId(string $threadId): void; } diff --git a/src/Memory/Stores/CacheStore.php b/src/Memory/Stores/CacheStore.php index 21e335b..0dff31b 100644 --- a/src/Memory/Stores/CacheStore.php +++ b/src/Memory/Stores/CacheStore.php @@ -70,4 +70,9 @@ public function getThreadId(): string { return $this->threadId; } + + public function setThreadId(string $threadId): void + { + $this->threadId = $threadId; + } } diff --git a/src/Memory/Stores/InMemoryStore.php b/src/Memory/Stores/InMemoryStore.php index 77237f0..8c096b3 100644 --- a/src/Memory/Stores/InMemoryStore.php +++ b/src/Memory/Stores/InMemoryStore.php @@ -44,4 +44,9 @@ public function getThreadId(): string { return $this->threadId; } + + public function setThreadId(string $threadId): void + { + $this->threadId = $threadId; + } } diff --git a/tests/Unit/Agents/AgentOldTest.php b/tests/Unit/Agents/AgentOldTest.php deleted file mode 100644 index 025b21a..0000000 --- a/tests/Unit/Agents/AgentOldTest.php +++ /dev/null @@ -1,214 +0,0 @@ -messages([ - new SystemMessage('You are a comedian.'), - new UserMessage('Tell me a joke about {topic}.'), - ]) - ->metadata( - provider: 'ollama', - model: 'ministral-3:14b', - structuredOutput: Schema::object()->properties( - Schema::string('setup')->required(), - Schema::string('punchline')->required(), - ), - ), - ); - - $result = $agent->stream(input: [ - 'topic' => 'dragons', - ]); - - foreach ($result->text() as $chunk) { - dump([ - 'type' => $chunk->type, - 'content' => $chunk->content(), - ]); - } - - dd($agent->getMemory()->getMessages()->toArray()); -})->todo(); - -test('it can create an agent with tools', function (): void { - // Event::listen(ChatModelStart::class, function (ChatModelStart $event): void { - // dump('llm start: ', $event->parameters); - // }); - - // Event::listen(ChatModelEnd::class, function (ChatModelEnd $event): void { - // dump('llm end: ', $event->result); - // }); - - $agent = new Agent( - name: 'Weather Forecaster', - prompt: 'You are a weather forecaster. Use the tool to get the weather for a given location.', - llm: llm('ollama', 'qwen2.5:14b')->ignoreFeatures(), - tools: [ - tool( - 'get_weather', - 'Get the current weather for a given location', - fn(string $location): string => - vsprintf('{"location": "%s", "conditions": "%s", "temperature": %s, "unit": "celsius"}', [ - $location, - Arr::random(['sunny', 'cloudy', 'rainy', 'snowing']), - 14, - ]), - ), - ], - ); - - // $result = $agent->invoke([ - // new UserMessage('What is the weather in London?'), - // ]); - - // dump($result->generation->message->content()); - // dump($agent->getMemory()->getMessages()->toArray()); - // dd($agent->getTotalUsage()->toArray()); - - // $result = $agent->invoke([ - // new UserMessage('What about Manchester?'), - // ]); - - // dump($result->generation->message->content()); - // dump($agent->getMemory()->getMessages()->toArray()); - - $result = $agent->stream([ - new UserMessage('What is the weather in London?'), - ]); - - foreach ($result as $chunk) { - dump($chunk->toArray()); - } - - dump($agent->getMemory()->getMessages()->toArray()); -})->todo(); - -test('it can create an agent with a prompt instance', function (): void { - Event::listen(ChatModelStart::class, function (ChatModelStart $event): void { - dump('llm start: ', $event->parameters); - }); - - Event::listen(ChatModelEnd::class, function (ChatModelEnd $event): void { - dump('llm end: ', $event->result); - }); - - $londonWeatherTool = tool( - 'london-weather-tool', - 'Returns year-to-date historical weather data for London', - function (): string { - $url = vsprintf('https://archive-api.open-meteo.com/v1/archive?latitude=51.5072&longitude=-0.1276&start_date=%s&end_date=%s&daily=temperature_2m_max,temperature_2m_min,precipitation_sum,windspeed_10m_max,snowfall_sum&timezone=auto', [ - today()->startOfYear()->format('Y-m-d'), - today()->format('Y-m-d'), - ]); - - $response = Http::get($url)->collect(); - - dd($response); - - return $response->mapWithKeys(fn(array $item): array => [ - 'date' => $item['daily']['time'], - 'temp_max' => $item['daily']['temperature_2m_max'], - 'temp_min' => $item['daily']['temperature_2m_min'], - 'rainfall' => $item['daily']['precipitation_sum'], - 'windspeed' => $item['daily']['windspeed_10m_max'], - 'snowfall' => $item['daily']['snowfall_sum'], - ])->toJson(); - }, - ); - - $weatherAgent = new Agent( - name: 'london-weather-agent', - prompt: <<ignoreFeatures(), - tools: [$londonWeatherTool], - ); - - $result = $weatherAgent->invoke([ - new UserMessage('How many times has it rained this year?'), - ]); - - // dd($result); - dump($result->generation->message->content()); - dump($weatherAgent->getMemory()->getMessages()->toArray()); - dd($weatherAgent->getTotalUsage()->toArray()); -})->todo(); - -test('it can pipe agents', function (): void { - // $result = WeatherAgent::make()->invoke(input: [ - // 'location' => 'Manchester', - // ]); - - $weatherAgent = new Agent( - name: 'weather', - prompt: Cortex::prompt([ - new SystemMessage('You are a weather assistant. Call the tool to get the weather for a given location.'), - new UserMessage('What is the weather in {location}?'), - ]), - // llm: 'lmstudio/gpt-oss:20b', - llm: 'openai/gpt-4.1-mini', - tools: [ - GetCurrentWeatherTool::class, - ], - output: [ - Schema::string('summary')->required(), - ], - ); - $umbrellaAgent = new Agent( - name: 'umbrella-agent', - prompt: Cortex::prompt([ - new SystemMessage("You are a helpful assistant that determines if an umbrella is needed based on the following information:\n{summary}"), - ]), - // llm: 'lmstudio/gpt-oss:20b', - llm: 'openai/gpt-4.1-mini', - output: [ - Schema::boolean('umbrella_needed')->required(), - Schema::string('reasoning')->required(), - ], - ); - - // dd(array_map(fn(object $stage): string => get_class($stage), $weatherAgent->pipe($umbrellaAgent)->getStages())); - - $umbrellaNeededResult = $weatherAgent - ->pipe($umbrellaAgent) - ->pipe(new JsonOutputParser()) - ->stream([ - 'location' => 'Manchester', - ]); - - foreach ($umbrellaNeededResult as $chunk) { - dump($chunk->message->content()); - } - - // dump($umbrellaAgent->getMemory()->getMessages()->toArray()); - // dd($umbrellaNeededResult); -})->todo(); diff --git a/tests/Unit/Agents/AgentTest.php b/tests/Unit/Agents/AgentTest.php index dd10b96..50d2f22 100644 --- a/tests/Unit/Agents/AgentTest.php +++ b/tests/Unit/Agents/AgentTest.php @@ -8,6 +8,7 @@ use Cortex\Agents\Agent; use Cortex\LLM\Data\Usage; use Cortex\Events\AgentEnd; +use Illuminate\Support\Str; use Cortex\Agents\Data\Step; use Cortex\Events\AgentStart; use Cortex\JsonSchema\Schema; @@ -16,6 +17,7 @@ use Cortex\LLM\Data\ChatResult; use Cortex\LLM\Enums\ChunkType; use Cortex\Events\AgentStepStart; +use Cortex\Pipeline\RuntimeConfig; use Cortex\LLM\Data\ChatStreamResult; use Cortex\LLM\Data\ToolCallCollection; use Cortex\LLM\Data\ChatGenerationChunk; @@ -1068,3 +1070,143 @@ function (int $x, int $y): int { ->and($messageHistory[2]->role()->value)->toBe('assistant') ->and($messageHistory[2]->content())->toBe('Hello there!'); }); + +test('thread id from RuntimeConfig is passed through to memory instance', function (): void { + $llm = OpenAIChat::fake([ + ChatCreateResponse::fake([ + 'model' => 'gpt-4o', + 'choices' => [ + [ + 'message' => [ + 'role' => 'assistant', + 'content' => 'Hello!', + ], + ], + ], + ]), + ], 'gpt-4o'); + + $agent = new Agent( + name: 'Helper', + prompt: 'You are a helpful assistant.', + llm: $llm, + ); + + // Create a RuntimeConfig with a specific thread ID + $customThreadId = 'test-thread-' . Str::uuid7()->toString(); + $runtimeConfig = new RuntimeConfig(threadId: $customThreadId); + + // Invoke the agent with the custom RuntimeConfig + $agent->invoke( + input: [ + 'query' => 'Hello', + ], + config: $runtimeConfig, + ); + + // Verify the memory instance has the correct thread ID + $memory = $agent->getMemory(); + expect($memory->getThreadId())->toBe($customThreadId, 'Memory should have the thread ID from RuntimeConfig'); + + // Verify the RuntimeConfig also maintains the same thread ID + expect($agent->getRuntimeConfig()->threadId)->toBe($customThreadId, 'RuntimeConfig should maintain the thread ID'); +}); + +test('thread id is generated when not provided in RuntimeConfig', function (): void { + $llm = OpenAIChat::fake([ + ChatCreateResponse::fake([ + 'model' => 'gpt-4o', + 'choices' => [ + [ + 'message' => [ + 'role' => 'assistant', + 'content' => 'Hello!', + ], + ], + ], + ]), + ], 'gpt-4o'); + + $agent = new Agent( + name: 'Helper', + prompt: 'You are a helpful assistant.', + llm: $llm, + ); + + // Invoke without providing a RuntimeConfig (or with one without a threadId) + $agent->invoke(input: [ + 'query' => 'Hello', + ]); + + // Verify the memory instance has a thread ID (should be auto-generated) + $memory = $agent->getMemory(); + $threadId = $memory->getThreadId(); + + expect($threadId)->not->toBeEmpty('Memory should have a generated thread ID') + ->and($threadId)->toBeString(); + + // Verify the RuntimeConfig has the same thread ID + $runtimeConfig = $agent->getRuntimeConfig(); + expect($runtimeConfig->threadId)->toBe($threadId, 'RuntimeConfig should have the same thread ID as memory'); +}); + +test('thread id persists across multiple invocations with same RuntimeConfig', function (): void { + $llm = OpenAIChat::fake([ + ChatCreateResponse::fake([ + 'model' => 'gpt-4o', + 'choices' => [ + [ + 'message' => [ + 'role' => 'assistant', + 'content' => 'First response', + ], + ], + ], + ]), + ChatCreateResponse::fake([ + 'model' => 'gpt-4o', + 'choices' => [ + [ + 'message' => [ + 'role' => 'assistant', + 'content' => 'Second response', + ], + ], + ], + ]), + ], 'gpt-4o'); + + $agent = new Agent( + name: 'Helper', + prompt: 'You are a helpful assistant.', + llm: $llm, + ); + + // Create a RuntimeConfig with a specific thread ID + $customThreadId = 'persistent-thread-' . Str::uuid7()->toString(); + $runtimeConfig = new RuntimeConfig(threadId: $customThreadId); + + // First invocation + $agent->invoke( + input: [ + 'query' => 'First message', + ], + config: $runtimeConfig, + ); + + $threadIdAfterFirst = $agent->getMemory()->getThreadId(); + expect($threadIdAfterFirst)->toBe($customThreadId, 'Thread ID should match after first invocation'); + + // Second invocation with the same RuntimeConfig + $agent->invoke( + input: [ + 'query' => 'Second message', + ], + config: $runtimeConfig, + ); + + $threadIdAfterSecond = $agent->getMemory()->getThreadId(); + expect($threadIdAfterSecond)->toBe($customThreadId, 'Thread ID should persist across invocations') + ->and($threadIdAfterSecond)->toBe($threadIdAfterFirst, 'Thread ID should remain the same') + ->and($runtimeConfig->threadId)->toBe($customThreadId, 'RuntimeConfig thread ID should remain unchanged'); +}); From fc80010d50caebdc56ad94bb442acbf65fbe0a6d Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Tue, 13 Jan 2026 17:47:20 +0000 Subject: [PATCH 22/54] wip --- .gitignore | 2 + composer.json | 2 + eslint.config.js | 44 + package-lock.json | 6202 +++++++++++++++++ package.json | 51 + resources/css/app.css | 9 + resources/js/app.tsx | 20 + resources/js/lib/utils.ts | 6 + resources/js/pages/dashboard.tsx | 15 + resources/js/ssr.tsx | 16 + resources/js/types/index.d.ts | 20 + resources/js/types/vite-env.d.ts | 1 + resources/views/app.blade.php | 23 + routes/web.php | 8 + src/CortexServiceProvider.php | 3 +- src/Http/Controllers/AgentsController.php | 2 + src/LLM/Enums/StreamingProtocol.php | 2 +- testbench.yaml | 11 +- tsconfig.json | 117 + vite.config.ts | 32 + .../app/Providers/CortexServiceProvider.php | 8 +- workbench/routes/web.php | 6 +- 22 files changed, 6588 insertions(+), 12 deletions(-) create mode 100644 eslint.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 resources/css/app.css create mode 100644 resources/js/app.tsx create mode 100644 resources/js/lib/utils.ts create mode 100644 resources/js/pages/dashboard.tsx create mode 100644 resources/js/ssr.tsx create mode 100644 resources/js/types/index.d.ts create mode 100644 resources/js/types/vite-env.d.ts create mode 100644 resources/views/app.blade.php create mode 100644 routes/web.php create mode 100644 tsconfig.json create mode 100644 vite.config.ts diff --git a/.gitignore b/.gitignore index ac4ef68..4cb990f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,5 @@ composer.lock .env .DS_Store .phpstan-cache +node_modules +public diff --git a/composer.json b/composer.json index 2285a9d..2454937 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,8 @@ "beyondcode/laravel-dump-server": "^2.1", "guzzlehttp/guzzle": "^7.9", "hkulekci/qdrant": "^0.5.8", + "inertiajs/inertia-laravel": "^2.0", + "laravel/wayfinder": "^0.1.13", "league/event": "^3.0", "mockery/mockery": "^1.6", "orchestra/testbench": "^10.6", diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..a136d22 --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,44 @@ +import js from '@eslint/js'; +import prettier from 'eslint-config-prettier'; +import react from 'eslint-plugin-react'; +import reactHooks from 'eslint-plugin-react-hooks'; +import globals from 'globals'; +import typescript from 'typescript-eslint'; + +/** @type {import('eslint').Linter.Config[]} */ +export default [ + js.configs.recommended, + ...typescript.configs.recommended, + { + ...react.configs.flat.recommended, + ...react.configs.flat['jsx-runtime'], // Required for React 17+ + languageOptions: { + globals: { + ...globals.browser, + }, + }, + rules: { + 'react/react-in-jsx-scope': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + }, + settings: { + react: { + version: 'detect', + }, + }, + }, + { + plugins: { + 'react-hooks': reactHooks, + }, + rules: { + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': 'warn', + }, + }, + { + ignores: ['vendor', 'node_modules', 'public', 'bootstrap/ssr', 'tailwind.config.js'], + }, + prettier, // Turn off all rules that might conflict with Prettier +]; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..99dbd9a --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6202 @@ +{ + "name": "cortex", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@inertiajs/react": "^2.3.7", + "@tailwindcss/vite": "^4.1.11", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "concurrently": "^9.0.1", + "globals": "^15.14.0", + "laravel-vite-plugin": "^2.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tailwind-merge": "^3.0.1", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.2", + "vite": "^7.0.4" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@laravel/vite-plugin-wayfinder": "^0.1.3", + "@types/node": "^22.13.5", + "babel-plugin-react-compiler": "^1.0.0", + "eslint": "^9.17.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^5.1.0", + "prettier": "^3.4.2", + "prettier-plugin-organize-imports": "^4.1.0", + "prettier-plugin-tailwindcss": "^0.6.11", + "typescript-eslint": "^8.23.0" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", + "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", + "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", + "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", + "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", + "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", + "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", + "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/generator": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.6", + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inertiajs/core": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@inertiajs/core/-/core-2.3.8.tgz", + "integrity": "sha512-WIF/ea9FH+yR/nLLrOX9TNN20X2pcHZBLQJYCLZ/oLMaS6GSlnMtHZv5GtKNjmIpSiMvg2PiAqtDF/mvbnr+rQ==", + "license": "MIT", + "dependencies": { + "@types/lodash-es": "^4.17.12", + "axios": "^1.13.2", + "laravel-precognition": "^1.0.0", + "lodash-es": "^4.17.21", + "qs": "^6.14.1" + } + }, + "node_modules/@inertiajs/react": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/@inertiajs/react/-/react-2.3.8.tgz", + "integrity": "sha512-n/e8QrOicGMQc1g9POdAHm2lgk1D5armOvPwRK0U6QY9zhySj3rbKxbKJmXA6K0LnCMh384AuBMAu2COThLmsA==", + "license": "MIT", + "dependencies": { + "@inertiajs/core": "2.3.8", + "@types/lodash-es": "^4.17.12", + "laravel-precognition": "^1.0.0", + "lodash-es": "^4.17.21" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.9.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@laravel/vite-plugin-wayfinder": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/@laravel/vite-plugin-wayfinder/-/vite-plugin-wayfinder-0.1.7.tgz", + "integrity": "sha512-yZYIr1iwuCQ7LFI+GsJk9vacw1HWMp3ZlDlW0pdfz3zXyKeu4US7oH79KmQQ031L0cYaSyaUMo/Ha1D4BosKqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", + "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/node": { + "version": "22.19.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.5.tgz", + "integrity": "sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", + "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", + "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/type-utils": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.53.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz", + "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz", + "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.0", + "@typescript-eslint/types": "^8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", + "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz", + "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz", + "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", + "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", + "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.53.0", + "@typescript-eslint/tsconfig-utils": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz", + "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", + "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", + "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/laravel-precognition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-1.0.0.tgz", + "integrity": "sha512-hvXPT7dayCQAidxnsY0hab9Q+Y2rsh7xRpH9uiFtXN8Dekc3tIZt+NrxrOZ9N5SwHBmRBze/Bv+ElfXac0kD6g==", + "license": "MIT", + "dependencies": { + "axios": "^1.4.0", + "lodash-es": "^4.17.21" + } + }, + "node_modules/laravel-vite-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", + "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^7.0.0" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "license": "MIT" + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", + "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-organize-imports": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.3.0.tgz", + "integrity": "sha512-FxFz0qFhyBsGdIsb697f/EkvHzi5SZOhWAjxcx2dLt+Q532bAlhswcXGYB1yzjZ69kW8UoadFBw7TyNwlq96Iw==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "prettier": ">=2.0", + "typescript": ">=2.9", + "vue-tsc": "^2.1.0 || 3" + }, + "peerDependenciesMeta": { + "vue-tsc": { + "optional": true + } + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.6.14", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.14.tgz", + "integrity": "sha512-pi2e/+ZygeIqntN+vC573BcW5Cve8zUB0SSAGxqpB4f96boZF4M3phPVoOFCeypwkpRYdi7+jQ5YJJUwrkGUAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/react": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.3" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", + "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve": { + "version": "2.0.0-next.5", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", + "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rollup": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", + "integrity": "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.55.1", + "@rollup/rollup-android-arm64": "4.55.1", + "@rollup/rollup-darwin-arm64": "4.55.1", + "@rollup/rollup-darwin-x64": "4.55.1", + "@rollup/rollup-freebsd-arm64": "4.55.1", + "@rollup/rollup-freebsd-x64": "4.55.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", + "@rollup/rollup-linux-arm-musleabihf": "4.55.1", + "@rollup/rollup-linux-arm64-gnu": "4.55.1", + "@rollup/rollup-linux-arm64-musl": "4.55.1", + "@rollup/rollup-linux-loong64-gnu": "4.55.1", + "@rollup/rollup-linux-loong64-musl": "4.55.1", + "@rollup/rollup-linux-ppc64-gnu": "4.55.1", + "@rollup/rollup-linux-ppc64-musl": "4.55.1", + "@rollup/rollup-linux-riscv64-gnu": "4.55.1", + "@rollup/rollup-linux-riscv64-musl": "4.55.1", + "@rollup/rollup-linux-s390x-gnu": "4.55.1", + "@rollup/rollup-linux-x64-gnu": "4.55.1", + "@rollup/rollup-linux-x64-musl": "4.55.1", + "@rollup/rollup-openbsd-x64": "4.55.1", + "@rollup/rollup-openharmony-arm64": "4.55.1", + "@rollup/rollup-win32-arm64-msvc": "4.55.1", + "@rollup/rollup-win32-ia32-msvc": "4.55.1", + "@rollup/rollup-win32-x64-gnu": "4.55.1", + "@rollup/rollup-win32-x64-msvc": "4.55.1", + "fsevents": "~2.3.2" + } + }, + "node_modules/rollup/node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.55.1.tgz", + "integrity": "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shell-quote": { + "version": "1.8.3", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", + "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.6", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-byte-offset": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "is-typed-array": "^1.1.13", + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.0.tgz", + "integrity": "sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.53.0", + "@typescript-eslint/parser": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "devOptional": true, + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vite": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-plugin-full-reload": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-full-reload/-/vite-plugin-full-reload-1.2.0.tgz", + "integrity": "sha512-kz18NW79x0IHbxRSHm0jttP4zoO9P9gXh+n6UTwlNKnviTTEpOlum6oS9SmecrTtSr+muHEn5TUuC75UovQzcA==", + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "picomatch": "^2.3.1" + } + }, + "node_modules/vite-plugin-full-reload/node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..8a7358d --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "$schema": "https://www.schemastore.org/package.json", + "private": true, + "type": "module", + "scripts": { + "build": "vite build", + "build:ssr": "vite build && vite build --ssr", + "dev": "vite", + "format": "prettier --write resources/", + "format:check": "prettier --check resources/", + "lint": "eslint . --fix", + "types": "tsc --noEmit" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@laravel/vite-plugin-wayfinder": "^0.1.3", + "@types/node": "^22.13.5", + "babel-plugin-react-compiler": "^1.0.0", + "eslint": "^9.17.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-react": "^7.37.3", + "eslint-plugin-react-hooks": "^5.1.0", + "prettier": "^3.4.2", + "prettier-plugin-organize-imports": "^4.1.0", + "prettier-plugin-tailwindcss": "^0.6.11", + "typescript-eslint": "^8.23.0" + }, + "dependencies": { + "@inertiajs/react": "^2.3.7", + "@tailwindcss/vite": "^4.1.11", + "@types/react": "^19.2.0", + "@types/react-dom": "^19.2.0", + "@vitejs/plugin-react": "^5.0.0", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "concurrently": "^9.0.1", + "globals": "^15.14.0", + "laravel-vite-plugin": "^2.0", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "tailwind-merge": "^3.0.1", + "tailwindcss": "^4.0.0", + "typescript": "^5.7.2", + "vite": "^7.0.4" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1" + } +} diff --git a/resources/css/app.css b/resources/css/app.css new file mode 100644 index 0000000..df611af --- /dev/null +++ b/resources/css/app.css @@ -0,0 +1,9 @@ +@import 'tailwindcss'; + +@source '../views'; +@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; + +@theme { + --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; +} diff --git a/resources/js/app.tsx b/resources/js/app.tsx new file mode 100644 index 0000000..895217a --- /dev/null +++ b/resources/js/app.tsx @@ -0,0 +1,20 @@ +import '../css/app.css'; + +import { createInertiaApp } from '@inertiajs/react'; +import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; +import { createRoot } from 'react-dom/client'; + +const appName = import.meta.env.VITE_APP_NAME || 'Cortex'; + +createInertiaApp({ + title: (title) => title ? `${title} - ${appName}` : appName, + resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')), + setup({ el, App, props }) { + const root = createRoot(el); + + root.render(); + }, + progress: { + color: '#4B5563', + }, +}); diff --git a/resources/js/lib/utils.ts b/resources/js/lib/utils.ts new file mode 100644 index 0000000..dd53ea8 --- /dev/null +++ b/resources/js/lib/utils.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/resources/js/pages/dashboard.tsx b/resources/js/pages/dashboard.tsx new file mode 100644 index 0000000..3398c36 --- /dev/null +++ b/resources/js/pages/dashboard.tsx @@ -0,0 +1,15 @@ +import { Head } from '@inertiajs/react'; + +export default function Dashboard() { + return ( + <> + + + + +
+ Hi Dashboard +
+ + ); +} diff --git a/resources/js/ssr.tsx b/resources/js/ssr.tsx new file mode 100644 index 0000000..ba5c6aa --- /dev/null +++ b/resources/js/ssr.tsx @@ -0,0 +1,16 @@ +import { createInertiaApp } from '@inertiajs/react'; +import createServer from '@inertiajs/react/server'; +import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; +import ReactDOMServer from 'react-dom/server'; + +const appName = import.meta.env.VITE_APP_NAME || 'Cortex'; + +createServer((page) => + createInertiaApp({ + page, + render: ReactDOMServer.renderToString, + title: (title) => title ? `${title} - ${appName}` : appName, + resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')), + setup: ({ App, props }) => , + }), +); diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts new file mode 100644 index 0000000..ee439d2 --- /dev/null +++ b/resources/js/types/index.d.ts @@ -0,0 +1,20 @@ +export interface Auth { + user: User; +} + +export interface SharedData { + name: string; + auth: Auth; + [key: string]: unknown; +} + +export interface User { + id: number; + name: string; + email: string; + avatar?: string; + email_verified_at: string | null; + created_at: string; + updated_at: string; + [key: string]: unknown; // This allows for additional properties... +} diff --git a/resources/js/types/vite-env.d.ts b/resources/js/types/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/resources/js/types/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/resources/views/app.blade.php b/resources/views/app.blade.php new file mode 100644 index 0000000..c514627 --- /dev/null +++ b/resources/views/app.blade.php @@ -0,0 +1,23 @@ + + ($appearance ?? 'system') == 'dark'])> + + + + + Cortex + + + + + + + + + @viteReactRefresh + @vite(['resources/js/app.tsx', "resources/js/pages/{$page['component']}.tsx"]) + @inertiaHead + + + @inertia + + diff --git a/routes/web.php b/routes/web.php new file mode 100644 index 0000000..c759d2f --- /dev/null +++ b/routes/web.php @@ -0,0 +1,8 @@ +rootView('cortex::app'); +})->name('cortex.dashboard'); diff --git a/src/CortexServiceProvider.php b/src/CortexServiceProvider.php index 7f9284e..908b3a7 100644 --- a/src/CortexServiceProvider.php +++ b/src/CortexServiceProvider.php @@ -26,7 +26,8 @@ public function configurePackage(Package $package): void { $package->name('cortex') ->hasConfigFile() - ->hasRoutes('api') + ->hasRoutes('api', 'web') + ->hasViews('cortex') ->hasCommand(AgentChat::class); } diff --git a/src/Http/Controllers/AgentsController.php b/src/Http/Controllers/AgentsController.php index 3f1bc3e..bdc5c69 100644 --- a/src/Http/Controllers/AgentsController.php +++ b/src/Http/Controllers/AgentsController.php @@ -99,6 +99,8 @@ public function stream(string $agent, Request $request): StreamedResponse messages: $messages, input: $request->input('input', []), config: new RuntimeConfig( + // temporary hack to get a unique thread id for the session + threadId: $request->input('id') . '-' . Arr::get($request->collect('messages')->first(), 'id', ''), tools: $tools->all(), ), ); diff --git a/src/LLM/Enums/StreamingProtocol.php b/src/LLM/Enums/StreamingProtocol.php index cf84fb1..298edb7 100644 --- a/src/LLM/Enums/StreamingProtocol.php +++ b/src/LLM/Enums/StreamingProtocol.php @@ -15,7 +15,7 @@ enum StreamingProtocol: string case Raw = 'raw'; case AGUI = 'agui'; case Vercel = 'vercel'; - case Text = 'vercel_text'; + case Text = 'text'; public function driver(): StreamingProtocolDriver { diff --git a/testbench.yaml b/testbench.yaml index b73b245..a90a32c 100644 --- a/testbench.yaml +++ b/testbench.yaml @@ -9,7 +9,7 @@ seeders: - Workbench\Database\Seeders\DatabaseSeeder workbench: - start: "/" + start: "/cortex" install: true health: false discovers: @@ -17,7 +17,10 @@ workbench: api: true commands: false components: false - views: false - build: [] + views: true + build: + - asset-publish assets: [] - sync: [] + sync: + - from: public/build + to: public/build diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..42777d4 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,117 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "ESNext" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + "moduleResolution": "bundler" /* Specify how TypeScript looks up a file from a given module specifier. */, + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + "allowJs": true /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */, + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + "noEmit": true /* Disable emitting files from a compilation. */, + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + // "outDir": "./", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + "isolatedModules": true /* Ensure that each file can be safely transpiled without relying on other imports. */, + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + "noImplicitAny": true /* Enable error reporting for expressions and declarations with an implied 'any' type. */, + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */, + "baseUrl": ".", + "paths": { + "@/*": ["./resources/js/*"] + }, + "jsx": "react-jsx" + }, + "include": ["resources/js/**/*.ts", "resources/js/**/*.d.ts", "resources/js/**/*.tsx"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..5368616 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,32 @@ +import { wayfinder } from '@laravel/vite-plugin-wayfinder'; +import tailwindcss from '@tailwindcss/vite'; +import react from '@vitejs/plugin-react'; +import laravel from 'laravel-vite-plugin'; +import { defineConfig } from 'vite'; +import path from "node:path"; + +const testbenchCommand = path.join(__dirname, 'vendor', 'bin', 'testbench'); +const testbenchPublicDir = path.join(__dirname, 'vendor', 'orchestra', 'testbench-core', 'laravel', 'public'); + +export default defineConfig({ + plugins: [ + laravel({ + input: ['resources/css/app.css', 'resources/js/app.tsx'], + ssr: 'resources/js/ssr.tsx', + refresh: true, + hotFile: path.join(testbenchPublicDir, 'hot'), + }), + react({ + babel: { + plugins: ['babel-plugin-react-compiler'], + }, + }), + tailwindcss(), + wayfinder({ + command: `${testbenchCommand} wayfinder:generate --with-form` + }), + ], + esbuild: { + jsx: 'automatic', + }, +}); diff --git a/workbench/app/Providers/CortexServiceProvider.php b/workbench/app/Providers/CortexServiceProvider.php index 9680d3c..6c9e09a 100644 --- a/workbench/app/Providers/CortexServiceProvider.php +++ b/workbench/app/Providers/CortexServiceProvider.php @@ -5,11 +5,12 @@ use Cortex\Cortex; use Cortex\Agents\Agent; use Cortex\JsonSchema\Schema; +use function Cortex\Support\tool; +use Cortex\Memory\Stores\CacheStore; use Illuminate\Support\ServiceProvider; + use Cortex\LLM\Data\Messages\UserMessage; use Cortex\LLM\Data\Messages\SystemMessage; - -use function Cortex\Support\tool; use function Orchestra\Testbench\package_path; class CortexServiceProvider extends ServiceProvider @@ -79,7 +80,8 @@ public function boot(): void Cortex::registerAgent(new Agent( name: 'generic', prompt: 'You are a helpful assistant.', - llm: 'lmstudio/openai/gpt-oss-20b' + llm: 'lmstudio/openai/gpt-oss-20b', + memoryStore: new CacheStore(cache: cache()->store('file'), threadId: 'generic'), )); Cortex::registerAgent(new Agent( diff --git a/workbench/routes/web.php b/workbench/routes/web.php index 86a06c5..329fc42 100644 --- a/workbench/routes/web.php +++ b/workbench/routes/web.php @@ -2,6 +2,6 @@ use Illuminate\Support\Facades\Route; -Route::get('/', function () { - return view('welcome'); -}); +// Route::get('/', function () { +// return view('welcome'); +// }); From f56e97593ef96d50fc2a79557fe62aa7ac4d1fb8 Mon Sep 17 00:00:00 2001 From: Sean Tymon Date: Tue, 13 Jan 2026 23:58:37 +0000 Subject: [PATCH 23/54] wip --- composer.json | 3 +- package-lock.json | 9336 ++++++++++++----- package.json | 34 +- resources/css/app.css | 138 +- .../Cortex/Http/Controllers/AGUIController.ts | 36 + .../Http/Controllers/AgentsController.ts | 228 + .../actions/Cortex/Http/Controllers/index.ts | 9 + resources/js/actions/Cortex/Http/index.ts | 7 + resources/js/actions/Cortex/index.ts | 7 + .../Http/Controllers/WorkbenchController.ts | 245 + .../Workbench/Http/Controllers/index.ts | 7 + .../actions/Orchestra/Workbench/Http/index.ts | 7 + .../js/actions/Orchestra/Workbench/index.ts | 7 + resources/js/actions/Orchestra/index.ts | 7 + resources/js/app.tsx | 19 +- resources/js/components/alert-error.tsx | 24 + resources/js/components/app-content.tsx | 25 + resources/js/components/app-header.tsx | 261 + resources/js/components/app-logo-icon.tsx | 13 + resources/js/components/app-logo.tsx | 16 + resources/js/components/app-shell.tsx | 20 + .../js/components/app-sidebar-header.tsx | 18 + resources/js/components/app-sidebar.tsx | 70 + .../js/components/appearance-dropdown.tsx | 67 + resources/js/components/appearance-tabs.tsx | 43 + .../js/components/assistant-ui/attachment.tsx | 235 + .../components/assistant-ui/markdown-text.tsx | 231 + .../js/components/assistant-ui/reasoning.tsx | 267 + .../assistant-ui/shiki-highlighter.tsx | 58 + .../components/assistant-ui/thread-list.tsx | 95 + .../js/components/assistant-ui/thread.tsx | 392 + .../assistant-ui/threadlist-sidebar.tsx | 73 + .../components/assistant-ui/tool-fallback.tsx | 46 + .../assistant-ui/tooltip-icon-button.tsx | 42 + .../components/assistant-ui/weather-card.tsx | 187 + resources/js/components/assistant.tsx | 121 + resources/js/components/breadcrumbs.tsx | 49 + resources/js/components/delete-user.tsx | 120 + resources/js/components/heading-small.tsx | 16 + resources/js/components/heading.tsx | 16 + resources/js/components/icon.tsx | 15 + resources/js/components/input-error.tsx | 17 + resources/js/components/nav-footer.tsx | 53 + resources/js/components/nav-main.tsx | 36 + resources/js/components/nav-user.tsx | 55 + resources/js/components/text-link.tsx | 23 + .../components/two-factor-recovery-codes.tsx | 164 + .../js/components/two-factor-setup-modal.tsx | 347 + resources/js/components/ui/alert.tsx | 66 + resources/js/components/ui/avatar.tsx | 51 + resources/js/components/ui/badge.tsx | 46 + resources/js/components/ui/breadcrumb.tsx | 109 + resources/js/components/ui/button.tsx | 58 + resources/js/components/ui/card.tsx | 68 + resources/js/components/ui/checkbox.tsx | 30 + resources/js/components/ui/collapsible.tsx | 31 + resources/js/components/ui/dialog.tsx | 133 + resources/js/components/ui/dropdown-menu.tsx | 255 + resources/js/components/ui/icon.tsx | 14 + resources/js/components/ui/input-otp.tsx | 69 + resources/js/components/ui/input.tsx | 21 + resources/js/components/ui/label.tsx | 22 + .../js/components/ui/navigation-menu.tsx | 168 + .../js/components/ui/placeholder-pattern.tsx | 20 + resources/js/components/ui/select.tsx | 179 + resources/js/components/ui/separator.tsx | 26 + resources/js/components/ui/sheet.tsx | 137 + resources/js/components/ui/sidebar.tsx | 721 ++ resources/js/components/ui/skeleton.tsx | 13 + resources/js/components/ui/spinner.tsx | 16 + resources/js/components/ui/toggle-group.tsx | 71 + resources/js/components/ui/toggle.tsx | 45 + resources/js/components/ui/tooltip.tsx | 59 + resources/js/components/user-info.tsx | 32 + resources/js/components/user-menu-content.tsx | 63 + resources/js/hooks/use-active-url.ts | 21 + resources/js/hooks/use-appearance.tsx | 100 + resources/js/hooks/use-clipboard.ts | 32 + resources/js/hooks/use-initials.tsx | 15 + resources/js/hooks/use-mobile-navigation.ts | 8 + resources/js/hooks/use-mobile.tsx | 36 + resources/js/hooks/use-two-factor-auth.ts | 104 + resources/js/layouts/app-layout.tsx | 14 + .../js/layouts/app/app-header-layout.tsx | 17 + .../js/layouts/app/app-sidebar-layout.tsx | 21 + resources/js/layouts/auth-layout.tsx | 18 + .../js/layouts/auth/auth-card-layout.tsx | 48 + .../js/layouts/auth/auth-simple-layout.tsx | 44 + .../js/layouts/auth/auth-split-layout.tsx | 50 + resources/js/layouts/settings/layout.tsx | 86 + resources/js/lib/utils.ts | 5 + resources/js/pages/dashboard.tsx | 37 +- resources/js/pages/playground.tsx | 27 + resources/js/routes/cortex/agents/index.ts | 231 + resources/js/routes/cortex/agui/index.ts | 40 + resources/js/routes/cortex/index.ts | 91 + resources/js/routes/storage/index.ts | 64 + resources/js/routes/workbench/index.ts | 250 + resources/js/ssr.tsx | 12 +- resources/js/types/index.d.ts | 22 + resources/js/wayfinder/index.ts | 158 + resources/views/app.blade.php | 28 +- routes/web.php | 4 + testbench.yaml | 3 +- vite.config.ts | 2 +- 105 files changed, 14933 insertions(+), 2483 deletions(-) create mode 100644 resources/js/actions/Cortex/Http/Controllers/AGUIController.ts create mode 100644 resources/js/actions/Cortex/Http/Controllers/AgentsController.ts create mode 100644 resources/js/actions/Cortex/Http/Controllers/index.ts create mode 100644 resources/js/actions/Cortex/Http/index.ts create mode 100644 resources/js/actions/Cortex/index.ts create mode 100644 resources/js/actions/Orchestra/Workbench/Http/Controllers/WorkbenchController.ts create mode 100644 resources/js/actions/Orchestra/Workbench/Http/Controllers/index.ts create mode 100644 resources/js/actions/Orchestra/Workbench/Http/index.ts create mode 100644 resources/js/actions/Orchestra/Workbench/index.ts create mode 100644 resources/js/actions/Orchestra/index.ts create mode 100644 resources/js/components/alert-error.tsx create mode 100644 resources/js/components/app-content.tsx create mode 100644 resources/js/components/app-header.tsx create mode 100644 resources/js/components/app-logo-icon.tsx create mode 100644 resources/js/components/app-logo.tsx create mode 100644 resources/js/components/app-shell.tsx create mode 100644 resources/js/components/app-sidebar-header.tsx create mode 100644 resources/js/components/app-sidebar.tsx create mode 100644 resources/js/components/appearance-dropdown.tsx create mode 100644 resources/js/components/appearance-tabs.tsx create mode 100644 resources/js/components/assistant-ui/attachment.tsx create mode 100644 resources/js/components/assistant-ui/markdown-text.tsx create mode 100644 resources/js/components/assistant-ui/reasoning.tsx create mode 100644 resources/js/components/assistant-ui/shiki-highlighter.tsx create mode 100644 resources/js/components/assistant-ui/thread-list.tsx create mode 100644 resources/js/components/assistant-ui/thread.tsx create mode 100644 resources/js/components/assistant-ui/threadlist-sidebar.tsx create mode 100644 resources/js/components/assistant-ui/tool-fallback.tsx create mode 100644 resources/js/components/assistant-ui/tooltip-icon-button.tsx create mode 100644 resources/js/components/assistant-ui/weather-card.tsx create mode 100644 resources/js/components/assistant.tsx create mode 100644 resources/js/components/breadcrumbs.tsx create mode 100644 resources/js/components/delete-user.tsx create mode 100644 resources/js/components/heading-small.tsx create mode 100644 resources/js/components/heading.tsx create mode 100644 resources/js/components/icon.tsx create mode 100644 resources/js/components/input-error.tsx create mode 100644 resources/js/components/nav-footer.tsx create mode 100644 resources/js/components/nav-main.tsx create mode 100644 resources/js/components/nav-user.tsx create mode 100644 resources/js/components/text-link.tsx create mode 100644 resources/js/components/two-factor-recovery-codes.tsx create mode 100644 resources/js/components/two-factor-setup-modal.tsx create mode 100644 resources/js/components/ui/alert.tsx create mode 100644 resources/js/components/ui/avatar.tsx create mode 100644 resources/js/components/ui/badge.tsx create mode 100644 resources/js/components/ui/breadcrumb.tsx create mode 100644 resources/js/components/ui/button.tsx create mode 100644 resources/js/components/ui/card.tsx create mode 100644 resources/js/components/ui/checkbox.tsx create mode 100644 resources/js/components/ui/collapsible.tsx create mode 100644 resources/js/components/ui/dialog.tsx create mode 100644 resources/js/components/ui/dropdown-menu.tsx create mode 100644 resources/js/components/ui/icon.tsx create mode 100644 resources/js/components/ui/input-otp.tsx create mode 100644 resources/js/components/ui/input.tsx create mode 100644 resources/js/components/ui/label.tsx create mode 100644 resources/js/components/ui/navigation-menu.tsx create mode 100644 resources/js/components/ui/placeholder-pattern.tsx create mode 100644 resources/js/components/ui/select.tsx create mode 100644 resources/js/components/ui/separator.tsx create mode 100644 resources/js/components/ui/sheet.tsx create mode 100644 resources/js/components/ui/sidebar.tsx create mode 100644 resources/js/components/ui/skeleton.tsx create mode 100644 resources/js/components/ui/spinner.tsx create mode 100644 resources/js/components/ui/toggle-group.tsx create mode 100644 resources/js/components/ui/toggle.tsx create mode 100644 resources/js/components/ui/tooltip.tsx create mode 100644 resources/js/components/user-info.tsx create mode 100644 resources/js/components/user-menu-content.tsx create mode 100644 resources/js/hooks/use-active-url.ts create mode 100644 resources/js/hooks/use-appearance.tsx create mode 100644 resources/js/hooks/use-clipboard.ts create mode 100644 resources/js/hooks/use-initials.tsx create mode 100644 resources/js/hooks/use-mobile-navigation.ts create mode 100644 resources/js/hooks/use-mobile.tsx create mode 100644 resources/js/hooks/use-two-factor-auth.ts create mode 100644 resources/js/layouts/app-layout.tsx create mode 100644 resources/js/layouts/app/app-header-layout.tsx create mode 100644 resources/js/layouts/app/app-sidebar-layout.tsx create mode 100644 resources/js/layouts/auth-layout.tsx create mode 100644 resources/js/layouts/auth/auth-card-layout.tsx create mode 100644 resources/js/layouts/auth/auth-simple-layout.tsx create mode 100644 resources/js/layouts/auth/auth-split-layout.tsx create mode 100644 resources/js/layouts/settings/layout.tsx create mode 100644 resources/js/pages/playground.tsx create mode 100644 resources/js/routes/cortex/agents/index.ts create mode 100644 resources/js/routes/cortex/agui/index.ts create mode 100644 resources/js/routes/cortex/index.ts create mode 100644 resources/js/routes/storage/index.ts create mode 100644 resources/js/routes/workbench/index.ts create mode 100644 resources/js/wayfinder/index.ts diff --git a/composer.json b/composer.json index 2454937..b185cac 100644 --- a/composer.json +++ b/composer.json @@ -90,7 +90,8 @@ "Composer\\Config::disableProcessTimeout", "@build", "@php vendor/bin/testbench serve --ansi" - ] + ], + "dev": "npx concurrently -c \"#93c5fd,#fdba74\" \"composer serve\" \"npm run dev\" --names=server,vite --kill-others" }, "config": { "sort-packages": true, diff --git a/package-lock.json b/package-lock.json index 99dbd9a..3313306 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,24 @@ "packages": { "": { "dependencies": { + "@assistant-ui/react": "^0.11.53", + "@assistant-ui/react-ai-sdk": "^1.1.20", + "@assistant-ui/react-markdown": "^0.11.9", + "@headlessui/react": "^2.2.0", "@inertiajs/react": "^2.3.7", + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-navigation-menu": "^1.2.5", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-toggle": "^1.1.2", + "@radix-ui/react-toggle-group": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.1.11", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", @@ -13,14 +30,22 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "concurrently": "^9.0.1", + "framer-motion": "^12.26.2", "globals": "^15.14.0", + "input-otp": "^1.4.2", "laravel-vite-plugin": "^2.0", + "lucide-react": "^0.475.0", + "motion": "^12.26.2", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-shiki": "^0.9.1", + "remark-gfm": "^4.0.1", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.0", + "tw-animate-css": "^1.4.0", "typescript": "^5.7.2", - "vite": "^7.0.4" + "vite": "^7.0.4", + "zustand": "^5.0.10" }, "devDependencies": { "@eslint/js": "^9.19.0", @@ -30,7 +55,7 @@ "eslint": "^9.17.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.37.3", - "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-hooks": "^7.0.0", "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.1.0", "prettier-plugin-tailwindcss": "^0.6.11", @@ -38,8 +63,192 @@ }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", - "lightningcss-linux-x64-gnu": "^1.29.1" + "@tailwindcss/oxide-win32-x64-msvc": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1", + "lightningcss-win32-x64-msvc": "^1.29.1" + } + }, + "node_modules/@ai-sdk/gateway": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.27.tgz", + "integrity": "sha512-8hbezMsGa0crSt7/DKjkYL1UbbJJW/UFxTfhmf5qcIeYeeWG4dTNmm+DWbUdIsTaWvp59KC4eeC9gYXBbTHd7w==", + "dependencies": { + "@ai-sdk/provider": "2.0.1", + "@ai-sdk/provider-utils": "3.0.20", + "@vercel/oidc": "3.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/provider": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-2.0.1.tgz", + "integrity": "sha512-KCUwswvsC5VsW2PWFqF8eJgSCu5Ysj7m1TxiHTVA6g7k360bk0RNQENT8KTMAYEs+8fWPD3Uu4dEmzGHc+jGng==", + "dependencies": { + "json-schema": "^0.4.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@ai-sdk/provider-utils": { + "version": "3.0.20", + "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.20.tgz", + "integrity": "sha512-iXHVe0apM2zUEzauqJwqmpC37A5rihrStAih5Ks+JE32iTe4LZ58y17UGBjpQQTCRw9YxMeo2UFLxLpBluyvLQ==", + "dependencies": { + "@ai-sdk/provider": "2.0.1", + "@standard-schema/spec": "^1.0.0", + "eventsource-parser": "^3.0.6" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" + } + }, + "node_modules/@ai-sdk/react": { + "version": "2.0.123", + "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-2.0.123.tgz", + "integrity": "sha512-exaEvHAsDdR0wgzF3l0BmC9U1nPLnkPK2CCnX3BP4RDj/PySZvPXjry3AOz1Ayb8KSPZgWklVRzxsQxrOYQJxA==", + "dependencies": { + "@ai-sdk/provider-utils": "3.0.20", + "ai": "5.0.121", + "swr": "^2.2.5", + "throttleit": "2.1.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^18 || ~19.0.1 || ~19.1.2 || ^19.2.1", + "zod": "^3.25.76 || ^4.1.8" + }, + "peerDependenciesMeta": { + "zod": { + "optional": true + } + } + }, + "node_modules/@assistant-ui/react": { + "version": "0.11.53", + "resolved": "https://registry.npmjs.org/@assistant-ui/react/-/react-0.11.53.tgz", + "integrity": "sha512-G5VB752Somw2Xv4JkGqnloZTxXRu2laHufOROs2H9yOE9Pu+o9aCjW/rn9p8FIev4gWh/ltDouX8T+z9Fh8dJw==", + "dependencies": { + "@assistant-ui/tap": "^0.3.5", + "@radix-ui/primitive": "^1.1.3", + "@radix-ui/react-compose-refs": "^1.1.2", + "@radix-ui/react-context": "^1.1.3", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-primitive": "^2.1.4", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-use-callback-ref": "^1.1.1", + "@radix-ui/react-use-escape-keydown": "^1.1.1", + "assistant-cloud": "^0.1.12", + "assistant-stream": "^0.2.46", + "nanoid": "5.1.6", + "react-textarea-autosize": "^8.5.9", + "zod": "^4.2.1", + "zustand": "^5.0.9" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^18 || ^19", + "react-dom": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@assistant-ui/react-ai-sdk": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/@assistant-ui/react-ai-sdk/-/react-ai-sdk-1.1.20.tgz", + "integrity": "sha512-1t+TBUIeNwq7ukb3rLMeSnPeQHrCj5LdwOuvqkYvx5d7dspNMUd2Zh954Gxdie0/iLHGn3ltpjscZeJWSrjSxg==", + "dependencies": { + "@ai-sdk/react": "^2.0.118", + "ai": "^5.0.116", + "zod": "^4.2.1" + }, + "peerDependencies": { + "@assistant-ui/react": "^0.11.53", + "@types/react": "*", + "assistant-cloud": "*", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "assistant-cloud": { + "optional": true + } + } + }, + "node_modules/@assistant-ui/react-markdown": { + "version": "0.11.9", + "resolved": "https://registry.npmjs.org/@assistant-ui/react-markdown/-/react-markdown-0.11.9.tgz", + "integrity": "sha512-zR0Ty4ID5htJgm4g1TVAbTsyfJZ8XHccDQ0sMODsq/PWAM75l7EmAbxdSKPbvCqny1A/FxvAB4dz1LA17ZgoWg==", + "dependencies": { + "@radix-ui/react-primitive": "^2.1.4", + "@radix-ui/react-use-callback-ref": "^1.1.1", + "classnames": "^2.5.1", + "react-markdown": "^10.1.0" + }, + "peerDependencies": { + "@assistant-ui/react": "^0.11.53", + "@types/react": "*", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@assistant-ui/react/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/@assistant-ui/tap": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@assistant-ui/tap/-/tap-0.3.5.tgz", + "integrity": "sha512-aI7lOKglkVYy17GrS9EdjSrOmEBmofWPBZ4F5wb96yqEynXflXY3qUAFCgmUwaP/TVkog72+o1ePyvsGphSmJQ==", + "peerDependencies": { + "@types/react": "*", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } } }, "node_modules/@babel/code-frame": { @@ -260,6 +469,14 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -878,6 +1095,79 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", + "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", + "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.7.3", + "@floating-ui/utils": "^0.2.10" + } + }, + "node_modules/@floating-ui/react": { + "version": "0.26.28", + "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz", + "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.1.2", + "@floating-ui/utils": "^0.2.8", + "tabbable": "^6.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", + "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "license": "MIT", + "dependencies": { + "@floating-ui/dom": "^1.7.4" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.10", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz", + "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==", + "license": "MIT" + }, + "node_modules/@headlessui/react": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.9.tgz", + "integrity": "sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/react": "^0.26.16", + "@react-aria/focus": "^3.20.2", + "@react-aria/interactions": "^3.25.0", + "@tanstack/react-virtual": "^3.13.9", + "use-sync-external-store": "^1.5.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^18 || ^19 || ^19.0.0-rc", + "react-dom": "^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -1011,261 +1301,2304 @@ "dev": true, "license": "MIT" }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.53", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", - "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", - "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", - "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-arrow": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz", + "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==", "license": "MIT", - "optional": true, - "os": [ - "android" - ] + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", - "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", - "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", - "cpu": [ - "x64" - ], + "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", - "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-avatar": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.11.tgz", + "integrity": "sha512-0Qk603AHGV28BOBO34p7IgD5m+V5Sg/YovfayABkoDDBM5d3NCx0Mp4gGrjzLGes1jV5eNOE1r3itqOR33VC6Q==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@radix-ui/react-context": "1.1.3", + "@radix-ui/react-primitive": "2.1.4", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-is-hydrated": "0.1.0", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", - "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", - "cpu": [ - "x64" - ], + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", - "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", - "cpu": [ - "arm" - ], + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", - "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", - "cpu": [ - "arm" - ], + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", - "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", - "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", - "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", - "cpu": [ - "loong64" - ], + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", - "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", - "cpu": [ - "loong64" - ], + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", - "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", - "cpu": [ - "ppc64" - ], + "node_modules/@radix-ui/react-collapsible/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", - "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", - "cpu": [ - "ppc64" - ], + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", - "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", - "cpu": [ - "riscv64" - ], + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", - "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", - "cpu": [ - "riscv64" - ], + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", - "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", - "cpu": [ - "s390x" - ], + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", - "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", - "cpu": [ - "x64" - ], + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", - "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", - "cpu": [ - "x64" - ], + "node_modules/@radix-ui/react-context": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz", + "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.55.1", + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", + "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", + "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-escape-keydown": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz", + "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-menu": "2.1.16", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", + "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", + "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", + "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.2.14", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.2.14.tgz", + "integrity": "sha512-YB9mTFQvCOAQMHU+C/jVl96WmuWeltyUEpRJJky51huhds5W2FQr1J8D/16sQlf0ozxkPK8uF3niQMdUwZPv5w==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-navigation-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", + "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==", + "license": "MIT", + "dependencies": { + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-rect": "1.1.1", + "@radix-ui/react-use-size": "1.1.1", + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", + "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle-group/-/react-toggle-group-1.1.11.tgz", + "integrity": "sha512-5umnS0T8JQzQT6HbPyO7Hh9dgd82NmS36DQr+X/YJ9ctFNCiiQd6IJAYYZ33LUwm8M+taCz5t2ui29fHZc4Y6Q==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-toggle": "1.1.10", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle-group/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", + "integrity": "sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-visually-hidden": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", + "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-is-hydrated": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-is-hydrated/-/react-use-is-hydrated-0.1.0.tgz", + "integrity": "sha512-U+UORVEq+cTnRIaostJv9AGdV3G6Y+zbVd+12e18jQ5A3c0xL03IhnHuiU4UV69wolOQp5GfR58NW/EgdQhwOA==", + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.5.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", + "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==", + "license": "MIT", + "dependencies": { + "@radix-ui/rect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz", + "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz", + "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==", + "license": "MIT" + }, + "node_modules/@react-aria/focus": { + "version": "3.21.3", + "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.3.tgz", + "integrity": "sha512-FsquWvjSCwC2/sBk4b+OqJyONETUIXQ2vM0YdPAuC+QFQh2DT6TIBo6dOZVSezlhudDla69xFBd6JvCFq1AbUw==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/interactions": "^3.26.0", + "@react-aria/utils": "^3.32.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/interactions": { + "version": "3.26.0", + "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.26.0.tgz", + "integrity": "sha512-AAEcHiltjfbmP1i9iaVw34Mb7kbkiHpYdqieWufldh4aplWgsF11YQZOfaCJW4QoR2ML4Zzoa9nfFwLXA52R7Q==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-aria/utils": "^3.32.0", + "@react-stately/flags": "^3.1.2", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/ssr": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz", + "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.32.0.tgz", + "integrity": "sha512-/7Rud06+HVBIlTwmwmJa2W8xVtgxgzm0+kLbuFooZRzKDON6hhozS1dOMR/YLMxyJOaYOTpImcP4vRR9gL1hEg==", + "license": "Apache-2.0", + "dependencies": { + "@react-aria/ssr": "^3.9.10", + "@react-stately/flags": "^3.1.2", + "@react-stately/utils": "^3.11.0", + "@react-types/shared": "^3.32.1", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-stately/flags": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz", + "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.11.0", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.11.0.tgz", + "integrity": "sha512-8LZpYowJ9eZmmYLpudbo/eclIRnbhWIJZ994ncmlKlouNzKohtM8qTC6B1w1pwUbiwGdUoyzLuQbeaIor5Dvcw==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@react-types/shared": { + "version": "3.32.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.1.tgz", + "integrity": "sha512-famxyD5emrGGpFuUlgOP6fVW2h/ZaF405G5KDi3zPHzyjAWys/8W6NAVJtNbkCkhedmvL0xOhvt8feGXyXaw5w==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.53", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.53.tgz", + "integrity": "sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==", + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz", + "integrity": "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.55.1.tgz", + "integrity": "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.55.1.tgz", + "integrity": "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.55.1.tgz", + "integrity": "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.55.1.tgz", + "integrity": "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.55.1.tgz", + "integrity": "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.55.1.tgz", + "integrity": "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.55.1.tgz", + "integrity": "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.55.1.tgz", + "integrity": "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.55.1.tgz", + "integrity": "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.55.1.tgz", + "integrity": "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.55.1.tgz", + "integrity": "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.55.1.tgz", + "integrity": "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.55.1.tgz", + "integrity": "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.55.1.tgz", + "integrity": "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.55.1.tgz", + "integrity": "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.55.1.tgz", + "integrity": "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.5.tgz", + "integrity": "sha512-Dq1bqBdLaZ1Gb/l2e5/+o3B18+8TI9ANlA1SkejZqDgdU/jK/ThYaMPMJpVMMXy2uRHvGKbkz9vheVGdq3cJfA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.55.1.tgz", + "integrity": "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.55.1", "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.55.1.tgz", "integrity": "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==", "cpu": [ @@ -1274,820 +3607,1602 @@ "license": "MIT", "optional": true, "os": [ - "openbsd" - ] + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", + "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", + "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", + "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", + "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.5.tgz", + "integrity": "sha512-1q+mykKE3Vot1kaFJIDoUFv5TuW+QQVaf2FmTT9krg86pQrGStOSJJ0Zil7CFagyxDuouTepzt5Y5TVzyajOdQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.21.0.tgz", + "integrity": "sha512-AXSQu/2n1UIQekY8euBJlvFYZIw0PHY63jUzGbrOma4wPxzznJXTXkri+QcHeBNaFxiiOljKxxJkVSoB3PjbyA==", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.5" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.21.0.tgz", + "integrity": "sha512-ATwv86xlbmfD9n9gKRiwuPpWgPENAWCLwYCGz9ugTJlsO2kOzhOkvoyV/UD+tJ0uT7YRyD530x6ugNSffmvIiQ==", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^4.3.4" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.21.0.tgz", + "integrity": "sha512-OYknTCct6qiwpQDqDdf3iedRdzj6hFlOPv5hMvI+hkWfCKs5mlJ4TXziBG9nyabLwGulrUjHiCq3xCspSzErYQ==", + "dependencies": { + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.21.0.tgz", + "integrity": "sha512-g6mn5m+Y6GBJ4wxmBYqalK9Sp0CFkUqfNzUy2pJglUginz6ZpWbaWjDB4fbQ/8SHzFjYbtU6Ddlp1pc+PPNDVA==", + "dependencies": { + "@shikijs/types": "3.21.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.21.0.tgz", + "integrity": "sha512-BAE4cr9EDiZyYzwIHEk7JTBJ9CzlPuM4PchfcA5ao1dWXb25nv6hYsoDiBq2aZK9E3dlt3WB78uI96UESD+8Mw==", + "dependencies": { + "@shikijs/types": "3.21.0" + } + }, + "node_modules/@shikijs/types": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.21.0.tgz", + "integrity": "sha512-zGrWOxZ0/+0ovPY7PvBU2gIS9tmhSUUt30jAcNV0Bq0gb2S98gwfjIs1vxlmH5zM7/4YxLamT6ChlqqAJmPPjA==", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==" + }, + "node_modules/@swc/helpers": { + "version": "0.5.18", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", + "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", + "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "tailwindcss": "4.1.18" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6 || ^7" + } + }, + "node_modules/@tanstack/react-virtual": { + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.18.tgz", + "integrity": "sha512-dZkhyfahpvlaV0rIKnvQiVoWPyURppl6w4m9IwMDpuIjcJ1sD9YGWrt0wISvgU7ewACXx2Ct46WPgI6qAD4v6A==", + "license": "MIT", + "dependencies": { + "@tanstack/virtual-core": "3.13.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tanstack/virtual-core": { + "version": "3.13.18", + "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.18.tgz", + "integrity": "sha512-Mx86Hqu1k39icq2Zusq+Ey2J6dDWTjDvEv43PJtRCoEYTLyfaPnxIQ6iy7YAOK0NV/qOEmZQ/uCufrppZxTgcg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "node_modules/@types/node": { + "version": "22.19.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.5.tgz", + "integrity": "sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", + "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", + "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/type-utils": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.53.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz", + "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz", + "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.53.0", + "@typescript-eslint/types": "^8.53.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", + "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.55.1.tgz", - "integrity": "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==", - "cpu": [ - "arm64" - ], + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz", + "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.55.1.tgz", - "integrity": "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==", - "cpu": [ - "arm64" - ], + "node_modules/@typescript-eslint/type-utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz", + "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0", + "@typescript-eslint/utils": "8.53.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.55.1.tgz", - "integrity": "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==", - "cpu": [ - "ia32" - ], + "node_modules/@typescript-eslint/types": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", + "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.55.1.tgz", - "integrity": "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", + "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "@typescript-eslint/project-service": "8.53.0", + "@typescript-eslint/tsconfig-utils": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/visitor-keys": "8.53.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.55.1", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", - "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", - "cpu": [ - "x64" - ], + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "dependencies": { + "balanced-match": "^1.0.0" + } }, - "node_modules/@tailwindcss/node": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", - "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz", + "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==", + "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/remapping": "^2.3.4", - "enhanced-resolve": "^5.18.3", - "jiti": "^2.6.1", - "lightningcss": "1.30.2", - "magic-string": "^0.30.21", - "source-map-js": "^1.2.1", - "tailwindcss": "4.1.18" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.53.0", + "@typescript-eslint/types": "8.53.0", + "@typescript-eslint/typescript-estree": "8.53.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@tailwindcss/oxide": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", - "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", + "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", + "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.53.0", + "eslint-visitor-keys": "^4.2.1" + }, "engines": { - "node": ">= 10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-arm64": "4.1.18", - "@tailwindcss/oxide-darwin-x64": "4.1.18", - "@tailwindcss/oxide-freebsd-x64": "4.1.18", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", - "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", - "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", - "@tailwindcss/oxide-linux-x64-musl": "4.1.18", - "@tailwindcss/oxide-wasm32-wasi": "4.1.18", - "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", - "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, + "node_modules/@vercel/oidc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", + "integrity": "sha512-Fw28YZpRnA3cAHHDlkt7xQHiJ0fcL+NRcIqsocZQUSmbzeIKRpwttJjik5ZGanXP+vlA4SbTg+AbA3bP363l+w==", + "engines": { + "node": ">= 20" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", + "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.5", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.53", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.18.0" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", - "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", - "cpu": [ - "arm64" - ], + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">= 10" + "node": ">=0.4.0" } }, - "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", - "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", - "cpu": [ - "arm64" - ], + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ai": { + "version": "5.0.121", + "resolved": "https://registry.npmjs.org/ai/-/ai-5.0.121.tgz", + "integrity": "sha512-3iYPdARKGLryC/7OA9RgBUaym1gynvWS7UPy8NwoRNCKP52lshldtHB5xcEfVviw7liWH2zJlW9yEzsDglcIEQ==", + "dependencies": { + "@ai-sdk/gateway": "2.0.27", + "@ai-sdk/provider": "2.0.1", + "@ai-sdk/provider-utils": "3.0.20", + "@opentelemetry/api": "1.9.0" + }, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "peerDependencies": { + "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", - "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", - "cpu": [ - "x64" - ], + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", - "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", - "cpu": [ - "x64" - ], + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", - "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", - "cpu": [ - "arm" - ], + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 10" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", - "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", - "cpu": [ - "arm64" - ], + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "tslib": "^2.0.0" + }, "engines": { - "node": ">= 10" + "node": ">=10" } }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", - "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", - "cpu": [ - "arm64" - ], + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", - "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", - "cpu": [ - "x64" - ], + "node_modules/array-includes": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", + "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.0", + "es-object-atoms": "^1.1.1", + "get-intrinsic": "^1.3.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", - "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", - "cpu": [ - "x64" - ], + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", - "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", - "bundleDependencies": [ - "@napi-rs/wasm-runtime", - "@emnapi/core", - "@emnapi/runtime", - "@tybys/wasm-util", - "@emnapi/wasi-threads", - "tslib" - ], - "cpu": [ - "wasm32" - ], + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1", - "@emnapi/wasi-threads": "^1.1.0", - "@napi-rs/wasm-runtime": "^1.1.0", - "@tybys/wasm-util": "^0.10.1", - "tslib": "^2.4.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", - "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", - "cpu": [ - "arm64" - ], + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", - "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", - "cpu": [ - "x64" - ], + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, "engines": { - "node": ">= 10" + "node": ">= 0.4" } }, - "node_modules/@tailwindcss/vite": { - "version": "4.1.18", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.1.18.tgz", - "integrity": "sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.1.18", - "@tailwindcss/oxide": "4.1.18", - "tailwindcss": "4.1.18" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, - "peerDependencies": { - "vite": "^5.2.0 || ^6 || ^7" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "license": "MIT", + "node_modules/assistant-cloud": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/assistant-cloud/-/assistant-cloud-0.1.12.tgz", + "integrity": "sha512-A2tY6QIdP9+RkE8Mmpm4kAoO0NyKsKpJKYebbYFZ3bAnQKyB15Bw/PS9AovpdeziGU9At97TyiMrT36pDjCD7A==", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "assistant-stream": "^0.2.46" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "license": "MIT", + "node_modules/assistant-stream": { + "version": "0.2.46", + "resolved": "https://registry.npmjs.org/assistant-stream/-/assistant-stream-0.2.46.tgz", + "integrity": "sha512-smcC4sqOcTrUO01YpiHPgdG3Wc57kmQlCIEdMXSNuWMgcDvo60hnRY3rPDhZQBJHZOXQ9Q1wLR8ugKDjxi72GQ==", "dependencies": { - "@babel/types": "^7.0.0" + "@standard-schema/spec": "^1.1.0", + "nanoid": "5.1.6", + "secure-json-parse": "^4.1.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "node_modules/assistant-stream/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" + "engines": { + "node": ">= 0.4" } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "license": "MIT" - }, - "node_modules/@types/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-RDvF6wTulMPjrNdCoYRC8gNR880JNGT8uB+REUpC2Ns4pRqQJhGz90wh7rgdXDPpCczF3VGktDuFGVnz8zP7HA==", - "license": "MIT" - }, - "node_modules/@types/lodash-es": { - "version": "4.17.12", - "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", - "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", "license": "MIT", "dependencies": { - "@types/lodash": "*" + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@types/node": { - "version": "22.19.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.5.tgz", - "integrity": "sha512-HfF8+mYcHPcPypui3w3mvzuIErlNOh2OAG+BCeBZCEwyiD5ls2SiCwEyT47OELtf7M3nHxBdu0FsmzdKxkN52Q==", - "devOptional": true, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@types/react": { - "version": "19.2.8", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.8.tgz", - "integrity": "sha512-3MbSL37jEchWZz2p2mjntRZtPt837ij10ApxKfgmXCTuHWagYg7iA5bqPw6C8BMPfwidlvfPI/fxOc42HLhcyg==", + "node_modules/babel-plugin-react-compiler": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", + "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", + "dev": true, "license": "MIT", "dependencies": { - "csstype": "^3.2.2" + "@babel/types": "^7.26.0" } }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.0.tgz", - "integrity": "sha512-eEXsVvLPu8Z4PkFibtuFJLJOTAV/nPdgtSjkGoPpddpFk3/ym2oy97jynY6ic2m6+nc5M8SE1e9v/mHKsulcJg==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.14", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", + "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.0", - "@typescript-eslint/type-utils": "8.53.0", - "@typescript-eslint/utils": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "bin": { + "browserslist": "cli.js" }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.53.0", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", "engines": { - "node": ">= 4" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/@typescript-eslint/parser": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.0.tgz", - "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==", + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "debug": "^4.4.3" + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.0.tgz", - "integrity": "sha512-Bl6Gdr7NqkqIP5yP9z1JU///Nmes4Eose6L1HwpuVHwScgDPPuEWbUVhvlZmb8hy0vX9syLk5EGNL700WcBlbg==", - "dev": true, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.0", - "@typescript-eslint/types": "^8.53.0", - "debug": "^4.4.3" + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "node": ">= 0.4" } }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.0.tgz", - "integrity": "sha512-kWNj3l01eOGSdVBnfAF2K1BTh06WS0Yet6JUgb9Cmkqaz3Jlu0fdVUjj9UI8gPidBWSMqDIglmEXifSgDT/D0g==", - "dev": true, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.0.tgz", - "integrity": "sha512-K6Sc0R5GIG6dNoPdOooQ+KtvT5KCKAvTcY8h2rIuul19vxH5OTQk7ArKkd4yTzkw66WnNY0kPPzzcmWA+XRmiA==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001764", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", + "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.0.tgz", - "integrity": "sha512-BBAUhlx7g4SmcLhn8cnbxoxtmS7hcq39xKCgiutL3oNx1TaIp+cny51s8ewnKMpVUKQUGb41RAUWZ9kxYdovuw==", - "dev": true, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0", - "@typescript-eslint/utils": "8.53.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@typescript-eslint/types": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.0.tgz", - "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==", - "dev": true, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "has-flag": "^4.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.0.tgz", - "integrity": "sha512-pw0c0Gdo7Z4xOG987u3nJ8akL9093yEEKv8QTJ+Bhkghj1xyj8cgPaavlr9rq8h7+s6plUJ4QJYw2gCZodqmGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.53.0", - "@typescript-eslint/tsconfig-utils": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/visitor-keys": "8.53.0", - "debug": "^4.4.3", - "minimatch": "^9.0.5", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, - "license": "ISC", + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" + "clsx": "^2.1.1" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://polar.sh/cva" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", - "dev": true, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=10" + "node": ">=12" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.0.tgz", - "integrity": "sha512-XDY4mXTez3Z1iRDI5mbRhH4DFSt46oaIFsLg+Zn97+sYrXACziXSQcSelMybnVZ5pa1P6xYkPr5cMJyunM1ZDA==", - "dev": true, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.0", - "@typescript-eslint/types": "8.53.0", - "@typescript-eslint/typescript-estree": "8.53.0" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "node": ">=6" } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.0.tgz", - "integrity": "sha512-LZ2NqIHFhvFwxG0qZeLL9DvdNAHPGCY5dIRwBhyYeU+LfLhcStE1ImjsuTG/WaVh3XysGaeLW8Rqq7cGkPCFvw==", - "dev": true, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.0", - "eslint-visitor-keys": "^4.2.1" + "color-name": "~1.1.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">=7.0.0" } }, - "node_modules/@vitejs/plugin-react": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.1.2.tgz", - "integrity": "sha512-EcA07pHJouywpzsoTUqNh5NwGayl2PPVEJKUSinGGSxFGYn+shYbqMGBg6FXDqgXum9Ou/ecb+411ssw8HImJQ==", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "license": "MIT", "dependencies": { - "@babel/core": "^7.28.5", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.53", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "node": ">= 0.8" } }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, + "license": "MIT" + }, + "node_modules/concurrently": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", + "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.3", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, "bin": { - "acorn": "bin/acorn" + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" } }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 8" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/array-buffer-byte-length": { + "node_modules/data-view-byte-length": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -2096,38 +5211,52 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" + "ms": "^2.1.3" }, "engines": { - "node": ">= 0.4" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decode-named-character-reference": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz", + "integrity": "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==", + "dependencies": { + "character-entities": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -2136,17 +5265,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -2155,37 +5283,163 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", + "license": "MIT" + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" + "gopd": "^1.2.0" }, "engines": { "node": ">= 0.4" } }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-abstract": { + "version": "1.24.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", + "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.1", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" }, "engines": { "node": ">= 0.4" @@ -2194,130 +5448,102 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", - "dev": true, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { "node": ">= 0.4" } }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/axios": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", - "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "node_modules/es-iterator-helpers": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", + "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", + "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.1", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "safe-array-concat": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/babel-plugin-react-compiler": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", - "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", - "dev": true, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.14", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.14.tgz", - "integrity": "sha512-B0xUquLkiGLgHhpPBqvl7GWegWBUNuujQ6kXd/r1U38ElPT6Ok8KZ8e+FpUGEc2ZoRQUzq/aUnaKFc/svWUGSg==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" } }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" + "hasown": "^2.0.2" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">= 0.4" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "node_modules/es-to-primitive": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -2326,322 +5552,431 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "node_modules/esbuild": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">= 0.4" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", - "engines": { - "node": ">=6" + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001764", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001764.tgz", - "integrity": "sha512-9JGuzl2M+vPL+pz70gtMF9sHdMFbY9FJaQBi186cHKH3pSzDvzoUJUPV6fqiKIMyXbud9ZLg4F3Yza1vJ1+93g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/eslint-plugin-react-hooks": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", + "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=18" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "license": "MIT", + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "has-flag": "^4.0.0" + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/class-variance-authority": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", - "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "clsx": "^2.1.1" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://polar.sh/cva" + "url": "https://opencollective.com/eslint" } }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "license": "ISC", + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">=12" - } - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", - "license": "MIT", - "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "color-name": "~1.1.4" + "estraverse": "^5.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=0.10" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "delayed-stream": "~1.0.0" + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.8" + "node": ">=4.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "license": "MIT" - }, - "node_modules/concurrently": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", - "license": "MIT", - "dependencies": { - "chalk": "4.1.2", - "rxjs": "7.8.2", - "shell-quote": "1.8.3", - "supports-color": "8.1.1", - "tree-kill": "1.2.2", - "yargs": "17.7.2" - }, - "bin": { - "conc": "dist/bin/concurrently.js", - "concurrently": "dist/bin/concurrently.js" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + "node": ">=4.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "node_modules/eventsource-parser": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, "license": "MIT" }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=12.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" + "node": ">=16.0.0" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "flatted": "^3.2.9", + "keyv": "^4.5.4" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=16" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", "dev": true, - "license": "MIT" + "license": "ISC" }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, "engines": { - "node": ">= 0.4" + "node": ">=4.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -2650,645 +5985,620 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/framer-motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.26.2.tgz", + "integrity": "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA==", "dependencies": { - "esutils": "^2.0.2" + "motion-dom": "^12.26.2", + "motion-utils": "^12.24.10", + "tslib": "^2.4.0" }, - "engines": { - "node": ">=0.10.0" + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } } }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/electron-to-chromium": { - "version": "1.5.267", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", - "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/enhanced-resolve": { - "version": "5.18.4", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", - "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "node": ">=10.13.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-abstract": { - "version": "1.24.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz", - "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==", + "node_modules/function.prototype.name": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=6.9.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", "engines": { - "node": ">= 0.4" + "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/es-iterator-helpers": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.2.tgz", - "integrity": "sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.1", + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.1.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-intrinsic": "^1.3.0", - "globalthis": "^1.0.4", + "get-proto": "^1.0.1", "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.5", - "safe-array-concat": "^1.1.3" + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, "engines": { - "node": ">= 0.4" + "node": ">=6" } }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" } }, - "node_modules/es-shim-unscopables": { + "node_modules/get-symbol-description": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10.13.0" } }, - "node_modules/esbuild": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", - "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", - "hasInstallScript": true, + "node_modules/globals": { + "version": "15.15.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", + "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, "engines": { "node": ">=18" }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.2", - "@esbuild/android-arm": "0.27.2", - "@esbuild/android-arm64": "0.27.2", - "@esbuild/android-x64": "0.27.2", - "@esbuild/darwin-arm64": "0.27.2", - "@esbuild/darwin-x64": "0.27.2", - "@esbuild/freebsd-arm64": "0.27.2", - "@esbuild/freebsd-x64": "0.27.2", - "@esbuild/linux-arm": "0.27.2", - "@esbuild/linux-arm64": "0.27.2", - "@esbuild/linux-ia32": "0.27.2", - "@esbuild/linux-loong64": "0.27.2", - "@esbuild/linux-mips64el": "0.27.2", - "@esbuild/linux-ppc64": "0.27.2", - "@esbuild/linux-riscv64": "0.27.2", - "@esbuild/linux-s390x": "0.27.2", - "@esbuild/linux-x64": "0.27.2", - "@esbuild/netbsd-arm64": "0.27.2", - "@esbuild/netbsd-x64": "0.27.2", - "@esbuild/openbsd-arm64": "0.27.2", - "@esbuild/openbsd-x64": "0.27.2", - "@esbuild/openharmony-arm64": "0.27.2", - "@esbuild/sunos-x64": "0.27.2", - "@esbuild/win32-arm64": "0.27.2", - "@esbuild/win32-ia32": "0.27.2", - "@esbuild/win32-x64": "0.27.2" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", - "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.2", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.12.4", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", - "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" + "dependencies": { + "es-define-property": "^1.0.0" }, "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, "license": "MIT", "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" + "dunder-proto": "^1.0.0" }, "engines": { - "node": ">=4" + "node": ">= 0.4" }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", - "dev": true, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 0.4" }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "has-symbols": "^1.0.3" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@types/hast": "^3.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" - }, + "hermes-estree": "0.25.1" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10" + "node": ">= 4" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "estraverse": "^5.2.0" + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" }, "engines": { - "node": ">=4.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">=0.8.19" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/inline-style-parser": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", + "integrity": "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==" + }, + "node_modules/input-otp": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/input-otp/-/input-otp-1.4.2.tgz", + "integrity": "sha512-l3jWwYNvrEa6NTCt7BECfCm48GvwuZzkoeG3gBL2w4CHeOXW3eKFmf9UNYkNfYc3mxMrthMnxjIE07MT0zLBQA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0.0 || ^19.0.0-rc" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { - "node": ">=16.0.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "has-bigints": "^1.0.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", "dev": true, "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" }, "engines": { - "node": ">=16" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "license": "ISC" - }, - "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.2.7" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -3297,58 +6607,33 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3357,60 +6642,62 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=0.10.0" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { - "node": ">=6.9.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=8" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -3419,29 +6706,63 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" + "has-tostringtag": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -3450,40 +6771,28 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "15.15.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz", - "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==", - "license": "MIT", + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "engines": { - "node": ">=18" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "dev": true, "license": "MIT", "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -3492,10 +6801,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3504,18 +6814,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -3523,36 +6830,49 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0" + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "license": "MIT", "dependencies": { - "dunder-proto": "^1.0.0" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -3561,10 +6881,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3573,13 +6894,14 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, "license": "MIT", "dependencies": { - "has-symbols": "^1.0.3" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -3588,953 +6910,1347 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, "engines": { - "node": ">= 4" + "node": ">= 0.4" } }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" }, "engines": { "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", "dev": true, "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, "engines": { - "node": ">=0.8.19" + "node": ">=4.0" } }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" + "json-buffer": "3.0.1" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", - "dev": true, + "node_modules/laravel-precognition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-1.0.0.tgz", + "integrity": "sha512-hvXPT7dayCQAidxnsY0hab9Q+Y2rsh7xRpH9uiFtXN8Dekc3tIZt+NrxrOZ9N5SwHBmRBze/Bv+ElfXac0kD6g==", "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "axios": "^1.4.0", + "lodash-es": "^4.17.21" } }, - "node_modules/is-async-function": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", - "dev": true, + "node_modules/laravel-vite-plugin": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", + "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", "license": "MIT", "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "picocolors": "^1.0.0", + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "vite": "^7.0.0" } }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { - "has-bigints": "^1.0.2" + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8.0" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", - "dev": true, - "license": "MIT", + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "license": "MPL-2.0", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "detect-libc": "^2.0.3" }, "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" + "node": ">= 12.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=8" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=0.10.0" + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.4" + "node": ">= 12.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "p-locate": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "node_modules/lodash-es": { + "version": "4.17.22", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", + "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, + "license": "MIT" + }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" + "js-tokens": "^3.0.0 || ^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "bin": { + "loose-envify": "cli.js" } }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/lucide-react": { + "version": "0.475.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.475.0.tgz", + "integrity": "sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", "dependencies": { - "which-typed-array": "^1.1.16" + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-find-and-replace/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-weakmap": { + "node_modules/mdast-util-from-markdown": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/jiti": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", - "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", - "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", "dependencies": { - "argparse": "^2.0.1" + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "license": "MIT", - "bin": { - "json5": "lib/cli.js" + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=6" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" }, - "engines": { - "node": ">=4.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/laravel-precognition": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/laravel-precognition/-/laravel-precognition-1.0.0.tgz", - "integrity": "sha512-hvXPT7dayCQAidxnsY0hab9Q+Y2rsh7xRpH9uiFtXN8Dekc3tIZt+NrxrOZ9N5SwHBmRBze/Bv+ElfXac0kD6g==", - "license": "MIT", + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", "dependencies": { - "axios": "^1.4.0", - "lodash-es": "^4.17.21" + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/laravel-vite-plugin": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-2.0.1.tgz", - "integrity": "sha512-zQuvzWfUKQu9oNVi1o0RZAJCwhGsdhx4NEOyrVQwJHaWDseGP9tl7XUPLY2T8Cj6+IrZ6lmyxlR1KC8unf3RLA==", - "license": "MIT", + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", "dependencies": { - "picocolors": "^1.0.0", - "vite-plugin-full-reload": "^1.1.0" + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" }, - "bin": { - "clean-orphaned-assets": "bin/clean.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^7.0.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, - "engines": { - "node": ">= 0.8.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", - "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", - "license": "MPL-2.0", + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.30.2", - "lightningcss-darwin-arm64": "1.30.2", - "lightningcss-darwin-x64": "1.30.2", - "lightningcss-freebsd-x64": "1.30.2", - "lightningcss-linux-arm-gnueabihf": "1.30.2", - "lightningcss-linux-arm64-gnu": "1.30.2", - "lightningcss-linux-arm64-musl": "1.30.2", - "lightningcss-linux-x64-gnu": "1.30.2", - "lightningcss-linux-x64-musl": "1.30.2", - "lightningcss-win32-arm64-msvc": "1.30.2", - "lightningcss-win32-x64-msvc": "1.30.2" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", - "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "dependencies": { + "@types/mdast": "^4.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", - "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", - "cpu": [ - "arm64" + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", - "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", - "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", - "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", - "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", - "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "dependencies": { + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", - "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://opencollective.com/unified" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", - "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", - "cpu": [ - "x64" + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", - "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", - "cpu": [ - "arm64" + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.30.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", - "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", - "cpu": [ - "x64" + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "dependencies": { + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lodash-es": { - "version": "4.17.22", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.22.tgz", - "integrity": "sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==", - "license": "MIT" + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "license": "ISC", + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "yallist": "^3.0.2" + "micromark-util-types": "^2.0.0" } }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "license": "MIT", + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -4569,6 +8285,44 @@ "node": "*" } }, + "node_modules/motion": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.26.2.tgz", + "integrity": "sha512-2Q6g0zK1gUJKhGT742DAe42LgietcdiJ3L3OcYAHCQaC1UkLnn6aC8S/obe4CxYTLAgid2asS1QdQ/blYfo5dw==", + "dependencies": { + "framer-motion": "^12.26.2", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.26.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.26.2.tgz", + "integrity": "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw==", + "dependencies": { + "motion-utils": "^12.24.10" + } + }, + "node_modules/motion-utils": { + "version": "12.24.10", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.24.10.tgz", + "integrity": "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww==" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -4713,6 +8467,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oniguruma-parser": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/oniguruma-parser/-/oniguruma-parser-0.12.1.tgz", + "integrity": "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==" + }, + "node_modules/oniguruma-to-es": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-4.3.4.tgz", + "integrity": "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==", + "dependencies": { + "oniguruma-parser": "^0.12.1", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4794,6 +8563,29 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==" + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -5019,6 +8811,15 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-from-env": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", @@ -5078,13 +8879,150 @@ "dev": true, "license": "MIT" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-refresh": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=0.10.0" + } + }, + "node_modules/react-remove-scroll": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", + "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.7", + "react-style-singleton": "^2.2.3", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.3", + "use-sidecar": "^1.1.3" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", + "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", + "license": "MIT", + "dependencies": { + "react-style-singleton": "^2.2.2", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-shiki": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/react-shiki/-/react-shiki-0.9.1.tgz", + "integrity": "sha512-Ln1PnISi7WaSlheSBRdxVruVbU1zMUkCmxe+vmbIvZSsHdfvOF5NBOgf1h4cCr6OjdR0dLAxmPKcx3tobdyxVA==", + "dependencies": { + "clsx": "^2.1.1", + "dequal": "^2.0.3", + "hast-util-to-jsx-runtime": "^2.3.6", + "shiki": "^3.11.0", + "unist-util-visit": "^5.0.0" + }, + "peerDependencies": { + "@types/react": ">=16.8.0", + "@types/react-dom": ">=16.8.0", + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", + "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", + "license": "MIT", + "dependencies": { + "get-nonce": "^1.0.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-textarea-autosize": { + "version": "8.5.9", + "resolved": "https://registry.npmjs.org/react-textarea-autosize/-/react-textarea-autosize-8.5.9.tgz", + "integrity": "sha512-U1DGlIQN5AwgjTyOEnI1oCcMuEr1pv1qOtklB2l4nyMGbHzWrI0eFsYK0zos2YWqAolJyG0IWJaqWmWj5ETh0A==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "use-composed-ref": "^1.3.0", + "use-latest": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/reflect.getprototypeof": { @@ -5110,6 +9048,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", @@ -5131,6 +9090,68 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -5225,6 +9246,19 @@ "linux" ] }, + "node_modules/rollup/node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.55.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.55.1.tgz", + "integrity": "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/rxjs": { "version": "7.8.2", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", @@ -5295,6 +9329,21 @@ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ] + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5388,6 +9437,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/shiki": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.21.0.tgz", + "integrity": "sha512-N65B/3bqL/TI2crrXr+4UivctrAGEjmsib5rPMMPpFp1xAx/w03v8WZ9RDDFYteXoEgY7qZ4HGgl5KBIu1153w==", + "dependencies": { + "@shikijs/core": "3.21.0", + "@shikijs/engine-javascript": "3.21.0", + "@shikijs/engine-oniguruma": "3.21.0", + "@shikijs/langs": "3.21.0", + "@shikijs/themes": "3.21.0", + "@shikijs/types": "3.21.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -5469,6 +9533,15 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -5595,6 +9668,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5620,6 +9706,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.21.tgz", + "integrity": "sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==", + "dependencies": { + "style-to-object": "1.0.14" + } + }, + "node_modules/style-to-object": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz", + "integrity": "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==", + "dependencies": { + "inline-style-parser": "0.2.7" + } + }, "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", @@ -5648,6 +9750,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.8.tgz", + "integrity": "sha512-gaCPRVoMq8WGDcWj9p4YWzCMPHzE0WNl6W8ADIx9c3JBEIdMkJGMzW+uzXvxHMltwcYACr9jP+32H8/hgwMR7w==", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", @@ -5677,6 +9797,17 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/throttleit": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-2.1.0.tgz", + "integrity": "sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -5702,6 +9833,24 @@ "tree-kill": "cli.js" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", @@ -5721,6 +9870,15 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5875,6 +10033,87 @@ "devOptional": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -5915,6 +10154,126 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", + "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-composed-ref": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/use-composed-ref/-/use-composed-ref-1.4.0.tgz", + "integrity": "sha512-djviaxuOOh7wkj0paeO1Q/4wMZ8Zrnag5H6yBvzN7AKKe8beOaED9SF5/ByLqsku8NP4zQqsvM2u3ew/tJK8/w==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-isomorphic-layout-effect": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.2.1.tgz", + "integrity": "sha512-tpZZ+EX0gaghDAiFR37hj5MgY6ZN55kLiPkJsKxBMZ6GZdOSPJXiOzPM984oPYZ5AnehYx5WQp1+ME8I/P/pRA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-latest": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/use-latest/-/use-latest-1.3.0.tgz", + "integrity": "sha512-mhg3xdm9NaM8q+gLT8KryJPnRFOz1/5XPBhmDEVZK1webPzDjrPk7f/mbpeLqTgB9msytYWANxgALOCJKnLvcQ==", + "dependencies": { + "use-isomorphic-layout-effect": "^1.1.1" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", + "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", + "license": "MIT", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "7.3.1", "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", @@ -6197,6 +10556,65 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.5.tgz", + "integrity": "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } + }, + "node_modules/zustand": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.10.tgz", + "integrity": "sha512-U1AiltS1O9hSy3rul+Ub82ut2fqIAefiSuwECWt6jlMVUGejvf+5omLcRBSzqbRagSM3hQZbtzdeRc6QVScXTg==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/package.json b/package.json index 8a7358d..eeb77d5 100644 --- a/package.json +++ b/package.json @@ -19,14 +19,31 @@ "eslint": "^9.17.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-react": "^7.37.3", - "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-hooks": "^7.0.0", "prettier": "^3.4.2", "prettier-plugin-organize-imports": "^4.1.0", "prettier-plugin-tailwindcss": "^0.6.11", "typescript-eslint": "^8.23.0" }, "dependencies": { + "@assistant-ui/react": "^0.11.53", + "@assistant-ui/react-ai-sdk": "^1.1.20", + "@assistant-ui/react-markdown": "^0.11.9", + "@headlessui/react": "^2.2.0", "@inertiajs/react": "^2.3.7", + "@radix-ui/react-avatar": "^1.1.3", + "@radix-ui/react-checkbox": "^1.1.4", + "@radix-ui/react-collapsible": "^1.1.3", + "@radix-ui/react-dialog": "^1.1.6", + "@radix-ui/react-dropdown-menu": "^2.1.6", + "@radix-ui/react-label": "^2.1.2", + "@radix-ui/react-navigation-menu": "^1.2.5", + "@radix-ui/react-select": "^2.1.6", + "@radix-ui/react-separator": "^1.1.2", + "@radix-ui/react-slot": "^1.2.3", + "@radix-ui/react-toggle": "^1.1.2", + "@radix-ui/react-toggle-group": "^1.1.2", + "@radix-ui/react-tooltip": "^1.1.8", "@tailwindcss/vite": "^4.1.11", "@types/react": "^19.2.0", "@types/react-dom": "^19.2.0", @@ -34,18 +51,29 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "concurrently": "^9.0.1", + "framer-motion": "^12.26.2", "globals": "^15.14.0", + "input-otp": "^1.4.2", "laravel-vite-plugin": "^2.0", + "lucide-react": "^0.475.0", + "motion": "^12.26.2", "react": "^19.2.0", "react-dom": "^19.2.0", + "react-shiki": "^0.9.1", + "remark-gfm": "^4.0.1", "tailwind-merge": "^3.0.1", "tailwindcss": "^4.0.0", + "tw-animate-css": "^1.4.0", "typescript": "^5.7.2", - "vite": "^7.0.4" + "vite": "^7.0.4", + "zustand": "^5.0.10" }, "optionalDependencies": { "@rollup/rollup-linux-x64-gnu": "4.9.5", + "@rollup/rollup-win32-x64-msvc": "4.9.5", "@tailwindcss/oxide-linux-x64-gnu": "^4.0.1", - "lightningcss-linux-x64-gnu": "^1.29.1" + "@tailwindcss/oxide-win32-x64-msvc": "^4.0.1", + "lightningcss-linux-x64-gnu": "^1.29.1", + "lightningcss-win32-x64-msvc": "^1.29.1" } } diff --git a/resources/css/app.css b/resources/css/app.css index df611af..7015d58 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -1,9 +1,143 @@ @import 'tailwindcss'; +@import 'tw-animate-css'; + @source '../views'; @source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php'; +@custom-variant dark (&:is(.dark *)); + @theme { - --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', - 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-sans: + 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; + + --radius-lg: var(--radius); + --radius-md: calc(var(--radius) - 2px); + --radius-sm: calc(var(--radius) - 4px); + + --color-background: var(--background); + --color-foreground: var(--foreground); + + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.87 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --radius: 0.625rem; + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.87 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.985 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.269 0 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.269 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.396 0.141 25.723); + --destructive-foreground: oklch(0.637 0.237 25.331); + --border: oklch(0.269 0 0); + --input: oklch(0.269 0 0); + --ring: oklch(0.439 0 0); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.985 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(0.269 0 0); + --sidebar-ring: oklch(0.439 0 0); +} + +@layer base { + * { + @apply border-border; + } + + body { + @apply bg-background text-foreground; + } } diff --git a/resources/js/actions/Cortex/Http/Controllers/AGUIController.ts b/resources/js/actions/Cortex/Http/Controllers/AGUIController.ts new file mode 100644 index 0000000..e5e6e3e --- /dev/null +++ b/resources/js/actions/Cortex/Http/Controllers/AGUIController.ts @@ -0,0 +1,36 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition } from './../../../../wayfinder' +/** +* @see \Cortex\Http\Controllers\AGUIController::__invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AGUIController.php:19 +* @route '/cortex/agui' +*/ +const AGUIController = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: AGUIController.url(options), + method: 'post', +}) + +AGUIController.definition = { + methods: ["post"], + url: '/cortex/agui', +} satisfies RouteDefinition<["post"]> + +/** +* @see \Cortex\Http\Controllers\AGUIController::__invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AGUIController.php:19 +* @route '/cortex/agui' +*/ +AGUIController.url = (options?: RouteQueryOptions) => { + return AGUIController.definition.url + queryParams(options) +} + +/** +* @see \Cortex\Http\Controllers\AGUIController::__invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AGUIController.php:19 +* @route '/cortex/agui' +*/ +AGUIController.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: AGUIController.url(options), + method: 'post', +}) + +export default AGUIController \ No newline at end of file diff --git a/resources/js/actions/Cortex/Http/Controllers/AgentsController.ts b/resources/js/actions/Cortex/Http/Controllers/AgentsController.ts new file mode 100644 index 0000000..45cc494 --- /dev/null +++ b/resources/js/actions/Cortex/Http/Controllers/AgentsController.ts @@ -0,0 +1,228 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, applyUrlDefaults } from './../../../../wayfinder' +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +export const invoke = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: invoke.url(args, options), + method: 'get', +}) + +invoke.definition = { + methods: ["get","head","post","put","patch","delete","options"], + url: '/cortex/agents/{agent}/invoke', +} satisfies RouteDefinition<["get","head","post","put","patch","delete","options"]> + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.url = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { agent: args } + } + + if (Array.isArray(args)) { + args = { + agent: args[0], + } + } + + args = applyUrlDefaults(args) + + const parsedArgs = { + agent: args.agent, + } + + return invoke.definition.url + .replace('{agent}', parsedArgs.agent.toString()) + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.get = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: invoke.url(args, options), + method: 'get', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.head = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: invoke.url(args, options), + method: 'head', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.post = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: invoke.url(args, options), + method: 'post', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.put = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'put'> => ({ + url: invoke.url(args, options), + method: 'put', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.patch = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'patch'> => ({ + url: invoke.url(args, options), + method: 'patch', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.delete = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'delete'> => ({ + url: invoke.url(args, options), + method: 'delete', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::invoke +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:22 +* @route '/cortex/agents/{agent}/invoke' +*/ +invoke.options = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'options'> => ({ + url: invoke.url(args, options), + method: 'options', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +export const stream = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: stream.url(args, options), + method: 'get', +}) + +stream.definition = { + methods: ["get","head","post","put","patch","delete","options"], + url: '/cortex/agents/{agent}/stream', +} satisfies RouteDefinition<["get","head","post","put","patch","delete","options"]> + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.url = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { agent: args } + } + + if (Array.isArray(args)) { + args = { + agent: args[0], + } + } + + args = applyUrlDefaults(args) + + const parsedArgs = { + agent: args.agent, + } + + return stream.definition.url + .replace('{agent}', parsedArgs.agent.toString()) + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.get = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: stream.url(args, options), + method: 'get', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.head = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: stream.url(args, options), + method: 'head', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.post = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: stream.url(args, options), + method: 'post', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.put = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'put'> => ({ + url: stream.url(args, options), + method: 'put', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.patch = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'patch'> => ({ + url: stream.url(args, options), + method: 'patch', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.delete = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'delete'> => ({ + url: stream.url(args, options), + method: 'delete', +}) + +/** +* @see \Cortex\Http\Controllers\AgentsController::stream +* @see Users/sean/Code/cortexphp/cortex/src/Http/Controllers/AgentsController.php:62 +* @route '/cortex/agents/{agent}/stream' +*/ +stream.options = (args: { agent: string | number } | [agent: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'options'> => ({ + url: stream.url(args, options), + method: 'options', +}) + +const AgentsController = { invoke, stream } + +export default AgentsController \ No newline at end of file diff --git a/resources/js/actions/Cortex/Http/Controllers/index.ts b/resources/js/actions/Cortex/Http/Controllers/index.ts new file mode 100644 index 0000000..c518e69 --- /dev/null +++ b/resources/js/actions/Cortex/Http/Controllers/index.ts @@ -0,0 +1,9 @@ +import AgentsController from './AgentsController' +import AGUIController from './AGUIController' + +const Controllers = { + AgentsController: Object.assign(AgentsController, AgentsController), + AGUIController: Object.assign(AGUIController, AGUIController), +} + +export default Controllers \ No newline at end of file diff --git a/resources/js/actions/Cortex/Http/index.ts b/resources/js/actions/Cortex/Http/index.ts new file mode 100644 index 0000000..ac9e00d --- /dev/null +++ b/resources/js/actions/Cortex/Http/index.ts @@ -0,0 +1,7 @@ +import Controllers from './Controllers' + +const Http = { + Controllers: Object.assign(Controllers, Controllers), +} + +export default Http \ No newline at end of file diff --git a/resources/js/actions/Cortex/index.ts b/resources/js/actions/Cortex/index.ts new file mode 100644 index 0000000..3e10e25 --- /dev/null +++ b/resources/js/actions/Cortex/index.ts @@ -0,0 +1,7 @@ +import Http from './Http' + +const Cortex = { + Http: Object.assign(Http, Http), +} + +export default Cortex \ No newline at end of file diff --git a/resources/js/actions/Orchestra/Workbench/Http/Controllers/WorkbenchController.ts b/resources/js/actions/Orchestra/Workbench/Http/Controllers/WorkbenchController.ts new file mode 100644 index 0000000..7db5b27 --- /dev/null +++ b/resources/js/actions/Orchestra/Workbench/Http/Controllers/WorkbenchController.ts @@ -0,0 +1,245 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, applyUrlDefaults, validateParameters } from './../../../../../wayfinder' +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::start +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:18 +* @route '/_workbench' +*/ +export const start = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: start.url(options), + method: 'get', +}) + +start.definition = { + methods: ["get","head"], + url: '/_workbench', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::start +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:18 +* @route '/_workbench' +*/ +start.url = (options?: RouteQueryOptions) => { + return start.definition.url + queryParams(options) +} + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::start +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:18 +* @route '/_workbench' +*/ +start.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: start.url(options), + method: 'get', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::start +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:18 +* @route '/_workbench' +*/ +start.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: start.url(options), + method: 'head', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::login +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:60 +* @route '/_workbench/login/{userId}/{guard?}' +*/ +export const login = (args: { userId: string | number, guard?: string | number } | [userId: string | number, guard: string | number ], options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: login.url(args, options), + method: 'get', +}) + +login.definition = { + methods: ["get","head"], + url: '/_workbench/login/{userId}/{guard?}', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::login +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:60 +* @route '/_workbench/login/{userId}/{guard?}' +*/ +login.url = (args: { userId: string | number, guard?: string | number } | [userId: string | number, guard: string | number ], options?: RouteQueryOptions) => { + if (Array.isArray(args)) { + args = { + userId: args[0], + guard: args[1], + } + } + + args = applyUrlDefaults(args) + + validateParameters(args, [ + "guard", + ]) + + const parsedArgs = { + userId: args.userId, + guard: args.guard, + } + + return login.definition.url + .replace('{userId}', parsedArgs.userId.toString()) + .replace('{guard?}', parsedArgs.guard?.toString() ?? '') + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::login +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:60 +* @route '/_workbench/login/{userId}/{guard?}' +*/ +login.get = (args: { userId: string | number, guard?: string | number } | [userId: string | number, guard: string | number ], options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: login.url(args, options), + method: 'get', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::login +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:60 +* @route '/_workbench/login/{userId}/{guard?}' +*/ +login.head = (args: { userId: string | number, guard?: string | number } | [userId: string | number, guard: string | number ], options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: login.url(args, options), + method: 'head', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::logout +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:84 +* @route '/_workbench/logout/{guard?}' +*/ +export const logout = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: logout.url(args, options), + method: 'get', +}) + +logout.definition = { + methods: ["get","head"], + url: '/_workbench/logout/{guard?}', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::logout +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:84 +* @route '/_workbench/logout/{guard?}' +*/ +logout.url = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { guard: args } + } + + if (Array.isArray(args)) { + args = { + guard: args[0], + } + } + + args = applyUrlDefaults(args) + + validateParameters(args, [ + "guard", + ]) + + const parsedArgs = { + guard: args?.guard, + } + + return logout.definition.url + .replace('{guard?}', parsedArgs.guard?.toString() ?? '') + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::logout +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:84 +* @route '/_workbench/logout/{guard?}' +*/ +logout.get = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: logout.url(args, options), + method: 'get', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::logout +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:84 +* @route '/_workbench/logout/{guard?}' +*/ +logout.head = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: logout.url(args, options), + method: 'head', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::user +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:39 +* @route '/_workbench/user/{guard?}' +*/ +export const user = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: user.url(args, options), + method: 'get', +}) + +user.definition = { + methods: ["get","head"], + url: '/_workbench/user/{guard?}', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::user +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:39 +* @route '/_workbench/user/{guard?}' +*/ +user.url = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { guard: args } + } + + if (Array.isArray(args)) { + args = { + guard: args[0], + } + } + + args = applyUrlDefaults(args) + + validateParameters(args, [ + "guard", + ]) + + const parsedArgs = { + guard: args?.guard, + } + + return user.definition.url + .replace('{guard?}', parsedArgs.guard?.toString() ?? '') + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::user +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:39 +* @route '/_workbench/user/{guard?}' +*/ +user.get = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: user.url(args, options), + method: 'get', +}) + +/** +* @see \Orchestra\Workbench\Http\Controllers\WorkbenchController::user +* @see Users/sean/Code/cortexphp/cortex/vendor/orchestra/workbench/src/Http/Controllers/WorkbenchController.php:39 +* @route '/_workbench/user/{guard?}' +*/ +user.head = (args?: { guard?: string | number } | [guard: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: user.url(args, options), + method: 'head', +}) + +const WorkbenchController = { start, login, logout, user } + +export default WorkbenchController \ No newline at end of file diff --git a/resources/js/actions/Orchestra/Workbench/Http/Controllers/index.ts b/resources/js/actions/Orchestra/Workbench/Http/Controllers/index.ts new file mode 100644 index 0000000..c779fe9 --- /dev/null +++ b/resources/js/actions/Orchestra/Workbench/Http/Controllers/index.ts @@ -0,0 +1,7 @@ +import WorkbenchController from './WorkbenchController' + +const Controllers = { + WorkbenchController: Object.assign(WorkbenchController, WorkbenchController), +} + +export default Controllers \ No newline at end of file diff --git a/resources/js/actions/Orchestra/Workbench/Http/index.ts b/resources/js/actions/Orchestra/Workbench/Http/index.ts new file mode 100644 index 0000000..ac9e00d --- /dev/null +++ b/resources/js/actions/Orchestra/Workbench/Http/index.ts @@ -0,0 +1,7 @@ +import Controllers from './Controllers' + +const Http = { + Controllers: Object.assign(Controllers, Controllers), +} + +export default Http \ No newline at end of file diff --git a/resources/js/actions/Orchestra/Workbench/index.ts b/resources/js/actions/Orchestra/Workbench/index.ts new file mode 100644 index 0000000..caaf2f7 --- /dev/null +++ b/resources/js/actions/Orchestra/Workbench/index.ts @@ -0,0 +1,7 @@ +import Http from './Http' + +const Workbench = { + Http: Object.assign(Http, Http), +} + +export default Workbench \ No newline at end of file diff --git a/resources/js/actions/Orchestra/index.ts b/resources/js/actions/Orchestra/index.ts new file mode 100644 index 0000000..88b0861 --- /dev/null +++ b/resources/js/actions/Orchestra/index.ts @@ -0,0 +1,7 @@ +import Workbench from './Workbench' + +const Orchestra = { + Workbench: Object.assign(Workbench, Workbench), +} + +export default Orchestra \ No newline at end of file diff --git a/resources/js/app.tsx b/resources/js/app.tsx index 895217a..1c402da 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -2,19 +2,32 @@ import '../css/app.css'; import { createInertiaApp } from '@inertiajs/react'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; +import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import { initializeTheme } from './hooks/use-appearance'; const appName = import.meta.env.VITE_APP_NAME || 'Cortex'; createInertiaApp({ - title: (title) => title ? `${title} - ${appName}` : appName, - resolve: (name) => resolvePageComponent(`./pages/${name}.tsx`, import.meta.glob('./pages/**/*.tsx')), + title: (title) => (title ? `${title} - ${appName}` : appName), + resolve: (name) => + resolvePageComponent( + `./pages/${name}.tsx`, + import.meta.glob('./pages/**/*.tsx'), + ), setup({ el, App, props }) { const root = createRoot(el); - root.render(); + root.render( + + + , + ); }, progress: { color: '#4B5563', }, }); + +// This will set light / dark mode on load... +initializeTheme(); diff --git a/resources/js/components/alert-error.tsx b/resources/js/components/alert-error.tsx new file mode 100644 index 0000000..8cc228b --- /dev/null +++ b/resources/js/components/alert-error.tsx @@ -0,0 +1,24 @@ +import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; +import { AlertCircleIcon } from 'lucide-react'; + +export default function AlertError({ + errors, + title, +}: { + errors: string[]; + title?: string; +}) { + return ( + + + {title || 'Something went wrong.'} + +
    + {Array.from(new Set(errors)).map((error, index) => ( +
  • {error}
  • + ))} +
+
+
+ ); +} diff --git a/resources/js/components/app-content.tsx b/resources/js/components/app-content.tsx new file mode 100644 index 0000000..995dcaa --- /dev/null +++ b/resources/js/components/app-content.tsx @@ -0,0 +1,25 @@ +import { SidebarInset } from '@/components/ui/sidebar'; +import * as React from 'react'; + +interface AppContentProps extends React.ComponentProps<'main'> { + variant?: 'header' | 'sidebar'; +} + +export function AppContent({ + variant = 'header', + children, + ...props +}: AppContentProps) { + if (variant === 'sidebar') { + return {children}; + } + + return ( +
+ {children} +
+ ); +} diff --git a/resources/js/components/app-header.tsx b/resources/js/components/app-header.tsx new file mode 100644 index 0000000..9766852 --- /dev/null +++ b/resources/js/components/app-header.tsx @@ -0,0 +1,261 @@ +import { Breadcrumbs } from '@/components/breadcrumbs'; +import { Icon } from '@/components/icon'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuList, + navigationMenuTriggerStyle, +} from '@/components/ui/navigation-menu'; +import { + Sheet, + SheetContent, + SheetHeader, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from '@/components/ui/tooltip'; +// import { UserMenuContent } from '@/components/user-menu-content'; +import { useInitials } from '@/hooks/use-initials'; +import { useActiveUrl } from '@/hooks/use-active-url'; +import { cn, toUrl } from '@/lib/utils'; +import { dashboard } from '@/routes/cortex'; +import { type BreadcrumbItem, type NavItem, type SharedData } from '@/types'; +import { Link, usePage } from '@inertiajs/react'; +import { BookOpen, Folder, LayoutGrid, Menu, Search } from 'lucide-react'; +import AppLogo from './app-logo'; +import AppLogoIcon from './app-logo-icon'; + +const mainNavItems: NavItem[] = [ + { + title: 'Dashboard', + href: dashboard(), + icon: LayoutGrid, + }, +]; + +const rightNavItems: NavItem[] = [ + { + title: 'Repository', + href: 'https://github.com/laravel/react-starter-kit', + icon: Folder, + }, + { + title: 'Documentation', + href: 'https://laravel.com/docs/starter-kits#react', + icon: BookOpen, + }, +]; + +const activeItemStyles = + 'text-neutral-900 dark:bg-neutral-800 dark:text-neutral-100'; + +interface AppHeaderProps { + breadcrumbs?: BreadcrumbItem[]; +} + +export function AppHeader({ breadcrumbs = [] }: AppHeaderProps) { + const page = usePage(); + const { auth } = page.props; + const getInitials = useInitials(); + const { urlIsActive } = useActiveUrl(); + return ( + <> +
+
+ {/* Mobile Menu */} +
+ + + + + + + Navigation Menu + + + + +
+
+
+ {mainNavItems.map((item) => ( + + {item.icon && ( + + )} + {item.title} + + ))} +
+ +
+ {rightNavItems.map((item) => ( + + {item.icon && ( + + )} + {item.title} + + ))} +
+
+
+
+
+
+ + + + + + {/* Desktop Navigation */} +
+ + + {mainNavItems.map((item, index) => ( + + + {item.icon && ( + + )} + {item.title} + + {urlIsActive(item.href) && ( +
+ )} +
+ ))} +
+
+
+ +
+
+ +
+ {rightNavItems.map((item) => ( + + + + + + {item.title} + + {item.icon && ( + + )} + + + +

{item.title}

+
+
+
+ ))} +
+
+ + + + + + {/* */} + + +
+
+
+ {breadcrumbs.length > 1 && ( +
+
+ +
+
+ )} + + ); +} diff --git a/resources/js/components/app-logo-icon.tsx b/resources/js/components/app-logo-icon.tsx new file mode 100644 index 0000000..9bd62ad --- /dev/null +++ b/resources/js/components/app-logo-icon.tsx @@ -0,0 +1,13 @@ +import { SVGAttributes } from 'react'; + +export default function AppLogoIcon(props: SVGAttributes) { + return ( + + + + ); +} diff --git a/resources/js/components/app-logo.tsx b/resources/js/components/app-logo.tsx new file mode 100644 index 0000000..ed88261 --- /dev/null +++ b/resources/js/components/app-logo.tsx @@ -0,0 +1,16 @@ +import AppLogoIcon from './app-logo-icon'; + +export default function AppLogo() { + return ( + <> +
+ +
+
+ + CORTEX + +
+ + ); +} diff --git a/resources/js/components/app-shell.tsx b/resources/js/components/app-shell.tsx new file mode 100644 index 0000000..f91fc4a --- /dev/null +++ b/resources/js/components/app-shell.tsx @@ -0,0 +1,20 @@ +import { SidebarProvider } from '@/components/ui/sidebar'; +import { SharedData } from '@/types'; +import { usePage } from '@inertiajs/react'; + +interface AppShellProps { + children: React.ReactNode; + variant?: 'header' | 'sidebar'; +} + +export function AppShell({ children, variant = 'header' }: AppShellProps) { + const isOpen = usePage().props.sidebarOpen; + + if (variant === 'header') { + return ( +
{children}
+ ); + } + + return {children}; +} diff --git a/resources/js/components/app-sidebar-header.tsx b/resources/js/components/app-sidebar-header.tsx new file mode 100644 index 0000000..a779df7 --- /dev/null +++ b/resources/js/components/app-sidebar-header.tsx @@ -0,0 +1,18 @@ +import { Breadcrumbs } from '@/components/breadcrumbs'; +import { SidebarTrigger } from '@/components/ui/sidebar'; +import { type BreadcrumbItem as BreadcrumbItemType } from '@/types'; + +export function AppSidebarHeader({ + breadcrumbs = [], +}: { + breadcrumbs?: BreadcrumbItemType[]; +}) { + return ( +
+
+ + +
+
+ ); +} diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx new file mode 100644 index 0000000..7fe0df7 --- /dev/null +++ b/resources/js/components/app-sidebar.tsx @@ -0,0 +1,70 @@ +import { NavFooter } from '@/components/nav-footer'; +import { NavMain } from '@/components/nav-main'; +// import { NavUser } from '@/components/nav-user'; +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, +} from '@/components/ui/sidebar'; +import { dashboard, playground } from '@/routes/cortex'; +import { type NavItem } from '@/types'; +import { Link } from '@inertiajs/react'; +import { BookOpen, Folder, LayoutGrid, Play } from 'lucide-react'; +import AppLogo from './app-logo'; + +const mainNavItems: NavItem[] = [ + { + title: 'Dashboard', + href: dashboard(), + icon: LayoutGrid, + }, + { + title: 'Playground', + href: playground(), + icon: Play, + }, +]; + +const footerNavItems: NavItem[] = [ + { + title: 'Repository', + href: 'https://github.com/cortexphp/cortex', + icon: Folder, + }, + { + title: 'Documentation', + href: 'https://laravel.com/docs/starter-kits#react', + icon: BookOpen, + }, +]; + +export function AppSidebar() { + return ( + + + + + + + + + + + + + + + + + + + + {/* */} + + + ); +} diff --git a/resources/js/components/appearance-dropdown.tsx b/resources/js/components/appearance-dropdown.tsx new file mode 100644 index 0000000..da87bb4 --- /dev/null +++ b/resources/js/components/appearance-dropdown.tsx @@ -0,0 +1,67 @@ +import { Button } from '@/components/ui/button'; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from '@/components/ui/dropdown-menu'; +import { useAppearance } from '@/hooks/use-appearance'; +import { Monitor, Moon, Sun } from 'lucide-react'; +import { HTMLAttributes } from 'react'; + +export default function AppearanceToggleDropdown({ + className = '', + ...props +}: HTMLAttributes) { + const { appearance, updateAppearance } = useAppearance(); + + const getCurrentIcon = () => { + switch (appearance) { + case 'dark': + return ; + case 'light': + return ; + default: + return ; + } + }; + + return ( +
+ + + + + + updateAppearance('light')}> + + + Light + + + updateAppearance('dark')}> + + + Dark + + + updateAppearance('system')} + > + + + System + + + + +
+ ); +} diff --git a/resources/js/components/appearance-tabs.tsx b/resources/js/components/appearance-tabs.tsx new file mode 100644 index 0000000..1a1e271 --- /dev/null +++ b/resources/js/components/appearance-tabs.tsx @@ -0,0 +1,43 @@ +import { Appearance, useAppearance } from '@/hooks/use-appearance'; +import { cn } from '@/lib/utils'; +import { LucideIcon, Monitor, Moon, Sun } from 'lucide-react'; +import { HTMLAttributes } from 'react'; + +export default function AppearanceToggleTab({ + className = '', + ...props +}: HTMLAttributes) { + const { appearance, updateAppearance } = useAppearance(); + + const tabs: { value: Appearance; icon: LucideIcon; label: string }[] = [ + { value: 'light', icon: Sun, label: 'Light' }, + { value: 'dark', icon: Moon, label: 'Dark' }, + { value: 'system', icon: Monitor, label: 'System' }, + ]; + + return ( +
+ {tabs.map(({ value, icon: Icon, label }) => ( + + ))} +
+ ); +} diff --git a/resources/js/components/assistant-ui/attachment.tsx b/resources/js/components/assistant-ui/attachment.tsx new file mode 100644 index 0000000..be43559 --- /dev/null +++ b/resources/js/components/assistant-ui/attachment.tsx @@ -0,0 +1,235 @@ +"use client"; + +import { PropsWithChildren, useEffect, useState, type FC } from "react"; +// import Image from "next/image"; +import { XIcon, PlusIcon, FileText } from "lucide-react"; +import { + AttachmentPrimitive, + ComposerPrimitive, + MessagePrimitive, + useAssistantState, + useAssistantApi, +} from "@assistant-ui/react"; +import { useShallow } from "zustand/shallow"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { + Dialog, + DialogTitle, + DialogContent, + DialogTrigger, +} from "@/components/ui/dialog"; +import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { cn } from "@/lib/utils"; + +const useFileSrc = (file: File | undefined) => { + const [src, setSrc] = useState(undefined); + + useEffect(() => { + if (!file) { + setSrc(undefined); + return; + } + + const objectUrl = URL.createObjectURL(file); + setSrc(objectUrl); + + return () => { + URL.revokeObjectURL(objectUrl); + }; + }, [file]); + + return src; +}; + +const useAttachmentSrc = () => { + const { file, src } = useAssistantState( + useShallow(({ attachment }): { file?: File; src?: string } => { + if (attachment.type !== "image") return {}; + if (attachment.file) return { file: attachment.file }; + const src = attachment.content?.filter((c) => c.type === "image")[0] + ?.image; + if (!src) return {}; + return { src }; + }), + ); + + return useFileSrc(file) ?? src; +}; + +type AttachmentPreviewProps = { + src: string; +}; + +const AttachmentPreview: FC = ({ src }) => { + const [isLoaded, setIsLoaded] = useState(false); + return ( + Image Preview setIsLoaded(true)} + priority={false} + /> + ); +}; + +const AttachmentPreviewDialog: FC = ({ children }) => { + const src = useAttachmentSrc(); + + if (!src) return children; + + return ( + + + {children} + + + + Image Attachment Preview + +
+ {/* */} +
+
+
+ ); +}; + +const AttachmentThumb: FC = () => { + const isImage = useAssistantState( + ({ attachment }) => attachment.type === "image", + ); + const src = useAttachmentSrc(); + + return ( + + + + + + + ); +}; + +const AttachmentUI: FC = () => { + const api = useAssistantApi(); + const isComposer = api.attachment.source === "composer"; + + const isImage = useAssistantState( + ({ attachment }) => attachment.type === "image", + ); + const typeLabel = useAssistantState(({ attachment }) => { + const type = attachment.type; + switch (type) { + case "image": + return "Image"; + case "document": + return "Document"; + case "file": + return "File"; + default: + const _exhaustiveCheck: never = type; + throw new Error(`Unknown attachment type: ${_exhaustiveCheck}`); + } + }); + + return ( + + #attachment-tile]:size-24", + )} + > + + +
+ +
+
+
+ {isComposer && } +
+ + + +
+ ); +}; + +const AttachmentRemove: FC = () => { + return ( + + + + + + ); +}; + +export const UserMessageAttachments: FC = () => { + return ( +
+ +
+ ); +}; + +export const ComposerAttachments: FC = () => { + return ( +
+ +
+ ); +}; + +export const ComposerAddAttachment: FC = () => { + return ( + + + + + + ); +}; diff --git a/resources/js/components/assistant-ui/markdown-text.tsx b/resources/js/components/assistant-ui/markdown-text.tsx new file mode 100644 index 0000000..413edda --- /dev/null +++ b/resources/js/components/assistant-ui/markdown-text.tsx @@ -0,0 +1,231 @@ +"use client"; + +import "@assistant-ui/react-markdown/styles/dot.css"; + +import { + type CodeHeaderProps, + MarkdownTextPrimitive, + unstable_memoizeMarkdownComponents as memoizeMarkdownComponents, + useIsMarkdownCodeBlock, +} from "@assistant-ui/react-markdown"; +import remarkGfm from "remark-gfm"; +import { type FC, memo, useState } from "react"; +import { CheckIcon, CopyIcon } from "lucide-react"; + +import { TooltipIconButton } from "@/components/assistant-ui/tooltip-icon-button"; +import { cn } from "@/lib/utils"; + +import { SyntaxHighlighter } from "./shiki-highlighter"; + +const MarkdownTextImpl = () => { + return ( + + ); +}; + +export const MarkdownText = memo(MarkdownTextImpl); + +const CodeHeader: FC = ({ language, code }) => { + const { isCopied, copyToClipboard } = useCopyToClipboard(); + const onCopy = () => { + if (!code || isCopied) return; + copyToClipboard(code); + }; + + return ( +
+ + {language} + + + {!isCopied && } + {isCopied && } + +
+ ); +}; + +const useCopyToClipboard = ({ + copiedDuration = 3000, +}: { + copiedDuration?: number; +} = {}) => { + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = (value: string) => { + if (!value) return; + + navigator.clipboard.writeText(value).then(() => { + setIsCopied(true); + setTimeout(() => setIsCopied(false), copiedDuration); + }); + }; + + return { isCopied, copyToClipboard }; +}; + +const defaultComponents = memoizeMarkdownComponents({ + SyntaxHighlighter, + h1: ({ className, ...props }) => ( +

+ ), + h2: ({ className, ...props }) => ( +

+ ), + h3: ({ className, ...props }) => ( +

+ ), + h4: ({ className, ...props }) => ( +

+ ), + h5: ({ className, ...props }) => ( +

+ ), + h6: ({ className, ...props }) => ( +
+ ), + p: ({ className, ...props }) => ( +

+ ), + a: ({ className, ...props }) => ( + + ), + blockquote: ({ className, ...props }) => ( +

+ ), + ul: ({ className, ...props }) => ( +