From e6ca1c9ee575f70f147f51129a933b5459d760ce Mon Sep 17 00:00:00 2001 From: Nikodem Eluszkiewicz Date: Thu, 20 Nov 2025 22:35:03 +0100 Subject: [PATCH 01/17] feat: Add support for OpenRouter's reasoning/thinking parameters in requests and process reasoning content in responses. --- formatRequest.ts | 30 +++++++++++++++++++++++++++++- formatResponse.ts | 15 +++++++++++++-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/formatRequest.ts b/formatRequest.ts index e97c04b..1c06e39 100644 --- a/formatRequest.ts +++ b/formatRequest.ts @@ -5,6 +5,17 @@ interface MessageCreateParamsBase { temperature?: number; tools?: any[]; stream?: boolean; + reasoning?: { + max_tokens?: number; + effort?: 'high' | 'medium' | 'low' | 'minimal' | 'none'; + exclude?: boolean; + enabled?: boolean; + }; + reasoning_effort?: string; // Keep for backward compatibility if needed, but reasoning.effort is preferred + thinking?: { + type: "enabled"; + budget_tokens: number; + }; } @@ -115,7 +126,7 @@ export function mapModel(anthropicModel: string): string { } export function formatAnthropicToOpenAI(body: MessageCreateParamsBase): any { - const { model, messages, system = [], temperature, tools, stream } = body; + const { model, messages, system = [], temperature, tools, stream, reasoning, reasoning_effort, thinking } = body; const openAIMessages = Array.isArray(messages) ? messages.flatMap((anthropicMessage) => { @@ -229,6 +240,23 @@ export function formatAnthropicToOpenAI(body: MessageCreateParamsBase): any { stream, }; + if (reasoning) { + data.reasoning = reasoning; + } else if (thinking && thinking.type === 'enabled') { + data.reasoning = { + max_tokens: thinking.budget_tokens + }; + } else { + // Default to enabled and high effort if no reasoning/thinking provided + data.reasoning = { + effort: 'high' + }; + } + + if (reasoning_effort) { + data.reasoning_effort = reasoning_effort; + } + if (tools) { data.tools = tools.map((item: any) => ({ type: "function", diff --git a/formatResponse.ts b/formatResponse.ts index 2fcc1d2..b5a7248 100644 --- a/formatResponse.ts +++ b/formatResponse.ts @@ -2,10 +2,20 @@ export function formatOpenAIToAnthropic(completion: any, model: string): any { const messageId = "msg_" + Date.now(); let content: any = []; + + // Handle reasoning content from OpenRouter + if (completion.choices[0].message.reasoning) { + content.push({ + type: "thinking", + thinking: completion.choices[0].message.reasoning, + signature: "openrouter-reasoning" // Placeholder signature + }); + } + if (completion.choices[0].message.content) { - content = [{ text: completion.choices[0].message.content, type: "text" }]; + content.push({ text: completion.choices[0].message.content, type: "text" }); } else if (completion.choices[0].message.tool_calls) { - content = completion.choices[0].message.tool_calls.map((item: any) => { + const toolCalls = completion.choices[0].message.tool_calls.map((item: any) => { return { type: 'tool_use', id: item.id, @@ -13,6 +23,7 @@ export function formatOpenAIToAnthropic(completion: any, model: string): any { input: item.function?.arguments ? JSON.parse(item.function.arguments) : {}, }; }); + content.push(...toolCalls); } const result = { From cf1c03f349dac051b47251e53d61937d8831ec30 Mon Sep 17 00:00:00 2001 From: Nikodem Eluszkiewicz Date: Thu, 20 Nov 2025 23:06:59 +0100 Subject: [PATCH 02/17] feat: Implement streaming support for AI reasoning/thinking blocks. --- streamResponse.ts | 44 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/streamResponse.ts b/streamResponse.ts index 41653dc..adf0228 100644 --- a/streamResponse.ts +++ b/streamResponse.ts @@ -26,6 +26,7 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str let contentBlockIndex = 0; let hasStartedTextBlock = false; + let hasStartedThinkingBlock = false; let isToolUse = false; let currentToolCallId: string | null = null; let toolCallJsonMap = new Map(); @@ -102,7 +103,7 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str const toolCallId = toolCall.id; if (toolCallId && toolCallId !== currentToolCallId) { - if (isToolUse || hasStartedTextBlock) { + if (isToolUse || hasStartedTextBlock || hasStartedThinkingBlock) { enqueueSSE(controller, "content_block_stop", { type: "content_block_stop", index: contentBlockIndex, @@ -111,6 +112,7 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str isToolUse = true; hasStartedTextBlock = false; // Reset text block flag + hasStartedThinkingBlock = false; // Reset thinking block flag currentToolCallId = toolCallId; contentBlockIndex++; toolCallJsonMap.set(toolCallId, ""); @@ -143,13 +145,49 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str }); } } + } else if (delta.reasoning) { + // Handle reasoning/thinking + if (isToolUse || hasStartedTextBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + isToolUse = false; + hasStartedTextBlock = false; + currentToolCallId = null; + contentBlockIndex++; + } + + if (!hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: { + type: "thinking", + thinking: "", + signature: "openrouter-reasoning" // Placeholder + }, + }); + hasStartedThinkingBlock = true; + } + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "thinking_delta", + thinking: delta.reasoning, + }, + }); + } else if (delta.content) { - if (isToolUse) { + if (isToolUse || hasStartedThinkingBlock) { enqueueSSE(controller, "content_block_stop", { type: "content_block_stop", index: contentBlockIndex, }); isToolUse = false; // Reset tool use flag + hasStartedThinkingBlock = false; // Reset thinking block flag currentToolCallId = null; contentBlockIndex++; // Increment for new text block } @@ -178,7 +216,7 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str } // Close last content block - if (isToolUse || hasStartedTextBlock) { + if (isToolUse || hasStartedTextBlock || hasStartedThinkingBlock) { enqueueSSE(controller, "content_block_stop", { type: "content_block_stop", index: contentBlockIndex, From 9c4654fd8c146138b06f40d9c47d78a689339f3a Mon Sep 17 00:00:00 2001 From: Nikodem Eluszkiewicz Date: Fri, 21 Nov 2025 09:53:56 +0100 Subject: [PATCH 03/17] feat: Add documentation for OpenRouter reasoning support, including bidirectional mapping and streaming. --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4ea6224..bbb6845 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,16 @@ y-router acts as a translation layer that: - Translates the response back to Anthropic's format - Supports both streaming and non-streaming responses +## OpenRouter Reasoning Support + +y-router fully supports OpenRouter's reasoning capabilities (e.g., for DeepSeek R1), bridging the gap between Claude's `thinking` parameter and OpenRouter's `reasoning` parameter. + +- **Bidirectional Mapping**: + - **Requests**: Claude's `thinking` block (e.g., from Claude Code) is converted to OpenRouter's `reasoning` parameter. + - **Responses**: OpenRouter's `reasoning` tokens are converted back into Claude's `thinking` content blocks. +- **Streaming Support**: Reasoning tokens are streamed in real-time as `thinking_delta` events, allowing you to see the model's thought process in compatible tools. +- **Smart Defaults**: If no reasoning parameters are provided, y-router defaults to `effort: 'high'` to ensure reasoning models perform at their best. + ## Perfect for Claude Code + OpenRouter This allows you to use [Claude Code](https://claude.ai/code) with OpenRouter's vast selection of models by: From a5a5c5527afb3cf87654928b0aacfaffededaed8 Mon Sep 17 00:00:00 2001 From: Nikodem Eluszkiewicz Date: Fri, 21 Nov 2025 17:43:12 +0100 Subject: [PATCH 04/17] chore: update package.json to add build script and wrangler as devDependency; add tsconfig.json for TypeScript configuration --- .dockerignore | 3 +- Dockerfile | 5 +- README.md | 13 + docker-compose.yml | 2 + package-lock.json | 1516 ++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- tsconfig.json | 15 + 7 files changed, 1557 insertions(+), 3 deletions(-) create mode 100644 package-lock.json create mode 100644 tsconfig.json diff --git a/.dockerignore b/.dockerignore index 6239775..9bf0a2f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,4 +9,5 @@ coverage .docker Dockerfile docker-compose.yml -.dockerignore \ No newline at end of file +.dockerignore +.wrangler \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2c25f6e..ce81667 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ WORKDIR /app RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* -RUN npm install -g wrangler +RUN npm install -g wrangler@4 COPY package*.json ./ @@ -14,4 +14,7 @@ COPY . . EXPOSE 8787 +RUN chown -R node:node /app +USER node + CMD ["npm", "run", "dev", "--", "--ip", "0.0.0.0"] \ No newline at end of file diff --git a/README.md b/README.md index bbb6845..9c60df8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ +# y-router +This project is a Cloudflare Worker that acts as an Anthropic→OpenAI proxy. + +**Quick dev (Docker Compose)**: Run `docker-compose up -d --build` and open `http://localhost:8787`. + +**Debugging note**: The development setup runs `wrangler@4` in Docker and removes stale `.wrangler/tmp` on startup. If you still encounter an error about a missing `middleware-loader.entry.ts`, try removing the `.wrangler/tmp` directory locally then restart the Compose service: +```bash +rm -rf .wrangler/tmp +docker compose up -d --build +``` + +**If you're building on the host** (e.g. running `wrangler build` outside Docker), ensure you use Wrangler v4 on the host to avoid absolute `/app` path mismatches introduced by older versions: `npx wrangler@4 build` or `npm install --save-dev wrangler@4`. + # y-router A Cloudflare Worker that translates between Anthropic's Claude API and OpenAI-compatible APIs, enabling you to use Claude Code with OpenRouter and other OpenAI-compatible providers. diff --git a/docker-compose.yml b/docker-compose.yml index 82f4025..3387c32 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,7 @@ services: y-router: build: . + user: "root" ports: - "8787:8787" environment: @@ -8,6 +9,7 @@ services: volumes: - .:/app - /app/node_modules + command: /bin/sh -lc "rm -rf /app/.wrangler/tmp || true; npm run dev -- --ip 0.0.0.0" stdin_open: true tty: true restart: unless-stopped diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..278a996 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1516 @@ +{ + "name": "y-router", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "y-router", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "wrangler": "^4" + } + }, + "node_modules/@cloudflare/kv-asset-handler": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.4.0.tgz", + "integrity": "sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "mime": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@cloudflare/unenv-preset": { + "version": "2.7.11", + "resolved": "https://registry.npmjs.org/@cloudflare/unenv-preset/-/unenv-preset-2.7.11.tgz", + "integrity": "sha512-se23f1D4PxKrMKOq+Stz+Yn7AJ9ITHcEecXo2Yjb+UgbUDCEBch1FXQC6hx6uT5fNA3kmX3mfzeZiUmpK1W9IQ==", + "dev": true, + "license": "MIT OR Apache-2.0", + "peerDependencies": { + "unenv": "2.0.0-rc.24", + "workerd": "^1.20251106.1" + }, + "peerDependenciesMeta": { + "workerd": { + "optional": true + } + } + }, + "node_modules/@cloudflare/workerd-darwin-64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20251118.0.tgz", + "integrity": "sha512-UmWmYEYS/LkK/4HFKN6xf3Hk8cw70PviR+ftr3hUvs9HYZS92IseZEp16pkL6ZBETrPRpZC7OrzoYF7ky6kHsg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-darwin-arm64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20251118.0.tgz", + "integrity": "sha512-RockU7Qzf4rxNfY1lx3j4rvwutNLjTIX7rr2hogbQ4mzLo8Ea40/oZTzXVxl+on75joLBrt0YpenGW8o/r44QA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20251118.0.tgz", + "integrity": "sha512-aT97GnOAbJDuuOG0zPVhgRk0xFtB1dzBMrxMZ09eubDLoU4djH4BuORaqvxNRMmHgKfa4T6drthckT0NjUvBdw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-linux-arm64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20251118.0.tgz", + "integrity": "sha512-bXZPJcwlq00MPOXqP7DMWjr+goYj0+Fqyw6zgEC2M3FR1+SWla4yjghnZ4IdpN+H1t7VbUrsi5np2LzMUFs0NA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cloudflare/workerd-windows-64": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20251118.0.tgz", + "integrity": "sha512-2LV99AHSlpr8WcCb/BYbU2QsYkXLUL1izN6YKWkN9Eibv80JKX0RtgmD3dfmajE5sNvClavxZejgzVvHD9N9Ag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.7.1.tgz", + "integrity": "sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz", + "integrity": "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.4.tgz", + "integrity": "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.4.tgz", + "integrity": "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.4.tgz", + "integrity": "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.4.tgz", + "integrity": "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.4.tgz", + "integrity": "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.4.tgz", + "integrity": "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.4.tgz", + "integrity": "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.4.tgz", + "integrity": "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.4.tgz", + "integrity": "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.4.tgz", + "integrity": "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.4.tgz", + "integrity": "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.4.tgz", + "integrity": "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.4.tgz", + "integrity": "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.4.tgz", + "integrity": "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.4.tgz", + "integrity": "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.4.tgz", + "integrity": "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.4.tgz", + "integrity": "sha512-vUnkBYxZW4hL/ie91hSqaSNjulOnYXE1VSLusnvHg2u3jewJBz3YzB9+oCw8DABeVqZGg94t9tyZFoHma8gWZQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.4.tgz", + "integrity": "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.4.tgz", + "integrity": "sha512-Ct2WcFEANlFDtp1nVAXSNBPDxyU+j7+tId//iHXU2f/lN5AmO4zLyhDcpR5Cz1r08mVxzt3Jpyt4PmXQ1O6+7A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.4.tgz", + "integrity": "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.4.tgz", + "integrity": "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.4.tgz", + "integrity": "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.4.tgz", + "integrity": "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.4.tgz", + "integrity": "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@poppinss/colors": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@poppinss/colors/-/colors-4.1.5.tgz", + "integrity": "sha512-FvdDqtcRCtz6hThExcFOgW0cWX+xwSMWcRuQe5ZEb2m7cVQOAVZOIMt+/v9RxGiD9/OY16qJBXK4CVKWAPalBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "kleur": "^4.1.5" + } + }, + "node_modules/@poppinss/dumper": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@poppinss/dumper/-/dumper-0.6.5.tgz", + "integrity": "sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@sindresorhus/is": "^7.0.2", + "supports-color": "^10.0.0" + } + }, + "node_modules/@poppinss/exception": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@poppinss/exception/-/exception-1.2.2.tgz", + "integrity": "sha512-m7bpKCD4QMlFCjA/nKTs23fuvoVFoA83brRKmObCUNmi/9tVu8Ve3w4YQAnJu4q3Tjf5fr685HYIC/IA2zHRSg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@sindresorhus/is": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-7.1.1.tgz", + "integrity": "sha512-rO92VvpgMc3kfiTjGT52LEtJ8Yc5kCWhZjLQ3LwlA4pSgPpQO7bVpYXParOD8Jwf+cVQECJo3yP/4I8aZtUQTQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@speed-highlight/core": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@speed-highlight/core/-/core-1.2.12.tgz", + "integrity": "sha512-uilwrK0Ygyri5dToHYdZSjcvpS2ZwX0w5aSt3GCEN9hrjxWCoeV4Z2DTXuxjwbntaLQIEEAlCeNQss5SoHvAEA==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/acorn": { + "version": "8.14.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", + "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/blake3-wasm": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", + "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", + "dev": true, + "license": "MIT" + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/error-stack-parser-es": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-1.0.5.tgz", + "integrity": "sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/esbuild": { + "version": "0.25.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.4.tgz", + "integrity": "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.4", + "@esbuild/android-arm": "0.25.4", + "@esbuild/android-arm64": "0.25.4", + "@esbuild/android-x64": "0.25.4", + "@esbuild/darwin-arm64": "0.25.4", + "@esbuild/darwin-x64": "0.25.4", + "@esbuild/freebsd-arm64": "0.25.4", + "@esbuild/freebsd-x64": "0.25.4", + "@esbuild/linux-arm": "0.25.4", + "@esbuild/linux-arm64": "0.25.4", + "@esbuild/linux-ia32": "0.25.4", + "@esbuild/linux-loong64": "0.25.4", + "@esbuild/linux-mips64el": "0.25.4", + "@esbuild/linux-ppc64": "0.25.4", + "@esbuild/linux-riscv64": "0.25.4", + "@esbuild/linux-s390x": "0.25.4", + "@esbuild/linux-x64": "0.25.4", + "@esbuild/netbsd-arm64": "0.25.4", + "@esbuild/netbsd-x64": "0.25.4", + "@esbuild/openbsd-arm64": "0.25.4", + "@esbuild/openbsd-x64": "0.25.4", + "@esbuild/sunos-x64": "0.25.4", + "@esbuild/win32-arm64": "0.25.4", + "@esbuild/win32-ia32": "0.25.4", + "@esbuild/win32-x64": "0.25.4" + } + }, + "node_modules/exit-hook": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", + "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/miniflare": { + "version": "4.20251118.1", + "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-4.20251118.1.tgz", + "integrity": "sha512-uLSAE/DvOm392fiaig4LOaatxLjM7xzIniFRG5Y3yF9IduOYLLK/pkCPQNCgKQH3ou0YJRHnTN+09LPfqYNTQQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspotcode/source-map-support": "0.8.1", + "acorn": "8.14.0", + "acorn-walk": "8.3.2", + "exit-hook": "2.2.1", + "glob-to-regexp": "0.4.1", + "sharp": "^0.33.5", + "stoppable": "1.1.0", + "undici": "7.14.0", + "workerd": "1.20251118.0", + "ws": "8.18.0", + "youch": "4.1.0-beta.10", + "zod": "3.22.3" + }, + "bin": { + "miniflare": "bootstrap.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/stoppable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", + "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "npm": ">=6" + } + }, + "node_modules/supports-color": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/undici": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.14.0.tgz", + "integrity": "sha512-Vqs8HTzjpQXZeXdpsfChQTlafcMQaaIwnGwLam1wudSSjlJeQ3bw1j+TLPePgrCnCpUXx7Ba5Pdpf5OBih62NQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/unenv": { + "version": "2.0.0-rc.24", + "resolved": "https://registry.npmjs.org/unenv/-/unenv-2.0.0-rc.24.tgz", + "integrity": "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "pathe": "^2.0.3" + } + }, + "node_modules/workerd": { + "version": "1.20251118.0", + "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20251118.0.tgz", + "integrity": "sha512-Om5ns0Lyx/LKtYI04IV0bjIrkBgoFNg0p6urzr2asekJlfP18RqFzyqMFZKf0i9Gnjtz/JfAS/Ol6tjCe5JJsQ==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "bin": { + "workerd": "bin/workerd" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "@cloudflare/workerd-darwin-64": "1.20251118.0", + "@cloudflare/workerd-darwin-arm64": "1.20251118.0", + "@cloudflare/workerd-linux-64": "1.20251118.0", + "@cloudflare/workerd-linux-arm64": "1.20251118.0", + "@cloudflare/workerd-windows-64": "1.20251118.0" + } + }, + "node_modules/wrangler": { + "version": "4.50.0", + "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-4.50.0.tgz", + "integrity": "sha512-+nuZuHZxDdKmAyXOSrHlciGshCoAPiy5dM+t6mEohWm7HpXvTHmWQGUf/na9jjWlWJHCJYOWzkA1P5HBJqrIEA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "dependencies": { + "@cloudflare/kv-asset-handler": "0.4.0", + "@cloudflare/unenv-preset": "2.7.11", + "blake3-wasm": "2.1.5", + "esbuild": "0.25.4", + "miniflare": "4.20251118.1", + "path-to-regexp": "6.3.0", + "unenv": "2.0.0-rc.24", + "workerd": "1.20251118.0" + }, + "bin": { + "wrangler": "bin/wrangler.js", + "wrangler2": "bin/wrangler.js" + }, + "engines": { + "node": ">=20.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@cloudflare/workers-types": "^4.20251118.0" + }, + "peerDependenciesMeta": { + "@cloudflare/workers-types": { + "optional": true + } + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/youch": { + "version": "4.1.0-beta.10", + "resolved": "https://registry.npmjs.org/youch/-/youch-4.1.0-beta.10.tgz", + "integrity": "sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/colors": "^4.1.5", + "@poppinss/dumper": "^0.6.4", + "@speed-highlight/core": "^1.2.7", + "cookie": "^1.0.2", + "youch-core": "^0.3.3" + } + }, + "node_modules/youch-core": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/youch-core/-/youch-core-0.3.3.tgz", + "integrity": "sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@poppinss/exception": "^1.2.2", + "error-stack-parser-es": "^1.0.5" + } + }, + "node_modules/zod": { + "version": "3.22.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.3.tgz", + "integrity": "sha512-EjIevzuJRiRPbVH4mGc8nApb/lVLKVpmUhAaR5R5doKGfAnGJ6Gr3CViAVjP+4FWSxCsybeWQdcgCtbX+7oZug==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/package.json b/package.json index 2c82415..644a6dc 100644 --- a/package.json +++ b/package.json @@ -4,10 +4,14 @@ "main": "index.ts", "scripts": { "dev": "wrangler dev", + "build": "wrangler build", "deploy": "wrangler deploy" }, "keywords": [], "author": "", "license": "MIT", - "description": "" + "description": "", + "devDependencies": { + "wrangler": "^4" + } } diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..2d97e7b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "resolveJsonModule": true + }, + "include": ["./**/*.ts"] +} From dbad934c8aa65e7879424a943109b3553236e503 Mon Sep 17 00:00:00 2001 From: Nikodem Eluszkiewicz Date: Fri, 21 Nov 2025 19:20:13 +0100 Subject: [PATCH 05/17] feat: dynamically track and report usage tokens in OpenAI to Anthropic stream conversion, and add corresponding test files. --- formatResponse.ts | 4 + streamResponse.ts | 22 +++- test_stream.js | 296 ++++++++++++++++++++++++++++++++++++++++++++++ test_stream.ts | 288 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 606 insertions(+), 4 deletions(-) create mode 100644 test_stream.js create mode 100644 test_stream.ts diff --git a/formatResponse.ts b/formatResponse.ts index b5a7248..3871a8b 100644 --- a/formatResponse.ts +++ b/formatResponse.ts @@ -34,6 +34,10 @@ export function formatOpenAIToAnthropic(completion: any, model: string): any { stop_reason: completion.choices[0].finish_reason === 'tool_calls' ? "tool_use" : "end_turn", stop_sequence: null, model, + usage: completion.usage ? { + input_tokens: completion.usage.prompt_tokens || 0, + output_tokens: completion.usage.completion_tokens || 0 + } : { input_tokens: 0, output_tokens: 0 }, }; return result; } \ No newline at end of file diff --git a/streamResponse.ts b/streamResponse.ts index adf0228..62019d5 100644 --- a/streamResponse.ts +++ b/streamResponse.ts @@ -19,7 +19,7 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str model, stop_reason: null, stop_sequence: null, - usage: { input_tokens: 1, output_tokens: 1 }, + usage: { input_tokens: 0, output_tokens: 0 }, }, }; enqueueSSE(controller, "message_start", messageStart); @@ -30,6 +30,7 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str let isToolUse = false; let currentToolCallId: string | null = null; let toolCallJsonMap = new Map(); + let usage: { prompt_tokens?: number; completion_tokens?: number } | undefined = undefined; const reader = openaiStream.getReader(); const decoder = new TextDecoder(); @@ -49,6 +50,9 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str try { const parsed = JSON.parse(data); + if (parsed.usage) { + usage = parsed.usage; + } const delta = parsed.choices?.[0]?.delta; if (delta) { processStreamDelta(delta); @@ -79,10 +83,14 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str try { const parsed = JSON.parse(data); + if (parsed.usage) { + usage = parsed.usage; + } const delta = parsed.choices?.[0]?.delta; - if (!delta) continue; - processStreamDelta(delta); + if (delta) { + processStreamDelta(delta); + } } catch (e) { // Parse error continue; @@ -95,6 +103,9 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str } function processStreamDelta(delta: any) { + if (delta.usage) { + usage = delta.usage; + } // Handle tool calls if (delta.tool_calls?.length > 0) { @@ -230,7 +241,10 @@ export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: str stop_reason: isToolUse ? "tool_use" : "end_turn", stop_sequence: null, }, - usage: { input_tokens: 100, output_tokens: 150 }, + usage: { + input_tokens: usage?.prompt_tokens || 0, + output_tokens: usage?.completion_tokens || 0, + }, }); enqueueSSE(controller, "message_stop", { diff --git a/test_stream.js b/test_stream.js new file mode 100644 index 0000000..0c73444 --- /dev/null +++ b/test_stream.js @@ -0,0 +1,296 @@ + +const { ReadableStream } = require('stream/web'); +const { TextEncoder, TextDecoder } = require('util'); + +function streamOpenAIToAnthropic(openaiStream, model) { + const messageId = "msg_" + Date.now(); + + const enqueueSSE = (controller, eventType, data) => { + const sseMessage = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`; + controller.enqueue(new TextEncoder().encode(sseMessage)); + }; + + return new ReadableStream({ + async start(controller) { + // Send message_start event + const messageStart = { + type: "message_start", + message: { + id: messageId, + type: "message", + role: "assistant", + content: [], + model, + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 0, output_tokens: 0 }, + }, + }; + enqueueSSE(controller, "message_start", messageStart); + + let contentBlockIndex = 0; + let hasStartedTextBlock = false; + let hasStartedThinkingBlock = false; + let isToolUse = false; + let currentToolCallId = null; + let toolCallJsonMap = new Map(); + let usage = undefined; + + const reader = openaiStream.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + // Process any remaining data in buffer + if (buffer.trim()) { + const lines = buffer.split('\n'); + for (const line of lines) { + if (line.trim() && line.startsWith('data: ')) { + const data = line.slice(6).trim(); + if (data === '[DONE]') continue; + + try { + const parsed = JSON.parse(data); + if (parsed.usage) { + usage = parsed.usage; + } + const delta = parsed.choices?.[0]?.delta; + if (delta) { + processStreamDelta(delta); + } + } catch (e) { + // Parse error + } + } + } + } + break; + } + + // Decode chunk and add to buffer + const chunk = decoder.decode(value, { stream: true }); + buffer += chunk; + + // Process complete lines from buffer + const lines = buffer.split('\n'); + // Keep the last potentially incomplete line in buffer + buffer = lines.pop() || ''; + + // Process complete lines in order + for (const line of lines) { + if (line.trim() && line.startsWith('data: ')) { + const data = line.slice(6).trim(); + if (data === '[DONE]') continue; + + try { + const parsed = JSON.parse(data); + if (parsed.usage) { + usage = parsed.usage; + } + const delta = parsed.choices?.[0]?.delta; + + if (delta) { + processStreamDelta(delta); + } + } catch (e) { + // Parse error + continue; + } + } + } + } + } finally { + reader.releaseLock(); + } + + function processStreamDelta(delta) { + if (delta.usage) { + usage = delta.usage; + } + + // Handle tool calls + if (delta.tool_calls?.length > 0) { + // Existing tool call logic + for (const toolCall of delta.tool_calls) { + const toolCallId = toolCall.id; + + if (toolCallId && toolCallId !== currentToolCallId) { + if (isToolUse || hasStartedTextBlock || hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + } + + isToolUse = true; + hasStartedTextBlock = false; // Reset text block flag + hasStartedThinkingBlock = false; // Reset thinking block flag + currentToolCallId = toolCallId; + contentBlockIndex++; + toolCallJsonMap.set(toolCallId, ""); + + const toolBlock = { + type: "tool_use", + id: toolCallId, + name: toolCall.function?.name, + input: {}, + }; + + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: toolBlock, + }); + } + + if (toolCall.function?.arguments && currentToolCallId) { + const currentJson = toolCallJsonMap.get(currentToolCallId) || ""; + toolCallJsonMap.set(currentToolCallId, currentJson + toolCall.function.arguments); + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "input_json_delta", + partial_json: toolCall.function.arguments, + }, + }); + } + } + } else if (delta.reasoning) { + // Handle reasoning/thinking + if (isToolUse || hasStartedTextBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + isToolUse = false; + hasStartedTextBlock = false; + currentToolCallId = null; + contentBlockIndex++; + } + + if (!hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: { + type: "thinking", + thinking: "", + signature: "openrouter-reasoning" // Placeholder + }, + }); + hasStartedThinkingBlock = true; + } + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "thinking_delta", + thinking: delta.reasoning, + }, + }); + + } else if (delta.content) { + if (isToolUse || hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + isToolUse = false; // Reset tool use flag + hasStartedThinkingBlock = false; // Reset thinking block flag + currentToolCallId = null; + contentBlockIndex++; // Increment for new text block + } + + if (!hasStartedTextBlock) { + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: { + type: "text", + text: "", + }, + }); + hasStartedTextBlock = true; + } + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "text_delta", + text: delta.content, + }, + }); + } + } + + // Close last content block + if (isToolUse || hasStartedTextBlock || hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + } + + // Send message_delta and message_stop + enqueueSSE(controller, "message_delta", { + type: "message_delta", + delta: { + stop_reason: isToolUse ? "tool_use" : "end_turn", + stop_sequence: null, + }, + usage: { + input_tokens: usage?.prompt_tokens || 0, + output_tokens: usage?.completion_tokens || 0, + }, + }); + + enqueueSSE(controller, "message_stop", { + type: "message_stop", + }); + + controller.close(); + }, + }); +} + +// Mock ReadableStream +function createMockStream(chunks) { + return new ReadableStream({ + start(controller) { + for (const chunk of chunks) { + const data = `data: ${JSON.stringify(chunk)}\n\n`; + controller.enqueue(new TextEncoder().encode(data)); + } + controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n')); + controller.close(); + } + }); +} + +async function runTest() { + const chunks = [ + { choices: [{ delta: { content: "Hello" } }] }, + { choices: [{ delta: { content: " world" } }] }, + { usage: { prompt_tokens: 100, completion_tokens: 50 }, choices: [] } // Usage at the end + ]; + + const mockStream = createMockStream(chunks); + const transformedStream = streamOpenAIToAnthropic(mockStream, 'test-model'); + const reader = transformedStream.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + console.log(decoder.decode(value)); + } +} + +runTest(); diff --git a/test_stream.ts b/test_stream.ts new file mode 100644 index 0000000..723fec1 --- /dev/null +++ b/test_stream.ts @@ -0,0 +1,288 @@ + +// Mock TextEncoder/TextDecoder for Node environment if needed (Node 11+ has them globally) + +export function streamOpenAIToAnthropic(openaiStream: ReadableStream, model: string): ReadableStream { + const messageId = "msg_" + Date.now(); + + const enqueueSSE = (controller: ReadableStreamDefaultController, eventType: string, data: any) => { + const sseMessage = `event: ${eventType}\ndata: ${JSON.stringify(data)}\n\n`; + controller.enqueue(new TextEncoder().encode(sseMessage)); + }; + + return new ReadableStream({ + async start(controller) { + // Send message_start event + const messageStart = { + type: "message_start", + message: { + id: messageId, + type: "message", + role: "assistant", + content: [], + model, + stop_reason: null, + stop_sequence: null, + usage: { input_tokens: 0, output_tokens: 0 }, + }, + }; + enqueueSSE(controller, "message_start", messageStart); + + let contentBlockIndex = 0; + let hasStartedTextBlock = false; + let hasStartedThinkingBlock = false; + let isToolUse = false; + let currentToolCallId: string | null = null; + let toolCallJsonMap = new Map(); + let usage: { prompt_tokens?: number; completion_tokens?: number } | undefined = undefined; + + const reader = openaiStream.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + try { + while (true) { + const { done, value } = await reader.read(); + if (done) { + // Process any remaining data in buffer + if (buffer.trim()) { + const lines = buffer.split('\n'); + for (const line of lines) { + if (line.trim() && line.startsWith('data: ')) { + const data = line.slice(6).trim(); + if (data === '[DONE]') continue; + + try { + const parsed = JSON.parse(data); + const delta = parsed.choices?.[0]?.delta; + if (delta) { + processStreamDelta(delta); + } + } catch (e) { + // Parse error + } + } + } + } + break; + } + + // Decode chunk and add to buffer + const chunk = decoder.decode(value, { stream: true }); + buffer += chunk; + + // Process complete lines from buffer + const lines = buffer.split('\n'); + // Keep the last potentially incomplete line in buffer + buffer = lines.pop() || ''; + + // Process complete lines in order + for (const line of lines) { + if (line.trim() && line.startsWith('data: ')) { + const data = line.slice(6).trim(); + if (data === '[DONE]') continue; + + try { + const parsed = JSON.parse(data); + const delta = parsed.choices?.[0]?.delta; + + if (!delta) continue; + processStreamDelta(delta); + } catch (e) { + // Parse error + continue; + } + } + } + } + } finally { + reader.releaseLock(); + } + + function processStreamDelta(delta: any) { + if (delta.usage) { + usage = delta.usage; + } + + // Handle tool calls + if (delta.tool_calls?.length > 0) { + // Existing tool call logic + for (const toolCall of delta.tool_calls) { + const toolCallId = toolCall.id; + + if (toolCallId && toolCallId !== currentToolCallId) { + if (isToolUse || hasStartedTextBlock || hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + } + + isToolUse = true; + hasStartedTextBlock = false; // Reset text block flag + hasStartedThinkingBlock = false; // Reset thinking block flag + currentToolCallId = toolCallId; + contentBlockIndex++; + toolCallJsonMap.set(toolCallId, ""); + + const toolBlock = { + type: "tool_use", + id: toolCallId, + name: toolCall.function?.name, + input: {}, + }; + + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: toolBlock, + }); + } + + if (toolCall.function?.arguments && currentToolCallId) { + const currentJson = toolCallJsonMap.get(currentToolCallId) || ""; + toolCallJsonMap.set(currentToolCallId, currentJson + toolCall.function.arguments); + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "input_json_delta", + partial_json: toolCall.function.arguments, + }, + }); + } + } + } else if (delta.reasoning) { + // Handle reasoning/thinking + if (isToolUse || hasStartedTextBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + isToolUse = false; + hasStartedTextBlock = false; + currentToolCallId = null; + contentBlockIndex++; + } + + if (!hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: { + type: "thinking", + thinking: "", + signature: "openrouter-reasoning" // Placeholder + }, + }); + hasStartedThinkingBlock = true; + } + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "thinking_delta", + thinking: delta.reasoning, + }, + }); + + } else if (delta.content) { + if (isToolUse || hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + isToolUse = false; // Reset tool use flag + hasStartedThinkingBlock = false; // Reset thinking block flag + currentToolCallId = null; + contentBlockIndex++; // Increment for new text block + } + + if (!hasStartedTextBlock) { + enqueueSSE(controller, "content_block_start", { + type: "content_block_start", + index: contentBlockIndex, + content_block: { + type: "text", + text: "", + }, + }); + hasStartedTextBlock = true; + } + + enqueueSSE(controller, "content_block_delta", { + type: "content_block_delta", + index: contentBlockIndex, + delta: { + type: "text_delta", + text: delta.content, + }, + }); + } + } + + // Close last content block + if (isToolUse || hasStartedTextBlock || hasStartedThinkingBlock) { + enqueueSSE(controller, "content_block_stop", { + type: "content_block_stop", + index: contentBlockIndex, + }); + } + + // Send message_delta and message_stop + enqueueSSE(controller, "message_delta", { + type: "message_delta", + delta: { + stop_reason: isToolUse ? "tool_use" : "end_turn", + stop_sequence: null, + }, + usage: { + input_tokens: usage?.prompt_tokens || 0, + output_tokens: usage?.completion_tokens || 0, + }, + }); + + enqueueSSE(controller, "message_stop", { + type: "message_stop", + }); + + controller.close(); + }, + }); +} + +// Mock ReadableStream +function createMockStream(chunks: any[]) { + return new ReadableStream({ + start(controller) { + for (const chunk of chunks) { + const data = `data: ${JSON.stringify(chunk)}\n\n`; + controller.enqueue(new TextEncoder().encode(data)); + } + controller.enqueue(new TextEncoder().encode('data: [DONE]\n\n')); + controller.close(); + } + }); +} + +async function runTest() { + const chunks = [ + { choices: [{ delta: { content: "Hello" } }] }, + { choices: [{ delta: { content: " world" } }] }, + { usage: { prompt_tokens: 100, completion_tokens: 50 }, choices: [] } // Usage at the end + ]; + + const mockStream = createMockStream(chunks); + const transformedStream = streamOpenAIToAnthropic(mockStream, 'test-model'); + const reader = transformedStream.getReader(); + const decoder = new TextDecoder(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + console.log(decoder.decode(value)); + } +} + +runTest(); From 88dab93e53c98e8710d42298de33b75fd41a83f6 Mon Sep 17 00:00:00 2001 From: Nikodem Eluszkiewicz Date: Fri, 21 Nov 2025 19:27:00 +0100 Subject: [PATCH 06/17] refactor: rename project from y-router to open-claude-router --- LICENSE | 2 +- README.md | 30 +++++++++++++++--------------- docker-compose.yml | 2 +- indexHtml.ts | 4 ++-- package-lock.json | 6 +++--- package.json | 4 ++-- privacyHtml.ts | 34 +++++++++++++++++----------------- termsHtml.ts | 24 ++++++++++++------------ wrangler.toml | 2 +- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/LICENSE b/LICENSE index 89f1207..164449f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright 2025 y-router Authors +Copyright 2025 open-claude-router Authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md index 9c60df8..24b9f04 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# y-router +# open-claude-router This project is a Cloudflare Worker that acts as an Anthropic→OpenAI proxy. **Quick dev (Docker Compose)**: Run `docker-compose up -d --build` and open `http://localhost:8787`. @@ -11,7 +11,7 @@ docker compose up -d --build **If you're building on the host** (e.g. running `wrangler build` outside Docker), ensure you use Wrangler v4 on the host to avoid absolute `/app` path mismatches introduced by older versions: `npx wrangler@4 build` or `npm install --save-dev wrangler@4`. -# y-router +# open-claude-router A Cloudflare Worker that translates between Anthropic's Claude API and OpenAI-compatible APIs, enabling you to use Claude Code with OpenRouter and other OpenAI-compatible providers. @@ -60,7 +60,7 @@ source ~/.bashrc claude ``` -That's it! Claude Code will now use OpenRouter's models through y-router. +That's it! Claude Code will now use OpenRouter's models through open-claude-router. ### Multiple Configurations @@ -91,7 +91,7 @@ Example workflows: ## What it does -y-router acts as a translation layer that: +open-claude-router acts as a translation layer that: - Accepts requests in Anthropic's API format (`/v1/messages`) - Converts them to OpenAI's chat completions format - Forwards to OpenRouter (or any OpenAI-compatible API) @@ -100,18 +100,18 @@ y-router acts as a translation layer that: ## OpenRouter Reasoning Support -y-router fully supports OpenRouter's reasoning capabilities (e.g., for DeepSeek R1), bridging the gap between Claude's `thinking` parameter and OpenRouter's `reasoning` parameter. +open-claude-router fully supports OpenRouter's reasoning capabilities (e.g., for DeepSeek R1), bridging the gap between Claude's `thinking` parameter and OpenRouter's `reasoning` parameter. - **Bidirectional Mapping**: - **Requests**: Claude's `thinking` block (e.g., from Claude Code) is converted to OpenRouter's `reasoning` parameter. - **Responses**: OpenRouter's `reasoning` tokens are converted back into Claude's `thinking` content blocks. - **Streaming Support**: Reasoning tokens are streamed in real-time as `thinking_delta` events, allowing you to see the model's thought process in compatible tools. -- **Smart Defaults**: If no reasoning parameters are provided, y-router defaults to `effort: 'high'` to ensure reasoning models perform at their best. +- **Smart Defaults**: If no reasoning parameters are provided, open-claude-router defaults to `effort: 'high'` to ensure reasoning models perform at their best. ## Perfect for Claude Code + OpenRouter This allows you to use [Claude Code](https://claude.ai/code) with OpenRouter's vast selection of models by: -1. Pointing Claude Code to your y-router deployment +1. Pointing Claude Code to your open-claude-router deployment 2. Using your OpenRouter API key 3. Accessing Claude models available on OpenRouter through Claude Code's interface @@ -122,7 +122,7 @@ This allows you to use [Claude Code](https://claude.ai/code) with OpenRouter's v 1. **Clone and start with Docker:** ```bash git clone - cd y-router + cd open-claude-router docker-compose up -d ``` @@ -136,7 +136,7 @@ This allows you to use [Claude Code](https://claude.ai/code) with OpenRouter's v 1. **Clone and deploy:** ```bash git clone - cd y-router + cd open-claude-router npm install -g wrangler wrangler deploy ``` @@ -189,7 +189,7 @@ For easier deployment and development, you can use Docker: ```bash # Clone the repository git clone -cd y-router +cd open-claude-router # Start with Docker Compose docker-compose up -d @@ -201,12 +201,12 @@ docker-compose up -d ```bash # Build the Docker image -docker build -t y-router . +docker build -t open-claude-router . # Run the container docker run -d -p 8787:8787 \ -e OPENROUTER_BASE_URL=https://openrouter.ai/api/v1 \ - y-router + open-claude-router ``` #### Environment Configuration @@ -242,7 +242,7 @@ docker-compose ps ## Thanks -Special thanks to these projects that inspired y-router: +Special thanks to these projects that inspired open-claude-router: - [claude-code-router](https://github.com/musistudio/claude-code-router) - [claude-code-proxy](https://github.com/kiyo-e/claude-code-proxy) @@ -250,11 +250,11 @@ Special thanks to these projects that inspired y-router: **Important Legal Notice:** -- **Third-party Tool**: y-router is an independent, unofficial tool and is not affiliated with, endorsed by, or supported by Anthropic PBC, OpenAI, or OpenRouter +- **Third-party Tool**: open-claude-router is an independent, unofficial tool and is not affiliated with, endorsed by, or supported by Anthropic PBC, OpenAI, or OpenRouter - **Service Terms**: Users are responsible for ensuring compliance with the Terms of Service of all involved parties (Anthropic, OpenRouter, and any other API providers) - **API Key Responsibility**: Users must use their own valid API keys and are solely responsible for any usage, costs, or violations associated with those keys - **No Warranty**: This software is provided "as is" without any warranties. The authors are not responsible for any damages, service interruptions, or legal issues arising from its use -- **Data Privacy**: While y-router does not intentionally store user data, users should review the privacy policies of all connected services +- **Data Privacy**: While open-claude-router does not intentionally store user data, users should review the privacy policies of all connected services - **Compliance**: Users are responsible for ensuring their use complies with applicable laws and regulations in their jurisdiction - **Commercial Use**: Any commercial use should be carefully evaluated against relevant terms of service and licensing requirements diff --git a/docker-compose.yml b/docker-compose.yml index 3387c32..6c100ea 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,5 @@ services: - y-router: + open-claude-router: build: . user: "root" ports: diff --git a/indexHtml.ts b/indexHtml.ts index 0b3f877..2098d89 100644 --- a/indexHtml.ts +++ b/indexHtml.ts @@ -225,12 +225,12 @@ export ANTHROPIC_SMALL_FAST_MODEL="google/gemini-2.5-flash"
-

For data privacy: Consider self-deploying y-router to Cloudflare Workers instead of using this shared instance.

+

For data privacy: Consider self-deploying open-claude-router to Cloudflare Workers instead of using this shared instance.