├── src ├── clients │ ├── index.ts │ ├── googleGenAIClient.ts │ └── baseClient.ts ├── enums │ ├── provider.ts │ ├── contentType.ts │ ├── timeout.ts │ ├── role.ts │ ├── imageFormat.ts │ ├── reasoningEffort.ts │ ├── model.ts │ └── index.ts ├── dtos │ ├── index.ts │ ├── response.ts │ ├── internal.ts │ └── request.ts ├── types │ ├── index.ts │ ├── retry.ts │ ├── completionContext.ts │ ├── hono.d.ts │ └── inference.ts ├── routes │ ├── health.ts │ └── completion.ts ├── services │ ├── providers │ │ ├── baseInferenceProvider.ts │ │ └── googleGenAIProvider.ts │ └── inferenceService.ts ├── utils │ ├── retry.ts │ └── imageUtils.ts ├── middlewares │ └── apiKeyProvider.ts └── index.ts ├── .github ├── FUNDING.yml ├── workflows │ ├── trufflehog.yaml │ └── ci.yaml └── dependabot.yml ├── assets └── thumbnail.png ├── pnpm-workspace.yaml ├── make ├── variables.mk ├── help.mk └── dev.mk ├── .dev.vars.example ├── Makefile ├── .prod.vars.example ├── .vscode └── settings.json ├── wrangler.jsonc ├── tsconfig.json ├── package.json ├── .gitignore ├── .windsurfrules └── instructions.md ├── README.md ├── LICENSE ├── AGENTS.md └── pnpm-lock.yaml /src/clients/index.ts: -------------------------------------------------------------------------------- 1 | // src/clients/index.ts 2 | 3 | export * from "./googleGenAIClient"; 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: louisbrulenaudet 4 | -------------------------------------------------------------------------------- /assets/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/louisbrulenaudet/genai-api/HEAD/assets/thumbnail.png -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - ./ 3 | 4 | ignoredBuiltDependencies: 5 | - esbuild 6 | - workerd 7 | -------------------------------------------------------------------------------- /src/enums/provider.ts: -------------------------------------------------------------------------------- 1 | // src/enums/provider.ts 2 | 3 | export enum Provider { 4 | GoogleAIStudio = "google-ai-studio", 5 | } 6 | -------------------------------------------------------------------------------- /src/dtos/index.ts: -------------------------------------------------------------------------------- 1 | // src/dtos/index.ts 2 | 3 | export * from "./internal"; 4 | export * from "./request"; 5 | export * from "./response"; 6 | -------------------------------------------------------------------------------- /src/enums/contentType.ts: -------------------------------------------------------------------------------- 1 | // src/enums/contentType.ts 2 | 3 | export enum ContentType { 4 | TEXT = "text", 5 | IMAGE_URL = "image_url", 6 | } 7 | -------------------------------------------------------------------------------- /src/enums/timeout.ts: -------------------------------------------------------------------------------- 1 | // src/enums/timeout.ts 2 | 3 | export enum Timeout { 4 | Short = 10000, 5 | Medium = 50000, 6 | Long = 120000, 7 | } 8 | -------------------------------------------------------------------------------- /src/enums/role.ts: -------------------------------------------------------------------------------- 1 | // src/enums/role.ts 2 | 3 | export enum Role { 4 | User = "user", 5 | System = "system", 6 | Developer = "developer", 7 | } 8 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | // src/types/index.ts 2 | 3 | export * from "./completionContext"; 4 | export * from "./inference"; 5 | export * from "./retry"; 6 | -------------------------------------------------------------------------------- /src/enums/imageFormat.ts: -------------------------------------------------------------------------------- 1 | // src/enums/imageFormat.ts 2 | 3 | export enum ImageFormat { 4 | JPEG = "jpeg", 5 | JPG = "jpg", 6 | PNG = "png", 7 | WEBP = "webp", 8 | } 9 | -------------------------------------------------------------------------------- /src/enums/reasoningEffort.ts: -------------------------------------------------------------------------------- 1 | // src/enums/reasoningEffort.ts 2 | 3 | export enum ReasoningEffort { 4 | None = "none", 5 | Low = "low", 6 | Medium = "medium", 7 | High = "high", 8 | } 9 | -------------------------------------------------------------------------------- /src/types/retry.ts: -------------------------------------------------------------------------------- 1 | // src/types/retry.ts 2 | 3 | export type RetryOptions = { 4 | maxRetries?: number; 5 | sleepTime?: number; 6 | raisesOnException?: boolean; 7 | nonRetryExceptions?: Array Error>; 8 | }; 9 | -------------------------------------------------------------------------------- /make/variables.mk: -------------------------------------------------------------------------------- 1 | # make/variables.mk 2 | 3 | # Project configuration 4 | PROJECT_NAME := ☁️ 5 | VERSION := 0.1.0 6 | 7 | # Colors for formatting 8 | BLUE := \033[1;34m 9 | CYAN := \033[1;36m 10 | WHITE := \033[1;37m 11 | RESET := \033[0m 12 | -------------------------------------------------------------------------------- /src/enums/model.ts: -------------------------------------------------------------------------------- 1 | // src/enums/model.ts 2 | 3 | export enum Model { 4 | Gemini25Flash = "gemini-2.5-flash", 5 | Gemini25FlashLite = "gemini-2.5-flash-lite", 6 | Gemini2Flash = "gemini-2.0-flash", 7 | Gemini2FlashLite = "gemini-2.0-flash-lite", 8 | } 9 | -------------------------------------------------------------------------------- /src/enums/index.ts: -------------------------------------------------------------------------------- 1 | // src/enums/index.ts 2 | 3 | export * from "./contentType"; 4 | export * from "./imageFormat"; 5 | export * from "./model"; 6 | export * from "./provider"; 7 | export * from "./reasoningEffort"; 8 | export * from "./role"; 9 | export * from "./timeout"; 10 | -------------------------------------------------------------------------------- /src/types/completionContext.ts: -------------------------------------------------------------------------------- 1 | // src/types/completionContext.ts 2 | 3 | import type { InferenceConfigType } from "../dtos/internal"; 4 | 5 | export type CompletionContext = { 6 | Bindings: Env; 7 | Variables: { 8 | AIProviderConfig: InferenceConfigType; 9 | }; 10 | }; 11 | -------------------------------------------------------------------------------- /src/types/hono.d.ts: -------------------------------------------------------------------------------- 1 | // src/types/hono.d.ts 2 | 3 | import "hono"; 4 | 5 | declare module "hono" { 6 | interface ContextVariableMap { 7 | AIProvider: { 8 | apiKey: string; 9 | model: string; 10 | baseURL: string; 11 | provider: string; 12 | }; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.dev.vars.example: -------------------------------------------------------------------------------- 1 | CLOUDFLARE_AI_GATEWAY_BASE_URL="https://gateway.ai.cloudflare.com/v1 2 | CLOUDFLARE_ACCOUNT_ID="your_cloudflare_account_id" 3 | CLOUDFLARE_AI_GATEWAY_ID="your_cloudflare_ai_gateway_id" 4 | GOOGLE_AI_STUDIO_API_KEY="your_google_ai_studio_api_key_here" 5 | BEARER_TOKEN="your_token_here" 6 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include make/variables.mk 2 | 3 | # Include specific command groups 4 | include make/help.mk 5 | include make/dev.mk 6 | 7 | # Default target 8 | .DEFAULT_GOAL := help 9 | 10 | # Ensure these targets work even if files with the same names exist 11 | .PHONY: help build test clean 12 | -------------------------------------------------------------------------------- /.prod.vars.example: -------------------------------------------------------------------------------- 1 | CLOUDFLARE_AI_GATEWAY_BASE_URL="https://gateway.ai.cloudflare.com/v1" 2 | CLOUDFLARE_ACCOUNT_ID="your_cloudflare_account_id" 3 | CLOUDFLARE_AI_GATEWAY_ID="your_cloudflare_ai_gateway_id" 4 | GOOGLE_AI_STUDIO_API_KEY="your_google_ai_studio_api_key_here" 5 | BEARER_TOKEN="your_token_here" 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "biome.organizeImports": true, 3 | "editor.formatOnSave": true, 4 | "cSpell.words": [ 5 | "Brulé", 6 | "cloc", 7 | "cloudflare", 8 | "dotenv", 9 | "dtos", 10 | "genai", 11 | "Hono", 12 | "multimodal", 13 | "Naudet", 14 | "openai", 15 | "Vitest" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/dtos/response.ts: -------------------------------------------------------------------------------- 1 | // src/dtos/response.ts 2 | 3 | import { z } from "zod"; 4 | 5 | export const HealthResponse = z.object({ 6 | status: z.literal("API successfully started ☁️"), 7 | }); 8 | 9 | export const InferenceResponse = z.object({ 10 | content: z.string(), 11 | }); 12 | 13 | export type HealthResponseType = z.infer; 14 | export type InferenceResponseType = z.infer; 15 | -------------------------------------------------------------------------------- /src/routes/health.ts: -------------------------------------------------------------------------------- 1 | // src/routes/health.ts 2 | 3 | import { Hono } from "hono"; 4 | import { HealthResponse } from "../dtos"; 5 | 6 | const health = new Hono(); 7 | 8 | health.get("/", (c) => { 9 | const response = { status: "API successfully started ☁️" }; 10 | const parseResponse = HealthResponse.safeParse(response); 11 | return c.json(parseResponse); 12 | }); 13 | 14 | export default health; 15 | export type HealthRoute = typeof health; 16 | -------------------------------------------------------------------------------- /.github/workflows/trufflehog.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | 4 | name: Secret Leaks 5 | 6 | permissions: 7 | contents: read 8 | 9 | jobs: 10 | trufflehog: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - name: Secret Scanning 18 | uses: trufflesecurity/trufflehog@main 19 | with: 20 | extra_args: --results=verified,unknown 21 | -------------------------------------------------------------------------------- /src/services/providers/baseInferenceProvider.ts: -------------------------------------------------------------------------------- 1 | // src/services/providers/baseInferenceProvider.ts 2 | 3 | import type { InferenceRequestType, InferenceResponseType } from "../../dtos"; 4 | 5 | import type { InferenceParams } from "../../types/inference"; 6 | 7 | export abstract class InferenceProvider { 8 | constructor(protected inferenceConfig: InferenceParams) {} 9 | 10 | abstract runInference( 11 | request: InferenceRequestType, 12 | ): Promise; 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | check: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | actions: read 15 | checks: write 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: '20' 21 | - uses: pnpm/action-setup@v2 22 | with: 23 | version: 8 24 | - run: make init 25 | - run: make check 26 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /wrangler.jsonc: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genai-api", 3 | "main": "src/index.ts", 4 | "account_id": "0c17ffb490d11f8f4b663d52217cd19f", 5 | "compatibility_date": "2025-02-04", 6 | "dev": { 7 | "port": 8788, 8 | "inspector_port": 0 9 | }, 10 | "observability": { 11 | "enabled": true 12 | }, 13 | "send_metrics": false, 14 | "vars": { 15 | "ENVIRONMENT": "dev", 16 | "CLOUDFLARE_ACCOUNT_ID": "0c17ffb490d11f8f4b663d52217cd19f", 17 | "CLOUDFLARE_AI_GATEWAY_ID": "genai-api", 18 | "CLOUDFLARE_AI_GATEWAY_BASE_URL": "https://gateway.ai.cloudflare.com/v1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/dtos/internal.ts: -------------------------------------------------------------------------------- 1 | // src/dtos/internal.ts 2 | 3 | import { z } from "zod"; 4 | import { InferenceRequest } from "./request"; 5 | 6 | export const InferenceConfig = z.intersection( 7 | InferenceRequest, 8 | z.object({ 9 | apiKey: z.string().optional(), 10 | baseURL: z.string().optional(), 11 | text_format: z.any().optional(), 12 | }), 13 | ); 14 | 15 | export const ClientConfigSchema = z.object({ 16 | apiKey: z.string().min(1, "API key is required"), 17 | baseUrl: z.url("baseUrl must be a valid URL"), 18 | }); 19 | 20 | export type ClientConfig = z.infer; 21 | export type InferenceConfigType = z.infer; 22 | -------------------------------------------------------------------------------- /make/help.mk: -------------------------------------------------------------------------------- 1 | # Define help sections 2 | define HELP_HEADER 3 | $(BLUE)$(PROJECT_NAME) Development Commands$(RESET) 4 | 5 | $(WHITE)Usage:$(RESET) 6 | make [command] 7 | 8 | $(WHITE)Available Commands:$(RESET) 9 | $(CYAN) %-20s %s$(RESET) 10 | %-20s %s 11 | 12 | endef 13 | export HELP_HEADER 14 | 15 | define HELP_EXAMPLES 16 | $(WHITE)Examples:$(RESET) 17 | make init # Initialize project environment 18 | make run-dev # Start development server 19 | make test # Execute test suite 20 | 21 | endef 22 | export HELP_EXAMPLES 23 | 24 | # Help command implementation 25 | help: 26 | @printf "$$HELP_HEADER" "Command" "Description" "-------" "-----------" 27 | @awk 'BEGIN {FS = ":.*##"} \ 28 | /^[a-zA-Z_-]+:.*?##/ { \ 29 | printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 \ 30 | }' $(MAKEFILE_LIST) 31 | @printf "\n$$HELP_EXAMPLES" 32 | -------------------------------------------------------------------------------- /src/clients/googleGenAIClient.ts: -------------------------------------------------------------------------------- 1 | // src/clients/googleGenAIClient.ts 2 | 3 | import { env } from "cloudflare:workers"; 4 | import { GoogleGenAI } from "@google/genai"; 5 | import type { ClientConfig } from "../dtos"; 6 | import { Provider } from "../enums"; 7 | import { BaseClient } from "./baseClient"; 8 | 9 | export function getGoogleGenAIClient(config: unknown): GoogleGenAI { 10 | return BaseClient.get( 11 | config, 12 | (parsed: ClientConfig) => 13 | new GoogleGenAI({ 14 | apiKey: parsed.apiKey, 15 | httpOptions: { 16 | baseUrl: parsed.baseUrl, 17 | }, 18 | }), 19 | ); 20 | } 21 | 22 | export const googleGenAIClient = getGoogleGenAIClient({ 23 | apiKey: env.GOOGLE_AI_STUDIO_API_KEY, 24 | baseUrl: `${env.CLOUDFLARE_AI_GATEWAY_BASE_URL}/${env.CLOUDFLARE_ACCOUNT_ID}/${env.CLOUDFLARE_AI_GATEWAY_ID}/${Provider.GoogleAIStudio}`, 25 | }); 26 | -------------------------------------------------------------------------------- /src/types/inference.ts: -------------------------------------------------------------------------------- 1 | // src/types/inference.ts 2 | 3 | import type { ContentType } from "../enums/contentType"; 4 | import type { Model } from "../enums/model"; 5 | import type { Provider } from "../enums/provider"; 6 | import type { ReasoningEffort } from "../enums/reasoningEffort"; 7 | import type { Role } from "../enums/role"; 8 | 9 | export interface TextContentBlock { 10 | type: ContentType.TEXT; 11 | text: string; 12 | } 13 | 14 | export interface ImageContentBlock { 15 | type: ContentType.IMAGE_URL; 16 | image_url: string; 17 | } 18 | 19 | export type ContentBlock = TextContentBlock | ImageContentBlock; 20 | 21 | export interface InferenceParams { 22 | provider: Provider; 23 | model: Model; 24 | messages: Array<{ 25 | role: Role; 26 | content: string | ContentBlock[]; 27 | }>; 28 | apiKey: string; 29 | baseURL?: string; 30 | temperature?: number; 31 | reasoning_effort: ReasoningEffort; 32 | accountId?: string; 33 | gatewayId?: string; 34 | } 35 | -------------------------------------------------------------------------------- /make/dev.mk: -------------------------------------------------------------------------------- 1 | init: ## Initialize the project 2 | @echo "🔧 Initializing the project..." 3 | pnpm install 4 | 5 | update: ## Update dependencies to their latest versions 6 | @echo "🔄 Updating dependencies..." 7 | pnpm update 8 | 9 | check: ## Check the codebase for issues 10 | @echo "🔍 Checking codebase..." 11 | pnpm run check 12 | 13 | dev: ## Run development server with hot reloading and local database 14 | @echo "💻 Starting development server..." 15 | pnpm run dev 16 | 17 | deploy: ## Deploy the application globally 18 | @echo "🚀 Deploying to global network..." 19 | pnpm run deploy 20 | 21 | format: ## Format the codebase using Biome 22 | @echo "📝 Formatting code..." 23 | pnpm run format 24 | 25 | lint: ## Lint the codebase using Biome 26 | @echo "🔍 Running code analysis..." 27 | pnpm run lint 28 | 29 | types: ## Creating worker-configuration.d.ts file for TypeScript types 30 | @echo "📄 Generating TypeScript types..." 31 | pnpm wrangler types 32 | 33 | cloc: ## Count lines of code in the source directory 34 | @echo "📊 Counting lines of code..." 35 | pnpm cloc src 36 | -------------------------------------------------------------------------------- /src/clients/baseClient.ts: -------------------------------------------------------------------------------- 1 | // src/clients/baseClient.ts 2 | 3 | import { type ClientConfig, ClientConfigSchema } from "../dtos"; 4 | 5 | export class BaseClient { 6 | private static readonly instances: Map = new Map(); 7 | private static MAX_INSTANCES = 50; 8 | 9 | protected constructor() {} 10 | 11 | static setMaxInstances(n: number) { 12 | BaseClient.MAX_INSTANCES = n; 13 | } 14 | 15 | static get(config: unknown, factory: (config: ClientConfig) => T): T { 16 | const parsed = ClientConfigSchema.parse(config); 17 | const key = `${parsed.apiKey}:${parsed.baseUrl}`; 18 | 19 | if (BaseClient.instances.has(key)) { 20 | const existing = BaseClient.instances.get(key) as T; 21 | BaseClient.instances.delete(key); 22 | BaseClient.instances.set(key, existing); 23 | return existing; 24 | } 25 | 26 | if (BaseClient.instances.size >= BaseClient.MAX_INSTANCES) { 27 | const oldestKey = BaseClient.instances.keys().next().value; 28 | if (oldestKey !== undefined) { 29 | BaseClient.instances.delete(oldestKey); 30 | } 31 | } 32 | 33 | const instance = factory(parsed); 34 | BaseClient.instances.set(key, instance); 35 | return instance; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/utils/retry.ts: -------------------------------------------------------------------------------- 1 | // src/utils/retry.ts 2 | 3 | import type { RetryOptions } from "../types/retry"; 4 | 5 | function sleep(ms: number) { 6 | return new Promise((resolve) => setTimeout(resolve, ms)); 7 | } 8 | 9 | export function retryAsync( 10 | options: RetryOptions = {}, 11 | ) { 12 | const { 13 | maxRetries = 3, 14 | sleepTime = 0, 15 | raisesOnException = true, 16 | nonRetryExceptions = [], 17 | } = options; 18 | 19 | return (fn: (...args: A) => Promise) => 20 | async (...args: A): Promise => { 21 | for (let i = 0; i < maxRetries; i++) { 22 | try { 23 | return await fn(...args); 24 | } catch (e) { 25 | const isNonRetry = 26 | nonRetryExceptions.length > 0 && 27 | nonRetryExceptions.some((ErrType) => e instanceof ErrType); 28 | 29 | if (i === maxRetries - 1 || isNonRetry) { 30 | if (raisesOnException) { 31 | throw e; 32 | } else { 33 | return undefined; 34 | } 35 | } 36 | if (sleepTime > 0) { 37 | await sleep(sleepTime * 1000); 38 | } 39 | } 40 | } 41 | // Should not reach here 42 | throw new Error("Retry logic failed unexpectedly."); 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /src/routes/completion.ts: -------------------------------------------------------------------------------- 1 | // src/routes/completion.ts 2 | 3 | import { zValidator } from "@hono/zod-validator"; 4 | import { Hono } from "hono"; 5 | import { InferenceRequest, InferenceResponse } from "../dtos"; 6 | import { apiKeyProvider } from "../middlewares/apiKeyProvider"; 7 | import { runInferenceWithRetry } from "../services/inferenceService"; 8 | 9 | import type { CompletionContext } from "../types"; 10 | 11 | const completion = new Hono(); 12 | 13 | completion.post( 14 | "/", 15 | zValidator("json", InferenceRequest), 16 | apiKeyProvider, 17 | async (c) => { 18 | const inferenceConfig = c.get("AIProviderConfig"); 19 | 20 | if (!inferenceConfig) { 21 | return c.json({ error: "Inference config missing" }, 500); 22 | } 23 | 24 | const result = await runInferenceWithRetry({ 25 | ...inferenceConfig, 26 | apiKey: inferenceConfig.apiKey ?? "", 27 | }); 28 | 29 | const parseResult = InferenceResponse.safeParse(result); 30 | if (!parseResult.success) { 31 | return c.json( 32 | { 33 | error: "Invalid response from inference service", 34 | details: parseResult.error.issues, 35 | }, 36 | 500, 37 | ); 38 | } 39 | 40 | return c.text(parseResult.data.content); 41 | }, 42 | ); 43 | 44 | export default completion; 45 | export type CompletionRoute = typeof completion; 46 | -------------------------------------------------------------------------------- /src/utils/imageUtils.ts: -------------------------------------------------------------------------------- 1 | // src/utils/imageUtils.ts 2 | 3 | import { ImageFormat } from "../enums"; 4 | 5 | export function extractImageFormat(dataUrl: string): ImageFormat | null { 6 | const regex = /^data:image\/([^;]+);base64,/; 7 | const match = regex.exec(dataUrl); 8 | if (!match) return null; 9 | 10 | const format = match[1]; 11 | // Handle jpg/jpeg equivalence 12 | const normalizedFormat = 13 | format === ImageFormat.JPG ? ImageFormat.JPEG : format; 14 | return Object.values(ImageFormat).includes(normalizedFormat as ImageFormat) 15 | ? (normalizedFormat as ImageFormat) 16 | : null; 17 | } 18 | 19 | export function isValidImageFormat(format: string): format is ImageFormat { 20 | return Object.values(ImageFormat).includes(format as ImageFormat); 21 | } 22 | 23 | export function createImageDataUrl( 24 | format: ImageFormat, 25 | base64Data: string, 26 | ): string { 27 | return `data:image/${format};base64,${base64Data}`; 28 | } 29 | 30 | export function isValidImageDataUrl(dataUrl: string): boolean { 31 | if (!dataUrl.startsWith("data:image/")) return false; 32 | return extractImageFormat(dataUrl) !== null; 33 | } 34 | 35 | export function getSupportedImageFormats(): ImageFormat[] { 36 | return Object.values(ImageFormat); 37 | } 38 | 39 | export function parseDataUrl( 40 | dataUrl: string, 41 | ): { mimeType: string; data: string } | null { 42 | const regex = /^data:([^;]+);base64,(.*)$/s; 43 | const match = regex.exec(dataUrl); 44 | if (!match) return null; 45 | const mimeType = match[1]; 46 | const data = match[2].replace(/\s+/g, ""); 47 | return { mimeType, data }; 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 6 | "target": "es2021", 7 | /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 8 | "lib": ["es2021"], 9 | /* Specify what JSX code is generated. */ 10 | "jsx": "react-jsx", 11 | 12 | /* Specify what module code is generated. */ 13 | "module": "es2022", 14 | /* Specify how TypeScript looks up a file from a given module specifier. */ 15 | "moduleResolution": "Bundler", 16 | /* Enable importing .json files */ 17 | "resolveJsonModule": true, 18 | 19 | /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 20 | "allowJs": true, 21 | /* Enable error reporting in type-checked JavaScript files. */ 22 | "checkJs": false, 23 | 24 | /* Disable emitting files from a compilation. */ 25 | "noEmit": true, 26 | 27 | /* Ensure that each file can be safely transpiled without relying on other imports. */ 28 | "isolatedModules": true, 29 | /* Allow 'import x from y' when a module doesn't have a default export. */ 30 | "allowSyntheticDefaultImports": true, 31 | /* Ensure that casing is correct in imports. */ 32 | "forceConsistentCasingInFileNames": true, 33 | 34 | /* Enable all strict type-checking options. */ 35 | "strict": true, 36 | 37 | /* Skip type checking all .d.ts files. */ 38 | "skipLibCheck": true, 39 | "types": ["./worker-configuration.d.ts"] 40 | }, 41 | "exclude": ["test"], 42 | "include": ["worker-configuration.d.ts", "src/**/*.ts"] 43 | } 44 | -------------------------------------------------------------------------------- /src/services/inferenceService.ts: -------------------------------------------------------------------------------- 1 | // src/services/inferenceService.ts 2 | 3 | import type { ZodType } from "zod"; 4 | import type { InferenceResponseType } from "../dtos"; 5 | import { Provider } from "../enums/provider"; 6 | import type { InferenceParams } from "../types"; 7 | import { retryAsync } from "../utils/retry"; 8 | import type { InferenceProvider } from "./providers/baseInferenceProvider"; 9 | import { GoogleGenAIProvider } from "./providers/googleGenAIProvider"; 10 | 11 | const ProviderRegistry: Record< 12 | Provider, 13 | new ( 14 | config: InferenceParams, 15 | ) => InferenceProvider 16 | > = { 17 | [Provider.GoogleAIStudio]: GoogleGenAIProvider, 18 | }; 19 | 20 | export async function runInference( 21 | request: InferenceParams & { text_format?: ZodType }, 22 | ): Promise { 23 | const ProviderClass = ProviderRegistry[request.provider as Provider]; 24 | if (!ProviderClass) { 25 | throw new Error(`Unsupported provider: ${request.provider}`); 26 | } 27 | const providerInstance = new ProviderClass(request); 28 | const content = await providerInstance.runInference(request); 29 | 30 | if (content === null) { 31 | throw new Error( 32 | `Completion message content is null for model ${request.model} and provider ${request.provider}.`, 33 | ); 34 | } 35 | 36 | if (request.text_format !== undefined) { 37 | const parsed = request.text_format.safeParse({ content }); 38 | if (!parsed.success) { 39 | throw new Error( 40 | `Response validation failed: ${JSON.stringify(parsed.error)}`, 41 | ); 42 | } 43 | } 44 | return content; 45 | } 46 | 47 | export const runInferenceWithRetry = retryAsync< 48 | unknown, 49 | [InferenceParams & { text_format?: ZodType }] 50 | >({ 51 | maxRetries: 3, 52 | sleepTime: 1, 53 | raisesOnException: true, 54 | nonRetryExceptions: [], 55 | })(runInference); 56 | -------------------------------------------------------------------------------- /src/middlewares/apiKeyProvider.ts: -------------------------------------------------------------------------------- 1 | // src/middlewares/apiKeyProvider.ts 2 | 3 | import type { Context } from "hono"; 4 | import { createMiddleware } from "hono/factory"; 5 | import { InferenceConfig } from "../dtos"; 6 | 7 | export const apiKeyProvider = createMiddleware(async (c: Context, next) => { 8 | let rawBody: unknown; 9 | try { 10 | rawBody = await c.req.json(); 11 | } catch { 12 | return c.json({ error: "Invalid or missing JSON body" }, 400); 13 | } 14 | 15 | const parseResult = InferenceConfig.safeParse(rawBody); 16 | if (!parseResult.success) { 17 | return c.json( 18 | { 19 | error: "Invalid request body", 20 | details: parseResult.error.issues, 21 | }, 22 | 400, 23 | ); 24 | } 25 | 26 | const config = parseResult.data; 27 | const provider = config.provider; 28 | 29 | // Directly use provider from validated request for env var lookup 30 | const envVarName = `${String(provider) 31 | .toUpperCase() 32 | .replace(/[^A-Z0-9]/g, "_")}_API_KEY`; 33 | 34 | const headerApiKey = c.req.header("X-API-Key"); 35 | const cleanedApiKey = headerApiKey 36 | ? headerApiKey.trim().replace(/^bearer\s+/i, "") 37 | : undefined; 38 | 39 | let apiKey: string | undefined; 40 | if (config.apiKey && config.apiKey !== "") { 41 | apiKey = config.apiKey; 42 | } else if (cleanedApiKey && cleanedApiKey !== "") { 43 | apiKey = cleanedApiKey; 44 | } else { 45 | apiKey = c.env[envVarName]; 46 | } 47 | 48 | if (!apiKey) { 49 | return c.json( 50 | { 51 | error: `API key not found for provider: ${provider}`, 52 | details: `Missing environment variable: ${envVarName} and no X-API-Key header or body provided.`, 53 | }, 54 | 500, 55 | ); 56 | } 57 | 58 | const fullConfig = { 59 | ...config, 60 | apiKey, 61 | accountId: config.accountId ?? c.env.CLOUDFLARE_ACCOUNT_ID, 62 | gatewayId: config.gatewayId ?? c.env.CLOUDFLARE_AI_GATEWAY_ID, 63 | }; 64 | 65 | c.set("AIProviderConfig", fullConfig); 66 | 67 | await next(); 68 | }); 69 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | // src/index.ts 2 | 3 | import { env } from "cloudflare:workers"; 4 | import { Hono } from "hono"; 5 | import { bearerAuth } from "hono/bearer-auth"; 6 | import { bodyLimit } from "hono/body-limit"; 7 | import { compress } from "hono/compress"; 8 | import { HTTPException } from "hono/http-exception"; 9 | import { prettyJSON } from "hono/pretty-json"; 10 | import { secureHeaders } from "hono/secure-headers"; 11 | import completionRoute from "./routes/completion"; 12 | import healthRoute from "./routes/health"; 13 | 14 | const api = new Hono<{ Bindings: Env }>() 15 | .route("/health", healthRoute) 16 | .route("/completion", completionRoute); 17 | 18 | api 19 | .use("*", async (c, next) => { 20 | const origin = c.req.header("origin"); 21 | if (origin) { 22 | c.res.headers.set("Access-Control-Allow-Origin", origin); 23 | } 24 | c.res.headers.set("Access-Control-Allow-Credentials", "true"); 25 | c.res.headers.set("Access-Control-Allow-Methods", "GET, POST"); 26 | c.res.headers.set( 27 | "Access-Control-Allow-Headers", 28 | "Content-Type, Authorization, X-API-Key", 29 | ); 30 | if (c.req.method === "OPTIONS") { 31 | return c.body(null, 204); 32 | } 33 | c.env = { 34 | ...(c.env || {}), 35 | }; 36 | await next(); 37 | }) 38 | .use(prettyJSON()) 39 | .use(compress()); 40 | 41 | const app = new Hono<{ Bindings: Env }>(); 42 | 43 | app.use(secureHeaders()); 44 | 45 | app.use("/api/v1/*", bearerAuth({ token: env.BEARER_TOKEN })); 46 | app.route("/api/v1", api); 47 | 48 | app.use( 49 | bodyLimit({ 50 | maxSize: 3000 * 1024, // 3mb 51 | onError: (c) => { 52 | return c.text("overflow :(", 413); 53 | }, 54 | }), 55 | ); 56 | 57 | app.onError((error, c) => { 58 | console.error(error); 59 | if (error instanceof HTTPException) { 60 | return c.json( 61 | { 62 | message: error.message, 63 | }, 64 | error.status, 65 | ); 66 | } 67 | 68 | return c.json( 69 | { 70 | message: "Something went wrong", 71 | }, 72 | 500, 73 | ); 74 | }); 75 | 76 | export default app; 77 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "genai-api", 3 | "version": "0.0.1", 4 | "private": true, 5 | "main": "src/index.ts", 6 | "type": "module", 7 | "description": "TypeScript API for generative AI inference and completion, built with Hono, Zod, OpenAI, and Google GenAI. Deployable as a Cloudflare Worker to supercharge Apple Shortcuts and developer workflows.", 8 | "keywords": [ 9 | "generative AI", 10 | "API", 11 | "TypeScript", 12 | "Hono", 13 | "Zod", 14 | "OpenAI", 15 | "Google GenAI", 16 | "Cloudflare Worker", 17 | "Apple Shortcuts", 18 | "inference", 19 | "completion", 20 | "multimodal", 21 | "Gemini", 22 | "AI integration", 23 | "developer tools" 24 | ], 25 | "homepage": "https://github.com/louisbrulenaudet/genai-api#readme", 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/louisbrulenaudet/genai-api.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/louisbrulenaudet/genai-api/issues", 32 | "email": "louisbrulenaudet@icloud.com" 33 | }, 34 | "license": "Apache-2.0", 35 | "author": { 36 | "name": "Louis Brulé Naudet", 37 | "email": "louisbrulenaudet@icloud.com", 38 | "url": "https://github.com/louisbrulenaudet" 39 | }, 40 | "contributors": [ 41 | { 42 | "name": "Louis Brulé Naudet", 43 | "email": "louisbrulenaudet@icloud.com", 44 | "url": "https://github.com/louisbrulenaudet" 45 | } 46 | ], 47 | "funding": { 48 | "type": "github", 49 | "url": "https://github.com/sponsors/louisbrulenaudet" 50 | }, 51 | "readme": "README.md", 52 | "scripts": { 53 | "format": "biome format --write src", 54 | "lint": "biome check --write src", 55 | "test": "vitest", 56 | "check": "biome check src", 57 | "dev": "wrangler dev src/index.ts", 58 | "deploy": "wrangler deploy --minify src/index.ts", 59 | "types": "wrangler types" 60 | }, 61 | "dependencies": { 62 | "@google/genai": "^1.31.0", 63 | "@hono/zod-validator": "^0.7.5", 64 | "dotenv": "^17.2.3", 65 | "hono": "^4.10.7", 66 | "openai": "^6.10.0", 67 | "zod": "^4.1.13" 68 | }, 69 | "devDependencies": { 70 | "@biomejs/biome": "2.2.4", 71 | "@cloudflare/vitest-pool-workers": "^0.10.14", 72 | "@types/node": "^24.10.1", 73 | "cloc": "2.6.0-cloc", 74 | "typescript": "5.9.2", 75 | "vitest": "~3.2.4", 76 | "wrangler": "^4.53.0" 77 | }, 78 | "packageManager": "pnpm@10.16.1", 79 | "engines": { 80 | "node": ">=18" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/dtos/request.ts: -------------------------------------------------------------------------------- 1 | // src/dtos/request.ts 2 | 3 | import { z } from "zod"; 4 | import { ContentType, Model, Provider, ReasoningEffort, Role } from "../enums"; 5 | import { 6 | getSupportedImageFormats, 7 | isValidImageDataUrl, 8 | } from "../utils/imageUtils"; 9 | 10 | export const TextContent = z.object({ 11 | type: z.literal(ContentType.TEXT), 12 | text: z.string().max(200000), 13 | }); 14 | 15 | export const ImageContent = z.object({ 16 | type: z.literal(ContentType.IMAGE_URL), 17 | image_url: z.string().refine((url) => isValidImageDataUrl(url), { 18 | message: `Invalid image data URL. Supported formats: ${getSupportedImageFormats().join(", ")}`, 19 | }), 20 | }); 21 | 22 | export const ContentBlock = z.union([TextContent, ImageContent]); 23 | 24 | export const Message = z 25 | .object({ 26 | content: z.union([z.string().max(200000), z.array(ContentBlock)]), 27 | role: z.enum(Role), 28 | }) 29 | .transform((msg) => { 30 | let content = msg.content; 31 | if (typeof msg.content === "string") { 32 | const isSystemOrDeveloper = [Role.System, Role.Developer].includes( 33 | msg.role, 34 | ); 35 | if (isSystemOrDeveloper && !msg.content.trim()) { 36 | content = 37 | "You are a helpful assistant. Always respond in the user's language."; 38 | } 39 | } 40 | 41 | return { 42 | ...msg, 43 | content, 44 | }; 45 | }); 46 | 47 | export const Messages = z.array(Message); 48 | 49 | export const InferenceRequest = z 50 | .object({ 51 | messages: Messages, 52 | temperature: z.number().min(0).max(2).default(0.2).optional(), 53 | provider: z.enum(Provider).default(Provider.GoogleAIStudio), 54 | model: z.enum(Model).default(Model.Gemini25Flash), 55 | reasoning_effort: z 56 | .enum(ReasoningEffort) 57 | .optional() 58 | .default(ReasoningEffort.None), 59 | accountId: z.string().optional(), 60 | gatewayId: z.string().optional(), 61 | }) 62 | .transform((req) => { 63 | const hasSystemOrDeveloper = req.messages.some( 64 | (m) => m.role === Role.System || m.role === Role.Developer, 65 | ); 66 | if (!hasSystemOrDeveloper) { 67 | return { 68 | ...req, 69 | messages: [ 70 | { 71 | role: Role.System, 72 | content: 73 | "You are a helpful assistant. Always respond in the user's language.", 74 | }, 75 | ...req.messages, 76 | ], 77 | }; 78 | } 79 | return req; 80 | }); 81 | 82 | export type MessageType = z.infer; 83 | export type MessagesType = z.infer; 84 | export type InferenceRequestType = z.infer; 85 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Snowpack dependency directory (https://snowpack.dev/) 45 | web_modules/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Optional stylelint cache 57 | .stylelintcache 58 | 59 | # Optional REPL history 60 | .node_repl_history 61 | 62 | # Output of 'npm pack' 63 | *.tgz 64 | 65 | # Yarn Integrity file 66 | .yarn-integrity 67 | 68 | # dotenv environment variable files 69 | .env 70 | .env.* 71 | !.env.example 72 | 73 | # parcel-bundler cache (https://parceljs.org/) 74 | .cache 75 | .parcel-cache 76 | 77 | # Next.js build output 78 | .next 79 | out 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and not Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # vuepress v2.x temp and cache directory 95 | .temp 96 | .cache 97 | 98 | # Sveltekit cache directory 99 | .svelte-kit/ 100 | 101 | # vitepress build output 102 | **/.vitepress/dist 103 | 104 | # vitepress cache directory 105 | **/.vitepress/cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # Firebase cache directory 120 | .firebase/ 121 | 122 | # TernJS port file 123 | .tern-port 124 | 125 | # Stores VSCode versions used for testing VSCode extensions 126 | .vscode-test 127 | 128 | # yarn v3 129 | .pnp.* 130 | .yarn/* 131 | !.yarn/patches 132 | !.yarn/plugins 133 | !.yarn/releases 134 | !.yarn/sdks 135 | !.yarn/versions 136 | 137 | # Vite logs files 138 | vite.config.js.timestamp-* 139 | vite.config.ts.timestamp-* 140 | 141 | # Cloudflare Wrangler build/cache 142 | .wrangler/ 143 | 144 | # Cloudflare Worker auto-generated types 145 | worker-configuration.d.ts 146 | 147 | # Build output 148 | dist/ 149 | 150 | # Local environment variable files 151 | .dev.vars 152 | .prod.vars 153 | 154 | # MacOS system files 155 | .DS_Store 156 | src/.DS_Store 157 | -------------------------------------------------------------------------------- /.windsurfrules/instructions.md: -------------------------------------------------------------------------------- 1 | # Coding Agent Instructions for genai-api 2 | 3 | ## Project Overview 4 | 5 | This repository implements a schema-driven TypeScript API for generative AI inference and completion, using Hono (web framework), Zod (validation), and OpenAI/Google GenAI as backends. It is designed for deployment as a Cloudflare Worker, exposing endpoints for model inference, completion, and health checks. All business logic is delegated to external services; the API is strictly a validation and routing layer. 6 | 7 | ## Architecture & Structure 8 | 9 | - **src/index.ts**: API entry point, sets up Hono app and routes. 10 | - **src/routes/**: Route handlers (e.g., `completion.ts`, `health.ts`). No business logic here—only request validation and delegation to services. 11 | - **src/dtos/**: Data transfer objects for request/response schemas. Use Zod for all validation. 12 | - **src/services/**: Service logic (e.g., `inferenceService.ts`), including provider abstractions for OpenAI and Google GenAI. 13 | - **src/services/providers/**: Provider implementations (e.g., `googleGenAIProvider.ts`). Each provider extends `baseInferenceProvider.ts` and encapsulates API-specific logic. 14 | - **src/middlewares/**: Middleware (e.g., `apiKeyProvider.ts`). 15 | - **src/enums/**, **src/types/**: Centralized enums and type definitions. 16 | - **src/utils/**: Utility functions (e.g., `retry.ts`, `imageUtils.ts`). 17 | 18 | ## Key Patterns & Conventions 19 | 20 | - **Strict TypeScript**: All code is type-safe and uses strict mode. No business logic in routes or DTOs. 21 | - **Validation**: All input/output is validated with Zod schemas in `src/dtos/`. 22 | - **Provider Abstraction**: Add new AI providers by extending `baseInferenceProvider.ts` and registering in `inferenceService.ts`. 23 | - **Environment Config**: Use `.dev.vars` and `.prod.vars` for secrets/configuration. Never hardcode secrets. 24 | - **Formatting/Linting**: Use Biome (spaces, double quotes). Run `make format` and `make lint` before committing. 25 | - **Automation**: Use the Makefile for all workflows (dev, deploy, format, lint, types, cloc). Example: `make dev` to start the dev server. 26 | - **No Business Logic in API**: The API is a thin layer; all business logic must be implemented in external consumers. 27 | 28 | ## Developer Workflows 29 | 30 | - **Install dependencies**: `make init` 31 | - **Start dev server**: `make dev` 32 | - **Format/lint**: `make format` / `make lint` 33 | - **Deploy**: `make deploy` 34 | - **Generate types**: `make types` 35 | - **Count lines**: `make cloc` 36 | 37 | ## Integration Points 38 | 39 | - **OpenAI/Google GenAI**: Providers in `src/services/providers/` handle API integration. See `googleGenAIProvider.ts` for request/response mapping. 40 | - **Cloudflare Worker**: Configured via `wrangler.jsonc`. 41 | - **Environment**: Managed via dotenv files and Makefile. 42 | 43 | ## Examples 44 | 45 | - To add a new provider, create a class in `src/services/providers/` extending `baseInferenceProvider.ts`, then register it in `inferenceService.ts`. 46 | - To add a new endpoint, create a route in `src/routes/`, validate with Zod DTOs, and delegate to a service. 47 | 48 | --- 49 | 50 | For further details, see `README.md` and `AGENTS.md`. All changes must pass lint, format, and tests before merging. 51 | -------------------------------------------------------------------------------- /src/services/providers/googleGenAIProvider.ts: -------------------------------------------------------------------------------- 1 | // src/services/providers/googleGenAIProvider.ts 2 | 3 | import { env } from "cloudflare:workers"; 4 | import type { Content, GenerationConfig } from "@google/genai"; 5 | import { getGoogleGenAIClient, googleGenAIClient } from "../../clients"; 6 | import type { InferenceRequestType, InferenceResponseType } from "../../dtos"; 7 | import { ContentType, Provider, ReasoningEffort, Role } from "../../enums"; 8 | import type { ContentBlock, InferenceParams } from "../../types/inference"; 9 | import { parseDataUrl } from "../../utils/imageUtils"; 10 | import { InferenceProvider } from "./baseInferenceProvider"; 11 | 12 | export class GoogleGenAIProvider extends InferenceProvider { 13 | private readonly client: 14 | | ReturnType 15 | | typeof googleGenAIClient; 16 | 17 | constructor(inferenceConfig: InferenceParams) { 18 | super(inferenceConfig); 19 | const apiKey = inferenceConfig.apiKey; 20 | const baseUrl = 21 | inferenceConfig.baseURL ?? 22 | `${env.CLOUDFLARE_AI_GATEWAY_BASE_URL}/${env.CLOUDFLARE_ACCOUNT_ID}/${env.CLOUDFLARE_AI_GATEWAY_ID}/${Provider.GoogleAIStudio}`; 23 | 24 | this.client = apiKey 25 | ? getGoogleGenAIClient({ apiKey, baseUrl }) 26 | : googleGenAIClient; 27 | } 28 | 29 | private convertBlockToPart(block: ContentBlock) { 30 | if (block.type === ContentType.TEXT) return { text: block.text }; 31 | if (block.type === ContentType.IMAGE_URL) { 32 | const parsed = parseDataUrl(block.image_url); 33 | return parsed 34 | ? { inlineData: { mimeType: parsed.mimeType, data: parsed.data } } 35 | : null; 36 | } 37 | return null; 38 | } 39 | 40 | private transformMessages( 41 | messages: InferenceRequestType["messages"], 42 | ): Content[] { 43 | return messages 44 | .filter((m) => m.role !== Role.System) 45 | .map((message) => { 46 | let parts: Array< 47 | { text: string } | { inlineData: { mimeType: string; data: string } } 48 | > = []; 49 | 50 | if (typeof message.content === "string") { 51 | parts = [{ text: message.content }]; 52 | } else if (Array.isArray(message.content)) { 53 | parts = message.content 54 | .map((b) => this.convertBlockToPart(b)) 55 | .filter(Boolean) as Array< 56 | | { text: string } 57 | | { inlineData: { mimeType: string; data: string } } 58 | >; 59 | } else { 60 | parts = []; 61 | } 62 | 63 | return { 64 | role: message.role.toLowerCase(), 65 | parts, 66 | } as Content; 67 | }); 68 | } 69 | 70 | async runInference( 71 | request: InferenceRequestType, 72 | ): Promise { 73 | const systemMsg = request.messages.find((m) => m.role === Role.System); 74 | const contents: Content[] = this.transformMessages(request.messages); 75 | 76 | const reasoningEffort = this.inferenceConfig.reasoning_effort; 77 | const thinkingBudget = reasoningEffort === ReasoningEffort.None ? 0 : -1; 78 | 79 | const systemInstruction = (() => { 80 | if (!systemMsg) return ""; 81 | const content = systemMsg.content; 82 | if (typeof content === "string") return content; 83 | if (Array.isArray(content) && content[0]?.type === ContentType.TEXT) { 84 | return content[0].text; 85 | } 86 | return ""; 87 | })(); 88 | 89 | const result = await this.client.models.generateContent({ 90 | model: request.model, 91 | contents, 92 | config: { 93 | temperature: request.temperature, 94 | thinkingBudget, 95 | thinkingConfig: { 96 | thinkingBudget: thinkingBudget, 97 | }, 98 | ...(systemMsg && { systemInstruction }), 99 | } as GenerationConfig, 100 | }); 101 | 102 | const content = result.candidates?.[0]?.content?.parts?.[0]?.text ?? ""; 103 | return { content }; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Bodyboard Thumbnail 3 |

4 | 5 | # Supercharge Apple's Shortcuts using Cloudflare Worker and Gemini within minutes ☁️✨ 6 | 7 | [![Biome](https://img.shields.io/badge/lint-biome-blue?logo=biome)](https://biomejs.dev/) 8 | [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](./LICENSE) 9 | [![Known Vulnerabilities](https://snyk.io/test/github/louisbrulenaudet/genai-api/badge.svg)](https://snyk.io/test/github/louisbrulenaudet/genai-api) 10 | [![Zod](https://img.shields.io/badge/validation-zod-blueviolet?logo=zod)](https://github.com/colinhacks/zod) 11 | 12 | This TypeScript API built with [Hono](https://hono.dev/), [OpenAI](https://openai.com/), and deployed as a Cloudflare Worker aims to boost the performance of Apple's Shortcuts by providing a seamless integration with generative AI capabilities, allowing users to create more powerful and intelligent shortcuts and leveraging the full potential of information extraction provided by the Apple ecosystem. 13 | 14 | The objective of this repo is to provide a very simple and easy-to-use API for developers to integrate generative AI capabilities into their shortcuts in a minute. You just have to download this repository, slightly edit the configuration files, notably the `wrangler.jsonc` file, and run `make init` (eventually `make update`), then: 15 | 16 | ```bash 17 | wrangler secret put GOOGLE_AI_STUDIO_API_KEY 18 | ``` 19 | 20 | Wrangler will prompt you to enter your API key, which will be securely stored as a secret environment variable in your Cloudflare Worker. If you don't have an API key yet, you can get one from the [Google AI Studio](https://aistudio.google.com/apikey). 21 | 22 | Then, you need to put a bearer token for security reasons: 23 | 24 | ```bash 25 | wrangler secret put BEARER_TOKEN 26 | ``` 27 | 28 | Once you have set up the secrets, you can deploy the API to Cloudflare Workers using: 29 | 30 | ```bash 31 | make deploy 32 | ``` 33 | 34 | Here you go! Your API is now deployed and ready to use. You can test it by sending requests to the Cloudflare Workers URL provided in the output. The `completion` endpoint must be accessible pinging something like `https://your-cloudflare-worker-url.com/api/v1/completion`. 35 | 36 | ## API Usage 37 | 38 | ### Providers & Models 39 | 40 | - **Providers:** `google-ai-studio` 41 | - **Models:** `gemini-2.5-flash`, `gemini-2.5-flash-lite`, `gemini-2.0-flash`, `gemini-2.0-flash-lite` 42 | 43 | ### POST `/completion` 44 | 45 | The API supports both text and image input (multimodal). You can send images (as data URLs) alongside text in your requests, enabling advanced use cases such as visual question answering, image captioning, and more. 46 | 47 | Send a POST request to `/completion` with a JSON body: 48 | 49 | ```http 50 | POST /completion 51 | Content-Type: application/json 52 | Authorization: Bearer your_token 53 | X-API-Key: your_provider_specific_api_key 54 | 55 | { 56 | "messages": [ 57 | { 58 | "role": "system", 59 | "content": "You are a helpful assistant." 60 | }, 61 | { 62 | "role": "user", 63 | "content": [ 64 | { 65 | "type": "text", 66 | "text": "What is in this image?" 67 | }, 68 | { 69 | "type": "image_url", 70 | "image_url": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..." 71 | } 72 | ] 73 | } 74 | ], 75 | "temperature": 0.7, 76 | "model": "gemini-2.0-flash" 77 | } 78 | ``` 79 | 80 | Or, with only text: 81 | 82 | ```http 83 | POST /completion 84 | Content-Type: application/json 85 | Authorization: Bearer your_token 86 | X-API-Key: your_provider_specific_api_key 87 | 88 | { 89 | "messages": [ 90 | { 91 | "role": "user", 92 | "content": "What is the capital of France?" 93 | } 94 | ] 95 | } 96 | ``` 97 | 98 | #### Request Parameters 99 | 100 | - `messages` (array, required): List of message objects, each with: 101 | - `role` (string, required): One of `"system"`, `"user"`, `"assistant"`, `"developer"`. 102 | - `content` (string or array, required): Either a string (text) or an array of content blocks: 103 | - Text block: `{ "type": "text", "text": "..." }` 104 | - Image block: `{ "type": "image_url", "image_url": "" }` 105 | - **Image must be a valid data URL.** 106 | - `temperature` (number, optional): Sampling temperature (default 0.2, range 0–2). 107 | - `provider` (string, optional): Provider name (default: `google-ai-studio`). 108 | - `model` (string, optional): Model name (default: `gemini-2.5-flash`). 109 | - `reasoning_effort` (string, optional): Reasoning effort level. 110 | 111 | The response will be plain text with the model's completion. 112 | 113 | Apple provide a helpful guide [here](https://support.apple.com/fr-fr/guide/shortcuts/apd58d46713f/ios) that you can follow to create your first shortcut using API calls. 114 | 115 | **Authentication:** 116 | - Always include the `Authorization` header with your Bearer token. 117 | - You may also send an `X-API-Key` header to use a provider-specific key for requests, without needing to rotate secrets in environment variables. 118 | 119 | Without the `Authorization` header, the request will fail. 120 | 121 | ### Development 122 | 123 | If you want to contribute to the development of this API or simply run it locally, you can follow these steps: 124 | 125 | 1. Clone the repository: 126 | 127 | ```sh 128 | git clone https://github.com/louisbrulenaudet/genai-api.git 129 | cd genai-api 130 | ``` 131 | 132 | 2. Install dependencies: 133 | 134 | ```sh 135 | make init 136 | ``` 137 | 138 | 3. Set up environment variables in `.dev.vars`: 139 | 140 | ```sh 141 | CLOUDFLARE_AI_GATEWAY_BASE_URL=https://gateway.ai.cloudflare.com/v1 142 | CLOUDFLARE_ACCOUNT_ID=your_account_id 143 | CLOUDFLARE_AI_GATEWAY_ID=your_cloudflare_ai_gateway_id 144 | GOOGLE_AI_STUDIO_API_KEY=your_api_key 145 | BEARER_TOKEN=your_bearer_token 146 | ``` 147 | 148 | 4. Start the development server: 149 | 150 | ```sh 151 | make dev 152 | ``` 153 | 154 | The API runs locally on port 8788. 155 | 156 | ## Makefile Commands 157 | 158 | | Command | Description | 159 | |-----------|---------------------------------------------| 160 | | init | Initialize the project | 161 | | update | Update dependencies | 162 | | dev | Run development server | 163 | | deploy | Deploy the application | 164 | | format | Format codebase with Biome | 165 | | lint | Lint codebase with Biome | 166 | | types | Generate TypeScript worker types | 167 | | cloc | Count lines of code in src/ | 168 | 169 | ## Feedback 170 | If you have any feedback, please reach out at [louisbrulenaudet@icloud.com](mailto:louisbrulenaudet@icloud.com). 171 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /AGENTS.md: -------------------------------------------------------------------------------- 1 | # Worker API Agent Instructions 2 | 3 | ## Project Overview 4 | 5 | This repository is a Cloudflare Worker that provides a REST API layer for generative AI inference and completion. It serves as a schema-driven HTTP API gateway, integrating with OpenAI and Google GenAI backends to provide a unified interface for AI model inference. The worker manages completion requests, supports multimodal input (text and images), and handles provider abstraction for multiple AI services. Built with Hono framework, it provides type-safe endpoints with comprehensive validation and error handling. 6 | 7 | ## Tech Stack 8 | 9 | - **Language:** TypeScript (strict mode, ESNext) 10 | - **Framework:** Hono (for Cloudflare Workers) 11 | - **Authentication:** Bearer token authentication 12 | - **Validation:** Zod schemas for request/response validation 13 | - **Middleware:** CORS, compression, body limits, secure headers, pretty JSON 14 | - **AI Providers:** OpenAI, Google GenAI (via @google/genai and openai packages) 15 | - **Runtime:** Cloudflare Workers 16 | - **Formatting/Linting:** Biome (spaces, double quotes, recommended rules) 17 | - **Build Tools:** tsx, Wrangler 18 | - **Automation:** Makefile, pnpm scripts 19 | - **Environment:** dotenv (.dev.vars, .prod.vars) 20 | - **Package Manager:** pnpm 21 | 22 | ## Project Structure 23 | 24 | ``` 25 | . 26 | ├── src/ 27 | │ ├── clients/ # Client implementations for AI providers 28 | │ │ ├── baseClient.ts # Base client interface 29 | │ │ └── googleGenAIClient.ts 30 | │ ├── dtos/ # Data transfer objects and Zod schemas 31 | │ │ ├── request.ts # Request validation schemas 32 | │ │ ├── response.ts # Response validation schemas 33 | │ │ └── internal.ts # Internal DTOs 34 | │ ├── routes/ # Route handlers 35 | │ │ ├── health.ts # Health check endpoint 36 | │ │ └── completion.ts # AI completion endpoint 37 | │ ├── services/ # Business logic services 38 | │ │ ├── inferenceService.ts # Main inference orchestration 39 | │ │ └── providers/ # AI provider implementations 40 | │ │ ├── baseInferenceProvider.ts # Abstract base class 41 | │ │ └── googleGenAIProvider.ts # Google GenAI implementation 42 | │ ├── middlewares/ # Custom middleware 43 | │ │ └── apiKeyProvider.ts # API key resolution middleware 44 | │ ├── enums/ # Type-safe enumerations 45 | │ │ ├── provider.ts # AI provider types 46 | │ │ ├── model.ts # Supported model names 47 | │ │ ├── role.ts # Message role types 48 | │ │ └── ... 49 | │ ├── types/ # TypeScript type definitions 50 | │ │ ├── inference.ts # Inference-related types 51 | │ │ └── completionContext.ts 52 | │ ├── utils/ # Utility functions 53 | │ │ ├── retry.ts # Retry logic for API calls 54 | │ │ └── imageUtils.ts # Image validation utilities 55 | │ └── index.ts # Main application entry point 56 | ├── biome.json # Biome formatting/linting config 57 | ├── tsconfig.json # TypeScript config (strict, path aliases) 58 | ├── wrangler.jsonc # Cloudflare Workers configuration 59 | ├── Makefile # CLI shortcuts for common tasks 60 | ├── package.json # Scripts, dependencies 61 | ├── .dev.vars/.prod.vars # Environment variables 62 | └── README.md # Usage and deployment instructions 63 | ``` 64 | 65 | ## Environment Configuration 66 | 67 | - **Development:** Runs on port 8788 (configurable in `wrangler.jsonc`) 68 | - **Production:** Deployed as Cloudflare Worker 69 | 70 | ### Setup Instructions 71 | 72 | 1. **Install dependencies:** 73 | 74 | `make init` 75 | 76 | 2. **Configure environment:** 77 | 78 | Create `.dev.vars` file with required variables: 79 | 80 | ``` 81 | BEARER_TOKEN=your_bearer_token_for_api_security 82 | GOOGLE_AI_STUDIO_API_KEY=your_google_ai_studio_api_key 83 | CLOUDFLARE_ACCOUNT_ID=your_cloudflare_account_id 84 | CLOUDFLARE_AI_GATEWAY_ID=your_gateway_id 85 | CLOUDFLARE_AI_GATEWAY_BASE_URL=https://gateway.ai.cloudflare.com/v1 86 | ``` 87 | 88 | For production, set secrets via Wrangler: 89 | 90 | ```bash 91 | wrangler secret put BEARER_TOKEN 92 | wrangler secret put GOOGLE_AI_STUDIO_API_KEY 93 | ``` 94 | 95 | 3. **Development:** 96 | 97 | `make dev` - Runs development server on port 8788 98 | 99 | 4. **Deploy:** 100 | 101 | `make deploy` - Deploys to Cloudflare Workers 102 | 103 | ## Common Commands 104 | 105 | The following Makefile commands are available for development, formatting, testing, and deployment: 106 | 107 | | Command | Description | 108 | |------------------------|---------------------------------------------| 109 | | `make init` | Initialize the project (install dependencies) | 110 | | `make update` | Update dependencies to latest versions | 111 | | `make dev` | Run development server with hot reloading | 112 | | `make format` | Format the codebase using Biome | 113 | | `make lint` | Lint the codebase using Biome | 114 | | `make check` | Check codebase for issues without fixing | 115 | | `make types` | Generate worker-configuration.d.ts file for TypeScript types | 116 | | `make cloc` | Count lines of code in src/ | 117 | | `make deploy` | Deploy to Cloudflare Workers | 118 | 119 | ## Middleware Stack 120 | 121 | The application uses a comprehensive middleware stack for security, performance, and functionality: 122 | 123 | ### Security Middleware 124 | 125 | - **Bearer Authentication:** Token-based authentication for protected routes (`/api/v1/*`) 126 | - **Secure Headers:** Security headers for production (via `hono/secure-headers`) 127 | - **CORS:** Manual CORS configuration allowing origin-based access with credentials 128 | 129 | ### Custom Middleware 130 | 131 | - **API Key Provider:** Resolves API keys from multiple sources (environment variables, headers, request body) and injects provider configuration into context 132 | 133 | ## Error Handling 134 | 135 | The worker implements structured error handling using Hono's `HTTPException` for consistent error responses: 136 | 137 | - **HTTPException:** Structured error responses with appropriate status codes 138 | - **Global Error Handler:** Centralized error handling with logging in `src/index.ts` 139 | - **Validation Errors:** Detailed validation error responses from Zod schemas 140 | - **Provider Errors:** Errors from AI providers are caught and returned as appropriate HTTP status codes 141 | 142 | ## Request Validation with Zod 143 | 144 | The API uses `@hono/zod-validator` middleware for type-safe request validation. This approach provides automatic validation, type inference, and error handling. 145 | 146 | ### Implementation Pattern 147 | 148 | ```typescript 149 | import { zValidator } from "@hono/zod-validator"; 150 | import { InferenceRequest } from "../dtos"; 151 | 152 | // Apply validation middleware 153 | completion.post( 154 | "/", 155 | zValidator("json", InferenceRequest), 156 | apiKeyProvider, 157 | async (c) => { 158 | // Access validated data with full type safety 159 | const request = c.req.valid('json'); 160 | // TypeScript knows the exact shape of request 161 | } 162 | ); 163 | ``` 164 | 165 | ### Validation Types 166 | 167 | - **JSON Body:** `zValidator('json', schema)` for POST request bodies 168 | - **Path Parameters:** `zValidator('param', schema)` for URL path parameters (if needed) 169 | - **Query Parameters:** `zValidator('query', schema)` for query string parameters (if needed) 170 | 171 | ### Request Schema Features 172 | 173 | The `InferenceRequest` schema in `src/dtos/request.ts` includes: 174 | 175 | - Message validation with role and content (text or multimodal) 176 | - Automatic system message injection if missing 177 | - Temperature validation (0-2 range) 178 | - Provider and model enum validation 179 | - Image data URL validation for multimodal content 180 | - Content length limits (200,000 characters) 181 | 182 | ## Provider Abstraction Pattern 183 | 184 | The API uses a provider abstraction pattern to support multiple AI backends. This allows easy addition of new providers without changing route handlers. 185 | 186 | ### Base Provider Interface 187 | 188 | All providers extend `InferenceProvider` from `src/services/providers/baseInferenceProvider.ts`: 189 | 190 | ```typescript 191 | export abstract class InferenceProvider { 192 | constructor(protected inferenceConfig: InferenceParams) {} 193 | abstract runInference(request: InferenceRequestType): Promise; 194 | } 195 | ``` 196 | 197 | ### Provider Registry 198 | 199 | Providers are registered in `src/services/inferenceService.ts`: 200 | 201 | ```typescript 202 | const ProviderRegistry: Record InferenceProvider> = { 203 | [Provider.GoogleAIStudio]: GoogleGenAIProvider, 204 | }; 205 | ``` 206 | 207 | ### Adding a New Provider 208 | 209 | 1. Create a new provider class in `src/services/providers/` extending `InferenceProvider` 210 | 2. Implement the `runInference` method with provider-specific logic 211 | 3. Register the provider in `ProviderRegistry` in `inferenceService.ts` 212 | 4. Add the provider enum value to `src/enums/provider.ts` 213 | 5. Add supported models to `src/enums/model.ts` 214 | 215 | ## Coding Conventions 216 | 217 | - Use **strict TypeScript** everywhere with proper type annotations 218 | - **Variables:** Use `camelCase` for all variables and functions (e.g., `apiKey`, `runInference()`) 219 | - **Enums:** Enum names in `PascalCase`, enum members in `CONSTANT_CASE` (e.g., `enum Provider { GoogleAIStudio = "google-ai-studio" }`) 220 | - All API endpoints must use **`zValidator` middleware** for request validation 221 | - Use **HTTPException** for structured error handling with appropriate status codes 222 | - **Bearer authentication** required on all protected routes (`/api/v1/*`) 223 | - **CORS middleware** must be configured first to handle preflight requests 224 | - **Error handling:** Return appropriate HTTP status codes and structured error messages 225 | - **Formatting:** Enforced by Biome (spaces, double quotes) 226 | - **Route organization:** Keep route handlers modular and focused on single responsibilities 227 | - **Service integration:** Use provider abstraction for AI service communication 228 | - **No business logic in routes:** Routes should only validate and delegate to services 229 | 230 | ## Best Practices 231 | 232 | - Always validate request data using Zod schemas before processing 233 | - Always use DTO objects for data propagation during runtime 234 | - Use proper HTTP status codes for different error scenarios 235 | - Implement comprehensive error handling with meaningful error messages 236 | - Keep route handlers focused and delegate business logic to services 237 | - Use environment variables for configuration and secrets (never hardcode) 238 | - Always run lint and format before committing (`make lint && make format`) 239 | - Use Makefile for common tasks to ensure consistency 240 | - Follow RESTful API design principles 241 | - Implement proper CORS configuration for production 242 | - Use compression middleware for better performance 243 | - Set appropriate request body size limits 244 | - Implement proper authentication flow with Bearer tokens 245 | - Use retry logic for external API calls (see `src/utils/retry.ts`) 246 | - Validate image data URLs before processing multimodal requests 247 | 248 | ## Authentication Flow 249 | 250 | The API uses Bearer token authentication with the following flow: 251 | 252 | 1. **Token Configuration:** Bearer token is set via environment variable `BEARER_TOKEN` or Wrangler secret 253 | 2. **API Requests:** Clients include Bearer token in `Authorization: Bearer ` header 254 | 3. **Token Validation:** `hono/bearer-auth` middleware validates tokens on protected routes (`/api/v1/*`) 255 | 4. **API Key Resolution:** For AI provider API keys, the `apiKeyProvider` middleware resolves keys from: 256 | 257 | - Request body `apiKey` field (highest priority) 258 | - `X-API-Key` header 259 | - Environment variables (e.g., `GOOGLE_AI_STUDIO_API_KEY`) 260 | 261 | ## API Key Management 262 | 263 | The `apiKeyProvider` middleware provides flexible API key resolution: 264 | 265 | 1. **Priority Order:** 266 | 267 | - Request body `apiKey` field 268 | - `X-API-Key` header 269 | - Environment variable (provider-specific, e.g., `GOOGLE_AI_STUDIO_API_KEY`) 270 | 271 | 2. **Environment Variable Naming:** 272 | 273 | - Format: `{PROVIDER}_API_KEY` (uppercase, underscores) 274 | - Example: `GOOGLE_AI_STUDIO_API_KEY` 275 | 276 | 3. **Configuration Injection:** 277 | 278 | - Resolved API key and configuration are injected into context via `c.set("AIProviderConfig", fullConfig)` 279 | - Routes can access via `c.get("AIProviderConfig")` 280 | 281 | ## Contribution 282 | 283 | - Follow all coding conventions and rules 284 | - Ensure all changes pass lint, format, and tests 285 | - Update this documentation when adding new endpoints or functionality 286 | - Use proper error handling and status codes 287 | - Validate all request/response data with Zod schemas 288 | - Follow RESTful API design principles 289 | - Implement proper CORS configuration for new endpoints 290 | - Add new providers by extending `InferenceProvider` base class 291 | - Update provider registry when adding new providers 292 | - Test multimodal requests with image data URLs 293 | - Use retry logic for external API calls 294 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | dependencies: 11 | '@google/genai': 12 | specifier: ^1.31.0 13 | version: 1.31.0 14 | '@hono/zod-validator': 15 | specifier: ^0.7.5 16 | version: 0.7.5(hono@4.10.7)(zod@4.1.13) 17 | dotenv: 18 | specifier: ^17.2.3 19 | version: 17.2.3 20 | hono: 21 | specifier: ^4.10.7 22 | version: 4.10.7 23 | openai: 24 | specifier: ^6.10.0 25 | version: 6.10.0(ws@8.18.3)(zod@4.1.13) 26 | zod: 27 | specifier: ^4.1.13 28 | version: 4.1.13 29 | devDependencies: 30 | '@biomejs/biome': 31 | specifier: 2.2.4 32 | version: 2.2.4 33 | '@cloudflare/vitest-pool-workers': 34 | specifier: ^0.10.14 35 | version: 0.10.14(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4(@types/node@24.10.1)) 36 | '@types/node': 37 | specifier: ^24.10.1 38 | version: 24.10.1 39 | cloc: 40 | specifier: 2.6.0-cloc 41 | version: 2.6.0-cloc 42 | typescript: 43 | specifier: 5.9.2 44 | version: 5.9.2 45 | vitest: 46 | specifier: ~3.2.4 47 | version: 3.2.4(@types/node@24.10.1) 48 | wrangler: 49 | specifier: ^4.53.0 50 | version: 4.53.0 51 | 52 | packages: 53 | 54 | '@biomejs/biome@2.2.4': 55 | resolution: {integrity: sha512-TBHU5bUy/Ok6m8c0y3pZiuO/BZoY/OcGxoLlrfQof5s8ISVwbVBdFINPQZyFfKwil8XibYWb7JMwnT8wT4WVPg==} 56 | engines: {node: '>=14.21.3'} 57 | hasBin: true 58 | 59 | '@biomejs/cli-darwin-arm64@2.2.4': 60 | resolution: {integrity: sha512-RJe2uiyaloN4hne4d2+qVj3d3gFJFbmrr5PYtkkjei1O9c+BjGXgpUPVbi8Pl8syumhzJjFsSIYkcLt2VlVLMA==} 61 | engines: {node: '>=14.21.3'} 62 | cpu: [arm64] 63 | os: [darwin] 64 | 65 | '@biomejs/cli-darwin-x64@2.2.4': 66 | resolution: {integrity: sha512-cFsdB4ePanVWfTnPVaUX+yr8qV8ifxjBKMkZwN7gKb20qXPxd/PmwqUH8mY5wnM9+U0QwM76CxFyBRJhC9tQwg==} 67 | engines: {node: '>=14.21.3'} 68 | cpu: [x64] 69 | os: [darwin] 70 | 71 | '@biomejs/cli-linux-arm64-musl@2.2.4': 72 | resolution: {integrity: sha512-7TNPkMQEWfjvJDaZRSkDCPT/2r5ESFPKx+TEev+I2BXDGIjfCZk2+b88FOhnJNHtksbOZv8ZWnxrA5gyTYhSsQ==} 73 | engines: {node: '>=14.21.3'} 74 | cpu: [arm64] 75 | os: [linux] 76 | 77 | '@biomejs/cli-linux-arm64@2.2.4': 78 | resolution: {integrity: sha512-M/Iz48p4NAzMXOuH+tsn5BvG/Jb07KOMTdSVwJpicmhN309BeEyRyQX+n1XDF0JVSlu28+hiTQ2L4rZPvu7nMw==} 79 | engines: {node: '>=14.21.3'} 80 | cpu: [arm64] 81 | os: [linux] 82 | 83 | '@biomejs/cli-linux-x64-musl@2.2.4': 84 | resolution: {integrity: sha512-m41nFDS0ksXK2gwXL6W6yZTYPMH0LughqbsxInSKetoH6morVj43szqKx79Iudkp8WRT5SxSh7qVb8KCUiewGg==} 85 | engines: {node: '>=14.21.3'} 86 | cpu: [x64] 87 | os: [linux] 88 | 89 | '@biomejs/cli-linux-x64@2.2.4': 90 | resolution: {integrity: sha512-orr3nnf2Dpb2ssl6aihQtvcKtLySLta4E2UcXdp7+RTa7mfJjBgIsbS0B9GC8gVu0hjOu021aU8b3/I1tn+pVQ==} 91 | engines: {node: '>=14.21.3'} 92 | cpu: [x64] 93 | os: [linux] 94 | 95 | '@biomejs/cli-win32-arm64@2.2.4': 96 | resolution: {integrity: sha512-NXnfTeKHDFUWfxAefa57DiGmu9VyKi0cDqFpdI+1hJWQjGJhJutHPX0b5m+eXvTKOaf+brU+P0JrQAZMb5yYaQ==} 97 | engines: {node: '>=14.21.3'} 98 | cpu: [arm64] 99 | os: [win32] 100 | 101 | '@biomejs/cli-win32-x64@2.2.4': 102 | resolution: {integrity: sha512-3Y4V4zVRarVh/B/eSHczR4LYoSVyv3Dfuvm3cWs5w/HScccS0+Wt/lHOcDTRYeHjQmMYVC3rIRWqyN2EI52+zg==} 103 | engines: {node: '>=14.21.3'} 104 | cpu: [x64] 105 | os: [win32] 106 | 107 | '@cloudflare/kv-asset-handler@0.4.1': 108 | resolution: {integrity: sha512-Nu8ahitGFFJztxUml9oD/DLb7Z28C8cd8F46IVQ7y5Btz575pvMY8AqZsXkX7Gds29eCKdMgIHjIvzskHgPSFg==} 109 | engines: {node: '>=18.0.0'} 110 | 111 | '@cloudflare/unenv-preset@2.7.13': 112 | resolution: {integrity: sha512-NulO1H8R/DzsJguLC0ndMuk4Ufv0KSlN+E54ay9rn9ZCQo0kpAPwwh3LhgpZ96a3Dr6L9LqW57M4CqC34iLOvw==} 113 | peerDependencies: 114 | unenv: 2.0.0-rc.24 115 | workerd: ^1.20251202.0 116 | peerDependenciesMeta: 117 | workerd: 118 | optional: true 119 | 120 | '@cloudflare/vitest-pool-workers@0.10.14': 121 | resolution: {integrity: sha512-RUUEuCbSnX7V+h/7yXDXQXaOmCST9ZkZcD0jtavt6/tIHcRfRDLY2FUNrKfZBM3b3ECpLTsAok8jHABfmh29Pw==} 122 | peerDependencies: 123 | '@vitest/runner': 2.0.x - 3.2.x 124 | '@vitest/snapshot': 2.0.x - 3.2.x 125 | vitest: 2.0.x - 3.2.x 126 | 127 | '@cloudflare/workerd-darwin-64@1.20251202.0': 128 | resolution: {integrity: sha512-/uvEAWEukTWb1geHhbjGUeZqcSSSyYzp0mvoPUBl+l0ont4NVGao3fgwM0q8wtKvgoKCHSG6zcG23wj9Opj3Nw==} 129 | engines: {node: '>=16'} 130 | cpu: [x64] 131 | os: [darwin] 132 | 133 | '@cloudflare/workerd-darwin-arm64@1.20251202.0': 134 | resolution: {integrity: sha512-f52xRvcI9cWRd6400EZStRtXiRC5XKEud7K5aFIbbUv0VeINltujFQQ9nHWtsF6g1quIXWkjhh5u01gPAYNNXA==} 135 | engines: {node: '>=16'} 136 | cpu: [arm64] 137 | os: [darwin] 138 | 139 | '@cloudflare/workerd-linux-64@1.20251202.0': 140 | resolution: {integrity: sha512-HYXinF5RBH7oXbsFUMmwKCj+WltpYbf5mRKUBG5v3EuPhUjSIFB84U+58pDyfBJjcynHdy3EtvTWcvh/+lcgow==} 141 | engines: {node: '>=16'} 142 | cpu: [x64] 143 | os: [linux] 144 | 145 | '@cloudflare/workerd-linux-arm64@1.20251202.0': 146 | resolution: {integrity: sha512-++L02Jdoxz7hEA9qDaQjbVU1RzQS+S+eqIi22DkPe2Tgiq2M3UfNpeu+75k5L9DGRIkZPYvwMBMbcmKvQqdIIg==} 147 | engines: {node: '>=16'} 148 | cpu: [arm64] 149 | os: [linux] 150 | 151 | '@cloudflare/workerd-windows-64@1.20251202.0': 152 | resolution: {integrity: sha512-gzeU6eDydTi7ib+Q9DD/c0hpXtqPucnHk2tfGU03mljPObYxzMkkPGgB5qxpksFvub3y4K0ChjqYxGJB4F+j3g==} 153 | engines: {node: '>=16'} 154 | cpu: [x64] 155 | os: [win32] 156 | 157 | '@cspotcode/source-map-support@0.8.1': 158 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 159 | engines: {node: '>=12'} 160 | 161 | '@emnapi/runtime@1.7.1': 162 | resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} 163 | 164 | '@esbuild/aix-ppc64@0.25.12': 165 | resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} 166 | engines: {node: '>=18'} 167 | cpu: [ppc64] 168 | os: [aix] 169 | 170 | '@esbuild/aix-ppc64@0.27.0': 171 | resolution: {integrity: sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==} 172 | engines: {node: '>=18'} 173 | cpu: [ppc64] 174 | os: [aix] 175 | 176 | '@esbuild/android-arm64@0.25.12': 177 | resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} 178 | engines: {node: '>=18'} 179 | cpu: [arm64] 180 | os: [android] 181 | 182 | '@esbuild/android-arm64@0.27.0': 183 | resolution: {integrity: sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==} 184 | engines: {node: '>=18'} 185 | cpu: [arm64] 186 | os: [android] 187 | 188 | '@esbuild/android-arm@0.25.12': 189 | resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} 190 | engines: {node: '>=18'} 191 | cpu: [arm] 192 | os: [android] 193 | 194 | '@esbuild/android-arm@0.27.0': 195 | resolution: {integrity: sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==} 196 | engines: {node: '>=18'} 197 | cpu: [arm] 198 | os: [android] 199 | 200 | '@esbuild/android-x64@0.25.12': 201 | resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} 202 | engines: {node: '>=18'} 203 | cpu: [x64] 204 | os: [android] 205 | 206 | '@esbuild/android-x64@0.27.0': 207 | resolution: {integrity: sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==} 208 | engines: {node: '>=18'} 209 | cpu: [x64] 210 | os: [android] 211 | 212 | '@esbuild/darwin-arm64@0.25.12': 213 | resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} 214 | engines: {node: '>=18'} 215 | cpu: [arm64] 216 | os: [darwin] 217 | 218 | '@esbuild/darwin-arm64@0.27.0': 219 | resolution: {integrity: sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==} 220 | engines: {node: '>=18'} 221 | cpu: [arm64] 222 | os: [darwin] 223 | 224 | '@esbuild/darwin-x64@0.25.12': 225 | resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} 226 | engines: {node: '>=18'} 227 | cpu: [x64] 228 | os: [darwin] 229 | 230 | '@esbuild/darwin-x64@0.27.0': 231 | resolution: {integrity: sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==} 232 | engines: {node: '>=18'} 233 | cpu: [x64] 234 | os: [darwin] 235 | 236 | '@esbuild/freebsd-arm64@0.25.12': 237 | resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} 238 | engines: {node: '>=18'} 239 | cpu: [arm64] 240 | os: [freebsd] 241 | 242 | '@esbuild/freebsd-arm64@0.27.0': 243 | resolution: {integrity: sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==} 244 | engines: {node: '>=18'} 245 | cpu: [arm64] 246 | os: [freebsd] 247 | 248 | '@esbuild/freebsd-x64@0.25.12': 249 | resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} 250 | engines: {node: '>=18'} 251 | cpu: [x64] 252 | os: [freebsd] 253 | 254 | '@esbuild/freebsd-x64@0.27.0': 255 | resolution: {integrity: sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==} 256 | engines: {node: '>=18'} 257 | cpu: [x64] 258 | os: [freebsd] 259 | 260 | '@esbuild/linux-arm64@0.25.12': 261 | resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} 262 | engines: {node: '>=18'} 263 | cpu: [arm64] 264 | os: [linux] 265 | 266 | '@esbuild/linux-arm64@0.27.0': 267 | resolution: {integrity: sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==} 268 | engines: {node: '>=18'} 269 | cpu: [arm64] 270 | os: [linux] 271 | 272 | '@esbuild/linux-arm@0.25.12': 273 | resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} 274 | engines: {node: '>=18'} 275 | cpu: [arm] 276 | os: [linux] 277 | 278 | '@esbuild/linux-arm@0.27.0': 279 | resolution: {integrity: sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==} 280 | engines: {node: '>=18'} 281 | cpu: [arm] 282 | os: [linux] 283 | 284 | '@esbuild/linux-ia32@0.25.12': 285 | resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} 286 | engines: {node: '>=18'} 287 | cpu: [ia32] 288 | os: [linux] 289 | 290 | '@esbuild/linux-ia32@0.27.0': 291 | resolution: {integrity: sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==} 292 | engines: {node: '>=18'} 293 | cpu: [ia32] 294 | os: [linux] 295 | 296 | '@esbuild/linux-loong64@0.25.12': 297 | resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} 298 | engines: {node: '>=18'} 299 | cpu: [loong64] 300 | os: [linux] 301 | 302 | '@esbuild/linux-loong64@0.27.0': 303 | resolution: {integrity: sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==} 304 | engines: {node: '>=18'} 305 | cpu: [loong64] 306 | os: [linux] 307 | 308 | '@esbuild/linux-mips64el@0.25.12': 309 | resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} 310 | engines: {node: '>=18'} 311 | cpu: [mips64el] 312 | os: [linux] 313 | 314 | '@esbuild/linux-mips64el@0.27.0': 315 | resolution: {integrity: sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==} 316 | engines: {node: '>=18'} 317 | cpu: [mips64el] 318 | os: [linux] 319 | 320 | '@esbuild/linux-ppc64@0.25.12': 321 | resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} 322 | engines: {node: '>=18'} 323 | cpu: [ppc64] 324 | os: [linux] 325 | 326 | '@esbuild/linux-ppc64@0.27.0': 327 | resolution: {integrity: sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==} 328 | engines: {node: '>=18'} 329 | cpu: [ppc64] 330 | os: [linux] 331 | 332 | '@esbuild/linux-riscv64@0.25.12': 333 | resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} 334 | engines: {node: '>=18'} 335 | cpu: [riscv64] 336 | os: [linux] 337 | 338 | '@esbuild/linux-riscv64@0.27.0': 339 | resolution: {integrity: sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==} 340 | engines: {node: '>=18'} 341 | cpu: [riscv64] 342 | os: [linux] 343 | 344 | '@esbuild/linux-s390x@0.25.12': 345 | resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} 346 | engines: {node: '>=18'} 347 | cpu: [s390x] 348 | os: [linux] 349 | 350 | '@esbuild/linux-s390x@0.27.0': 351 | resolution: {integrity: sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==} 352 | engines: {node: '>=18'} 353 | cpu: [s390x] 354 | os: [linux] 355 | 356 | '@esbuild/linux-x64@0.25.12': 357 | resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} 358 | engines: {node: '>=18'} 359 | cpu: [x64] 360 | os: [linux] 361 | 362 | '@esbuild/linux-x64@0.27.0': 363 | resolution: {integrity: sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==} 364 | engines: {node: '>=18'} 365 | cpu: [x64] 366 | os: [linux] 367 | 368 | '@esbuild/netbsd-arm64@0.25.12': 369 | resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} 370 | engines: {node: '>=18'} 371 | cpu: [arm64] 372 | os: [netbsd] 373 | 374 | '@esbuild/netbsd-arm64@0.27.0': 375 | resolution: {integrity: sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==} 376 | engines: {node: '>=18'} 377 | cpu: [arm64] 378 | os: [netbsd] 379 | 380 | '@esbuild/netbsd-x64@0.25.12': 381 | resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} 382 | engines: {node: '>=18'} 383 | cpu: [x64] 384 | os: [netbsd] 385 | 386 | '@esbuild/netbsd-x64@0.27.0': 387 | resolution: {integrity: sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==} 388 | engines: {node: '>=18'} 389 | cpu: [x64] 390 | os: [netbsd] 391 | 392 | '@esbuild/openbsd-arm64@0.25.12': 393 | resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} 394 | engines: {node: '>=18'} 395 | cpu: [arm64] 396 | os: [openbsd] 397 | 398 | '@esbuild/openbsd-arm64@0.27.0': 399 | resolution: {integrity: sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==} 400 | engines: {node: '>=18'} 401 | cpu: [arm64] 402 | os: [openbsd] 403 | 404 | '@esbuild/openbsd-x64@0.25.12': 405 | resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} 406 | engines: {node: '>=18'} 407 | cpu: [x64] 408 | os: [openbsd] 409 | 410 | '@esbuild/openbsd-x64@0.27.0': 411 | resolution: {integrity: sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==} 412 | engines: {node: '>=18'} 413 | cpu: [x64] 414 | os: [openbsd] 415 | 416 | '@esbuild/openharmony-arm64@0.25.12': 417 | resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} 418 | engines: {node: '>=18'} 419 | cpu: [arm64] 420 | os: [openharmony] 421 | 422 | '@esbuild/openharmony-arm64@0.27.0': 423 | resolution: {integrity: sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==} 424 | engines: {node: '>=18'} 425 | cpu: [arm64] 426 | os: [openharmony] 427 | 428 | '@esbuild/sunos-x64@0.25.12': 429 | resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} 430 | engines: {node: '>=18'} 431 | cpu: [x64] 432 | os: [sunos] 433 | 434 | '@esbuild/sunos-x64@0.27.0': 435 | resolution: {integrity: sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==} 436 | engines: {node: '>=18'} 437 | cpu: [x64] 438 | os: [sunos] 439 | 440 | '@esbuild/win32-arm64@0.25.12': 441 | resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} 442 | engines: {node: '>=18'} 443 | cpu: [arm64] 444 | os: [win32] 445 | 446 | '@esbuild/win32-arm64@0.27.0': 447 | resolution: {integrity: sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==} 448 | engines: {node: '>=18'} 449 | cpu: [arm64] 450 | os: [win32] 451 | 452 | '@esbuild/win32-ia32@0.25.12': 453 | resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} 454 | engines: {node: '>=18'} 455 | cpu: [ia32] 456 | os: [win32] 457 | 458 | '@esbuild/win32-ia32@0.27.0': 459 | resolution: {integrity: sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==} 460 | engines: {node: '>=18'} 461 | cpu: [ia32] 462 | os: [win32] 463 | 464 | '@esbuild/win32-x64@0.25.12': 465 | resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} 466 | engines: {node: '>=18'} 467 | cpu: [x64] 468 | os: [win32] 469 | 470 | '@esbuild/win32-x64@0.27.0': 471 | resolution: {integrity: sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==} 472 | engines: {node: '>=18'} 473 | cpu: [x64] 474 | os: [win32] 475 | 476 | '@google/genai@1.31.0': 477 | resolution: {integrity: sha512-rK0RKXxNkbK35eDl+G651SxtxwHNEOogjyeZJUJe+Ed4yxu3xy5ufCiU0+QLT7xo4M9Spey8OAYfD8LPRlYBKw==} 478 | engines: {node: '>=20.0.0'} 479 | peerDependencies: 480 | '@modelcontextprotocol/sdk': ^1.20.1 481 | peerDependenciesMeta: 482 | '@modelcontextprotocol/sdk': 483 | optional: true 484 | 485 | '@hono/zod-validator@0.7.5': 486 | resolution: {integrity: sha512-n4l4hutkfYU07PzRUHBOVzUEn38VSfrh+UVE5d0w4lyfWDOEhzxIupqo5iakRiJL44c3vTuFJBvcmUl8b9agIA==} 487 | peerDependencies: 488 | hono: '>=3.9.0' 489 | zod: ^3.25.0 || ^4.0.0 490 | 491 | '@img/sharp-darwin-arm64@0.33.5': 492 | resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} 493 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 494 | cpu: [arm64] 495 | os: [darwin] 496 | 497 | '@img/sharp-darwin-x64@0.33.5': 498 | resolution: {integrity: sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==} 499 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 500 | cpu: [x64] 501 | os: [darwin] 502 | 503 | '@img/sharp-libvips-darwin-arm64@1.0.4': 504 | resolution: {integrity: sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==} 505 | cpu: [arm64] 506 | os: [darwin] 507 | 508 | '@img/sharp-libvips-darwin-x64@1.0.4': 509 | resolution: {integrity: sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==} 510 | cpu: [x64] 511 | os: [darwin] 512 | 513 | '@img/sharp-libvips-linux-arm64@1.0.4': 514 | resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} 515 | cpu: [arm64] 516 | os: [linux] 517 | 518 | '@img/sharp-libvips-linux-arm@1.0.5': 519 | resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} 520 | cpu: [arm] 521 | os: [linux] 522 | 523 | '@img/sharp-libvips-linux-s390x@1.0.4': 524 | resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} 525 | cpu: [s390x] 526 | os: [linux] 527 | 528 | '@img/sharp-libvips-linux-x64@1.0.4': 529 | resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} 530 | cpu: [x64] 531 | os: [linux] 532 | 533 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 534 | resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} 535 | cpu: [arm64] 536 | os: [linux] 537 | 538 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 539 | resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} 540 | cpu: [x64] 541 | os: [linux] 542 | 543 | '@img/sharp-linux-arm64@0.33.5': 544 | resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} 545 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 546 | cpu: [arm64] 547 | os: [linux] 548 | 549 | '@img/sharp-linux-arm@0.33.5': 550 | resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} 551 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 552 | cpu: [arm] 553 | os: [linux] 554 | 555 | '@img/sharp-linux-s390x@0.33.5': 556 | resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} 557 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 558 | cpu: [s390x] 559 | os: [linux] 560 | 561 | '@img/sharp-linux-x64@0.33.5': 562 | resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} 563 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 564 | cpu: [x64] 565 | os: [linux] 566 | 567 | '@img/sharp-linuxmusl-arm64@0.33.5': 568 | resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} 569 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 570 | cpu: [arm64] 571 | os: [linux] 572 | 573 | '@img/sharp-linuxmusl-x64@0.33.5': 574 | resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} 575 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 576 | cpu: [x64] 577 | os: [linux] 578 | 579 | '@img/sharp-wasm32@0.33.5': 580 | resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} 581 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 582 | cpu: [wasm32] 583 | 584 | '@img/sharp-win32-ia32@0.33.5': 585 | resolution: {integrity: sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==} 586 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 587 | cpu: [ia32] 588 | os: [win32] 589 | 590 | '@img/sharp-win32-x64@0.33.5': 591 | resolution: {integrity: sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==} 592 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 593 | cpu: [x64] 594 | os: [win32] 595 | 596 | '@isaacs/cliui@8.0.2': 597 | resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 598 | engines: {node: '>=12'} 599 | 600 | '@jridgewell/resolve-uri@3.1.2': 601 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 602 | engines: {node: '>=6.0.0'} 603 | 604 | '@jridgewell/sourcemap-codec@1.5.5': 605 | resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} 606 | 607 | '@jridgewell/trace-mapping@0.3.9': 608 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 609 | 610 | '@pkgjs/parseargs@0.11.0': 611 | resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 612 | engines: {node: '>=14'} 613 | 614 | '@poppinss/colors@4.1.5': 615 | resolution: {integrity: sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==} 616 | 617 | '@poppinss/dumper@0.6.5': 618 | resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} 619 | 620 | '@poppinss/exception@1.2.2': 621 | resolution: {integrity: sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==} 622 | 623 | '@rollup/rollup-android-arm-eabi@4.53.3': 624 | resolution: {integrity: sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==} 625 | cpu: [arm] 626 | os: [android] 627 | 628 | '@rollup/rollup-android-arm64@4.53.3': 629 | resolution: {integrity: sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==} 630 | cpu: [arm64] 631 | os: [android] 632 | 633 | '@rollup/rollup-darwin-arm64@4.53.3': 634 | resolution: {integrity: sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==} 635 | cpu: [arm64] 636 | os: [darwin] 637 | 638 | '@rollup/rollup-darwin-x64@4.53.3': 639 | resolution: {integrity: sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==} 640 | cpu: [x64] 641 | os: [darwin] 642 | 643 | '@rollup/rollup-freebsd-arm64@4.53.3': 644 | resolution: {integrity: sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==} 645 | cpu: [arm64] 646 | os: [freebsd] 647 | 648 | '@rollup/rollup-freebsd-x64@4.53.3': 649 | resolution: {integrity: sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==} 650 | cpu: [x64] 651 | os: [freebsd] 652 | 653 | '@rollup/rollup-linux-arm-gnueabihf@4.53.3': 654 | resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} 655 | cpu: [arm] 656 | os: [linux] 657 | 658 | '@rollup/rollup-linux-arm-musleabihf@4.53.3': 659 | resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} 660 | cpu: [arm] 661 | os: [linux] 662 | 663 | '@rollup/rollup-linux-arm64-gnu@4.53.3': 664 | resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} 665 | cpu: [arm64] 666 | os: [linux] 667 | 668 | '@rollup/rollup-linux-arm64-musl@4.53.3': 669 | resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} 670 | cpu: [arm64] 671 | os: [linux] 672 | 673 | '@rollup/rollup-linux-loong64-gnu@4.53.3': 674 | resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} 675 | cpu: [loong64] 676 | os: [linux] 677 | 678 | '@rollup/rollup-linux-ppc64-gnu@4.53.3': 679 | resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} 680 | cpu: [ppc64] 681 | os: [linux] 682 | 683 | '@rollup/rollup-linux-riscv64-gnu@4.53.3': 684 | resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} 685 | cpu: [riscv64] 686 | os: [linux] 687 | 688 | '@rollup/rollup-linux-riscv64-musl@4.53.3': 689 | resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} 690 | cpu: [riscv64] 691 | os: [linux] 692 | 693 | '@rollup/rollup-linux-s390x-gnu@4.53.3': 694 | resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} 695 | cpu: [s390x] 696 | os: [linux] 697 | 698 | '@rollup/rollup-linux-x64-gnu@4.53.3': 699 | resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} 700 | cpu: [x64] 701 | os: [linux] 702 | 703 | '@rollup/rollup-linux-x64-musl@4.53.3': 704 | resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} 705 | cpu: [x64] 706 | os: [linux] 707 | 708 | '@rollup/rollup-openharmony-arm64@4.53.3': 709 | resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} 710 | cpu: [arm64] 711 | os: [openharmony] 712 | 713 | '@rollup/rollup-win32-arm64-msvc@4.53.3': 714 | resolution: {integrity: sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==} 715 | cpu: [arm64] 716 | os: [win32] 717 | 718 | '@rollup/rollup-win32-ia32-msvc@4.53.3': 719 | resolution: {integrity: sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==} 720 | cpu: [ia32] 721 | os: [win32] 722 | 723 | '@rollup/rollup-win32-x64-gnu@4.53.3': 724 | resolution: {integrity: sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==} 725 | cpu: [x64] 726 | os: [win32] 727 | 728 | '@rollup/rollup-win32-x64-msvc@4.53.3': 729 | resolution: {integrity: sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==} 730 | cpu: [x64] 731 | os: [win32] 732 | 733 | '@sindresorhus/is@7.1.1': 734 | resolution: {integrity: sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==} 735 | engines: {node: '>=18'} 736 | 737 | '@speed-highlight/core@1.2.12': 738 | resolution: {integrity: sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==} 739 | 740 | '@types/chai@5.2.3': 741 | resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} 742 | 743 | '@types/deep-eql@4.0.2': 744 | resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} 745 | 746 | '@types/estree@1.0.8': 747 | resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} 748 | 749 | '@types/node@24.10.1': 750 | resolution: {integrity: sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==} 751 | 752 | '@vitest/expect@3.2.4': 753 | resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} 754 | 755 | '@vitest/mocker@3.2.4': 756 | resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} 757 | peerDependencies: 758 | msw: ^2.4.9 759 | vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 760 | peerDependenciesMeta: 761 | msw: 762 | optional: true 763 | vite: 764 | optional: true 765 | 766 | '@vitest/pretty-format@3.2.4': 767 | resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} 768 | 769 | '@vitest/runner@3.2.4': 770 | resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} 771 | 772 | '@vitest/snapshot@3.2.4': 773 | resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} 774 | 775 | '@vitest/spy@3.2.4': 776 | resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} 777 | 778 | '@vitest/utils@3.2.4': 779 | resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} 780 | 781 | acorn-walk@8.3.2: 782 | resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} 783 | engines: {node: '>=0.4.0'} 784 | 785 | acorn@8.14.0: 786 | resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} 787 | engines: {node: '>=0.4.0'} 788 | hasBin: true 789 | 790 | agent-base@7.1.4: 791 | resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} 792 | engines: {node: '>= 14'} 793 | 794 | ansi-regex@5.0.1: 795 | resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 796 | engines: {node: '>=8'} 797 | 798 | ansi-regex@6.2.2: 799 | resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} 800 | engines: {node: '>=12'} 801 | 802 | ansi-styles@4.3.0: 803 | resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 804 | engines: {node: '>=8'} 805 | 806 | ansi-styles@6.2.3: 807 | resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} 808 | engines: {node: '>=12'} 809 | 810 | assertion-error@2.0.1: 811 | resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} 812 | engines: {node: '>=12'} 813 | 814 | balanced-match@1.0.2: 815 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 816 | 817 | base64-js@1.5.1: 818 | resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} 819 | 820 | bignumber.js@9.3.1: 821 | resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} 822 | 823 | birpc@0.2.14: 824 | resolution: {integrity: sha512-37FHE8rqsYM5JEKCnXFyHpBCzvgHEExwVVTq+nUmloInU7l8ezD1TpOhKpS8oe1DTYFqEK27rFZVKG43oTqXRA==} 825 | 826 | blake3-wasm@2.1.5: 827 | resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} 828 | 829 | brace-expansion@2.0.2: 830 | resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} 831 | 832 | buffer-equal-constant-time@1.0.1: 833 | resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} 834 | 835 | cac@6.7.14: 836 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 837 | engines: {node: '>=8'} 838 | 839 | chai@5.3.3: 840 | resolution: {integrity: sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==} 841 | engines: {node: '>=18'} 842 | 843 | check-error@2.1.1: 844 | resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} 845 | engines: {node: '>= 16'} 846 | 847 | cjs-module-lexer@1.4.3: 848 | resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} 849 | 850 | cloc@2.6.0-cloc: 851 | resolution: {integrity: sha512-fCHPvFN6I7WmC005u99WqLxqWnWhEra0YCppc2k8lpRZrPFIEaHaNxgJ+funlSRIZMSIOqxwpt7pmEKDPFCz2g==} 852 | hasBin: true 853 | 854 | color-convert@2.0.1: 855 | resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 856 | engines: {node: '>=7.0.0'} 857 | 858 | color-name@1.1.4: 859 | resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 860 | 861 | color-string@1.9.1: 862 | resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} 863 | 864 | color@4.2.3: 865 | resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} 866 | engines: {node: '>=12.5.0'} 867 | 868 | cookie@1.1.1: 869 | resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} 870 | engines: {node: '>=18'} 871 | 872 | cross-spawn@7.0.6: 873 | resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} 874 | engines: {node: '>= 8'} 875 | 876 | data-uri-to-buffer@4.0.1: 877 | resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} 878 | engines: {node: '>= 12'} 879 | 880 | debug@4.4.3: 881 | resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} 882 | engines: {node: '>=6.0'} 883 | peerDependencies: 884 | supports-color: '*' 885 | peerDependenciesMeta: 886 | supports-color: 887 | optional: true 888 | 889 | deep-eql@5.0.2: 890 | resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} 891 | engines: {node: '>=6'} 892 | 893 | detect-libc@2.1.2: 894 | resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} 895 | engines: {node: '>=8'} 896 | 897 | devalue@5.5.0: 898 | resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==} 899 | 900 | dotenv@17.2.3: 901 | resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} 902 | engines: {node: '>=12'} 903 | 904 | eastasianwidth@0.2.0: 905 | resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 906 | 907 | ecdsa-sig-formatter@1.0.11: 908 | resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} 909 | 910 | emoji-regex@8.0.0: 911 | resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 912 | 913 | emoji-regex@9.2.2: 914 | resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 915 | 916 | error-stack-parser-es@1.0.5: 917 | resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} 918 | 919 | es-module-lexer@1.7.0: 920 | resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} 921 | 922 | esbuild@0.25.12: 923 | resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} 924 | engines: {node: '>=18'} 925 | hasBin: true 926 | 927 | esbuild@0.27.0: 928 | resolution: {integrity: sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==} 929 | engines: {node: '>=18'} 930 | hasBin: true 931 | 932 | estree-walker@3.0.3: 933 | resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} 934 | 935 | exit-hook@2.2.1: 936 | resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} 937 | engines: {node: '>=6'} 938 | 939 | expect-type@1.2.2: 940 | resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} 941 | engines: {node: '>=12.0.0'} 942 | 943 | extend@3.0.2: 944 | resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} 945 | 946 | fdir@6.5.0: 947 | resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} 948 | engines: {node: '>=12.0.0'} 949 | peerDependencies: 950 | picomatch: ^3 || ^4 951 | peerDependenciesMeta: 952 | picomatch: 953 | optional: true 954 | 955 | fetch-blob@3.2.0: 956 | resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} 957 | engines: {node: ^12.20 || >= 14.13} 958 | 959 | foreground-child@3.3.1: 960 | resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} 961 | engines: {node: '>=14'} 962 | 963 | formdata-polyfill@4.0.10: 964 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} 965 | engines: {node: '>=12.20.0'} 966 | 967 | fsevents@2.3.3: 968 | resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} 969 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 970 | os: [darwin] 971 | 972 | gaxios@7.1.3: 973 | resolution: {integrity: sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==} 974 | engines: {node: '>=18'} 975 | 976 | gcp-metadata@8.1.2: 977 | resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==} 978 | engines: {node: '>=18'} 979 | 980 | glob-to-regexp@0.4.1: 981 | resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} 982 | 983 | glob@10.5.0: 984 | resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} 985 | hasBin: true 986 | 987 | google-auth-library@10.5.0: 988 | resolution: {integrity: sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==} 989 | engines: {node: '>=18'} 990 | 991 | google-logging-utils@1.1.3: 992 | resolution: {integrity: sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==} 993 | engines: {node: '>=14'} 994 | 995 | gtoken@8.0.0: 996 | resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} 997 | engines: {node: '>=18'} 998 | 999 | hono@4.10.7: 1000 | resolution: {integrity: sha512-icXIITfw/07Q88nLSkB9aiUrd8rYzSweK681Kjo/TSggaGbOX4RRyxxm71v+3PC8C/j+4rlxGeoTRxQDkaJkUw==} 1001 | engines: {node: '>=16.9.0'} 1002 | 1003 | https-proxy-agent@7.0.6: 1004 | resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} 1005 | engines: {node: '>= 14'} 1006 | 1007 | is-arrayish@0.3.4: 1008 | resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} 1009 | 1010 | is-fullwidth-code-point@3.0.0: 1011 | resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 1012 | engines: {node: '>=8'} 1013 | 1014 | isexe@2.0.0: 1015 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 1016 | 1017 | jackspeak@3.4.3: 1018 | resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} 1019 | 1020 | js-tokens@9.0.1: 1021 | resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} 1022 | 1023 | json-bigint@1.0.0: 1024 | resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} 1025 | 1026 | jwa@2.0.1: 1027 | resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} 1028 | 1029 | jws@4.0.1: 1030 | resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} 1031 | 1032 | kleur@4.1.5: 1033 | resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} 1034 | engines: {node: '>=6'} 1035 | 1036 | loupe@3.2.1: 1037 | resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} 1038 | 1039 | lru-cache@10.4.3: 1040 | resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} 1041 | 1042 | magic-string@0.30.21: 1043 | resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} 1044 | 1045 | mime@3.0.0: 1046 | resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} 1047 | engines: {node: '>=10.0.0'} 1048 | hasBin: true 1049 | 1050 | miniflare@4.20251202.1: 1051 | resolution: {integrity: sha512-cRp2QNgnt9wpLMoNs4MOzzomyfe9UTS9sPRxIpUvxMl+mweCZ0FHpWWQvCnU7wWlfAP8VGZrHwqSsV5ERA6ahQ==} 1052 | engines: {node: '>=18.0.0'} 1053 | hasBin: true 1054 | 1055 | minimatch@9.0.5: 1056 | resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} 1057 | engines: {node: '>=16 || 14 >=14.17'} 1058 | 1059 | minipass@7.1.2: 1060 | resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} 1061 | engines: {node: '>=16 || 14 >=14.17'} 1062 | 1063 | ms@2.1.3: 1064 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 1065 | 1066 | nanoid@3.3.11: 1067 | resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} 1068 | engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} 1069 | hasBin: true 1070 | 1071 | node-domexception@1.0.0: 1072 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 1073 | engines: {node: '>=10.5.0'} 1074 | deprecated: Use your platform's native DOMException instead 1075 | 1076 | node-fetch@3.3.2: 1077 | resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} 1078 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 1079 | 1080 | openai@6.10.0: 1081 | resolution: {integrity: sha512-ITxOGo7rO3XRMiKA5l7tQ43iNNu+iXGFAcf2t+aWVzzqRaS0i7m1K2BhxNdaveB+5eENhO0VY1FkiZzhBk4v3A==} 1082 | hasBin: true 1083 | peerDependencies: 1084 | ws: ^8.18.0 1085 | zod: ^3.25 || ^4.0 1086 | peerDependenciesMeta: 1087 | ws: 1088 | optional: true 1089 | zod: 1090 | optional: true 1091 | 1092 | package-json-from-dist@1.0.1: 1093 | resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} 1094 | 1095 | path-key@3.1.1: 1096 | resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1097 | engines: {node: '>=8'} 1098 | 1099 | path-scurry@1.11.1: 1100 | resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} 1101 | engines: {node: '>=16 || 14 >=14.18'} 1102 | 1103 | path-to-regexp@6.3.0: 1104 | resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} 1105 | 1106 | pathe@2.0.3: 1107 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 1108 | 1109 | pathval@2.0.1: 1110 | resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} 1111 | engines: {node: '>= 14.16'} 1112 | 1113 | picocolors@1.1.1: 1114 | resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} 1115 | 1116 | picomatch@4.0.3: 1117 | resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} 1118 | engines: {node: '>=12'} 1119 | 1120 | postcss@8.5.6: 1121 | resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} 1122 | engines: {node: ^10 || ^12 || >=14} 1123 | 1124 | rimraf@5.0.10: 1125 | resolution: {integrity: sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==} 1126 | hasBin: true 1127 | 1128 | rollup@4.53.3: 1129 | resolution: {integrity: sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==} 1130 | engines: {node: '>=18.0.0', npm: '>=8.0.0'} 1131 | hasBin: true 1132 | 1133 | safe-buffer@5.2.1: 1134 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 1135 | 1136 | semver@7.7.3: 1137 | resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} 1138 | engines: {node: '>=10'} 1139 | hasBin: true 1140 | 1141 | sharp@0.33.5: 1142 | resolution: {integrity: sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==} 1143 | engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} 1144 | 1145 | shebang-command@2.0.0: 1146 | resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 1147 | engines: {node: '>=8'} 1148 | 1149 | shebang-regex@3.0.0: 1150 | resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1151 | engines: {node: '>=8'} 1152 | 1153 | siginfo@2.0.0: 1154 | resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} 1155 | 1156 | signal-exit@4.1.0: 1157 | resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 1158 | engines: {node: '>=14'} 1159 | 1160 | simple-swizzle@0.2.4: 1161 | resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} 1162 | 1163 | source-map-js@1.2.1: 1164 | resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} 1165 | engines: {node: '>=0.10.0'} 1166 | 1167 | stackback@0.0.2: 1168 | resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} 1169 | 1170 | std-env@3.10.0: 1171 | resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} 1172 | 1173 | stoppable@1.1.0: 1174 | resolution: {integrity: sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==} 1175 | engines: {node: '>=4', npm: '>=6'} 1176 | 1177 | string-width@4.2.3: 1178 | resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 1179 | engines: {node: '>=8'} 1180 | 1181 | string-width@5.1.2: 1182 | resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 1183 | engines: {node: '>=12'} 1184 | 1185 | strip-ansi@6.0.1: 1186 | resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1187 | engines: {node: '>=8'} 1188 | 1189 | strip-ansi@7.1.2: 1190 | resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} 1191 | engines: {node: '>=12'} 1192 | 1193 | strip-literal@3.1.0: 1194 | resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} 1195 | 1196 | supports-color@10.2.2: 1197 | resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} 1198 | engines: {node: '>=18'} 1199 | 1200 | tinybench@2.9.0: 1201 | resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} 1202 | 1203 | tinyexec@0.3.2: 1204 | resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} 1205 | 1206 | tinyglobby@0.2.15: 1207 | resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} 1208 | engines: {node: '>=12.0.0'} 1209 | 1210 | tinypool@1.1.1: 1211 | resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} 1212 | engines: {node: ^18.0.0 || >=20.0.0} 1213 | 1214 | tinyrainbow@2.0.0: 1215 | resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} 1216 | engines: {node: '>=14.0.0'} 1217 | 1218 | tinyspy@4.0.4: 1219 | resolution: {integrity: sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==} 1220 | engines: {node: '>=14.0.0'} 1221 | 1222 | tslib@2.8.1: 1223 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1224 | 1225 | typescript@5.9.2: 1226 | resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} 1227 | engines: {node: '>=14.17'} 1228 | hasBin: true 1229 | 1230 | undici-types@7.16.0: 1231 | resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} 1232 | 1233 | undici@7.14.0: 1234 | resolution: {integrity: sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==} 1235 | engines: {node: '>=20.18.1'} 1236 | 1237 | unenv@2.0.0-rc.24: 1238 | resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} 1239 | 1240 | vite-node@3.2.4: 1241 | resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} 1242 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 1243 | hasBin: true 1244 | 1245 | vite@7.2.6: 1246 | resolution: {integrity: sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==} 1247 | engines: {node: ^20.19.0 || >=22.12.0} 1248 | hasBin: true 1249 | peerDependencies: 1250 | '@types/node': ^20.19.0 || >=22.12.0 1251 | jiti: '>=1.21.0' 1252 | less: ^4.0.0 1253 | lightningcss: ^1.21.0 1254 | sass: ^1.70.0 1255 | sass-embedded: ^1.70.0 1256 | stylus: '>=0.54.8' 1257 | sugarss: ^5.0.0 1258 | terser: ^5.16.0 1259 | tsx: ^4.8.1 1260 | yaml: ^2.4.2 1261 | peerDependenciesMeta: 1262 | '@types/node': 1263 | optional: true 1264 | jiti: 1265 | optional: true 1266 | less: 1267 | optional: true 1268 | lightningcss: 1269 | optional: true 1270 | sass: 1271 | optional: true 1272 | sass-embedded: 1273 | optional: true 1274 | stylus: 1275 | optional: true 1276 | sugarss: 1277 | optional: true 1278 | terser: 1279 | optional: true 1280 | tsx: 1281 | optional: true 1282 | yaml: 1283 | optional: true 1284 | 1285 | vitest@3.2.4: 1286 | resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} 1287 | engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} 1288 | hasBin: true 1289 | peerDependencies: 1290 | '@edge-runtime/vm': '*' 1291 | '@types/debug': ^4.1.12 1292 | '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 1293 | '@vitest/browser': 3.2.4 1294 | '@vitest/ui': 3.2.4 1295 | happy-dom: '*' 1296 | jsdom: '*' 1297 | peerDependenciesMeta: 1298 | '@edge-runtime/vm': 1299 | optional: true 1300 | '@types/debug': 1301 | optional: true 1302 | '@types/node': 1303 | optional: true 1304 | '@vitest/browser': 1305 | optional: true 1306 | '@vitest/ui': 1307 | optional: true 1308 | happy-dom: 1309 | optional: true 1310 | jsdom: 1311 | optional: true 1312 | 1313 | web-streams-polyfill@3.3.3: 1314 | resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} 1315 | engines: {node: '>= 8'} 1316 | 1317 | which@2.0.2: 1318 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 1319 | engines: {node: '>= 8'} 1320 | hasBin: true 1321 | 1322 | why-is-node-running@2.3.0: 1323 | resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} 1324 | engines: {node: '>=8'} 1325 | hasBin: true 1326 | 1327 | workerd@1.20251202.0: 1328 | resolution: {integrity: sha512-p08YfrUMHkjCECNdT36r+6DpJIZX4kixbZ4n6GMUcLR5Gh18fakSCsiQrh72iOm4M9QHv/rM7P8YvCrUPWT5sg==} 1329 | engines: {node: '>=16'} 1330 | hasBin: true 1331 | 1332 | wrangler@4.53.0: 1333 | resolution: {integrity: sha512-/wvnHlRnlHsqaeIgGbmcEJE5NFYdTUWHCKow+U5Tv2XwQXI9vXUqBwCLAGy/BwqyS5nnycRt2kppqCzgHgyb7Q==} 1334 | engines: {node: '>=20.0.0'} 1335 | hasBin: true 1336 | peerDependencies: 1337 | '@cloudflare/workers-types': ^4.20251202.0 1338 | peerDependenciesMeta: 1339 | '@cloudflare/workers-types': 1340 | optional: true 1341 | 1342 | wrap-ansi@7.0.0: 1343 | resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 1344 | engines: {node: '>=10'} 1345 | 1346 | wrap-ansi@8.1.0: 1347 | resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 1348 | engines: {node: '>=12'} 1349 | 1350 | ws@8.18.0: 1351 | resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} 1352 | engines: {node: '>=10.0.0'} 1353 | peerDependencies: 1354 | bufferutil: ^4.0.1 1355 | utf-8-validate: '>=5.0.2' 1356 | peerDependenciesMeta: 1357 | bufferutil: 1358 | optional: true 1359 | utf-8-validate: 1360 | optional: true 1361 | 1362 | ws@8.18.3: 1363 | resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} 1364 | engines: {node: '>=10.0.0'} 1365 | peerDependencies: 1366 | bufferutil: ^4.0.1 1367 | utf-8-validate: '>=5.0.2' 1368 | peerDependenciesMeta: 1369 | bufferutil: 1370 | optional: true 1371 | utf-8-validate: 1372 | optional: true 1373 | 1374 | youch-core@0.3.3: 1375 | resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} 1376 | 1377 | youch@4.1.0-beta.10: 1378 | resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} 1379 | 1380 | zod@3.22.3: 1381 | resolution: {integrity: sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==} 1382 | 1383 | zod@3.25.76: 1384 | resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} 1385 | 1386 | zod@4.1.13: 1387 | resolution: {integrity: sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==} 1388 | 1389 | snapshots: 1390 | 1391 | '@biomejs/biome@2.2.4': 1392 | optionalDependencies: 1393 | '@biomejs/cli-darwin-arm64': 2.2.4 1394 | '@biomejs/cli-darwin-x64': 2.2.4 1395 | '@biomejs/cli-linux-arm64': 2.2.4 1396 | '@biomejs/cli-linux-arm64-musl': 2.2.4 1397 | '@biomejs/cli-linux-x64': 2.2.4 1398 | '@biomejs/cli-linux-x64-musl': 2.2.4 1399 | '@biomejs/cli-win32-arm64': 2.2.4 1400 | '@biomejs/cli-win32-x64': 2.2.4 1401 | 1402 | '@biomejs/cli-darwin-arm64@2.2.4': 1403 | optional: true 1404 | 1405 | '@biomejs/cli-darwin-x64@2.2.4': 1406 | optional: true 1407 | 1408 | '@biomejs/cli-linux-arm64-musl@2.2.4': 1409 | optional: true 1410 | 1411 | '@biomejs/cli-linux-arm64@2.2.4': 1412 | optional: true 1413 | 1414 | '@biomejs/cli-linux-x64-musl@2.2.4': 1415 | optional: true 1416 | 1417 | '@biomejs/cli-linux-x64@2.2.4': 1418 | optional: true 1419 | 1420 | '@biomejs/cli-win32-arm64@2.2.4': 1421 | optional: true 1422 | 1423 | '@biomejs/cli-win32-x64@2.2.4': 1424 | optional: true 1425 | 1426 | '@cloudflare/kv-asset-handler@0.4.1': 1427 | dependencies: 1428 | mime: 3.0.0 1429 | 1430 | '@cloudflare/unenv-preset@2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251202.0)': 1431 | dependencies: 1432 | unenv: 2.0.0-rc.24 1433 | optionalDependencies: 1434 | workerd: 1.20251202.0 1435 | 1436 | '@cloudflare/vitest-pool-workers@0.10.14(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4(@types/node@24.10.1))': 1437 | dependencies: 1438 | '@vitest/runner': 3.2.4 1439 | '@vitest/snapshot': 3.2.4 1440 | birpc: 0.2.14 1441 | cjs-module-lexer: 1.4.3 1442 | devalue: 5.5.0 1443 | miniflare: 4.20251202.1 1444 | semver: 7.7.3 1445 | vitest: 3.2.4(@types/node@24.10.1) 1446 | wrangler: 4.53.0 1447 | zod: 3.25.76 1448 | transitivePeerDependencies: 1449 | - '@cloudflare/workers-types' 1450 | - bufferutil 1451 | - utf-8-validate 1452 | 1453 | '@cloudflare/workerd-darwin-64@1.20251202.0': 1454 | optional: true 1455 | 1456 | '@cloudflare/workerd-darwin-arm64@1.20251202.0': 1457 | optional: true 1458 | 1459 | '@cloudflare/workerd-linux-64@1.20251202.0': 1460 | optional: true 1461 | 1462 | '@cloudflare/workerd-linux-arm64@1.20251202.0': 1463 | optional: true 1464 | 1465 | '@cloudflare/workerd-windows-64@1.20251202.0': 1466 | optional: true 1467 | 1468 | '@cspotcode/source-map-support@0.8.1': 1469 | dependencies: 1470 | '@jridgewell/trace-mapping': 0.3.9 1471 | 1472 | '@emnapi/runtime@1.7.1': 1473 | dependencies: 1474 | tslib: 2.8.1 1475 | optional: true 1476 | 1477 | '@esbuild/aix-ppc64@0.25.12': 1478 | optional: true 1479 | 1480 | '@esbuild/aix-ppc64@0.27.0': 1481 | optional: true 1482 | 1483 | '@esbuild/android-arm64@0.25.12': 1484 | optional: true 1485 | 1486 | '@esbuild/android-arm64@0.27.0': 1487 | optional: true 1488 | 1489 | '@esbuild/android-arm@0.25.12': 1490 | optional: true 1491 | 1492 | '@esbuild/android-arm@0.27.0': 1493 | optional: true 1494 | 1495 | '@esbuild/android-x64@0.25.12': 1496 | optional: true 1497 | 1498 | '@esbuild/android-x64@0.27.0': 1499 | optional: true 1500 | 1501 | '@esbuild/darwin-arm64@0.25.12': 1502 | optional: true 1503 | 1504 | '@esbuild/darwin-arm64@0.27.0': 1505 | optional: true 1506 | 1507 | '@esbuild/darwin-x64@0.25.12': 1508 | optional: true 1509 | 1510 | '@esbuild/darwin-x64@0.27.0': 1511 | optional: true 1512 | 1513 | '@esbuild/freebsd-arm64@0.25.12': 1514 | optional: true 1515 | 1516 | '@esbuild/freebsd-arm64@0.27.0': 1517 | optional: true 1518 | 1519 | '@esbuild/freebsd-x64@0.25.12': 1520 | optional: true 1521 | 1522 | '@esbuild/freebsd-x64@0.27.0': 1523 | optional: true 1524 | 1525 | '@esbuild/linux-arm64@0.25.12': 1526 | optional: true 1527 | 1528 | '@esbuild/linux-arm64@0.27.0': 1529 | optional: true 1530 | 1531 | '@esbuild/linux-arm@0.25.12': 1532 | optional: true 1533 | 1534 | '@esbuild/linux-arm@0.27.0': 1535 | optional: true 1536 | 1537 | '@esbuild/linux-ia32@0.25.12': 1538 | optional: true 1539 | 1540 | '@esbuild/linux-ia32@0.27.0': 1541 | optional: true 1542 | 1543 | '@esbuild/linux-loong64@0.25.12': 1544 | optional: true 1545 | 1546 | '@esbuild/linux-loong64@0.27.0': 1547 | optional: true 1548 | 1549 | '@esbuild/linux-mips64el@0.25.12': 1550 | optional: true 1551 | 1552 | '@esbuild/linux-mips64el@0.27.0': 1553 | optional: true 1554 | 1555 | '@esbuild/linux-ppc64@0.25.12': 1556 | optional: true 1557 | 1558 | '@esbuild/linux-ppc64@0.27.0': 1559 | optional: true 1560 | 1561 | '@esbuild/linux-riscv64@0.25.12': 1562 | optional: true 1563 | 1564 | '@esbuild/linux-riscv64@0.27.0': 1565 | optional: true 1566 | 1567 | '@esbuild/linux-s390x@0.25.12': 1568 | optional: true 1569 | 1570 | '@esbuild/linux-s390x@0.27.0': 1571 | optional: true 1572 | 1573 | '@esbuild/linux-x64@0.25.12': 1574 | optional: true 1575 | 1576 | '@esbuild/linux-x64@0.27.0': 1577 | optional: true 1578 | 1579 | '@esbuild/netbsd-arm64@0.25.12': 1580 | optional: true 1581 | 1582 | '@esbuild/netbsd-arm64@0.27.0': 1583 | optional: true 1584 | 1585 | '@esbuild/netbsd-x64@0.25.12': 1586 | optional: true 1587 | 1588 | '@esbuild/netbsd-x64@0.27.0': 1589 | optional: true 1590 | 1591 | '@esbuild/openbsd-arm64@0.25.12': 1592 | optional: true 1593 | 1594 | '@esbuild/openbsd-arm64@0.27.0': 1595 | optional: true 1596 | 1597 | '@esbuild/openbsd-x64@0.25.12': 1598 | optional: true 1599 | 1600 | '@esbuild/openbsd-x64@0.27.0': 1601 | optional: true 1602 | 1603 | '@esbuild/openharmony-arm64@0.25.12': 1604 | optional: true 1605 | 1606 | '@esbuild/openharmony-arm64@0.27.0': 1607 | optional: true 1608 | 1609 | '@esbuild/sunos-x64@0.25.12': 1610 | optional: true 1611 | 1612 | '@esbuild/sunos-x64@0.27.0': 1613 | optional: true 1614 | 1615 | '@esbuild/win32-arm64@0.25.12': 1616 | optional: true 1617 | 1618 | '@esbuild/win32-arm64@0.27.0': 1619 | optional: true 1620 | 1621 | '@esbuild/win32-ia32@0.25.12': 1622 | optional: true 1623 | 1624 | '@esbuild/win32-ia32@0.27.0': 1625 | optional: true 1626 | 1627 | '@esbuild/win32-x64@0.25.12': 1628 | optional: true 1629 | 1630 | '@esbuild/win32-x64@0.27.0': 1631 | optional: true 1632 | 1633 | '@google/genai@1.31.0': 1634 | dependencies: 1635 | google-auth-library: 10.5.0 1636 | ws: 8.18.3 1637 | transitivePeerDependencies: 1638 | - bufferutil 1639 | - supports-color 1640 | - utf-8-validate 1641 | 1642 | '@hono/zod-validator@0.7.5(hono@4.10.7)(zod@4.1.13)': 1643 | dependencies: 1644 | hono: 4.10.7 1645 | zod: 4.1.13 1646 | 1647 | '@img/sharp-darwin-arm64@0.33.5': 1648 | optionalDependencies: 1649 | '@img/sharp-libvips-darwin-arm64': 1.0.4 1650 | optional: true 1651 | 1652 | '@img/sharp-darwin-x64@0.33.5': 1653 | optionalDependencies: 1654 | '@img/sharp-libvips-darwin-x64': 1.0.4 1655 | optional: true 1656 | 1657 | '@img/sharp-libvips-darwin-arm64@1.0.4': 1658 | optional: true 1659 | 1660 | '@img/sharp-libvips-darwin-x64@1.0.4': 1661 | optional: true 1662 | 1663 | '@img/sharp-libvips-linux-arm64@1.0.4': 1664 | optional: true 1665 | 1666 | '@img/sharp-libvips-linux-arm@1.0.5': 1667 | optional: true 1668 | 1669 | '@img/sharp-libvips-linux-s390x@1.0.4': 1670 | optional: true 1671 | 1672 | '@img/sharp-libvips-linux-x64@1.0.4': 1673 | optional: true 1674 | 1675 | '@img/sharp-libvips-linuxmusl-arm64@1.0.4': 1676 | optional: true 1677 | 1678 | '@img/sharp-libvips-linuxmusl-x64@1.0.4': 1679 | optional: true 1680 | 1681 | '@img/sharp-linux-arm64@0.33.5': 1682 | optionalDependencies: 1683 | '@img/sharp-libvips-linux-arm64': 1.0.4 1684 | optional: true 1685 | 1686 | '@img/sharp-linux-arm@0.33.5': 1687 | optionalDependencies: 1688 | '@img/sharp-libvips-linux-arm': 1.0.5 1689 | optional: true 1690 | 1691 | '@img/sharp-linux-s390x@0.33.5': 1692 | optionalDependencies: 1693 | '@img/sharp-libvips-linux-s390x': 1.0.4 1694 | optional: true 1695 | 1696 | '@img/sharp-linux-x64@0.33.5': 1697 | optionalDependencies: 1698 | '@img/sharp-libvips-linux-x64': 1.0.4 1699 | optional: true 1700 | 1701 | '@img/sharp-linuxmusl-arm64@0.33.5': 1702 | optionalDependencies: 1703 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 1704 | optional: true 1705 | 1706 | '@img/sharp-linuxmusl-x64@0.33.5': 1707 | optionalDependencies: 1708 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 1709 | optional: true 1710 | 1711 | '@img/sharp-wasm32@0.33.5': 1712 | dependencies: 1713 | '@emnapi/runtime': 1.7.1 1714 | optional: true 1715 | 1716 | '@img/sharp-win32-ia32@0.33.5': 1717 | optional: true 1718 | 1719 | '@img/sharp-win32-x64@0.33.5': 1720 | optional: true 1721 | 1722 | '@isaacs/cliui@8.0.2': 1723 | dependencies: 1724 | string-width: 5.1.2 1725 | string-width-cjs: string-width@4.2.3 1726 | strip-ansi: 7.1.2 1727 | strip-ansi-cjs: strip-ansi@6.0.1 1728 | wrap-ansi: 8.1.0 1729 | wrap-ansi-cjs: wrap-ansi@7.0.0 1730 | 1731 | '@jridgewell/resolve-uri@3.1.2': {} 1732 | 1733 | '@jridgewell/sourcemap-codec@1.5.5': {} 1734 | 1735 | '@jridgewell/trace-mapping@0.3.9': 1736 | dependencies: 1737 | '@jridgewell/resolve-uri': 3.1.2 1738 | '@jridgewell/sourcemap-codec': 1.5.5 1739 | 1740 | '@pkgjs/parseargs@0.11.0': 1741 | optional: true 1742 | 1743 | '@poppinss/colors@4.1.5': 1744 | dependencies: 1745 | kleur: 4.1.5 1746 | 1747 | '@poppinss/dumper@0.6.5': 1748 | dependencies: 1749 | '@poppinss/colors': 4.1.5 1750 | '@sindresorhus/is': 7.1.1 1751 | supports-color: 10.2.2 1752 | 1753 | '@poppinss/exception@1.2.2': {} 1754 | 1755 | '@rollup/rollup-android-arm-eabi@4.53.3': 1756 | optional: true 1757 | 1758 | '@rollup/rollup-android-arm64@4.53.3': 1759 | optional: true 1760 | 1761 | '@rollup/rollup-darwin-arm64@4.53.3': 1762 | optional: true 1763 | 1764 | '@rollup/rollup-darwin-x64@4.53.3': 1765 | optional: true 1766 | 1767 | '@rollup/rollup-freebsd-arm64@4.53.3': 1768 | optional: true 1769 | 1770 | '@rollup/rollup-freebsd-x64@4.53.3': 1771 | optional: true 1772 | 1773 | '@rollup/rollup-linux-arm-gnueabihf@4.53.3': 1774 | optional: true 1775 | 1776 | '@rollup/rollup-linux-arm-musleabihf@4.53.3': 1777 | optional: true 1778 | 1779 | '@rollup/rollup-linux-arm64-gnu@4.53.3': 1780 | optional: true 1781 | 1782 | '@rollup/rollup-linux-arm64-musl@4.53.3': 1783 | optional: true 1784 | 1785 | '@rollup/rollup-linux-loong64-gnu@4.53.3': 1786 | optional: true 1787 | 1788 | '@rollup/rollup-linux-ppc64-gnu@4.53.3': 1789 | optional: true 1790 | 1791 | '@rollup/rollup-linux-riscv64-gnu@4.53.3': 1792 | optional: true 1793 | 1794 | '@rollup/rollup-linux-riscv64-musl@4.53.3': 1795 | optional: true 1796 | 1797 | '@rollup/rollup-linux-s390x-gnu@4.53.3': 1798 | optional: true 1799 | 1800 | '@rollup/rollup-linux-x64-gnu@4.53.3': 1801 | optional: true 1802 | 1803 | '@rollup/rollup-linux-x64-musl@4.53.3': 1804 | optional: true 1805 | 1806 | '@rollup/rollup-openharmony-arm64@4.53.3': 1807 | optional: true 1808 | 1809 | '@rollup/rollup-win32-arm64-msvc@4.53.3': 1810 | optional: true 1811 | 1812 | '@rollup/rollup-win32-ia32-msvc@4.53.3': 1813 | optional: true 1814 | 1815 | '@rollup/rollup-win32-x64-gnu@4.53.3': 1816 | optional: true 1817 | 1818 | '@rollup/rollup-win32-x64-msvc@4.53.3': 1819 | optional: true 1820 | 1821 | '@sindresorhus/is@7.1.1': {} 1822 | 1823 | '@speed-highlight/core@1.2.12': {} 1824 | 1825 | '@types/chai@5.2.3': 1826 | dependencies: 1827 | '@types/deep-eql': 4.0.2 1828 | assertion-error: 2.0.1 1829 | 1830 | '@types/deep-eql@4.0.2': {} 1831 | 1832 | '@types/estree@1.0.8': {} 1833 | 1834 | '@types/node@24.10.1': 1835 | dependencies: 1836 | undici-types: 7.16.0 1837 | 1838 | '@vitest/expect@3.2.4': 1839 | dependencies: 1840 | '@types/chai': 5.2.3 1841 | '@vitest/spy': 3.2.4 1842 | '@vitest/utils': 3.2.4 1843 | chai: 5.3.3 1844 | tinyrainbow: 2.0.0 1845 | 1846 | '@vitest/mocker@3.2.4(vite@7.2.6(@types/node@24.10.1))': 1847 | dependencies: 1848 | '@vitest/spy': 3.2.4 1849 | estree-walker: 3.0.3 1850 | magic-string: 0.30.21 1851 | optionalDependencies: 1852 | vite: 7.2.6(@types/node@24.10.1) 1853 | 1854 | '@vitest/pretty-format@3.2.4': 1855 | dependencies: 1856 | tinyrainbow: 2.0.0 1857 | 1858 | '@vitest/runner@3.2.4': 1859 | dependencies: 1860 | '@vitest/utils': 3.2.4 1861 | pathe: 2.0.3 1862 | strip-literal: 3.1.0 1863 | 1864 | '@vitest/snapshot@3.2.4': 1865 | dependencies: 1866 | '@vitest/pretty-format': 3.2.4 1867 | magic-string: 0.30.21 1868 | pathe: 2.0.3 1869 | 1870 | '@vitest/spy@3.2.4': 1871 | dependencies: 1872 | tinyspy: 4.0.4 1873 | 1874 | '@vitest/utils@3.2.4': 1875 | dependencies: 1876 | '@vitest/pretty-format': 3.2.4 1877 | loupe: 3.2.1 1878 | tinyrainbow: 2.0.0 1879 | 1880 | acorn-walk@8.3.2: {} 1881 | 1882 | acorn@8.14.0: {} 1883 | 1884 | agent-base@7.1.4: {} 1885 | 1886 | ansi-regex@5.0.1: {} 1887 | 1888 | ansi-regex@6.2.2: {} 1889 | 1890 | ansi-styles@4.3.0: 1891 | dependencies: 1892 | color-convert: 2.0.1 1893 | 1894 | ansi-styles@6.2.3: {} 1895 | 1896 | assertion-error@2.0.1: {} 1897 | 1898 | balanced-match@1.0.2: {} 1899 | 1900 | base64-js@1.5.1: {} 1901 | 1902 | bignumber.js@9.3.1: {} 1903 | 1904 | birpc@0.2.14: {} 1905 | 1906 | blake3-wasm@2.1.5: {} 1907 | 1908 | brace-expansion@2.0.2: 1909 | dependencies: 1910 | balanced-match: 1.0.2 1911 | 1912 | buffer-equal-constant-time@1.0.1: {} 1913 | 1914 | cac@6.7.14: {} 1915 | 1916 | chai@5.3.3: 1917 | dependencies: 1918 | assertion-error: 2.0.1 1919 | check-error: 2.1.1 1920 | deep-eql: 5.0.2 1921 | loupe: 3.2.1 1922 | pathval: 2.0.1 1923 | 1924 | check-error@2.1.1: {} 1925 | 1926 | cjs-module-lexer@1.4.3: {} 1927 | 1928 | cloc@2.6.0-cloc: {} 1929 | 1930 | color-convert@2.0.1: 1931 | dependencies: 1932 | color-name: 1.1.4 1933 | 1934 | color-name@1.1.4: {} 1935 | 1936 | color-string@1.9.1: 1937 | dependencies: 1938 | color-name: 1.1.4 1939 | simple-swizzle: 0.2.4 1940 | 1941 | color@4.2.3: 1942 | dependencies: 1943 | color-convert: 2.0.1 1944 | color-string: 1.9.1 1945 | 1946 | cookie@1.1.1: {} 1947 | 1948 | cross-spawn@7.0.6: 1949 | dependencies: 1950 | path-key: 3.1.1 1951 | shebang-command: 2.0.0 1952 | which: 2.0.2 1953 | 1954 | data-uri-to-buffer@4.0.1: {} 1955 | 1956 | debug@4.4.3: 1957 | dependencies: 1958 | ms: 2.1.3 1959 | 1960 | deep-eql@5.0.2: {} 1961 | 1962 | detect-libc@2.1.2: {} 1963 | 1964 | devalue@5.5.0: {} 1965 | 1966 | dotenv@17.2.3: {} 1967 | 1968 | eastasianwidth@0.2.0: {} 1969 | 1970 | ecdsa-sig-formatter@1.0.11: 1971 | dependencies: 1972 | safe-buffer: 5.2.1 1973 | 1974 | emoji-regex@8.0.0: {} 1975 | 1976 | emoji-regex@9.2.2: {} 1977 | 1978 | error-stack-parser-es@1.0.5: {} 1979 | 1980 | es-module-lexer@1.7.0: {} 1981 | 1982 | esbuild@0.25.12: 1983 | optionalDependencies: 1984 | '@esbuild/aix-ppc64': 0.25.12 1985 | '@esbuild/android-arm': 0.25.12 1986 | '@esbuild/android-arm64': 0.25.12 1987 | '@esbuild/android-x64': 0.25.12 1988 | '@esbuild/darwin-arm64': 0.25.12 1989 | '@esbuild/darwin-x64': 0.25.12 1990 | '@esbuild/freebsd-arm64': 0.25.12 1991 | '@esbuild/freebsd-x64': 0.25.12 1992 | '@esbuild/linux-arm': 0.25.12 1993 | '@esbuild/linux-arm64': 0.25.12 1994 | '@esbuild/linux-ia32': 0.25.12 1995 | '@esbuild/linux-loong64': 0.25.12 1996 | '@esbuild/linux-mips64el': 0.25.12 1997 | '@esbuild/linux-ppc64': 0.25.12 1998 | '@esbuild/linux-riscv64': 0.25.12 1999 | '@esbuild/linux-s390x': 0.25.12 2000 | '@esbuild/linux-x64': 0.25.12 2001 | '@esbuild/netbsd-arm64': 0.25.12 2002 | '@esbuild/netbsd-x64': 0.25.12 2003 | '@esbuild/openbsd-arm64': 0.25.12 2004 | '@esbuild/openbsd-x64': 0.25.12 2005 | '@esbuild/openharmony-arm64': 0.25.12 2006 | '@esbuild/sunos-x64': 0.25.12 2007 | '@esbuild/win32-arm64': 0.25.12 2008 | '@esbuild/win32-ia32': 0.25.12 2009 | '@esbuild/win32-x64': 0.25.12 2010 | 2011 | esbuild@0.27.0: 2012 | optionalDependencies: 2013 | '@esbuild/aix-ppc64': 0.27.0 2014 | '@esbuild/android-arm': 0.27.0 2015 | '@esbuild/android-arm64': 0.27.0 2016 | '@esbuild/android-x64': 0.27.0 2017 | '@esbuild/darwin-arm64': 0.27.0 2018 | '@esbuild/darwin-x64': 0.27.0 2019 | '@esbuild/freebsd-arm64': 0.27.0 2020 | '@esbuild/freebsd-x64': 0.27.0 2021 | '@esbuild/linux-arm': 0.27.0 2022 | '@esbuild/linux-arm64': 0.27.0 2023 | '@esbuild/linux-ia32': 0.27.0 2024 | '@esbuild/linux-loong64': 0.27.0 2025 | '@esbuild/linux-mips64el': 0.27.0 2026 | '@esbuild/linux-ppc64': 0.27.0 2027 | '@esbuild/linux-riscv64': 0.27.0 2028 | '@esbuild/linux-s390x': 0.27.0 2029 | '@esbuild/linux-x64': 0.27.0 2030 | '@esbuild/netbsd-arm64': 0.27.0 2031 | '@esbuild/netbsd-x64': 0.27.0 2032 | '@esbuild/openbsd-arm64': 0.27.0 2033 | '@esbuild/openbsd-x64': 0.27.0 2034 | '@esbuild/openharmony-arm64': 0.27.0 2035 | '@esbuild/sunos-x64': 0.27.0 2036 | '@esbuild/win32-arm64': 0.27.0 2037 | '@esbuild/win32-ia32': 0.27.0 2038 | '@esbuild/win32-x64': 0.27.0 2039 | 2040 | estree-walker@3.0.3: 2041 | dependencies: 2042 | '@types/estree': 1.0.8 2043 | 2044 | exit-hook@2.2.1: {} 2045 | 2046 | expect-type@1.2.2: {} 2047 | 2048 | extend@3.0.2: {} 2049 | 2050 | fdir@6.5.0(picomatch@4.0.3): 2051 | optionalDependencies: 2052 | picomatch: 4.0.3 2053 | 2054 | fetch-blob@3.2.0: 2055 | dependencies: 2056 | node-domexception: 1.0.0 2057 | web-streams-polyfill: 3.3.3 2058 | 2059 | foreground-child@3.3.1: 2060 | dependencies: 2061 | cross-spawn: 7.0.6 2062 | signal-exit: 4.1.0 2063 | 2064 | formdata-polyfill@4.0.10: 2065 | dependencies: 2066 | fetch-blob: 3.2.0 2067 | 2068 | fsevents@2.3.3: 2069 | optional: true 2070 | 2071 | gaxios@7.1.3: 2072 | dependencies: 2073 | extend: 3.0.2 2074 | https-proxy-agent: 7.0.6 2075 | node-fetch: 3.3.2 2076 | rimraf: 5.0.10 2077 | transitivePeerDependencies: 2078 | - supports-color 2079 | 2080 | gcp-metadata@8.1.2: 2081 | dependencies: 2082 | gaxios: 7.1.3 2083 | google-logging-utils: 1.1.3 2084 | json-bigint: 1.0.0 2085 | transitivePeerDependencies: 2086 | - supports-color 2087 | 2088 | glob-to-regexp@0.4.1: {} 2089 | 2090 | glob@10.5.0: 2091 | dependencies: 2092 | foreground-child: 3.3.1 2093 | jackspeak: 3.4.3 2094 | minimatch: 9.0.5 2095 | minipass: 7.1.2 2096 | package-json-from-dist: 1.0.1 2097 | path-scurry: 1.11.1 2098 | 2099 | google-auth-library@10.5.0: 2100 | dependencies: 2101 | base64-js: 1.5.1 2102 | ecdsa-sig-formatter: 1.0.11 2103 | gaxios: 7.1.3 2104 | gcp-metadata: 8.1.2 2105 | google-logging-utils: 1.1.3 2106 | gtoken: 8.0.0 2107 | jws: 4.0.1 2108 | transitivePeerDependencies: 2109 | - supports-color 2110 | 2111 | google-logging-utils@1.1.3: {} 2112 | 2113 | gtoken@8.0.0: 2114 | dependencies: 2115 | gaxios: 7.1.3 2116 | jws: 4.0.1 2117 | transitivePeerDependencies: 2118 | - supports-color 2119 | 2120 | hono@4.10.7: {} 2121 | 2122 | https-proxy-agent@7.0.6: 2123 | dependencies: 2124 | agent-base: 7.1.4 2125 | debug: 4.4.3 2126 | transitivePeerDependencies: 2127 | - supports-color 2128 | 2129 | is-arrayish@0.3.4: {} 2130 | 2131 | is-fullwidth-code-point@3.0.0: {} 2132 | 2133 | isexe@2.0.0: {} 2134 | 2135 | jackspeak@3.4.3: 2136 | dependencies: 2137 | '@isaacs/cliui': 8.0.2 2138 | optionalDependencies: 2139 | '@pkgjs/parseargs': 0.11.0 2140 | 2141 | js-tokens@9.0.1: {} 2142 | 2143 | json-bigint@1.0.0: 2144 | dependencies: 2145 | bignumber.js: 9.3.1 2146 | 2147 | jwa@2.0.1: 2148 | dependencies: 2149 | buffer-equal-constant-time: 1.0.1 2150 | ecdsa-sig-formatter: 1.0.11 2151 | safe-buffer: 5.2.1 2152 | 2153 | jws@4.0.1: 2154 | dependencies: 2155 | jwa: 2.0.1 2156 | safe-buffer: 5.2.1 2157 | 2158 | kleur@4.1.5: {} 2159 | 2160 | loupe@3.2.1: {} 2161 | 2162 | lru-cache@10.4.3: {} 2163 | 2164 | magic-string@0.30.21: 2165 | dependencies: 2166 | '@jridgewell/sourcemap-codec': 1.5.5 2167 | 2168 | mime@3.0.0: {} 2169 | 2170 | miniflare@4.20251202.1: 2171 | dependencies: 2172 | '@cspotcode/source-map-support': 0.8.1 2173 | acorn: 8.14.0 2174 | acorn-walk: 8.3.2 2175 | exit-hook: 2.2.1 2176 | glob-to-regexp: 0.4.1 2177 | sharp: 0.33.5 2178 | stoppable: 1.1.0 2179 | undici: 7.14.0 2180 | workerd: 1.20251202.0 2181 | ws: 8.18.0 2182 | youch: 4.1.0-beta.10 2183 | zod: 3.22.3 2184 | transitivePeerDependencies: 2185 | - bufferutil 2186 | - utf-8-validate 2187 | 2188 | minimatch@9.0.5: 2189 | dependencies: 2190 | brace-expansion: 2.0.2 2191 | 2192 | minipass@7.1.2: {} 2193 | 2194 | ms@2.1.3: {} 2195 | 2196 | nanoid@3.3.11: {} 2197 | 2198 | node-domexception@1.0.0: {} 2199 | 2200 | node-fetch@3.3.2: 2201 | dependencies: 2202 | data-uri-to-buffer: 4.0.1 2203 | fetch-blob: 3.2.0 2204 | formdata-polyfill: 4.0.10 2205 | 2206 | openai@6.10.0(ws@8.18.3)(zod@4.1.13): 2207 | optionalDependencies: 2208 | ws: 8.18.3 2209 | zod: 4.1.13 2210 | 2211 | package-json-from-dist@1.0.1: {} 2212 | 2213 | path-key@3.1.1: {} 2214 | 2215 | path-scurry@1.11.1: 2216 | dependencies: 2217 | lru-cache: 10.4.3 2218 | minipass: 7.1.2 2219 | 2220 | path-to-regexp@6.3.0: {} 2221 | 2222 | pathe@2.0.3: {} 2223 | 2224 | pathval@2.0.1: {} 2225 | 2226 | picocolors@1.1.1: {} 2227 | 2228 | picomatch@4.0.3: {} 2229 | 2230 | postcss@8.5.6: 2231 | dependencies: 2232 | nanoid: 3.3.11 2233 | picocolors: 1.1.1 2234 | source-map-js: 1.2.1 2235 | 2236 | rimraf@5.0.10: 2237 | dependencies: 2238 | glob: 10.5.0 2239 | 2240 | rollup@4.53.3: 2241 | dependencies: 2242 | '@types/estree': 1.0.8 2243 | optionalDependencies: 2244 | '@rollup/rollup-android-arm-eabi': 4.53.3 2245 | '@rollup/rollup-android-arm64': 4.53.3 2246 | '@rollup/rollup-darwin-arm64': 4.53.3 2247 | '@rollup/rollup-darwin-x64': 4.53.3 2248 | '@rollup/rollup-freebsd-arm64': 4.53.3 2249 | '@rollup/rollup-freebsd-x64': 4.53.3 2250 | '@rollup/rollup-linux-arm-gnueabihf': 4.53.3 2251 | '@rollup/rollup-linux-arm-musleabihf': 4.53.3 2252 | '@rollup/rollup-linux-arm64-gnu': 4.53.3 2253 | '@rollup/rollup-linux-arm64-musl': 4.53.3 2254 | '@rollup/rollup-linux-loong64-gnu': 4.53.3 2255 | '@rollup/rollup-linux-ppc64-gnu': 4.53.3 2256 | '@rollup/rollup-linux-riscv64-gnu': 4.53.3 2257 | '@rollup/rollup-linux-riscv64-musl': 4.53.3 2258 | '@rollup/rollup-linux-s390x-gnu': 4.53.3 2259 | '@rollup/rollup-linux-x64-gnu': 4.53.3 2260 | '@rollup/rollup-linux-x64-musl': 4.53.3 2261 | '@rollup/rollup-openharmony-arm64': 4.53.3 2262 | '@rollup/rollup-win32-arm64-msvc': 4.53.3 2263 | '@rollup/rollup-win32-ia32-msvc': 4.53.3 2264 | '@rollup/rollup-win32-x64-gnu': 4.53.3 2265 | '@rollup/rollup-win32-x64-msvc': 4.53.3 2266 | fsevents: 2.3.3 2267 | 2268 | safe-buffer@5.2.1: {} 2269 | 2270 | semver@7.7.3: {} 2271 | 2272 | sharp@0.33.5: 2273 | dependencies: 2274 | color: 4.2.3 2275 | detect-libc: 2.1.2 2276 | semver: 7.7.3 2277 | optionalDependencies: 2278 | '@img/sharp-darwin-arm64': 0.33.5 2279 | '@img/sharp-darwin-x64': 0.33.5 2280 | '@img/sharp-libvips-darwin-arm64': 1.0.4 2281 | '@img/sharp-libvips-darwin-x64': 1.0.4 2282 | '@img/sharp-libvips-linux-arm': 1.0.5 2283 | '@img/sharp-libvips-linux-arm64': 1.0.4 2284 | '@img/sharp-libvips-linux-s390x': 1.0.4 2285 | '@img/sharp-libvips-linux-x64': 1.0.4 2286 | '@img/sharp-libvips-linuxmusl-arm64': 1.0.4 2287 | '@img/sharp-libvips-linuxmusl-x64': 1.0.4 2288 | '@img/sharp-linux-arm': 0.33.5 2289 | '@img/sharp-linux-arm64': 0.33.5 2290 | '@img/sharp-linux-s390x': 0.33.5 2291 | '@img/sharp-linux-x64': 0.33.5 2292 | '@img/sharp-linuxmusl-arm64': 0.33.5 2293 | '@img/sharp-linuxmusl-x64': 0.33.5 2294 | '@img/sharp-wasm32': 0.33.5 2295 | '@img/sharp-win32-ia32': 0.33.5 2296 | '@img/sharp-win32-x64': 0.33.5 2297 | 2298 | shebang-command@2.0.0: 2299 | dependencies: 2300 | shebang-regex: 3.0.0 2301 | 2302 | shebang-regex@3.0.0: {} 2303 | 2304 | siginfo@2.0.0: {} 2305 | 2306 | signal-exit@4.1.0: {} 2307 | 2308 | simple-swizzle@0.2.4: 2309 | dependencies: 2310 | is-arrayish: 0.3.4 2311 | 2312 | source-map-js@1.2.1: {} 2313 | 2314 | stackback@0.0.2: {} 2315 | 2316 | std-env@3.10.0: {} 2317 | 2318 | stoppable@1.1.0: {} 2319 | 2320 | string-width@4.2.3: 2321 | dependencies: 2322 | emoji-regex: 8.0.0 2323 | is-fullwidth-code-point: 3.0.0 2324 | strip-ansi: 6.0.1 2325 | 2326 | string-width@5.1.2: 2327 | dependencies: 2328 | eastasianwidth: 0.2.0 2329 | emoji-regex: 9.2.2 2330 | strip-ansi: 7.1.2 2331 | 2332 | strip-ansi@6.0.1: 2333 | dependencies: 2334 | ansi-regex: 5.0.1 2335 | 2336 | strip-ansi@7.1.2: 2337 | dependencies: 2338 | ansi-regex: 6.2.2 2339 | 2340 | strip-literal@3.1.0: 2341 | dependencies: 2342 | js-tokens: 9.0.1 2343 | 2344 | supports-color@10.2.2: {} 2345 | 2346 | tinybench@2.9.0: {} 2347 | 2348 | tinyexec@0.3.2: {} 2349 | 2350 | tinyglobby@0.2.15: 2351 | dependencies: 2352 | fdir: 6.5.0(picomatch@4.0.3) 2353 | picomatch: 4.0.3 2354 | 2355 | tinypool@1.1.1: {} 2356 | 2357 | tinyrainbow@2.0.0: {} 2358 | 2359 | tinyspy@4.0.4: {} 2360 | 2361 | tslib@2.8.1: 2362 | optional: true 2363 | 2364 | typescript@5.9.2: {} 2365 | 2366 | undici-types@7.16.0: {} 2367 | 2368 | undici@7.14.0: {} 2369 | 2370 | unenv@2.0.0-rc.24: 2371 | dependencies: 2372 | pathe: 2.0.3 2373 | 2374 | vite-node@3.2.4(@types/node@24.10.1): 2375 | dependencies: 2376 | cac: 6.7.14 2377 | debug: 4.4.3 2378 | es-module-lexer: 1.7.0 2379 | pathe: 2.0.3 2380 | vite: 7.2.6(@types/node@24.10.1) 2381 | transitivePeerDependencies: 2382 | - '@types/node' 2383 | - jiti 2384 | - less 2385 | - lightningcss 2386 | - sass 2387 | - sass-embedded 2388 | - stylus 2389 | - sugarss 2390 | - supports-color 2391 | - terser 2392 | - tsx 2393 | - yaml 2394 | 2395 | vite@7.2.6(@types/node@24.10.1): 2396 | dependencies: 2397 | esbuild: 0.25.12 2398 | fdir: 6.5.0(picomatch@4.0.3) 2399 | picomatch: 4.0.3 2400 | postcss: 8.5.6 2401 | rollup: 4.53.3 2402 | tinyglobby: 0.2.15 2403 | optionalDependencies: 2404 | '@types/node': 24.10.1 2405 | fsevents: 2.3.3 2406 | 2407 | vitest@3.2.4(@types/node@24.10.1): 2408 | dependencies: 2409 | '@types/chai': 5.2.3 2410 | '@vitest/expect': 3.2.4 2411 | '@vitest/mocker': 3.2.4(vite@7.2.6(@types/node@24.10.1)) 2412 | '@vitest/pretty-format': 3.2.4 2413 | '@vitest/runner': 3.2.4 2414 | '@vitest/snapshot': 3.2.4 2415 | '@vitest/spy': 3.2.4 2416 | '@vitest/utils': 3.2.4 2417 | chai: 5.3.3 2418 | debug: 4.4.3 2419 | expect-type: 1.2.2 2420 | magic-string: 0.30.21 2421 | pathe: 2.0.3 2422 | picomatch: 4.0.3 2423 | std-env: 3.10.0 2424 | tinybench: 2.9.0 2425 | tinyexec: 0.3.2 2426 | tinyglobby: 0.2.15 2427 | tinypool: 1.1.1 2428 | tinyrainbow: 2.0.0 2429 | vite: 7.2.6(@types/node@24.10.1) 2430 | vite-node: 3.2.4(@types/node@24.10.1) 2431 | why-is-node-running: 2.3.0 2432 | optionalDependencies: 2433 | '@types/node': 24.10.1 2434 | transitivePeerDependencies: 2435 | - jiti 2436 | - less 2437 | - lightningcss 2438 | - msw 2439 | - sass 2440 | - sass-embedded 2441 | - stylus 2442 | - sugarss 2443 | - supports-color 2444 | - terser 2445 | - tsx 2446 | - yaml 2447 | 2448 | web-streams-polyfill@3.3.3: {} 2449 | 2450 | which@2.0.2: 2451 | dependencies: 2452 | isexe: 2.0.0 2453 | 2454 | why-is-node-running@2.3.0: 2455 | dependencies: 2456 | siginfo: 2.0.0 2457 | stackback: 0.0.2 2458 | 2459 | workerd@1.20251202.0: 2460 | optionalDependencies: 2461 | '@cloudflare/workerd-darwin-64': 1.20251202.0 2462 | '@cloudflare/workerd-darwin-arm64': 1.20251202.0 2463 | '@cloudflare/workerd-linux-64': 1.20251202.0 2464 | '@cloudflare/workerd-linux-arm64': 1.20251202.0 2465 | '@cloudflare/workerd-windows-64': 1.20251202.0 2466 | 2467 | wrangler@4.53.0: 2468 | dependencies: 2469 | '@cloudflare/kv-asset-handler': 0.4.1 2470 | '@cloudflare/unenv-preset': 2.7.13(unenv@2.0.0-rc.24)(workerd@1.20251202.0) 2471 | blake3-wasm: 2.1.5 2472 | esbuild: 0.27.0 2473 | miniflare: 4.20251202.1 2474 | path-to-regexp: 6.3.0 2475 | unenv: 2.0.0-rc.24 2476 | workerd: 1.20251202.0 2477 | optionalDependencies: 2478 | fsevents: 2.3.3 2479 | transitivePeerDependencies: 2480 | - bufferutil 2481 | - utf-8-validate 2482 | 2483 | wrap-ansi@7.0.0: 2484 | dependencies: 2485 | ansi-styles: 4.3.0 2486 | string-width: 4.2.3 2487 | strip-ansi: 6.0.1 2488 | 2489 | wrap-ansi@8.1.0: 2490 | dependencies: 2491 | ansi-styles: 6.2.3 2492 | string-width: 5.1.2 2493 | strip-ansi: 7.1.2 2494 | 2495 | ws@8.18.0: {} 2496 | 2497 | ws@8.18.3: {} 2498 | 2499 | youch-core@0.3.3: 2500 | dependencies: 2501 | '@poppinss/exception': 1.2.2 2502 | error-stack-parser-es: 1.0.5 2503 | 2504 | youch@4.1.0-beta.10: 2505 | dependencies: 2506 | '@poppinss/colors': 4.1.5 2507 | '@poppinss/dumper': 0.6.5 2508 | '@speed-highlight/core': 1.2.12 2509 | cookie: 1.1.1 2510 | youch-core: 0.3.3 2511 | 2512 | zod@3.22.3: {} 2513 | 2514 | zod@3.25.76: {} 2515 | 2516 | zod@4.1.13: {} 2517 | --------------------------------------------------------------------------------