diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..84aa78f64cb6cc76511014a5ec6fa119f4c4ab89 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules + +.vscode/ + +.git/ +.github/ +.gitignore + +dist/ +tests/ +*.md + +.eslintcache diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000000000000000000000000000000000..0c71cd8283a2430b92e1935fdb8b8eac4c1c2d44 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,15 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: ericc_ch +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +polar: # Replace with a single Polar username +buy_me_a_coffee: # Replace with a single Buy Me a Coffee username +thanks_dev: # Replace with a single thanks.dev username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..34715c31e6bbed237fa7cd23e365f50c81e35471 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [master] + pull_request: + types: [opened, synchronize, reopened] + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Run linter + run: bun run lint:all + + - name: Run type check + run: bun run typecheck + + - name: Run tests + run: bun test + + - name: Build + run: bun run build \ No newline at end of file diff --git a/.github/workflows/deploy-pages.yml b/.github/workflows/deploy-pages.yml new file mode 100644 index 0000000000000000000000000000000000000000..e5b05974c7e37334c456b2bac3ce4a9c3abec804 --- /dev/null +++ b/.github/workflows/deploy-pages.yml @@ -0,0 +1,40 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: [ "main" ] + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + deploy: + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: ./pages + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 \ No newline at end of file diff --git a/.github/workflows/release-docker.yml b/.github/workflows/release-docker.yml new file mode 100644 index 0000000000000000000000000000000000000000..97b124ba102a525b715c92d58f281f3bcbab5f78 --- /dev/null +++ b/.github/workflows/release-docker.yml @@ -0,0 +1,91 @@ +name: Docker Build and Push + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +on: + push: + # branches: [ "main" ] + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + paths-ignore: + - 'docs/**' + +env: + # Use docker.io for Docker Hub if empty + REGISTRY: ghcr.io + # github.repository as / + #IMAGE_NAME: ${{ github.repository }} + + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Set version + id: version + run: | + mkdir -p handlers + echo ${GITHUB_REF#refs/tags/v} > handlers/VERSION + + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@main + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + with: + platforms: 'arm64,amd64' + + # Workaround: https://github.com/docker/build-push-action/issues/461 + - name: Setup Docker buildx + uses: docker/setup-buildx-action@v2 + + # Login against a Docker registry except on PR + # https://github.com/docker/login-action + - name: Log into registry ${{ env.REGISTRY }} + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + # Extract metadata (tags, labels) for Docker + # https://github.com/docker/metadata-action + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v4 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + images: ${{ env.REGISTRY }}/${{ github.repository }} + tags: | + type=semver,pattern=v{{version}} + type=semver,pattern=v{{major}}.{{minor}} + type=semver,pattern=v{{major}} + + # Build and push Docker image with Buildx (don't push on PR) + # https://github.com/docker/build-push-action + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v3 + with: + context: . + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + platforms: linux/amd64,linux/arm64 + labels: ${{ steps.meta.outputs.labels }} + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000000000000000000000000000000000..4bfcf04332d894feb8210e660601f24f6d28d864 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,26 @@ +name: Release + +permissions: + id-token: write + contents: write + +on: + push: + tags: + - "v*" + +jobs: + release: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bunx changelogithub + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..577a4f19978c9ee14fe0373314264259e0544978 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# deps +node_modules/ + +# local env +*.local + +# aider +.aider* + +# eslint cache +.eslintcache + +# build output +dist/ \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000000000000000000000000000000000..b53ebbc306ee9ec668e3f7c1a71f2c4c368c4217 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "deno.enable": false, + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.organizeImports": "never" + }, + "typescript.tsdk": "node_modules/typescript/lib" +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000000000000000000000000000000000..d509d5b6b60ac971569ccc60b4933684a467d6e9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,47 @@ +# AGENTS.md + +## Build, Lint, and Test Commands + +- **Build:** + `bun run build` (uses tsup) +- **Dev:** + `bun run dev` +- **Lint:** + `bun run lint` (uses @echristian/eslint-config) +- **Lint & Fix staged files:** + `bunx lint-staged` +- **Test all:** + `bun test` +- **Test single file:** + `bun test tests/claude-request.test.ts` +- **Start (prod):** + `bun run start` + +## Code Style Guidelines + +- **Imports:** + Use ESNext syntax. Prefer absolute imports via `~/*` for `src/*` (see `tsconfig.json`). +- **Formatting:** + Follows Prettier (with `prettier-plugin-packagejson`). Run `bun run lint` to auto-fix. +- **Types:** + Strict TypeScript (`strict: true`). Avoid `any`; use explicit types and interfaces. +- **Naming:** + Use `camelCase` for variables/functions, `PascalCase` for types/classes. +- **Error Handling:** + Use explicit error classes (see `src/lib/error.ts`). Avoid silent failures. +- **Unused:** + Unused imports/variables are errors (`noUnusedLocals`, `noUnusedParameters`). +- **Switches:** + No fallthrough in switch statements. +- **Modules:** + Use ESNext modules, no CommonJS. +- **Testing:** + Use Bun's built-in test runner. Place tests in `tests/`, name as `*.test.ts`. +- **Linting:** + Uses `@echristian/eslint-config` (see npm for details). Includes stylistic, unused imports, regex, and package.json rules. +- **Paths:** + Use path aliases (`~/*`) for imports from `src/`. + +--- + +This file is tailored for agentic coding agents. For more details, see the configs in `eslint.config.js` and `tsconfig.json`. No Cursor or Copilot rules detected. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..1265220efa57a83a41a2d4c447dcbcceb43782a8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM oven/bun:1.2.19-alpine AS builder +WORKDIR /app + +COPY ./package.json ./bun.lock ./ +RUN bun install --frozen-lockfile + +COPY . . +RUN bun run build + +FROM oven/bun:1.2.19-alpine AS runner +WORKDIR /app + +COPY ./package.json ./bun.lock ./ +RUN bun install --frozen-lockfile --production --ignore-scripts --no-cache + +COPY --from=builder /app/dist ./dist + +EXPOSE 4141 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD wget --spider -q http://localhost:4141/ || exit 1 + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..a2c56a19ea5cf31cfb2a8e5ca8bbfe3c50a7cd52 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Erick Christian Purwanto + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 6c4ed40429093d796c2b53fce92366637f9696c2..0d36c13c969c4fc827a6f86367b19e48abb2ecda 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,351 @@ +# Copilot API Proxy + +> [!WARNING] +> This is a reverse-engineered proxy of GitHub Copilot API. It is not supported by GitHub, and may break unexpectedly. Use at your own risk. + +> [!WARNING] +> **GitHub Security Notice:** +> Excessive automated or scripted use of Copilot (including rapid or bulk requests, such as via automated tools) may trigger GitHub's abuse-detection systems. +> You may receive a warning from GitHub Security, and further anomalous activity could result in temporary suspension of your Copilot access. +> +> GitHub prohibits use of their servers for excessive automated bulk activity or any activity that places undue burden on their infrastructure. +> +> Please review: +> +> - [GitHub Acceptable Use Policies](https://docs.github.com/site-policy/acceptable-use-policies/github-acceptable-use-policies#4-spam-and-inauthentic-activity-on-github) +> - [GitHub Copilot Terms](https://docs.github.com/site-policy/github-terms/github-terms-for-additional-products-and-features#github-copilot) +> +> Use this proxy responsibly to avoid account restrictions. + +[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/E1E519XS7W) + --- -title: Copilot Api -emoji: 🦀 -colorFrom: yellow -colorTo: green -sdk: docker -pinned: false -license: mit -short_description: copilot-api + +**Note:** If you are using [opencode](https://github.com/sst/opencode), you do not need this project. Opencode supports GitHub Copilot provider out of the box. + --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +## Project Overview + +A reverse-engineered proxy for the GitHub Copilot API that exposes it as an OpenAI and Anthropic compatible service. This allows you to use GitHub Copilot with any tool that supports the OpenAI Chat Completions API or the Anthropic Messages API, including to power [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview). + +## Features + +- **OpenAI & Anthropic Compatibility**: Exposes GitHub Copilot as an OpenAI-compatible (`/v1/chat/completions`, `/v1/models`, `/v1/embeddings`) and Anthropic-compatible (`/v1/messages`) API. +- **Claude Code Integration**: Easily configure and launch [Claude Code](https://docs.anthropic.com/en/docs/claude-code/overview) to use Copilot as its backend with a simple command-line flag (`--claude-code`). +- **Usage Dashboard**: A web-based dashboard to monitor your Copilot API usage, view quotas, and see detailed statistics. +- **Rate Limit Control**: Manage API usage with rate-limiting options (`--rate-limit`) and a waiting mechanism (`--wait`) to prevent errors from rapid requests. +- **Manual Request Approval**: Manually approve or deny each API request for fine-grained control over usage (`--manual`). +- **Token Visibility**: Option to display GitHub and Copilot tokens during authentication and refresh for debugging (`--show-token`). +- **Flexible Authentication**: Authenticate interactively or provide a GitHub token directly, suitable for CI/CD environments. +- **Support for Different Account Types**: Works with individual, business, and enterprise GitHub Copilot plans. + +## Demo + +https://github.com/user-attachments/assets/7654b383-669d-4eb9-b23c-06d7aefee8c5 + +## Prerequisites + +- Bun (>= 1.2.x) +- GitHub account with Copilot subscription (individual, business, or enterprise) + +## Installation + +To install dependencies, run: + +```sh +bun install +``` + +## Using with Docker + +Build image + +```sh +docker build -t copilot-api . +``` + +Run the container + +```sh +# Create a directory on your host to persist the GitHub token and related data +mkdir -p ./copilot-data + +# Run the container with a bind mount to persist the token +# This ensures your authentication survives container restarts + +docker run -p 4141:4141 -v $(pwd)/copilot-data:/root/.local/share/copilot-api copilot-api +``` + +> **Note:** +> The GitHub token and related data will be stored in `copilot-data` on your host. This is mapped to `/root/.local/share/copilot-api` inside the container, ensuring persistence across restarts. + +### Docker with Environment Variables + +You can pass the GitHub token directly to the container using environment variables: + +```sh +# Build with GitHub token +docker build --build-arg GH_TOKEN=your_github_token_here -t copilot-api . + +# Run with GitHub token +docker run -p 4141:4141 -e GH_TOKEN=your_github_token_here copilot-api + +# Run with additional options +docker run -p 4141:4141 -e GH_TOKEN=your_token copilot-api start --verbose --port 4141 +``` + +### Docker Compose Example + +```yaml +version: "3.8" +services: + copilot-api: + build: . + ports: + - "4141:4141" + environment: + - GH_TOKEN=your_github_token_here + restart: unless-stopped +``` + +The Docker image includes: + +- Multi-stage build for optimized image size +- Non-root user for enhanced security +- Health check for container monitoring +- Pinned base image version for reproducible builds + +## Using with npx + +You can run the project directly using npx: + +```sh +npx copilot-api@latest start +``` + +With options: + +```sh +npx copilot-api@latest start --port 8080 +``` + +For authentication only: + +```sh +npx copilot-api@latest auth +``` + +## Command Structure + +Copilot API now uses a subcommand structure with these main commands: + +- `start`: Start the Copilot API server. This command will also handle authentication if needed. +- `auth`: Run GitHub authentication flow without starting the server. This is typically used if you need to generate a token for use with the `--github-token` option, especially in non-interactive environments. +- `check-usage`: Show your current GitHub Copilot usage and quota information directly in the terminal (no server required). +- `debug`: Display diagnostic information including version, runtime details, file paths, and authentication status. Useful for troubleshooting and support. + +## Command Line Options + +### Start Command Options + +The following command line options are available for the `start` command: + +| Option | Description | Default | Alias | +| -------------- | ----------------------------------------------------------------------------- | ---------- | ----- | +| --port | Port to listen on | 4141 | -p | +| --verbose | Enable verbose logging | false | -v | +| --account-type | Account type to use (individual, business, enterprise) | individual | -a | +| --manual | Enable manual request approval | false | none | +| --rate-limit | Rate limit in seconds between requests | none | -r | +| --wait | Wait instead of error when rate limit is hit | false | -w | +| --github-token | Provide GitHub token directly (must be generated using the `auth` subcommand) | none | -g | +| --claude-code | Generate a command to launch Claude Code with Copilot API config | false | -c | +| --show-token | Show GitHub and Copilot tokens on fetch and refresh | false | none | +| --proxy-env | Initialize proxy from environment variables | false | none | + +### Auth Command Options + +| Option | Description | Default | Alias | +| ------------ | ------------------------- | ------- | ----- | +| --verbose | Enable verbose logging | false | -v | +| --show-token | Show GitHub token on auth | false | none | + +### Debug Command Options + +| Option | Description | Default | Alias | +| ------ | ------------------------- | ------- | ----- | +| --json | Output debug info as JSON | false | none | + +## API Endpoints + +The server exposes several endpoints to interact with the Copilot API. It provides OpenAI-compatible endpoints and now also includes support for Anthropic-compatible endpoints, allowing for greater flexibility with different tools and services. + +### OpenAI Compatible Endpoints + +These endpoints mimic the OpenAI API structure. + +| Endpoint | Method | Description | +| --------------------------- | ------ | --------------------------------------------------------- | +| `POST /v1/chat/completions` | `POST` | Creates a model response for the given chat conversation. | +| `GET /v1/models` | `GET` | Lists the currently available models. | +| `POST /v1/embeddings` | `POST` | Creates an embedding vector representing the input text. | + +### Anthropic Compatible Endpoints + +These endpoints are designed to be compatible with the Anthropic Messages API. + +| Endpoint | Method | Description | +| -------------------------------- | ------ | ------------------------------------------------------------ | +| `POST /v1/messages` | `POST` | Creates a model response for a given conversation. | +| `POST /v1/messages/count_tokens` | `POST` | Calculates the number of tokens for a given set of messages. | + +### Usage Monitoring Endpoints + +New endpoints for monitoring your Copilot usage and quotas. + +| Endpoint | Method | Description | +| ------------ | ------ | ------------------------------------------------------------ | +| `GET /usage` | `GET` | Get detailed Copilot usage statistics and quota information. | +| `GET /token` | `GET` | Get the current Copilot token being used by the API. | + +## Example Usage + +Using with npx: + +```sh +# Basic usage with start command +npx copilot-api@latest start + +# Run on custom port with verbose logging +npx copilot-api@latest start --port 8080 --verbose + +# Use with a business plan GitHub account +npx copilot-api@latest start --account-type business + +# Use with an enterprise plan GitHub account +npx copilot-api@latest start --account-type enterprise + +# Enable manual approval for each request +npx copilot-api@latest start --manual + +# Set rate limit to 30 seconds between requests +npx copilot-api@latest start --rate-limit 30 + +# Wait instead of error when rate limit is hit +npx copilot-api@latest start --rate-limit 30 --wait + +# Provide GitHub token directly +npx copilot-api@latest start --github-token ghp_YOUR_TOKEN_HERE + +# Run only the auth flow +npx copilot-api@latest auth + +# Run auth flow with verbose logging +npx copilot-api@latest auth --verbose + +# Show your Copilot usage/quota in the terminal (no server needed) +npx copilot-api@latest check-usage + +# Display debug information for troubleshooting +npx copilot-api@latest debug + +# Display debug information in JSON format +npx copilot-api@latest debug --json + +# Initialize proxy from environment variables (HTTP_PROXY, HTTPS_PROXY, etc.) +npx copilot-api@latest start --proxy-env +``` + +## Using the Usage Viewer + +After starting the server, a URL to the Copilot Usage Dashboard will be displayed in your console. This dashboard is a web interface for monitoring your API usage. + +1. Start the server. For example, using npx: + ```sh + npx copilot-api@latest start + ``` +2. The server will output a URL to the usage viewer. Copy and paste this URL into your browser. It will look something like this: + `https://ericc-ch.github.io/copilot-api?endpoint=http://localhost:4141/usage` + - If you use the `start.bat` script on Windows, this page will open automatically. + +The dashboard provides a user-friendly interface to view your Copilot usage data: + +- **API Endpoint URL**: The dashboard is pre-configured to fetch data from your local server endpoint via the URL query parameter. You can change this URL to point to any other compatible API endpoint. +- **Fetch Data**: Click the "Fetch" button to load or refresh the usage data. The dashboard will automatically fetch data on load. +- **Usage Quotas**: View a summary of your usage quotas for different services like Chat and Completions, displayed with progress bars for a quick overview. +- **Detailed Information**: See the full JSON response from the API for a detailed breakdown of all available usage statistics. +- **URL-based Configuration**: You can also specify the API endpoint directly in the URL using a query parameter. This is useful for bookmarks or sharing links. For example: + `https://ericc-ch.github.io/copilot-api?endpoint=http://your-api-server/usage` + +## Using with Claude Code + +This proxy can be used to power [Claude Code](https://docs.anthropic.com/en/claude-code), an experimental conversational AI assistant for developers from Anthropic. + +There are two ways to configure Claude Code to use this proxy: + +### Interactive Setup with `--claude-code` flag + +To get started, run the `start` command with the `--claude-code` flag: + +```sh +npx copilot-api@latest start --claude-code +``` + +You will be prompted to select a primary model and a "small, fast" model for background tasks. After selecting the models, a command will be copied to your clipboard. This command sets the necessary environment variables for Claude Code to use the proxy. + +Paste and run this command in a new terminal to launch Claude Code. + +### Manual Configuration with `settings.json` + +Alternatively, you can configure Claude Code by creating a `.claude/settings.json` file in your project's root directory. This file should contain the environment variables needed by Claude Code. This way you don't need to run the interactive setup every time. + +Here is an example `.claude/settings.json` file: + +```json +{ + "env": { + "ANTHROPIC_BASE_URL": "http://localhost:4141", + "ANTHROPIC_AUTH_TOKEN": "dummy", + "ANTHROPIC_MODEL": "gpt-4.1", + "ANTHROPIC_DEFAULT_SONNET_MODEL": "gpt-4.1", + "ANTHROPIC_SMALL_FAST_MODEL": "gpt-4.1", + "ANTHROPIC_DEFAULT_HAIKU_MODEL": "gpt-4.1", + "DISABLE_NON_ESSENTIAL_MODEL_CALLS": "1", + "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1" + }, + "permissions": { + "deny": [ + "WebSearch" + ] + } +} +``` + +You can find more options here: [Claude Code settings](https://docs.anthropic.com/en/docs/claude-code/settings#environment-variables) + +You can also read more about IDE integration here: [Add Claude Code to your IDE](https://docs.anthropic.com/en/docs/claude-code/ide-integrations) + +## Running from Source + +The project can be run from source in several ways: + +### Development Mode + +```sh +bun run dev +``` + +### Production Mode + +```sh +bun run start +``` + +## Usage Tips + +- To avoid hitting GitHub Copilot's rate limits, you can use the following flags: + - `--manual`: Enables manual approval for each request, giving you full control over when requests are sent. + - `--rate-limit `: Enforces a minimum time interval between requests. For example, `copilot-api start --rate-limit 30` will ensure there's at least a 30-second gap between requests. + - `--wait`: Use this with `--rate-limit`. It makes the server wait for the cooldown period to end instead of rejecting the request with an error. This is useful for clients that don't automatically retry on rate limit errors. +- If you have a GitHub business or enterprise plan account with Copilot, use the `--account-type` flag (e.g., `--account-type business`). See the [official documentation](https://docs.github.com/en/enterprise-cloud@latest/copilot/managing-copilot/managing-github-copilot-in-your-organization/managing-access-to-github-copilot-in-your-organization/managing-github-copilot-access-to-your-organizations-network#configuring-copilot-subscription-based-network-routing-for-your-enterprise-or-organization) for more details. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000000000000000000000000000000000000..20e895e7f87165f6a47b0cb4b725d4c7467a8972 --- /dev/null +++ b/bun.lock @@ -0,0 +1,1031 @@ +{ + "lockfileVersion": 1, + "workspaces": { + "": { + "name": "copilot-api", + "dependencies": { + "citty": "^0.1.6", + "clipboardy": "^5.0.0", + "consola": "^3.4.2", + "fetch-event-stream": "^0.1.5", + "gpt-tokenizer": "^3.0.1", + "hono": "^4.9.9", + "proxy-from-env": "^1.1.0", + "srvx": "^0.8.9", + "tiny-invariant": "^1.3.3", + "undici": "^7.16.0", + "zod": "^4.1.11", + }, + "devDependencies": { + "@echristian/eslint-config": "^0.0.54", + "@types/bun": "^1.2.23", + "@types/proxy-from-env": "^1.0.4", + "bumpp": "^10.2.3", + "eslint": "^9.37.0", + "knip": "^5.64.1", + "lint-staged": "^16.2.3", + "prettier-plugin-packagejson": "^2.5.19", + "simple-git-hooks": "^2.13.1", + "tsdown": "^0.15.6", + "typescript": "^5.9.3", + }, + }, + }, + "packages": { + "@altano/repository-tools": ["@altano/repository-tools@2.0.1", "", {}, "sha512-YE/52CkFtb+YtHPgbWPai7oo5N9AKnMuP5LM+i2AG7G1H2jdYBCO1iDnkDE3dZ3C1MIgckaF+d5PNRulgt0bdw=="], + + "@babel/generator": ["@babel/generator@7.28.3", "", { "dependencies": { "@babel/parser": "^7.28.3", "@babel/types": "^7.28.2", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="], + + "@babel/parser": ["@babel/parser@7.28.4", "", { "dependencies": { "@babel/types": "^7.28.4" }, "bin": "./bin/babel-parser.js" }, "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg=="], + + "@babel/types": ["@babel/types@7.28.4", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q=="], + + "@echristian/eslint-config": ["@echristian/eslint-config@0.0.54", "", { "dependencies": { "@eslint-react/eslint-plugin": "^1.52.7", "@eslint/js": "^9.34.0", "@eslint/json": "^0.13.2", "@stylistic/eslint-plugin": "^5.2.3", "defu": "^6.1.4", "eslint-config-flat-gitignore": "^2.1.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-de-morgan": "^1.3.1", "eslint-plugin-jsx-a11y": "^6.10.2", "eslint-plugin-package-json": "^0.56.0", "eslint-plugin-perfectionist": "^4.15.0", "eslint-plugin-prettier": "^5.5.4", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-regexp": "^2.10.0", "eslint-plugin-unicorn": "^60.0.0", "eslint-plugin-unused-imports": "^4.2.0", "globals": "^16.3.0", "prettier": "^3.6.2", "typescript-eslint": "^8.41.0" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-aR8vS932kZ9kW5ue1AhaYsTs0lwP0eITNzgBMAMnhBNq+8Sy2mP7I6m3zEzY3Mob4RsrBM5uY9H5SlnEw8+cEg=="], + + "@emnapi/core": ["@emnapi/core@1.5.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.1.0", "tslib": "^2.4.0" } }, "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg=="], + + "@emnapi/runtime": ["@emnapi/runtime@1.5.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ=="], + + "@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.1.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ=="], + + "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.0", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g=="], + + "@eslint-community/regexpp": ["@eslint-community/regexpp@4.12.1", "", {}, "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ=="], + + "@eslint-react/ast": ["@eslint-react/ast@1.53.1", "", { "dependencies": { "@eslint-react/eff": "1.53.1", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/typescript-estree": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" } }, "sha512-qvUC99ewtriJp9quVEOvZ6+RHcsMLfVQ0OhZ4/LupZUDhjW7GiX1dxJsFaxHdJ9rLNLhQyLSPmbAToeqUrSruQ=="], + + "@eslint-react/core": ["@eslint-react/core@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/type-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "birecord": "^0.1.1", "ts-pattern": "^5.8.0" } }, "sha512-8prroos5/Uvvh8Tjl1HHCpq4HWD3hV9tYkm7uXgKA6kqj0jHlgRcQzuO6ZPP7feBcK3uOeug7xrq03BuG8QKCA=="], + + "@eslint-react/eff": ["@eslint-react/eff@1.53.1", "", {}, "sha512-uq20lPRAmsWRjIZm+mAV/2kZsU2nDqn5IJslxGWe3Vfdw23hoyhEw3S1KKlxbftwbTvsZjKvVP0iw3bZo/NUpg=="], + + "@eslint-react/eslint-plugin": ["@eslint-react/eslint-plugin@1.53.1", "", { "dependencies": { "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/type-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "eslint-plugin-react-debug": "1.53.1", "eslint-plugin-react-dom": "1.53.1", "eslint-plugin-react-hooks-extra": "1.53.1", "eslint-plugin-react-naming-convention": "1.53.1", "eslint-plugin-react-web-api": "1.53.1", "eslint-plugin-react-x": "1.53.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["typescript"] }, "sha512-JZ2ciXNCC9CtBBAqYtwWH+Jy/7ZzLw+whei8atP4Fxsbh+Scs30MfEwBzuiEbNw6uF9eZFfPidchpr5RaEhqxg=="], + + "@eslint-react/kit": ["@eslint-react/kit@1.53.1", "", { "dependencies": { "@eslint-react/eff": "1.53.1", "@typescript-eslint/utils": "^8.43.0", "ts-pattern": "^5.8.0", "zod": "^4.1.5" } }, "sha512-zOi2le9V4rMrJvQV4OeedGvMGvDT46OyFPOwXKs7m0tQu5vXVJ8qwIPaVQT1n/WIuvOg49OfmAVaHpGxK++xLQ=="], + + "@eslint-react/shared": ["@eslint-react/shared@1.53.1", "", { "dependencies": { "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@typescript-eslint/utils": "^8.43.0", "ts-pattern": "^5.8.0", "zod": "^4.1.5" } }, "sha512-gomJQmFqQgQVI3Ra4vTMG/s6a4bx3JqeNiTBjxBJt4C9iGaBj458GkP4LJHX7TM6xUzX+fMSKOPX7eV3C/+UCw=="], + + "@eslint-react/var": ["@eslint-react/var@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/eff": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" } }, "sha512-yzwopvPntcHU7mmDvWzRo1fb8QhjD8eDRRohD11rTV1u7nWO4QbJi0pOyugQakvte1/W11Y0Vr8Of0Ojk/A6zg=="], + + "@eslint/compat": ["@eslint/compat@1.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0" }, "peerDependencies": { "eslint": "^8.40 || 9" }, "optionalPeers": ["eslint"] }, "sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg=="], + + "@eslint/config-array": ["@eslint/config-array@0.21.0", "", { "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", "minimatch": "^3.1.2" } }, "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ=="], + + "@eslint/config-helpers": ["@eslint/config-helpers@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0" } }, "sha512-WUFvV4WoIwW8Bv0KeKCIIEgdSiFOsulyN0xrMu+7z43q/hkOLXjvb5u7UC9jDxvRzcrbEmuZBX5yJZz1741jog=="], + + "@eslint/core": ["@eslint/core@0.16.0", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-nmC8/totwobIiFcGkDza3GIKfAw1+hLiYVrh3I1nIomQ8PEr5cxg34jnkmGawul/ep52wGRAcyeDCNtWKSOj4Q=="], + + "@eslint/eslintrc": ["@eslint/eslintrc@3.3.1", "", { "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", "minimatch": "^3.1.2", "strip-json-comments": "^3.1.1" } }, "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ=="], + + "@eslint/js": ["@eslint/js@9.37.0", "", {}, "sha512-jaS+NJ+hximswBG6pjNX0uEJZkrT0zwpVi3BA3vX22aFGjJjmgSTSmPpZCRKmoBL5VY/M6p0xsSJx7rk7sy5gg=="], + + "@eslint/json": ["@eslint/json@0.13.2", "", { "dependencies": { "@eslint/core": "^0.15.2", "@eslint/plugin-kit": "^0.3.5", "@humanwhocodes/momoa": "^3.3.9", "natural-compare": "^1.4.0" } }, "sha512-yWLyRE18rHgHXhWigRpiyv1LDPkvWtC6oa7QHXW7YdP6gosJoq7BiLZW2yCs9U7zN7X4U3ZeOJjepA10XAOIMw=="], + + "@eslint/object-schema": ["@eslint/object-schema@2.1.6", "", {}, "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA=="], + + "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.0", "", { "dependencies": { "@eslint/core": "^0.16.0", "levn": "^0.4.1" } }, "sha512-sB5uyeq+dwCWyPi31B2gQlVlo+j5brPlWx4yZBrEaRo/nhdDE8Xke1gsGgtiBdaBTxuTkceLVuVt/pclrasb0A=="], + + "@humanfs/core": ["@humanfs/core@0.19.1", "", {}, "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA=="], + + "@humanfs/node": ["@humanfs/node@0.16.7", "", { "dependencies": { "@humanfs/core": "^0.19.1", "@humanwhocodes/retry": "^0.4.0" } }, "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ=="], + + "@humanwhocodes/module-importer": ["@humanwhocodes/module-importer@1.0.1", "", {}, "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="], + + "@humanwhocodes/momoa": ["@humanwhocodes/momoa@3.3.9", "", {}, "sha512-LHw6Op4bJb3/3KZgOgwflJx5zY9XOy0NU1NuyUFKGdTwHYmP+PbnQGCYQJ8NVNlulLfQish34b0VuUlLYP3AXA=="], + + "@humanwhocodes/retry": ["@humanwhocodes/retry@0.4.3", "", {}, "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.0.6", "", { "dependencies": { "@emnapi/core": "^1.5.0", "@emnapi/runtime": "^1.5.0", "@tybys/wasm-util": "^0.10.1" } }, "sha512-DXj75ewm11LIWUk198QSKUTxjyRjsBwk09MuMk5DGK+GDUtyPhhEHOGP/Xwwj3DjQXXkivoBirmOnKrLfc0+9g=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@oxc-project/types": ["@oxc-project/types@0.93.0", "", {}, "sha512-yNtwmWZIBtJsMr5TEfoZFDxIWV6OdScOpza/f5YxbqUMJk+j6QX3Cf3jgZShGEFYWQJ5j9mJ6jM0tZHu2J9Yrg=="], + + "@oxc-resolver/binding-android-arm-eabi": ["@oxc-resolver/binding-android-arm-eabi@11.9.0", "", { "os": "android", "cpu": "arm" }, "sha512-4AxaG6TkSBQ2FiC5oGZEJQ35DjsSfAbW6/AJauebq4EzIPVOIgDJCF4de+PvX/Xi9BkNw6VtJuMXJdWW97iEAA=="], + + "@oxc-resolver/binding-android-arm64": ["@oxc-resolver/binding-android-arm64@11.9.0", "", { "os": "android", "cpu": "arm64" }, "sha512-oOEg7rUd2M6YlmRkvPcszJ6KO6TaLGN21oDdcs27gbTVYbQQtCWYbZz5jRW5zEBJu6dopoWVx+shJNGtG1qDFw=="], + + "@oxc-resolver/binding-darwin-arm64": ["@oxc-resolver/binding-darwin-arm64@11.9.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-fM6zE/j6o3C1UIkcZPV7C1f186R7w97guY2N4lyNLlhlgwwhd46acnOezLARvRNU5oyKNev4PvOJhGCCDnFMGg=="], + + "@oxc-resolver/binding-darwin-x64": ["@oxc-resolver/binding-darwin-x64@11.9.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-Bg3Orw7gAxbUqQlt64YPWvHDVo3bo2JfI26Qmzv6nKo7mIMTDhQKl7YmywtLNMYbX0IgUM4qu1V90euu+WCDOw=="], + + "@oxc-resolver/binding-freebsd-x64": ["@oxc-resolver/binding-freebsd-x64@11.9.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-eBqVZqTETH6miBfIZXvpzUe98WATz2+Sh+LEFwuRpGsTsKkIpTyb4p1kwylCLkxrd3Yx7wkxQku+L0AMEGBiAA=="], + + "@oxc-resolver/binding-linux-arm-gnueabihf": ["@oxc-resolver/binding-linux-arm-gnueabihf@11.9.0", "", { "os": "linux", "cpu": "arm" }, "sha512-QgCk/IJnGBvpbc8rYTVgO+A3m3edJjH1zfv8Nvx7fmsxpbXwWH2l4b4tY3/SLMzasxsp7x7k87+HWt095bI5Lg=="], + + "@oxc-resolver/binding-linux-arm-musleabihf": ["@oxc-resolver/binding-linux-arm-musleabihf@11.9.0", "", { "os": "linux", "cpu": "arm" }, "sha512-xkJH0jldIXD2GwoHpCDEF0ucJ7fvRETCL+iFLctM679o7qeDXvtzsO/E401EgFFXcWBJNKXWvH+ZfdYMKyowfA=="], + + "@oxc-resolver/binding-linux-arm64-gnu": ["@oxc-resolver/binding-linux-arm64-gnu@11.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TWq+y2psMzbMtZB9USAq2bSA7NV1TMmh9lhAFbMGQ8Yp2YV4BRC/HilD6qF++efQl6shueGBFOv0LVe9BUXaIA=="], + + "@oxc-resolver/binding-linux-arm64-musl": ["@oxc-resolver/binding-linux-arm64-musl@11.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-8WwGLfXk7yttc6rD6g53+RnYfX5B8xOot1ffthLn8oCXzVRO4cdChlmeHStxwLD/MWx8z8BGeyfyINNrsh9N2w=="], + + "@oxc-resolver/binding-linux-ppc64-gnu": ["@oxc-resolver/binding-linux-ppc64-gnu@11.9.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ZWiAXfan6actlSzayaFS/kYO2zD6k1k0fmLb1opbujXYMKepEnjjVOvKdzCIYR/zKzudqI39dGc+ywqVdsPIpQ=="], + + "@oxc-resolver/binding-linux-riscv64-gnu": ["@oxc-resolver/binding-linux-riscv64-gnu@11.9.0", "", { "os": "linux", "cpu": "none" }, "sha512-p9mCSb+Bym+eycNo9k+81wQ5SAE31E+/rtfbDmF4/7krPotkEjPsEBSc3rqunRwO+FtsUn7H68JLY7hlai49eQ=="], + + "@oxc-resolver/binding-linux-riscv64-musl": ["@oxc-resolver/binding-linux-riscv64-musl@11.9.0", "", { "os": "linux", "cpu": "none" }, "sha512-/SePuVxgFhLPciRwsJ8kLVltr+rxh0b6riGFuoPnFXBbHFclKnjNIt3TfqzUj0/vOnslXw3cVGPpmtkm2TgCgg=="], + + "@oxc-resolver/binding-linux-s390x-gnu": ["@oxc-resolver/binding-linux-s390x-gnu@11.9.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-zLuEjlYIzfnr1Ei2UZYQBbCTa/9deh+BEjO9rh1ai8BfEq4uj6RupTtNpgHfgAsEYdqOBVExw9EU1S6SW3RCAw=="], + + "@oxc-resolver/binding-linux-x64-gnu": ["@oxc-resolver/binding-linux-x64-gnu@11.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-cxdg73WG+aVlPu/k4lEQPRVOhWunYOUglW6OSzclZLJJAXZU0tSZ5ymKaqPRkfTsyNSAafj1cA1XYd+P9UxBgw=="], + + "@oxc-resolver/binding-linux-x64-musl": ["@oxc-resolver/binding-linux-x64-musl@11.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sy5nkVdMvNgqcx9sIY7G6U9TYZUZC4cmMGw/wKhJNuuD2/HFGtbje62ttXSwBAbVbmJ2GgZ4ZUo/S1OMyU+/OA=="], + + "@oxc-resolver/binding-wasm32-wasi": ["@oxc-resolver/binding-wasm32-wasi@11.9.0", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-dfi/a0Xh6o6nOLbJdaYuy7txncEcwkRHp9DGGZaAP7zxDiepkBZ6ewSJODQrWwhjVmMteXo+XFzEOMjsC7WUtQ=="], + + "@oxc-resolver/binding-win32-arm64-msvc": ["@oxc-resolver/binding-win32-arm64-msvc@11.9.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-b1yKr+eFwyi8pZMjAQwW352rXpaHAmz7FLK03vFIxdyWzWiiL6S3UrfMu+nKQud38963zu4wNNLm7rdXQazgRA=="], + + "@oxc-resolver/binding-win32-ia32-msvc": ["@oxc-resolver/binding-win32-ia32-msvc@11.9.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-DxRT+1HjCpRH8qYCmGHzgsRCYiK+X14PUM9Fb+aD4TljplA7MdDQXqMISTb4zBZ70AuclvlXKTbW+K1GZop3xA=="], + + "@oxc-resolver/binding-win32-x64-msvc": ["@oxc-resolver/binding-win32-x64-msvc@11.9.0", "", { "os": "win32", "cpu": "x64" }, "sha512-gE3QJvhh0Yj9cSAkkHjRLKPmC7BTJeiaB5YyhVKVUwbnWQgTszV92lZ9pvZtNPEghP7jPbhEs4c6983A0ojQwA=="], + + "@pkgr/core": ["@pkgr/core@0.2.9", "", {}, "sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA=="], + + "@quansync/fs": ["@quansync/fs@0.1.5", "", { "dependencies": { "quansync": "^0.2.11" } }, "sha512-lNS9hL2aS2NZgNW7BBj+6EBl4rOf8l+tQ0eRY6JWCI8jI2kc53gSoqbjojU0OnAWhzoXiOjFyGsHcDGePB3lhA=="], + + "@rolldown/binding-android-arm64": ["@rolldown/binding-android-arm64@1.0.0-beta.41", "", { "os": "android", "cpu": "arm64" }, "sha512-Edflndd9lU7JVhVIvJlZhdCj5DkhYDJPIRn4Dx0RUdfc8asP9xHOI5gMd8MesDDx+BJpdIT/uAmVTearteU/mQ=="], + + "@rolldown/binding-darwin-arm64": ["@rolldown/binding-darwin-arm64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "arm64" }, "sha512-XGCzqfjdk7550PlyZRTBKbypXrB7ATtXhw/+bjtxnklLQs0mKP/XkQVOKyn9qGKSlvH8I56JLYryVxl0PCvSNw=="], + + "@rolldown/binding-darwin-x64": ["@rolldown/binding-darwin-x64@1.0.0-beta.41", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ho6lIwGJed98zub7n0xcRKuEtnZgbxevAmO4x3zn3C3N4GVXZD5xvCvTVxSMoeBJwTcIYzkVDRTIhylQNsTgLQ=="], + + "@rolldown/binding-freebsd-x64": ["@rolldown/binding-freebsd-x64@1.0.0-beta.41", "", { "os": "freebsd", "cpu": "x64" }, "sha512-ijAZETywvL+gACjbT4zBnCp5ez1JhTRs6OxRN4J+D6AzDRbU2zb01Esl51RP5/8ZOlvB37xxsRQ3X4YRVyYb3g=="], + + "@rolldown/binding-linux-arm-gnueabihf": ["@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm" }, "sha512-EgIOZt7UildXKFEFvaiLNBXm+4ggQyGe3E5Z1QP9uRcJJs9omihOnm897FwOBQdCuMvI49iBgjFrkhH+wMJ2MA=="], + + "@rolldown/binding-linux-arm64-gnu": ["@rolldown/binding-linux-arm64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-F8bUwJq8v/JAU8HSwgF4dztoqJ+FjdyjuvX4//3+Fbe2we9UktFeZ27U4lRMXF1vxWtdV4ey6oCSqI7yUrSEeg=="], + + "@rolldown/binding-linux-arm64-musl": ["@rolldown/binding-linux-arm64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "arm64" }, "sha512-MioXcCIX/wB1pBnBoJx8q4OGucUAfC1+/X1ilKFsjDK05VwbLZGRgOVD5OJJpUQPK86DhQciNBrfOKDiatxNmg=="], + + "@rolldown/binding-linux-x64-gnu": ["@rolldown/binding-linux-x64-gnu@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-m66M61fizvRCwt5pOEiZQMiwBL9/y0bwU/+Kc4Ce/Pef6YfoEkR28y+DzN9rMdjo8Z28NXjsDPq9nH4mXnAP0g=="], + + "@rolldown/binding-linux-x64-musl": ["@rolldown/binding-linux-x64-musl@1.0.0-beta.41", "", { "os": "linux", "cpu": "x64" }, "sha512-yRxlSfBvWnnfrdtJfvi9lg8xfG5mPuyoSHm0X01oiE8ArmLRvoJGHUTJydCYz+wbK2esbq5J4B4Tq9WAsOlP1Q=="], + + "@rolldown/binding-openharmony-arm64": ["@rolldown/binding-openharmony-arm64@1.0.0-beta.41", "", { "os": "none", "cpu": "arm64" }, "sha512-PHVxYhBpi8UViS3/hcvQQb9RFqCtvFmFU1PvUoTRiUdBtgHA6fONNHU4x796lgzNlVSD3DO/MZNk1s5/ozSMQg=="], + + "@rolldown/binding-wasm32-wasi": ["@rolldown/binding-wasm32-wasi@1.0.0-beta.41", "", { "dependencies": { "@napi-rs/wasm-runtime": "^1.0.5" }, "cpu": "none" }, "sha512-OAfcO37ME6GGWmj9qTaDT7jY4rM0T2z0/8ujdQIJQ2x2nl+ztO32EIwURfmXOK0U1tzkyuaKYvE34Pug/ucXlQ=="], + + "@rolldown/binding-win32-arm64-msvc": ["@rolldown/binding-win32-arm64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "arm64" }, "sha512-NIYGuCcuXaq5BC4Q3upbiMBvmZsTsEPG9k/8QKQdmrch+ocSy5Jv9tdpdmXJyighKqm182nh/zBt+tSJkYoNlg=="], + + "@rolldown/binding-win32-ia32-msvc": ["@rolldown/binding-win32-ia32-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "ia32" }, "sha512-kANdsDbE5FkEOb5NrCGBJBCaZ2Sabp3D7d4PRqMYJqyLljwh9mDyYyYSv5+QNvdAmifj+f3lviNEUUuUZPEFPw=="], + + "@rolldown/binding-win32-x64-msvc": ["@rolldown/binding-win32-x64-msvc@1.0.0-beta.41", "", { "os": "win32", "cpu": "x64" }, "sha512-UlpxKmFdik0Y2VjZrgUCgoYArZJiZllXgIipdBRV1hw6uK45UbQabSTW6Kp6enuOu7vouYWftwhuxfpE8J2JAg=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-beta.41", "", {}, "sha512-ycMEPrS3StOIeb87BT3/+bu+blEtyvwQ4zmo2IcJQy0Rd1DAAhKksA0iUZ3MYSpJtjlPhg0Eo6mvVS6ggPhRbw=="], + + "@sec-ant/readable-stream": ["@sec-ant/readable-stream@0.4.1", "", {}, "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg=="], + + "@sindresorhus/merge-streams": ["@sindresorhus/merge-streams@4.0.0", "", {}, "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ=="], + + "@stylistic/eslint-plugin": ["@stylistic/eslint-plugin@5.4.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.0", "@typescript-eslint/types": "^8.44.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.3" }, "peerDependencies": { "eslint": ">=9.0.0" } }, "sha512-UG8hdElzuBDzIbjG1QDwnYH0MQ73YLXDFHgZzB4Zh/YJfnw8XNsloVtytqzx0I2Qky9THSdpTmi8Vjn/pf/Lew=="], + + "@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="], + + "@types/node": ["@types/node@24.6.2", "", { "dependencies": { "undici-types": "~7.13.0" } }, "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang=="], + + "@types/proxy-from-env": ["@types/proxy-from-env@1.0.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-TPR9/bCZAr3V1eHN4G3LD3OLicdJjqX1QRXWuNcCYgE66f/K8jO2ZRtHxI2D9MbnuUP6+qiKSS8eUHp6TFHGCw=="], + + "@types/react": ["@types/react@19.2.0", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA=="], + + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.45.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/type-utils": "8.45.0", "@typescript-eslint/utils": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.45.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-HC3y9CVuevvWCl/oyZuI47dOeDF9ztdMEfMH8/DW/Mhwa9cCLnK1oD7JoTVGW/u7kFzNZUKUoyJEqkaJh5y3Wg=="], + + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.45.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-TGf22kon8KW+DeKaUmOibKWktRY8b2NSAZNdtWh798COm1NWx8+xJ6iFBtk3IvLdv6+LGLJLRlyhrhEDZWargQ=="], + + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.45.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.45.0", "@typescript-eslint/types": "^8.45.0", "debug": "^4.3.4" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-3pcVHwMG/iA8afdGLMuTibGR7pDsn9RjDev6CCB+naRsSYs2pns5QbinF4Xqw6YC/Sj3lMrm/Im0eMfaa61WUg=="], + + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0" } }, "sha512-clmm8XSNj/1dGvJeO6VGH7EUSeA0FMs+5au/u3lrA3KfG8iJ4u8ym9/j2tTEoacAffdW1TVUzXO30W1JTJS7dA=="], + + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.45.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-aFdr+c37sc+jqNMGhH+ajxPXwjv9UtFZk79k8pLoJ6p4y0snmYpPA52GuWHgt2ZF4gRRW6odsEj41uZLojDt5w=="], + + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/utils": "8.45.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bpjepLlHceKgyMEPglAeULX1vixJDgaKocp0RVJ5u4wLJIMNuKtUXIczpJCPcn2waII0yuvks/5m5/h3ZQKs0A=="], + + "@typescript-eslint/types": ["@typescript-eslint/types@8.45.0", "", {}, "sha512-WugXLuOIq67BMgQInIxxnsSyRLFxdkJEJu8r4ngLR56q/4Q5LrbfkFRH27vMTjxEK8Pyz7QfzuZe/G15qQnVRA=="], + + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.45.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.45.0", "@typescript-eslint/tsconfig-utils": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/visitor-keys": "8.45.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", "minimatch": "^9.0.4", "semver": "^7.6.0", "ts-api-utils": "^2.1.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.0.0" } }, "sha512-GfE1NfVbLam6XQ0LcERKwdTTPlLvHvXXhOeUGC1OXi4eQBoyy1iVsW+uzJ/J9jtCz6/7GCQ9MtrQ0fml/jWCnA=="], + + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.45.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", "@typescript-eslint/scope-manager": "8.45.0", "@typescript-eslint/types": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-bxi1ht+tLYg4+XV2knz/F7RVhU0k6VrSMc9sb8DQ6fyCTrGQLHfo7lDtN0QJjZjKkLA2ThrKuCdHEvLReqtIGg=="], + + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.45.0", "", { "dependencies": { "@typescript-eslint/types": "8.45.0", "eslint-visitor-keys": "^4.2.1" } }, "sha512-qsaFBA3e09MIDAGFUrTk+dzqtfv1XPVz8t8d1f0ybTzrCY7BKiMC5cjrl1O/P7UmHsNyW90EYSkU/ZWpmXelag=="], + + "acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], + + "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], + + "ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], + + "ansi-escapes": ["ansi-escapes@7.1.1", "", { "dependencies": { "environment": "^1.0.0" } }, "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q=="], + + "ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "ansis": ["ansis@4.2.0", "", {}, "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig=="], + + "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="], + + "args-tokenizer": ["args-tokenizer@0.3.0", "", {}, "sha512-xXAd7G2Mll5W8uo37GETpQ2VrE84M181Z7ugHFGQnJZ50M2mbOv0osSZ9VsSgPfJQ+LVG0prSi0th+ELMsno7Q=="], + + "aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="], + + "array-buffer-byte-length": ["array-buffer-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "is-array-buffer": "^3.0.5" } }, "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw=="], + + "array-includes": ["array-includes@3.1.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.4", "define-properties": "^1.2.1", "es-abstract": "^1.24.0", "es-object-atoms": "^1.1.1", "get-intrinsic": "^1.3.0", "is-string": "^1.1.1", "math-intrinsics": "^1.1.0" } }, "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ=="], + + "array.prototype.flat": ["array.prototype.flat@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg=="], + + "array.prototype.flatmap": ["array.prototype.flatmap@1.3.3", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-shim-unscopables": "^1.0.2" } }, "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg=="], + + "arraybuffer.prototype.slice": ["arraybuffer.prototype.slice@1.0.4", "", { "dependencies": { "array-buffer-byte-length": "^1.0.1", "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "is-array-buffer": "^3.0.4" } }, "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ=="], + + "ast-kit": ["ast-kit@2.1.2", "", { "dependencies": { "@babel/parser": "^7.28.0", "pathe": "^2.0.3" } }, "sha512-cl76xfBQM6pztbrFWRnxbrDm9EOqDr1BF6+qQnnDZG2Co2LjyUktkN9GTJfBAfdae+DbT2nJf2nCGAdDDN7W2g=="], + + "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="], + + "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="], + + "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="], + + "axe-core": ["axe-core@4.10.3", "", {}, "sha512-Xm7bpRXnDSX2YE2YFfBk2FnF0ep6tmG7xPh8iHee8MIcrgq762Nkce856dYtJYLkuIoYZvGfTs/PbZhideTcEg=="], + + "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.8.11", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-i+sRXGhz4+QW8aACZ3+r1GAKMt0wlFpeA8M5rOQd0HEYw9zhDrlx9Wc8uQ0IdXakjJRthzglEwfB/yqIjO6iDg=="], + + "birecord": ["birecord@0.1.1", "", {}, "sha512-VUpsf/qykW0heRlC8LooCq28Kxn3mAqKohhDG/49rrsQ1dT1CXyj/pgXS+5BSRzFTR/3DyIBOqQOrGyZOh71Aw=="], + + "birpc": ["birpc@2.6.1", "", {}, "sha512-LPnFhlDpdSH6FJhJyn4M0kFO7vtQ5iPw24FnG0y21q09xC7e8+1LeR31S1MAIrDAHp4m7aas4bEkTDTvMAtebQ=="], + + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "browserslist": ["browserslist@4.26.3", "", { "dependencies": { "baseline-browser-mapping": "^2.8.9", "caniuse-lite": "^1.0.30001746", "electron-to-chromium": "^1.5.227", "node-releases": "^2.0.21", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w=="], + + "builtin-modules": ["builtin-modules@5.0.0", "", {}, "sha512-bkXY9WsVpY7CvMhKSR6pZilZu9Ln5WDrKVBUXf2S443etkmEO4V58heTecXcUIsNsi4Rx8JUO4NfX1IcQl4deg=="], + + "bumpp": ["bumpp@10.2.3", "", { "dependencies": { "ansis": "^4.1.0", "args-tokenizer": "^0.3.0", "c12": "^3.2.0", "cac": "^6.7.14", "escalade": "^3.2.0", "jsonc-parser": "^3.3.1", "package-manager-detector": "^1.3.0", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.14", "yaml": "^2.8.1" }, "bin": { "bumpp": "bin/bumpp.mjs" } }, "sha512-nsFBZACxuBVu6yzDSaZZaWpX5hTQ+++9WtYkmO+0Bd3cpSq0Mzvqw5V83n+fOyRj3dYuZRFCQf5Z9NNfZj+Rnw=="], + + "bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="], + + "c12": ["c12@3.3.0", "", { "dependencies": { "chokidar": "^4.0.3", "confbox": "^0.2.2", "defu": "^6.1.4", "dotenv": "^17.2.2", "exsolve": "^1.0.7", "giget": "^2.0.0", "jiti": "^2.5.1", "ohash": "^2.0.11", "pathe": "^2.0.3", "perfect-debounce": "^2.0.0", "pkg-types": "^2.3.0", "rc9": "^2.1.2" }, "peerDependencies": { "magicast": "^0.3.5" }, "optionalPeers": ["magicast"] }, "sha512-K9ZkuyeJQeqLEyqldbYLG3wjqwpw4BVaAqvmxq3GYKK0b1A/yYQdIcJxkzAOWcNVWhJpRXAPfZFueekiY/L8Dw=="], + + "cac": ["cac@6.7.14", "", {}, "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ=="], + + "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="], + + "call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="], + + "call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="], + + "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001747", "", {}, "sha512-mzFa2DGIhuc5490Nd/G31xN1pnBnYMadtkyTjefPI7wzypqgCEpeWu9bJr0OnDsyKrW75zA9ZAt7pbQFmwLsQg=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "change-case": ["change-case@5.4.4", "", {}, "sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w=="], + + "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], + + "ci-info": ["ci-info@4.3.0", "", {}, "sha512-l+2bNRMiQgcfILUi33labAZYIWlH1kWDp+ecNo5iisRKrbm0xcRyCww71/YU0Fkw0mAFpz9bJayXPjey6vkmaQ=="], + + "citty": ["citty@0.1.6", "", { "dependencies": { "consola": "^3.2.3" } }, "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ=="], + + "clean-regexp": ["clean-regexp@1.0.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw=="], + + "cli-cursor": ["cli-cursor@5.0.0", "", { "dependencies": { "restore-cursor": "^5.0.0" } }, "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw=="], + + "cli-truncate": ["cli-truncate@5.1.0", "", { "dependencies": { "slice-ansi": "^7.1.0", "string-width": "^8.0.0" } }, "sha512-7JDGG+4Zp0CsknDCedl0DYdaeOhc46QNpXi3NLQblkZpXXgA6LncLDUUyvrjSvZeF3VRQa+KiMGomazQrC1V8g=="], + + "clipboardy": ["clipboardy@5.0.0", "", { "dependencies": { "execa": "^9.6.0", "is-wayland": "^0.1.0", "is-wsl": "^3.1.0", "is64bit": "^2.0.0" } }, "sha512-MQfKHaD09eP80Pev4qBxZLbxJK/ONnqfSYAPlCmPh+7BDboYtO/3BmB6HGzxDIT0SlTRc2tzS8lQqfcdLtZ0Kg=="], + + "cliui": ["cliui@9.0.1", "", { "dependencies": { "string-width": "^7.2.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-k7ndgKhwoQveBL+/1tqGJYNz097I7WOvwbmmU2AR5+magtbjPWQTS1C5vzGkBC8Ym8UWRzfKUzUUqFLypY4Q+w=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="], + + "commander": ["commander@14.0.1", "", {}, "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A=="], + + "comment-parser": ["comment-parser@1.4.1", "", {}, "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg=="], + + "compare-versions": ["compare-versions@6.1.1", "", {}, "sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "confbox": ["confbox@0.2.2", "", {}, "sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ=="], + + "consola": ["consola@3.4.2", "", {}, "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA=="], + + "cookie-es": ["cookie-es@2.0.0", "", {}, "sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg=="], + + "core-js-compat": ["core-js-compat@3.45.1", "", { "dependencies": { "browserslist": "^4.25.3" } }, "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA=="], + + "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], + + "damerau-levenshtein": ["damerau-levenshtein@1.0.8", "", {}, "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA=="], + + "data-view-buffer": ["data-view-buffer@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ=="], + + "data-view-byte-length": ["data-view-byte-length@1.0.2", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-data-view": "^1.0.2" } }, "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ=="], + + "data-view-byte-offset": ["data-view-byte-offset@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" } }, "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="], + + "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="], + + "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="], + + "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], + + "destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="], + + "detect-indent": ["detect-indent@7.0.2", "", {}, "sha512-y+8xyqdGLL+6sh0tVeHcfP/QDd8gUgbasolJJpY7NgeQGSZ739bDtSiaiDgtoicy+mtYB81dKLxO9xRhCyIB3A=="], + + "detect-newline": ["detect-newline@4.0.1", "", {}, "sha512-qE3Veg1YXzGHQhlA6jzebZN2qVf6NX+A7m7qlhCGG30dJixrAQhYOsJjsnBjJkCSmuOPpCk30145fr8FV0bzog=="], + + "diff": ["diff@8.0.2", "", {}, "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg=="], + + "dotenv": ["dotenv@17.2.3", "", {}, "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w=="], + + "dts-resolver": ["dts-resolver@2.1.2", "", { "peerDependencies": { "oxc-resolver": ">=11.0.0" }, "optionalPeers": ["oxc-resolver"] }, "sha512-xeXHBQkn2ISSXxbJWD828PFjtyg+/UrMDo7W4Ffcs7+YWCquxU8YjV1KoxuiL+eJ5pg3ll+bC6flVv61L3LKZg=="], + + "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.230", "", {}, "sha512-A6A6Fd3+gMdaed9wX83CvHYJb4UuapPD5X5SLq72VZJzxHSY0/LUweGXRWmQlh2ln7KV7iw7jnwXK7dlPoOnHQ=="], + + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], + + "empathic": ["empathic@2.0.0", "", {}, "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA=="], + + "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], + + "es-abstract": ["es-abstract@1.24.0", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.3.0", "get-proto": "^1.0.1", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-negative-zero": "^2.0.3", "is-regex": "^1.2.1", "is-set": "^2.0.3", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.1", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.4", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.4", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "stop-iteration-iterator": "^1.1.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.19" } }, "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg=="], + + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], + + "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], + + "es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="], + + "es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="], + + "es-shim-unscopables": ["es-shim-unscopables@1.1.0", "", { "dependencies": { "hasown": "^2.0.2" } }, "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw=="], + + "es-to-primitive": ["es-to-primitive@1.3.0", "", { "dependencies": { "is-callable": "^1.2.7", "is-date-object": "^1.0.5", "is-symbol": "^1.0.4" } }, "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "eslint": ["eslint@9.37.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.21.0", "@eslint/config-helpers": "^0.4.0", "@eslint/core": "^0.16.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.37.0", "@eslint/plugin-kit": "^0.4.0", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.4.0", "eslint-visitor-keys": "^4.2.1", "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "optionalPeers": ["jiti"], "bin": { "eslint": "bin/eslint.js" } }, "sha512-XyLmROnACWqSxiGYArdef1fItQd47weqB7iwtfr9JHwRrqIXZdcFMvvEcL9xHCmL0SNsOvF0c42lWyM1U5dgig=="], + + "eslint-config-flat-gitignore": ["eslint-config-flat-gitignore@2.1.0", "", { "dependencies": { "@eslint/compat": "^1.2.5" }, "peerDependencies": { "eslint": "^9.5.0" } }, "sha512-cJzNJ7L+psWp5mXM7jBX+fjHtBvvh06RBlcweMhKD8jWqQw0G78hOW5tpVALGHGFPsBV+ot2H+pdDGJy6CV8pA=="], + + "eslint-config-prettier": ["eslint-config-prettier@10.1.8", "", { "peerDependencies": { "eslint": ">=7.0.0" }, "bin": { "eslint-config-prettier": "bin/cli.js" } }, "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w=="], + + "eslint-fix-utils": ["eslint-fix-utils@0.4.0", "", { "peerDependencies": { "@types/estree": ">=1", "eslint": ">=8" }, "optionalPeers": ["@types/estree"] }, "sha512-nCEciwqByGxsKiWqZjqK7xfL+7dUX9Pi0UL3J0tOwfxVN9e6Y59UxEt1ZYsc3XH0ce6T1WQM/QU2DbKK/6IG7g=="], + + "eslint-plugin-de-morgan": ["eslint-plugin-de-morgan@1.3.1", "", { "peerDependencies": { "eslint": ">=8.0.0" } }, "sha512-pB0xqHPXCRgCFnFSLvQDSP/egYrlccYMI0txz4gzBF6RuT2X+4LsZl0JoWAQc7dphEjJBQ3dWyMqXfBgdP2UVg=="], + + "eslint-plugin-jsx-a11y": ["eslint-plugin-jsx-a11y@6.10.2", "", { "dependencies": { "aria-query": "^5.3.2", "array-includes": "^3.1.8", "array.prototype.flatmap": "^1.3.2", "ast-types-flow": "^0.0.8", "axe-core": "^4.10.0", "axobject-query": "^4.1.0", "damerau-levenshtein": "^1.0.8", "emoji-regex": "^9.2.2", "hasown": "^2.0.2", "jsx-ast-utils": "^3.3.5", "language-tags": "^1.0.9", "minimatch": "^3.1.2", "object.fromentries": "^2.0.8", "safe-regex-test": "^1.0.3", "string.prototype.includes": "^2.0.1" }, "peerDependencies": { "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" } }, "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q=="], + + "eslint-plugin-package-json": ["eslint-plugin-package-json@0.56.3", "", { "dependencies": { "@altano/repository-tools": "^2.0.1", "change-case": "^5.4.4", "detect-indent": "^7.0.1", "detect-newline": "^4.0.1", "eslint-fix-utils": "~0.4.0", "package-json-validator": "~0.30.0", "semver": "^7.5.4", "sort-object-keys": "^1.1.3", "sort-package-json": "^3.3.0", "validate-npm-package-name": "^6.0.2" }, "peerDependencies": { "eslint": ">=8.0.0", "jsonc-eslint-parser": "^2.0.0" } }, "sha512-ArN3wnOAsduM/6a0egB83DQQfF/4KzxE53U8qcvELCXT929TnBy2IeCli4+in3QSHxcVYSIDa2Y5T2vVAXbe6A=="], + + "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@4.15.0", "", { "dependencies": { "@typescript-eslint/types": "^8.34.1", "@typescript-eslint/utils": "^8.34.1", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": ">=8.45.0" } }, "sha512-pC7PgoXyDnEXe14xvRUhBII8A3zRgggKqJFx2a82fjrItDs1BSI7zdZnQtM2yQvcyod6/ujmzb7ejKPx8lZTnw=="], + + "eslint-plugin-prettier": ["eslint-plugin-prettier@5.5.4", "", { "dependencies": { "prettier-linter-helpers": "^1.0.0", "synckit": "^0.11.7" }, "peerDependencies": { "@types/eslint": ">=8.0.0", "eslint": ">=8.0.0", "eslint-config-prettier": ">= 7.0.0 <10.0.0 || >=10.1.0", "prettier": ">=3.0.0" }, "optionalPeers": ["@types/eslint", "eslint-config-prettier"] }, "sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg=="], + + "eslint-plugin-react-debug": ["eslint-plugin-react-debug@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/core": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/type-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["typescript"] }, "sha512-WNOiQ6jhodJE88VjBU/IVDM+2Zr9gKHlBFDUSA3fQ0dMB5RiBVj5wMtxbxRuipK/GqNJbteqHcZoYEod7nfddg=="], + + "eslint-plugin-react-dom": ["eslint-plugin-react-dom@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/core": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "compare-versions": "^6.1.1", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["typescript"] }, "sha512-UYrWJ2cS4HpJ1A5XBuf1HfMpPoLdfGil+27g/ldXfGemb4IXqlxHt4ANLyC8l2CWcE3SXGJW7mTslL34MG0qTQ=="], + + "eslint-plugin-react-hooks": ["eslint-plugin-react-hooks@5.2.0", "", { "peerDependencies": { "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg=="], + + "eslint-plugin-react-hooks-extra": ["eslint-plugin-react-hooks-extra@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/core": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/type-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["typescript"] }, "sha512-fshTnMWNn9NjFLIuy7HzkRgGK29vKv4ZBO9UMr+kltVAfKLMeXXP6021qVKk66i/XhQjbktiS+vQsu1Rd3ZKvg=="], + + "eslint-plugin-react-naming-convention": ["eslint-plugin-react-naming-convention@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/core": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/type-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["typescript"] }, "sha512-rvZ/B/CSVF8d34HQ4qIt90LRuxotVx+KUf3i1OMXAyhsagEFMRe4gAlPJiRufZ+h9lnuu279bEdd+NINsXOteA=="], + + "eslint-plugin-react-web-api": ["eslint-plugin-react-web-api@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/core": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["typescript"] }, "sha512-INVZ3Cbl9/b+sizyb43ChzEPXXYuDsBGU9BIg7OVTNPyDPloCXdI+dQFAcSlDocZhPrLxhPV3eT6+gXbygzYXg=="], + + "eslint-plugin-react-x": ["eslint-plugin-react-x@1.53.1", "", { "dependencies": { "@eslint-react/ast": "1.53.1", "@eslint-react/core": "1.53.1", "@eslint-react/eff": "1.53.1", "@eslint-react/kit": "1.53.1", "@eslint-react/shared": "1.53.1", "@eslint-react/var": "1.53.1", "@typescript-eslint/scope-manager": "^8.43.0", "@typescript-eslint/type-utils": "^8.43.0", "@typescript-eslint/types": "^8.43.0", "@typescript-eslint/utils": "^8.43.0", "compare-versions": "^6.1.1", "is-immutable-type": "^5.0.1", "string-ts": "^2.2.1", "ts-pattern": "^5.8.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "ts-api-utils": "^2.1.0", "typescript": "^4.9.5 || ^5.3.3" }, "optionalPeers": ["ts-api-utils", "typescript"] }, "sha512-MwMNnVwiPem0U6SlejDF/ddA4h/lmP6imL1RDZ2m3pUBrcdcOwOx0gyiRVTA3ENnhRlWfHljHf5y7m8qDSxMEg=="], + + "eslint-plugin-regexp": ["eslint-plugin-regexp@2.10.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.11.0", "comment-parser": "^1.4.0", "jsdoc-type-pratt-parser": "^4.0.0", "refa": "^0.12.1", "regexp-ast-analysis": "^0.7.1", "scslre": "^0.3.0" }, "peerDependencies": { "eslint": ">=8.44.0" } }, "sha512-ovzQT8ESVn5oOe5a7gIDPD5v9bCSjIFJu57sVPDqgPRXicQzOnYfFN21WoQBQF18vrhT5o7UMKFwJQVVjyJ0ng=="], + + "eslint-plugin-unicorn": ["eslint-plugin-unicorn@60.0.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "@eslint-community/eslint-utils": "^4.7.0", "@eslint/plugin-kit": "^0.3.3", "change-case": "^5.4.4", "ci-info": "^4.3.0", "clean-regexp": "^1.0.0", "core-js-compat": "^3.44.0", "esquery": "^1.6.0", "find-up-simple": "^1.0.1", "globals": "^16.3.0", "indent-string": "^5.0.0", "is-builtin-module": "^5.0.0", "jsesc": "^3.1.0", "pluralize": "^8.0.0", "regexp-tree": "^0.1.27", "regjsparser": "^0.12.0", "semver": "^7.7.2", "strip-indent": "^4.0.0" }, "peerDependencies": { "eslint": ">=9.29.0" } }, "sha512-QUzTefvP8stfSXsqKQ+vBQSEsXIlAiCduS/V1Em+FKgL9c21U/IIm20/e3MFy1jyCf14tHAhqC1sX8OTy6VUCg=="], + + "eslint-plugin-unused-imports": ["eslint-plugin-unused-imports@4.2.0", "", { "peerDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0", "eslint": "^9.0.0 || ^8.0.0" }, "optionalPeers": ["@typescript-eslint/eslint-plugin"] }, "sha512-hLbJ2/wnjKq4kGA9AUaExVFIbNzyxYdVo49QZmKCnhk5pc9wcYRbfgLHvWJ8tnsdcseGhoUAddm9gn/lt+d74w=="], + + "eslint-scope": ["eslint-scope@8.4.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg=="], + + "eslint-visitor-keys": ["eslint-visitor-keys@4.2.1", "", {}, "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ=="], + + "espree": ["espree@10.4.0", "", { "dependencies": { "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.1" } }, "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ=="], + + "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="], + + "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="], + + "estraverse": ["estraverse@5.3.0", "", {}, "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="], + + "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="], + + "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], + + "execa": ["execa@9.6.0", "", { "dependencies": { "@sindresorhus/merge-streams": "^4.0.0", "cross-spawn": "^7.0.6", "figures": "^6.1.0", "get-stream": "^9.0.0", "human-signals": "^8.0.1", "is-plain-obj": "^4.1.0", "is-stream": "^4.0.1", "npm-run-path": "^6.0.0", "pretty-ms": "^9.2.0", "signal-exit": "^4.1.0", "strip-final-newline": "^4.0.0", "yoctocolors": "^2.1.1" } }, "sha512-jpWzZ1ZhwUmeWRhS7Qv3mhpOhLfwI+uAX4e5fOcXqwMR7EcJ0pj2kV1CVzHVMX/LphnKWD3LObjZCoJ71lKpHw=="], + + "exsolve": ["exsolve@1.0.7", "", {}, "sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-diff": ["fast-diff@1.3.0", "", {}, "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-stable-stringify": ["fast-json-stable-stringify@2.1.0", "", {}, "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="], + + "fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="], + + "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="], + + "fd-package-json": ["fd-package-json@2.0.0", "", { "dependencies": { "walk-up-path": "^4.0.0" } }, "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "fetch-event-stream": ["fetch-event-stream@0.1.5", "", {}, "sha512-V1PWovkspxQfssq/NnxoEyQo1DV+MRK/laPuPblIZmSjMN8P5u46OhlFQznSr9p/t0Sp8Uc6SbM3yCMfr0KU8g=="], + + "figures": ["figures@6.1.0", "", { "dependencies": { "is-unicode-supported": "^2.0.0" } }, "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg=="], + + "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], + + "find-up-simple": ["find-up-simple@1.0.1", "", {}, "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ=="], + + "flat-cache": ["flat-cache@4.0.1", "", { "dependencies": { "flatted": "^3.2.9", "keyv": "^4.5.4" } }, "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw=="], + + "flatted": ["flatted@3.3.3", "", {}, "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg=="], + + "for-each": ["for-each@0.3.5", "", { "dependencies": { "is-callable": "^1.2.7" } }, "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg=="], + + "formatly": ["formatly@0.3.0", "", { "dependencies": { "fd-package-json": "^2.0.0" }, "bin": { "formatly": "bin/index.mjs" } }, "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w=="], + + "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="], + + "function.prototype.name": ["function.prototype.name@1.1.8", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "functions-have-names": "^1.2.3", "hasown": "^2.0.2", "is-callable": "^1.2.7" } }, "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q=="], + + "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="], + + "generator-function": ["generator-function@2.0.1", "", {}, "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="], + + "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="], + + "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="], + + "get-stream": ["get-stream@9.0.1", "", { "dependencies": { "@sec-ant/readable-stream": "^0.4.1", "is-stream": "^4.0.1" } }, "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA=="], + + "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], + + "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], + + "giget": ["giget@2.0.0", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.0", "defu": "^6.1.4", "node-fetch-native": "^1.6.6", "nypm": "^0.6.0", "pathe": "^2.0.3" }, "bin": { "giget": "dist/cli.mjs" } }, "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA=="], + + "git-hooks-list": ["git-hooks-list@4.1.1", "", {}, "sha512-cmP497iLq54AZnv4YRAEMnEyQ1eIn4tGKbmswqwmFV4GBnAqE8NLtWxxdXa++AalfgL5EBH4IxTPyquEuGY/jA=="], + + "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="], + + "globals": ["globals@16.4.0", "", {}, "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw=="], + + "globalthis": ["globalthis@1.0.4", "", { "dependencies": { "define-properties": "^1.2.1", "gopd": "^1.0.1" } }, "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ=="], + + "gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="], + + "gpt-tokenizer": ["gpt-tokenizer@3.0.1", "", {}, "sha512-5jdaspBq/w4sWw322SvQj1Fku+CN4OAfYZeeEg8U7CWtxBz+zkxZ3h0YOHD43ee+nZYZ5Ud70HRN0ANcdIj4qg=="], + + "graphemer": ["graphemer@1.4.0", "", {}, "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="], + + "has-bigints": ["has-bigints@1.1.0", "", {}, "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "has-property-descriptors": ["has-property-descriptors@1.0.2", "", { "dependencies": { "es-define-property": "^1.0.0" } }, "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg=="], + + "has-proto": ["has-proto@1.2.0", "", { "dependencies": { "dunder-proto": "^1.0.0" } }, "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ=="], + + "has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="], + + "has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="], + + "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="], + + "hono": ["hono@4.9.9", "", {}, "sha512-Hxw4wT6zjJGZJdkJzAx9PyBdf7ZpxaTSA0NfxqjLghwMrLBX8p33hJBzoETRakF3UJu6OdNQBZAlNSkGqKFukw=="], + + "hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="], + + "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + + "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="], + + "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@5.0.0", "", {}, "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg=="], + + "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="], + + "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="], + + "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="], + + "is-bigint": ["is-bigint@1.1.0", "", { "dependencies": { "has-bigints": "^1.0.2" } }, "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ=="], + + "is-boolean-object": ["is-boolean-object@1.2.2", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A=="], + + "is-builtin-module": ["is-builtin-module@5.0.0", "", { "dependencies": { "builtin-modules": "^5.0.0" } }, "sha512-f4RqJKBUe5rQkJ2eJEJBXSticB3hGbN9j0yxxMQFqIW89Jp9WYFtzfTcRlstDKVUTRzSOTLKRfO9vIztenwtxA=="], + + "is-callable": ["is-callable@1.2.7", "", {}, "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA=="], + + "is-data-view": ["is-data-view@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" } }, "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw=="], + + "is-date-object": ["is-date-object@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" } }, "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg=="], + + "is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-finalizationregistry": ["is-finalizationregistry@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@5.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.1" } }, "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ=="], + + "is-generator-function": ["is-generator-function@1.1.2", "", { "dependencies": { "call-bound": "^1.0.4", "generator-function": "^2.0.0", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-immutable-type": ["is-immutable-type@5.0.1", "", { "dependencies": { "@typescript-eslint/type-utils": "^8.0.0", "ts-api-utils": "^2.0.0", "ts-declaration-location": "^1.0.4" }, "peerDependencies": { "eslint": "*", "typescript": ">=4.7.4" } }, "sha512-LkHEOGVZZXxGl8vDs+10k3DvP++SEoYEAJLRk6buTFi6kD7QekThV7xHS0j6gpnUCQ0zpud/gMDGiV4dQneLTg=="], + + "is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="], + + "is-map": ["is-map@2.0.3", "", {}, "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw=="], + + "is-negative-zero": ["is-negative-zero@2.0.3", "", {}, "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "is-number-object": ["is-number-object@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw=="], + + "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], + + "is-regex": ["is-regex@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g=="], + + "is-set": ["is-set@2.0.3", "", {}, "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg=="], + + "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="], + + "is-stream": ["is-stream@4.0.1", "", {}, "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A=="], + + "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="], + + "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="], + + "is-typed-array": ["is-typed-array@1.1.15", "", { "dependencies": { "which-typed-array": "^1.1.16" } }, "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ=="], + + "is-unicode-supported": ["is-unicode-supported@2.1.0", "", {}, "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ=="], + + "is-wayland": ["is-wayland@0.1.0", "", {}, "sha512-QkbMsWkIfkrzOPxenwye0h56iAXirZYHG9eHVPb22fO9y+wPbaX/CHacOWBa/I++4ohTcByimhM1/nyCsH8KNA=="], + + "is-weakmap": ["is-weakmap@2.0.2", "", {}, "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w=="], + + "is-weakref": ["is-weakref@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew=="], + + "is-weakset": ["is-weakset@2.0.4", "", { "dependencies": { "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ=="], + + "is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], + + "is64bit": ["is64bit@2.0.0", "", { "dependencies": { "system-architecture": "^0.1.0" } }, "sha512-jv+8jaWCl0g2lSBkNSVXdzfBA0npK1HGC2KtWM9FumFRoGS94g3NbCCLVnCYHLjp4GrW2KZeeSTMo5ddtznmGw=="], + + "isarray": ["isarray@2.0.5", "", {}, "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="], + + "jsdoc-type-pratt-parser": ["jsdoc-type-pratt-parser@4.8.0", "", {}, "sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="], + + "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="], + + "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="], + + "jsonc-eslint-parser": ["jsonc-eslint-parser@2.4.1", "", { "dependencies": { "acorn": "^8.5.0", "eslint-visitor-keys": "^3.0.0", "espree": "^9.0.0", "semver": "^7.3.5" } }, "sha512-uuPNLJkKN8NXAlZlQ6kmUF9qO+T6Kyd7oV4+/7yy8Jz6+MZNyhPq8EdLpdfnPVzUC8qSf1b4j1azKaGnFsjmsw=="], + + "jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], + + "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="], + + "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="], + + "knip": ["knip@5.64.1", "", { "dependencies": { "@nodelib/fs.walk": "^1.2.3", "fast-glob": "^3.3.3", "formatly": "^0.3.0", "jiti": "^2.6.0", "js-yaml": "^4.1.0", "minimist": "^1.2.8", "oxc-resolver": "^11.8.3", "picocolors": "^1.1.1", "picomatch": "^4.0.1", "smol-toml": "^1.4.1", "strip-json-comments": "5.0.2", "zod": "^4.1.11" }, "peerDependencies": { "@types/node": ">=18", "typescript": ">=5.0.4 <7" }, "bin": { "knip": "bin/knip.js", "knip-bun": "bin/knip-bun.js" } }, "sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg=="], + + "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="], + + "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="], + + "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="], + + "lint-staged": ["lint-staged@16.2.3", "", { "dependencies": { "commander": "^14.0.1", "listr2": "^9.0.4", "micromatch": "^4.0.8", "nano-spawn": "^1.0.3", "pidtree": "^0.6.0", "string-argv": "^0.3.2", "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" } }, "sha512-1OnJEESB9zZqsp61XHH2fvpS1es3hRCxMplF/AJUDa8Ho8VrscYDIuxGrj3m8KPXbcWZ8fT9XTMUhEQmOVKpKw=="], + + "listr2": ["listr2@9.0.4", "", { "dependencies": { "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", "rfdc": "^1.4.1", "wrap-ansi": "^9.0.0" } }, "sha512-1wd/kpAdKRLwv7/3OKC8zZ5U8e/fajCfWMxacUvB79S5nLrYGPtUI/8chMQhn3LQjsRVErTb9i1ECAwW0ZIHnQ=="], + + "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="], + + "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="], + + "log-update": ["log-update@6.1.0", "", { "dependencies": { "ansi-escapes": "^7.0.0", "cli-cursor": "^5.0.0", "slice-ansi": "^7.1.0", "strip-ansi": "^7.1.0", "wrap-ansi": "^9.0.0" } }, "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w=="], + + "magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="], + + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "mimic-function": ["mimic-function@5.0.1", "", {}, "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA=="], + + "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nano-spawn": ["nano-spawn@1.0.3", "", {}, "sha512-jtpsQDetTnvS2Ts1fiRdci5rx0VYws5jGyC+4IYOTnIQ/wwdf6JdomlHBwqC3bJYOvaKu0C2GSZ1A60anrYpaA=="], + + "natural-compare": ["natural-compare@1.4.0", "", {}, "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="], + + "natural-orderby": ["natural-orderby@5.0.0", "", {}, "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg=="], + + "node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="], + + "node-releases": ["node-releases@2.0.23", "", {}, "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg=="], + + "npm-run-path": ["npm-run-path@6.0.0", "", { "dependencies": { "path-key": "^4.0.0", "unicorn-magic": "^0.3.0" } }, "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA=="], + + "nypm": ["nypm@0.6.2", "", { "dependencies": { "citty": "^0.1.6", "consola": "^3.4.2", "pathe": "^2.0.3", "pkg-types": "^2.3.0", "tinyexec": "^1.0.1" }, "bin": { "nypm": "dist/cli.mjs" } }, "sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g=="], + + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], + + "object-keys": ["object-keys@1.1.1", "", {}, "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="], + + "object.assign": ["object.assign@4.1.7", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0", "has-symbols": "^1.1.0", "object-keys": "^1.1.1" } }, "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw=="], + + "object.fromentries": ["object.fromentries@2.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.2", "es-object-atoms": "^1.0.0" } }, "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ=="], + + "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="], + + "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], + + "onetime": ["onetime@7.0.0", "", { "dependencies": { "mimic-function": "^5.0.0" } }, "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ=="], + + "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], + + "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="], + + "oxc-resolver": ["oxc-resolver@11.9.0", "", { "optionalDependencies": { "@oxc-resolver/binding-android-arm-eabi": "11.9.0", "@oxc-resolver/binding-android-arm64": "11.9.0", "@oxc-resolver/binding-darwin-arm64": "11.9.0", "@oxc-resolver/binding-darwin-x64": "11.9.0", "@oxc-resolver/binding-freebsd-x64": "11.9.0", "@oxc-resolver/binding-linux-arm-gnueabihf": "11.9.0", "@oxc-resolver/binding-linux-arm-musleabihf": "11.9.0", "@oxc-resolver/binding-linux-arm64-gnu": "11.9.0", "@oxc-resolver/binding-linux-arm64-musl": "11.9.0", "@oxc-resolver/binding-linux-ppc64-gnu": "11.9.0", "@oxc-resolver/binding-linux-riscv64-gnu": "11.9.0", "@oxc-resolver/binding-linux-riscv64-musl": "11.9.0", "@oxc-resolver/binding-linux-s390x-gnu": "11.9.0", "@oxc-resolver/binding-linux-x64-gnu": "11.9.0", "@oxc-resolver/binding-linux-x64-musl": "11.9.0", "@oxc-resolver/binding-wasm32-wasi": "11.9.0", "@oxc-resolver/binding-win32-arm64-msvc": "11.9.0", "@oxc-resolver/binding-win32-ia32-msvc": "11.9.0", "@oxc-resolver/binding-win32-x64-msvc": "11.9.0" } }, "sha512-u714L0DBBXpD0ERErCQlun2XwinuBfIGo2T8bA7xE8WLQ4uaJudO/VOEQCWslOmcDY2nEkS+UVir5PpyvSG23w=="], + + "p-limit": ["p-limit@3.1.0", "", { "dependencies": { "yocto-queue": "^0.1.0" } }, "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ=="], + + "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], + + "package-json-validator": ["package-json-validator@0.30.0", "", { "dependencies": { "semver": "^7.7.2", "validate-npm-package-license": "^3.0.4", "yargs": "~18.0.0" }, "bin": { "pjv": "lib/bin/pjv.js" } }, "sha512-gOLW+BBye32t+IB2trIALIcL3DZBy3s4G4ZV6dAgDM+qLs/7jUNOV7iO7PwXqyf+3izI12qHBwtS4kOSJp5Tdg=="], + + "package-manager-detector": ["package-manager-detector@1.3.0", "", {}, "sha512-ZsEbbZORsyHuO00lY1kV3/t72yp6Ysay6Pd17ZAlNGuGwmWDLCJxFpRs0IzfXfj1o4icJOkUEioexFHzyPurSQ=="], + + "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + + "parse-ms": ["parse-ms@4.0.0", "", {}, "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="], + + "pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], + + "perfect-debounce": ["perfect-debounce@2.0.0", "", {}, "sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], + + "pidtree": ["pidtree@0.6.0", "", { "bin": { "pidtree": "bin/pidtree.js" } }, "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g=="], + + "pkg-types": ["pkg-types@2.3.0", "", { "dependencies": { "confbox": "^0.2.2", "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig=="], + + "pluralize": ["pluralize@8.0.0", "", {}, "sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA=="], + + "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="], + + "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], + + "prettier": ["prettier@3.6.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="], + + "prettier-linter-helpers": ["prettier-linter-helpers@1.0.0", "", { "dependencies": { "fast-diff": "^1.1.2" } }, "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w=="], + + "prettier-plugin-packagejson": ["prettier-plugin-packagejson@2.5.19", "", { "dependencies": { "sort-package-json": "3.4.0", "synckit": "0.11.11" }, "peerDependencies": { "prettier": ">= 1.16.0" }, "optionalPeers": ["prettier"] }, "sha512-Qsqp4+jsZbKMpEGZB1UP1pxeAT8sCzne2IwnKkr+QhUe665EXUo3BAvTf1kAPCqyMv9kg3ZmO0+7eOni/C6Uag=="], + + "pretty-ms": ["pretty-ms@9.3.0", "", { "dependencies": { "parse-ms": "^4.0.0" } }, "sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ=="], + + "proxy-from-env": ["proxy-from-env@1.1.0", "", {}, "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="], + + "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], + + "quansync": ["quansync@0.2.11", "", {}, "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "rc9": ["rc9@2.1.2", "", { "dependencies": { "defu": "^6.1.4", "destr": "^2.0.3" } }, "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg=="], + + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], + + "refa": ["refa@0.12.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0" } }, "sha512-J8rn6v4DBb2nnFqkqwy6/NnTYMcgLA+sLr0iIO41qpv0n+ngb7ksag2tMRl0inb1bbO/esUwzW1vbJi7K0sI0g=="], + + "reflect.getprototypeof": ["reflect.getprototypeof@1.0.10", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-abstract": "^1.23.9", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.1", "which-builtin-type": "^1.2.1" } }, "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw=="], + + "regexp-ast-analysis": ["regexp-ast-analysis@0.7.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.1" } }, "sha512-sZuz1dYW/ZsfG17WSAG7eS85r5a0dDsvg+7BiiYR5o6lKCAtUrEwdmRmaGF6rwVj3LcmAeYkOWKEPlbPzN3Y3A=="], + + "regexp-tree": ["regexp-tree@0.1.27", "", { "bin": { "regexp-tree": "bin/regexp-tree" } }, "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA=="], + + "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="], + + "regjsparser": ["regjsparser@0.12.0", "", { "dependencies": { "jsesc": "~3.0.2" }, "bin": { "regjsparser": "bin/parser" } }, "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ=="], + + "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="], + + "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="], + + "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "rolldown": ["rolldown@1.0.0-beta.41", "", { "dependencies": { "@oxc-project/types": "=0.93.0", "@rolldown/pluginutils": "1.0.0-beta.41", "ansis": "=4.2.0" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-arm64": "1.0.0-beta.41", "@rolldown/binding-darwin-x64": "1.0.0-beta.41", "@rolldown/binding-freebsd-x64": "1.0.0-beta.41", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-arm64-musl": "1.0.0-beta.41", "@rolldown/binding-linux-x64-gnu": "1.0.0-beta.41", "@rolldown/binding-linux-x64-musl": "1.0.0-beta.41", "@rolldown/binding-openharmony-arm64": "1.0.0-beta.41", "@rolldown/binding-wasm32-wasi": "1.0.0-beta.41", "@rolldown/binding-win32-arm64-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-ia32-msvc": "1.0.0-beta.41", "@rolldown/binding-win32-x64-msvc": "1.0.0-beta.41" }, "bin": { "rolldown": "bin/cli.mjs" } }, "sha512-U+NPR0Bkg3wm61dteD2L4nAM1U9dtaqVrpDXwC36IKRHpEO/Ubpid4Nijpa2imPchcVNHfxVFwSSMJdwdGFUbg=="], + + "rolldown-plugin-dts": ["rolldown-plugin-dts@0.16.11", "", { "dependencies": { "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.4", "@babel/types": "^7.28.4", "ast-kit": "^2.1.2", "birpc": "^2.6.1", "debug": "^4.4.3", "dts-resolver": "^2.1.2", "get-tsconfig": "^4.10.1", "magic-string": "^0.30.19" }, "peerDependencies": { "@ts-macro/tsc": "^0.3.6", "@typescript/native-preview": ">=7.0.0-dev.20250601.1", "rolldown": "^1.0.0-beta.9", "typescript": "^5.0.0", "vue-tsc": "~3.1.0" }, "optionalPeers": ["@ts-macro/tsc", "@typescript/native-preview", "typescript", "vue-tsc"] }, "sha512-9IQDaPvPqTx3RjG2eQCK5GYZITo203BxKunGI80AGYicu1ySFTUyugicAaTZWRzFWh9DSnzkgNeMNbDWBbSs0w=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "safe-array-concat": ["safe-array-concat@1.1.3", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "get-intrinsic": "^1.2.6", "has-symbols": "^1.1.0", "isarray": "^2.0.5" } }, "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q=="], + + "safe-push-apply": ["safe-push-apply@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "isarray": "^2.0.5" } }, "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA=="], + + "safe-regex-test": ["safe-regex-test@1.1.0", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-regex": "^1.2.1" } }, "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw=="], + + "scslre": ["scslre@0.3.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.8.0", "refa": "^0.12.0", "regexp-ast-analysis": "^0.7.0" } }, "sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ=="], + + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], + + "set-function-length": ["set-function-length@1.2.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "function-bind": "^1.1.2", "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", "has-property-descriptors": "^1.0.2" } }, "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg=="], + + "set-function-name": ["set-function-name@2.0.2", "", { "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", "functions-have-names": "^1.2.3", "has-property-descriptors": "^1.0.2" } }, "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ=="], + + "set-proto": ["set-proto@1.0.0", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0" } }, "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw=="], + + "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], + + "shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="], + + "side-channel": ["side-channel@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", "side-channel-list": "^1.0.0", "side-channel-map": "^1.0.1", "side-channel-weakmap": "^1.0.2" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="], + + "side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="], + + "side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="], + + "side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3", "side-channel-map": "^1.0.1" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="], + + "signal-exit": ["signal-exit@4.1.0", "", {}, "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw=="], + + "simple-git-hooks": ["simple-git-hooks@2.13.1", "", { "bin": { "simple-git-hooks": "cli.js" } }, "sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ=="], + + "slice-ansi": ["slice-ansi@7.1.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], + + "smol-toml": ["smol-toml@1.4.2", "", {}, "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g=="], + + "sort-object-keys": ["sort-object-keys@1.1.3", "", {}, "sha512-855pvK+VkU7PaKYPc+Jjnmt4EzejQHyhhF33q31qG8x7maDzkeFhAAThdCYay11CISO+qAMwjOBP+fPZe0IPyg=="], + + "sort-package-json": ["sort-package-json@3.4.0", "", { "dependencies": { "detect-indent": "^7.0.1", "detect-newline": "^4.0.1", "git-hooks-list": "^4.0.0", "is-plain-obj": "^4.1.0", "semver": "^7.7.1", "sort-object-keys": "^1.1.3", "tinyglobby": "^0.2.12" }, "bin": { "sort-package-json": "cli.js" } }, "sha512-97oFRRMM2/Js4oEA9LJhjyMlde+2ewpZQf53pgue27UkbEXfHJnDzHlUxQ/DWUkzqmp7DFwJp8D+wi/TYeQhpA=="], + + "spdx-correct": ["spdx-correct@3.2.0", "", { "dependencies": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA=="], + + "spdx-exceptions": ["spdx-exceptions@2.5.0", "", {}, "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w=="], + + "spdx-expression-parse": ["spdx-expression-parse@3.0.1", "", { "dependencies": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q=="], + + "spdx-license-ids": ["spdx-license-ids@3.0.22", "", {}, "sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ=="], + + "srvx": ["srvx@0.8.9", "", { "dependencies": { "cookie-es": "^2.0.0" }, "bin": { "srvx": "bin/srvx.mjs" } }, "sha512-wYc3VLZHRzwYrWJhkEqkhLb31TI0SOkfYZDkUhXdp3NoCnNS0FqajiQszZZjfow/VYEuc6Q5sZh9nM6kPy2NBQ=="], + + "stop-iteration-iterator": ["stop-iteration-iterator@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "internal-slot": "^1.1.0" } }, "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ=="], + + "string-argv": ["string-argv@0.3.2", "", {}, "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q=="], + + "string-ts": ["string-ts@2.2.1", "", {}, "sha512-Q2u0gko67PLLhbte5HmPfdOjNvUKbKQM+mCNQae6jE91DmoFHY6HH9GcdqCeNx87DZ2KKjiFxmA0R/42OneGWw=="], + + "string-width": ["string-width@8.1.0", "", { "dependencies": { "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" } }, "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg=="], + + "string.prototype.includes": ["string.prototype.includes@2.0.1", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-abstract": "^1.23.3" } }, "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg=="], + + "string.prototype.trim": ["string.prototype.trim@1.2.10", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-data-property": "^1.1.4", "define-properties": "^1.2.1", "es-abstract": "^1.23.5", "es-object-atoms": "^1.0.0", "has-property-descriptors": "^1.0.2" } }, "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA=="], + + "string.prototype.trimend": ["string.prototype.trimend@1.0.9", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ=="], + + "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="], + + "strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="], + + "strip-final-newline": ["strip-final-newline@4.0.0", "", {}, "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw=="], + + "strip-indent": ["strip-indent@4.1.0", "", {}, "sha512-OA95x+JPmL7kc7zCu+e+TeYxEiaIyndRx0OrBcK2QPPH09oAndr2ALvymxWA+Lx1PYYvFUm4O63pRkdJAaW96w=="], + + "strip-json-comments": ["strip-json-comments@5.0.2", "", {}, "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "synckit": ["synckit@0.11.11", "", { "dependencies": { "@pkgr/core": "^0.2.9" } }, "sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw=="], + + "system-architecture": ["system-architecture@0.1.0", "", {}, "sha512-ulAk51I9UVUyJgxlv9M6lFot2WP3e7t8Kz9+IS6D4rVba1tR9kON+Ey69f+1R4Q8cd45Lod6a4IcJIxnzGc/zA=="], + + "tiny-invariant": ["tiny-invariant@1.3.3", "", {}, "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="], + + "tinyexec": ["tinyexec@1.0.1", "", {}, "sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw=="], + + "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="], + + "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="], + + "ts-declaration-location": ["ts-declaration-location@1.0.7", "", { "dependencies": { "picomatch": "^4.0.2" }, "peerDependencies": { "typescript": ">=4.0.0" } }, "sha512-EDyGAwH1gO0Ausm9gV6T2nUvBgXT5kGoCMJPllOaooZ+4VvJiKBdZE7wK18N1deEowhcUptS+5GXZK8U/fvpwA=="], + + "ts-pattern": ["ts-pattern@5.8.0", "", {}, "sha512-kIjN2qmWiHnhgr5DAkAafF9fwb0T5OhMVSWrm8XEdTFnX6+wfXwYOFjeF86UZ54vduqiR7BfqScFmXSzSaH8oA=="], + + "tsdown": ["tsdown@0.15.6", "", { "dependencies": { "ansis": "^4.1.0", "cac": "^6.7.14", "chokidar": "^4.0.3", "debug": "^4.4.3", "diff": "^8.0.2", "empathic": "^2.0.0", "hookable": "^5.5.3", "rolldown": "latest", "rolldown-plugin-dts": "^0.16.8", "semver": "^7.7.2", "tinyexec": "^1.0.1", "tinyglobby": "^0.2.15", "tree-kill": "^1.2.2", "unconfig": "^7.3.3" }, "peerDependencies": { "@arethetypeswrong/core": "^0.18.1", "publint": "^0.3.0", "typescript": "^5.0.0", "unplugin-lightningcss": "^0.4.0", "unplugin-unused": "^0.5.0" }, "optionalPeers": ["@arethetypeswrong/core", "publint", "typescript", "unplugin-lightningcss", "unplugin-unused"], "bin": { "tsdown": "dist/run.mjs" } }, "sha512-W6++O3JeV9gm3JY6P/vLiC7zzTcJbZhQxXb+p3AvRMpDOPBIg82yXULyZCcwjsihY/bFG+Qw37HkezZbP7fzUg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], + + "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="], + + "typed-array-byte-length": ["typed-array-byte-length@1.0.3", "", { "dependencies": { "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.14" } }, "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg=="], + + "typed-array-byte-offset": ["typed-array-byte-offset@1.0.4", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "for-each": "^0.3.3", "gopd": "^1.2.0", "has-proto": "^1.2.0", "is-typed-array": "^1.1.15", "reflect.getprototypeof": "^1.0.9" } }, "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ=="], + + "typed-array-length": ["typed-array-length@1.0.7", "", { "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", "is-typed-array": "^1.1.13", "possible-typed-array-names": "^1.0.0", "reflect.getprototypeof": "^1.0.6" } }, "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "typescript-eslint": ["typescript-eslint@8.45.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.45.0", "@typescript-eslint/parser": "8.45.0", "@typescript-eslint/typescript-estree": "8.45.0", "@typescript-eslint/utils": "8.45.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } }, "sha512-qzDmZw/Z5beNLUrXfd0HIW6MzIaAV5WNDxmMs9/3ojGOpYavofgNAAD/nC6tGV2PczIi0iw8vot2eAe/sBn7zg=="], + + "unbox-primitive": ["unbox-primitive@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "has-bigints": "^1.0.2", "has-symbols": "^1.1.0", "which-boxed-primitive": "^1.1.1" } }, "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw=="], + + "unconfig": ["unconfig@7.3.3", "", { "dependencies": { "@quansync/fs": "^0.1.5", "defu": "^6.1.4", "jiti": "^2.5.1", "quansync": "^0.2.11" } }, "sha512-QCkQoOnJF8L107gxfHL0uavn7WD9b3dpBcFX6HtfQYmjw2YzWxGuFQ0N0J6tE9oguCBJn9KOvfqYDCMPHIZrBA=="], + + "undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="], + + "undici-types": ["undici-types@7.13.0", "", {}, "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ=="], + + "unicorn-magic": ["unicorn-magic@0.3.0", "", {}, "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA=="], + + "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="], + + "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="], + + "validate-npm-package-license": ["validate-npm-package-license@3.0.4", "", { "dependencies": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" } }, "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew=="], + + "validate-npm-package-name": ["validate-npm-package-name@6.0.2", "", {}, "sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ=="], + + "walk-up-path": ["walk-up-path@4.0.0", "", {}, "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "which-boxed-primitive": ["which-boxed-primitive@1.1.1", "", { "dependencies": { "is-bigint": "^1.1.0", "is-boolean-object": "^1.2.1", "is-number-object": "^1.1.1", "is-string": "^1.1.1", "is-symbol": "^1.1.1" } }, "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA=="], + + "which-builtin-type": ["which-builtin-type@1.2.1", "", { "dependencies": { "call-bound": "^1.0.2", "function.prototype.name": "^1.1.6", "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.1.0", "is-finalizationregistry": "^1.1.0", "is-generator-function": "^1.0.10", "is-regex": "^1.2.1", "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.1.0", "which-collection": "^1.0.2", "which-typed-array": "^1.1.16" } }, "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q=="], + + "which-collection": ["which-collection@1.0.2", "", { "dependencies": { "is-map": "^2.0.3", "is-set": "^2.0.3", "is-weakmap": "^2.0.2", "is-weakset": "^2.0.3" } }, "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw=="], + + "which-typed-array": ["which-typed-array@1.1.19", "", { "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.4", "for-each": "^0.3.5", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" } }, "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw=="], + + "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="], + + "wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="], + + "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], + + "yargs": ["yargs@18.0.0", "", { "dependencies": { "cliui": "^9.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "string-width": "^7.2.0", "y18n": "^5.0.5", "yargs-parser": "^22.0.0" } }, "sha512-4UEqdc2RYGHZc7Doyqkrqiln3p9X2DZVxaGbwhn2pi7MrRagKaOcIKe8L3OxYcbhXLgLFUS3zAYuQjKBQgmuNg=="], + + "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="], + + "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="], + + "yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="], + + "zod": ["zod@4.1.11", "", {}, "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg=="], + + "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="], + + "@eslint/eslintrc/strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="], + + "@eslint/json/@eslint/core": ["@eslint/core@0.15.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg=="], + + "@eslint/json/@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], + + "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="], + + "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + + "clean-regexp/escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="], + + "cliui/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "eslint-plugin-unicorn/@eslint/plugin-kit": ["@eslint/plugin-kit@0.3.5", "", { "dependencies": { "@eslint/core": "^0.15.2", "levn": "^0.4.1" } }, "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w=="], + + "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "jsonc-eslint-parser/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="], + + "jsonc-eslint-parser/espree": ["espree@9.6.1", "", { "dependencies": { "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^3.4.1" } }, "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ=="], + + "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], + + "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="], + + "regjsparser/jsesc": ["jsesc@3.0.2", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g=="], + + "slice-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi/ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], + + "wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "yargs/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="], + + "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="], + + "cliui/string-width/emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="], + + "eslint-plugin-unicorn/@eslint/plugin-kit/@eslint/core": ["@eslint/core@0.15.2", "", { "dependencies": { "@types/json-schema": "^7.0.15" } }, "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg=="], + + "wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="], + + "yargs/string-width/emoji-regex": ["emoji-regex@10.5.0", "", {}, "sha512-lb49vf1Xzfx080OKA0o6l8DQQpV+6Vg95zyCJX9VB/BqKYlhG7N4wgROUUHRA+ZPUefLnteQOad7z1kT2bV7bg=="], + } +} diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000000000000000000000000000000000000..dfe63c902b829522becc68f7c9d3de1e98f1326f --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,9 @@ +#!/bin/sh +if [ "$1" = "--auth" ]; then + # Run auth command + exec bun run dist/main.js auth +else + # Default command + exec bun run dist/main.js start -g "$GH_TOKEN" "$@" +fi + diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000000000000000000000000000000000000..c9f79bea56110e4987fa382065c2ca84a0add45c --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,7 @@ +import config from "@echristian/eslint-config" + +export default config({ + prettier: { + plugins: ["prettier-plugin-packagejson"], + }, +}) diff --git a/opencode.json b/opencode.json new file mode 100644 index 0000000000000000000000000000000000000000..b7590f5ff6f8512722bfd6cfdfd8cb84340eaa1f --- /dev/null +++ b/opencode.json @@ -0,0 +1,20 @@ +{ + "mcp": { + "playwright": { + "type": "local", + "command": [ + "docker", + "container", + "run", + "-i", + "--rm", + "--init", + "--network", + "host", + "mcr.microsoft.com/playwright/mcp" + ], + "enabled": true + } + }, + "$schema": "https://opencode.ai/config.json" +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..a5adbb8e75f5dcacc3edc2d721cdb3c36e04d8d3 --- /dev/null +++ b/package.json @@ -0,0 +1,68 @@ +{ + "name": "copilot-api", + "version": "0.7.0", + "description": "Turn GitHub Copilot into OpenAI/Anthropic API compatible server. Usable with Claude Code!", + "keywords": [ + "proxy", + "github-copilot", + "openai-compatible" + ], + "homepage": "https://github.com/ericc-ch/copilot-api", + "bugs": "https://github.com/ericc-ch/copilot-api/issues", + "repository": { + "type": "git", + "url": "git+https://github.com/ericc-ch/copilot-api.git" + }, + "author": "Erick Christian ", + "type": "module", + "bin": { + "copilot-api": "./dist/main.js" + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsdown", + "dev": "bun run --watch ./src/main.ts", + "knip": "knip-bun", + "lint": "eslint --cache", + "lint:all": "eslint --cache .", + "prepack": "bun run build", + "prepare": "simple-git-hooks", + "release": "bumpp && bun publish --access public", + "start": "NODE_ENV=production bun run ./src/main.ts", + "typecheck": "tsc" + }, + "simple-git-hooks": { + "pre-commit": "bunx lint-staged" + }, + "lint-staged": { + "*": "bun run lint --fix" + }, + "dependencies": { + "citty": "^0.1.6", + "clipboardy": "^5.0.0", + "consola": "^3.4.2", + "fetch-event-stream": "^0.1.5", + "gpt-tokenizer": "^3.0.1", + "hono": "^4.9.9", + "proxy-from-env": "^1.1.0", + "srvx": "^0.8.9", + "tiny-invariant": "^1.3.3", + "undici": "^7.16.0", + "zod": "^4.1.11" + }, + "devDependencies": { + "@echristian/eslint-config": "^0.0.54", + "@types/bun": "^1.2.23", + "@types/proxy-from-env": "^1.0.4", + "bumpp": "^10.2.3", + "eslint": "^9.37.0", + "knip": "^5.64.1", + "lint-staged": "^16.2.3", + "prettier-plugin-packagejson": "^2.5.19", + "simple-git-hooks": "^2.13.1", + "tsdown": "^0.15.6", + "typescript": "^5.9.3" + } +} diff --git a/pages/index.html b/pages/index.html new file mode 100644 index 0000000000000000000000000000000000000000..57d16ef02311e0d66964937a3d6e1ee36928b649 --- /dev/null +++ b/pages/index.html @@ -0,0 +1,556 @@ + + + + + + Copilot API Usage Dashboard + + + + + + + + + + + + +
+
+ +
+

+ + + + + + Copilot API Usage Dashboard +

+

+ Should be the same as the one in VSCode +

+
+ + +
+
+ + + +
+
+ + +
+
+
+ + + + + + diff --git a/src/auth.ts b/src/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..cb31ff6f833573c1c3e65d12eb981a486cf37315 --- /dev/null +++ b/src/auth.ts @@ -0,0 +1,52 @@ +#!/usr/bin/env node + +import { defineCommand } from "citty" +import consola from "consola" + +import { PATHS, ensurePaths } from "./lib/paths" +import { state } from "./lib/state" +import { setupGitHubToken } from "./lib/token" + +interface RunAuthOptions { + verbose: boolean + showToken: boolean +} + +export async function runAuth(options: RunAuthOptions): Promise { + if (options.verbose) { + consola.level = 5 + consola.info("Verbose logging enabled") + } + + state.showToken = options.showToken + + await ensurePaths() + await setupGitHubToken({ force: true }) + consola.success("GitHub token written to", PATHS.GITHUB_TOKEN_PATH) +} + +export const auth = defineCommand({ + meta: { + name: "auth", + description: "Run GitHub auth flow without running the server", + }, + args: { + verbose: { + alias: "v", + type: "boolean", + default: false, + description: "Enable verbose logging", + }, + "show-token": { + type: "boolean", + default: false, + description: "Show GitHub token on auth", + }, + }, + run({ args }) { + return runAuth({ + verbose: args.verbose, + showToken: args["show-token"], + }) + }, +}) diff --git a/src/check-usage.ts b/src/check-usage.ts new file mode 100644 index 0000000000000000000000000000000000000000..1236ebc6946ea283fa2060d6b4d694c184b3294b --- /dev/null +++ b/src/check-usage.ts @@ -0,0 +1,58 @@ +import { defineCommand } from "citty" +import consola from "consola" + +import { ensurePaths } from "./lib/paths" +import { setupGitHubToken } from "./lib/token" +import { + getCopilotUsage, + type QuotaDetail, +} from "./services/github/get-copilot-usage" + +export const checkUsage = defineCommand({ + meta: { + name: "check-usage", + description: "Show current GitHub Copilot usage/quota information", + }, + async run() { + await ensurePaths() + await setupGitHubToken() + try { + const usage = await getCopilotUsage() + const premium = usage.quota_snapshots.premium_interactions + const premiumTotal = premium.entitlement + const premiumUsed = premiumTotal - premium.remaining + const premiumPercentUsed = + premiumTotal > 0 ? (premiumUsed / premiumTotal) * 100 : 0 + const premiumPercentRemaining = premium.percent_remaining + + // Helper to summarize a quota snapshot + function summarizeQuota(name: string, snap: QuotaDetail | undefined) { + if (!snap) return `${name}: N/A` + const total = snap.entitlement + const used = total - snap.remaining + const percentUsed = total > 0 ? (used / total) * 100 : 0 + const percentRemaining = snap.percent_remaining + return `${name}: ${used}/${total} used (${percentUsed.toFixed(1)}% used, ${percentRemaining.toFixed(1)}% remaining)` + } + + const premiumLine = `Premium: ${premiumUsed}/${premiumTotal} used (${premiumPercentUsed.toFixed(1)}% used, ${premiumPercentRemaining.toFixed(1)}% remaining)` + const chatLine = summarizeQuota("Chat", usage.quota_snapshots.chat) + const completionsLine = summarizeQuota( + "Completions", + usage.quota_snapshots.completions, + ) + + consola.box( + `Copilot Usage (plan: ${usage.copilot_plan})\n` + + `Quota resets: ${usage.quota_reset_date}\n` + + `\nQuotas:\n` + + ` ${premiumLine}\n` + + ` ${chatLine}\n` + + ` ${completionsLine}`, + ) + } catch (err) { + consola.error("Failed to fetch Copilot usage:", err) + process.exit(1) + } + }, +}) diff --git a/src/debug.ts b/src/debug.ts new file mode 100644 index 0000000000000000000000000000000000000000..b2aff86711f9b688d49dbc1b7679e597667bedd7 --- /dev/null +++ b/src/debug.ts @@ -0,0 +1,127 @@ +#!/usr/bin/env node + +import { defineCommand } from "citty" +import consola from "consola" +import fs from "node:fs/promises" +import os from "node:os" + +import { PATHS } from "./lib/paths" + +interface DebugInfo { + version: string + runtime: { + name: string + version: string + platform: string + arch: string + } + paths: { + APP_DIR: string + GITHUB_TOKEN_PATH: string + } + tokenExists: boolean +} + +interface RunDebugOptions { + json: boolean +} + +async function getPackageVersion(): Promise { + try { + const packageJsonPath = new URL("../package.json", import.meta.url).pathname + // @ts-expect-error https://github.com/sindresorhus/eslint-plugin-unicorn/blob/v59.0.1/docs/rules/prefer-json-parse-buffer.md + // JSON.parse() can actually parse buffers + const packageJson = JSON.parse(await fs.readFile(packageJsonPath)) as { + version: string + } + return packageJson.version + } catch { + return "unknown" + } +} + +function getRuntimeInfo() { + const isBun = typeof Bun !== "undefined" + + return { + name: isBun ? "bun" : "node", + version: isBun ? Bun.version : process.version.slice(1), + platform: os.platform(), + arch: os.arch(), + } +} + +async function checkTokenExists(): Promise { + try { + const stats = await fs.stat(PATHS.GITHUB_TOKEN_PATH) + if (!stats.isFile()) return false + + const content = await fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8") + return content.trim().length > 0 + } catch { + return false + } +} + +async function getDebugInfo(): Promise { + const [version, tokenExists] = await Promise.all([ + getPackageVersion(), + checkTokenExists(), + ]) + + return { + version, + runtime: getRuntimeInfo(), + paths: { + APP_DIR: PATHS.APP_DIR, + GITHUB_TOKEN_PATH: PATHS.GITHUB_TOKEN_PATH, + }, + tokenExists, + } +} + +function printDebugInfoPlain(info: DebugInfo): void { + consola.info(`copilot-api debug + +Version: ${info.version} +Runtime: ${info.runtime.name} ${info.runtime.version} (${info.runtime.platform} ${info.runtime.arch}) + +Paths: +- APP_DIR: ${info.paths.APP_DIR} +- GITHUB_TOKEN_PATH: ${info.paths.GITHUB_TOKEN_PATH} + +Token exists: ${info.tokenExists ? "Yes" : "No"}`) +} + +function printDebugInfoJson(info: DebugInfo): void { + console.log(JSON.stringify(info, null, 2)) +} + +export async function runDebug(options: RunDebugOptions): Promise { + const debugInfo = await getDebugInfo() + + if (options.json) { + printDebugInfoJson(debugInfo) + } else { + printDebugInfoPlain(debugInfo) + } +} + +export const debug = defineCommand({ + meta: { + name: "debug", + description: "Print debug information about the application", + }, + args: { + json: { + type: "boolean", + default: false, + description: "Output debug information as JSON", + }, + }, + run({ args }) { + return runDebug({ + json: args.json, + }) + }, +}) diff --git a/src/lib/api-config.ts b/src/lib/api-config.ts new file mode 100644 index 0000000000000000000000000000000000000000..83bce92ad890e49861b04ea55216ab980630e4cf --- /dev/null +++ b/src/lib/api-config.ts @@ -0,0 +1,52 @@ +import { randomUUID } from "node:crypto" + +import type { State } from "./state" + +export const standardHeaders = () => ({ + "content-type": "application/json", + accept: "application/json", +}) + +const COPILOT_VERSION = "0.26.7" +const EDITOR_PLUGIN_VERSION = `copilot-chat/${COPILOT_VERSION}` +const USER_AGENT = `GitHubCopilotChat/${COPILOT_VERSION}` + +const API_VERSION = "2025-04-01" + +export const copilotBaseUrl = (state: State) => + state.accountType === "individual" ? + "https://api.githubcopilot.com" + : `https://api.${state.accountType}.githubcopilot.com` +export const copilotHeaders = (state: State, vision: boolean = false) => { + const headers: Record = { + Authorization: `Bearer ${state.copilotToken}`, + "content-type": standardHeaders()["content-type"], + "copilot-integration-id": "vscode-chat", + "editor-version": `vscode/${state.vsCodeVersion}`, + "editor-plugin-version": EDITOR_PLUGIN_VERSION, + "user-agent": USER_AGENT, + "openai-intent": "conversation-panel", + "x-github-api-version": API_VERSION, + "x-request-id": randomUUID(), + "x-vscode-user-agent-library-version": "electron-fetch", + } + + if (vision) headers["copilot-vision-request"] = "true" + + return headers +} + +export const GITHUB_API_BASE_URL = "https://api.github.com" +export const githubHeaders = (state: State) => ({ + ...standardHeaders(), + authorization: `token ${state.githubToken}`, + "editor-version": `vscode/${state.vsCodeVersion}`, + "editor-plugin-version": EDITOR_PLUGIN_VERSION, + "user-agent": USER_AGENT, + "x-github-api-version": API_VERSION, + "x-vscode-user-agent-library-version": "electron-fetch", +}) + +export const GITHUB_BASE_URL = "https://github.com" +export const GITHUB_CLIENT_ID = "Iv1.b507a08c87ecfe98" +export const GITHUB_APP_SCOPES = ["read:user"].join(" ") diff --git a/src/lib/approval.ts b/src/lib/approval.ts new file mode 100644 index 0000000000000000000000000000000000000000..35e4e27527e0968510e6c54cb49821df2a7e438d --- /dev/null +++ b/src/lib/approval.ts @@ -0,0 +1,15 @@ +import consola from "consola" + +import { HTTPError } from "./error" + +export const awaitApproval = async () => { + const response = await consola.prompt(`Accept incoming request?`, { + type: "confirm", + }) + + if (!response) + throw new HTTPError( + "Request rejected", + Response.json({ message: "Request rejected" }, { status: 403 }), + ) +} diff --git a/src/lib/error.ts b/src/lib/error.ts new file mode 100644 index 0000000000000000000000000000000000000000..c39c225968c7f02423a2fae13a02a89a796641d6 --- /dev/null +++ b/src/lib/error.ts @@ -0,0 +1,47 @@ +import type { Context } from "hono" +import type { ContentfulStatusCode } from "hono/utils/http-status" + +import consola from "consola" + +export class HTTPError extends Error { + response: Response + + constructor(message: string, response: Response) { + super(message) + this.response = response + } +} + +export async function forwardError(c: Context, error: unknown) { + consola.error("Error occurred:", error) + + if (error instanceof HTTPError) { + const errorText = await error.response.text() + let errorJson: unknown + try { + errorJson = JSON.parse(errorText) + } catch { + errorJson = errorText + } + consola.error("HTTP error:", errorJson) + return c.json( + { + error: { + message: errorText, + type: "error", + }, + }, + error.response.status as ContentfulStatusCode, + ) + } + + return c.json( + { + error: { + message: (error as Error).message, + type: "error", + }, + }, + 500, + ) +} diff --git a/src/lib/paths.ts b/src/lib/paths.ts new file mode 100644 index 0000000000000000000000000000000000000000..8d0a9f02ba60eb5baa95080453c299261dced92e --- /dev/null +++ b/src/lib/paths.ts @@ -0,0 +1,26 @@ +import fs from "node:fs/promises" +import os from "node:os" +import path from "node:path" + +const APP_DIR = path.join(os.homedir(), ".local", "share", "copilot-api") + +const GITHUB_TOKEN_PATH = path.join(APP_DIR, "github_token") + +export const PATHS = { + APP_DIR, + GITHUB_TOKEN_PATH, +} + +export async function ensurePaths(): Promise { + await fs.mkdir(PATHS.APP_DIR, { recursive: true }) + await ensureFile(PATHS.GITHUB_TOKEN_PATH) +} + +async function ensureFile(filePath: string): Promise { + try { + await fs.access(filePath, fs.constants.W_OK) + } catch { + await fs.writeFile(filePath, "") + await fs.chmod(filePath, 0o600) + } +} diff --git a/src/lib/proxy.ts b/src/lib/proxy.ts new file mode 100644 index 0000000000000000000000000000000000000000..22beb5819c23c772999a85a50ddc90f3a115d551 --- /dev/null +++ b/src/lib/proxy.ts @@ -0,0 +1,66 @@ +import consola from "consola" +import { getProxyForUrl } from "proxy-from-env" +import { Agent, ProxyAgent, setGlobalDispatcher, type Dispatcher } from "undici" + +export function initProxyFromEnv(): void { + if (typeof Bun !== "undefined") return + + try { + const direct = new Agent() + const proxies = new Map() + + // We only need a minimal dispatcher that implements `dispatch` at runtime. + // Typing the object as `Dispatcher` forces TypeScript to require many + // additional methods. Instead, keep a plain object and cast when passing + // to `setGlobalDispatcher`. + const dispatcher = { + dispatch( + options: Dispatcher.DispatchOptions, + handler: Dispatcher.DispatchHandler, + ) { + try { + const origin = + typeof options.origin === "string" ? + new URL(options.origin) + : (options.origin as URL) + const get = getProxyForUrl as unknown as ( + u: string, + ) => string | undefined + const raw = get(origin.toString()) + const proxyUrl = raw && raw.length > 0 ? raw : undefined + if (!proxyUrl) { + consola.debug(`HTTP proxy bypass: ${origin.hostname}`) + return (direct as unknown as Dispatcher).dispatch(options, handler) + } + let agent = proxies.get(proxyUrl) + if (!agent) { + agent = new ProxyAgent(proxyUrl) + proxies.set(proxyUrl, agent) + } + let label = proxyUrl + try { + const u = new URL(proxyUrl) + label = `${u.protocol}//${u.host}` + } catch { + /* noop */ + } + consola.debug(`HTTP proxy route: ${origin.hostname} via ${label}`) + return (agent as unknown as Dispatcher).dispatch(options, handler) + } catch { + return (direct as unknown as Dispatcher).dispatch(options, handler) + } + }, + close() { + return direct.close() + }, + destroy() { + return direct.destroy() + }, + } + + setGlobalDispatcher(dispatcher as unknown as Dispatcher) + consola.debug("HTTP proxy configured from environment (per-URL)") + } catch (err) { + consola.debug("Proxy setup skipped:", err) + } +} diff --git a/src/lib/rate-limit.ts b/src/lib/rate-limit.ts new file mode 100644 index 0000000000000000000000000000000000000000..e41f582979cca7a0d1d9ccc4bf3712628fd3d9e8 --- /dev/null +++ b/src/lib/rate-limit.ts @@ -0,0 +1,46 @@ +import consola from "consola" + +import type { State } from "./state" + +import { HTTPError } from "./error" +import { sleep } from "./utils" + +export async function checkRateLimit(state: State) { + if (state.rateLimitSeconds === undefined) return + + const now = Date.now() + + if (!state.lastRequestTimestamp) { + state.lastRequestTimestamp = now + return + } + + const elapsedSeconds = (now - state.lastRequestTimestamp) / 1000 + + if (elapsedSeconds > state.rateLimitSeconds) { + state.lastRequestTimestamp = now + return + } + + const waitTimeSeconds = Math.ceil(state.rateLimitSeconds - elapsedSeconds) + + if (!state.rateLimitWait) { + consola.warn( + `Rate limit exceeded. Need to wait ${waitTimeSeconds} more seconds.`, + ) + throw new HTTPError( + "Rate limit exceeded", + Response.json({ message: "Rate limit exceeded" }, { status: 429 }), + ) + } + + const waitTimeMs = waitTimeSeconds * 1000 + consola.warn( + `Rate limit reached. Waiting ${waitTimeSeconds} seconds before proceeding...`, + ) + await sleep(waitTimeMs) + // eslint-disable-next-line require-atomic-updates + state.lastRequestTimestamp = now + consola.info("Rate limit wait completed, proceeding with request") + return +} diff --git a/src/lib/shell.ts b/src/lib/shell.ts new file mode 100644 index 0000000000000000000000000000000000000000..6a00520043739391c0df92267f82e43b77811404 --- /dev/null +++ b/src/lib/shell.ts @@ -0,0 +1,88 @@ +import { execSync } from "node:child_process" +import process from "node:process" + +type ShellName = "bash" | "zsh" | "fish" | "powershell" | "cmd" | "sh" +type EnvVars = Record + +function getShell(): ShellName { + const { platform, ppid, env } = process + + if (platform === "win32") { + try { + const command = `wmic process get ParentProcessId,Name | findstr "${ppid}"` + const parentProcess = execSync(command, { stdio: "pipe" }).toString() + + if (parentProcess.toLowerCase().includes("powershell.exe")) { + return "powershell" + } + } catch { + return "cmd" + } + + return "cmd" + } else { + const shellPath = env.SHELL + if (shellPath) { + if (shellPath.endsWith("zsh")) return "zsh" + if (shellPath.endsWith("fish")) return "fish" + if (shellPath.endsWith("bash")) return "bash" + } + + return "sh" + } +} + +/** + * Generates a copy-pasteable script to set multiple environment variables + * and run a subsequent command. + * @param {EnvVars} envVars - An object of environment variables to set. + * @param {string} commandToRun - The command to run after setting the variables. + * @returns {string} The formatted script string. + */ +export function generateEnvScript( + envVars: EnvVars, + commandToRun: string = "", +): string { + const shell = getShell() + const filteredEnvVars = Object.entries(envVars).filter( + ([, value]) => value !== undefined, + ) as Array<[string, string]> + + let commandBlock: string + + switch (shell) { + case "powershell": { + commandBlock = filteredEnvVars + .map(([key, value]) => `$env:${key} = ${value}`) + .join("; ") + break + } + case "cmd": { + commandBlock = filteredEnvVars + .map(([key, value]) => `set ${key}=${value}`) + .join(" & ") + break + } + case "fish": { + commandBlock = filteredEnvVars + .map(([key, value]) => `set -gx ${key} ${value}`) + .join("; ") + break + } + default: { + // bash, zsh, sh + const assignments = filteredEnvVars + .map(([key, value]) => `${key}=${value}`) + .join(" ") + commandBlock = filteredEnvVars.length > 0 ? `export ${assignments}` : "" + break + } + } + + if (commandBlock && commandToRun) { + const separator = shell === "cmd" ? " & " : " && " + return `${commandBlock}${separator}${commandToRun}` + } + + return commandBlock || commandToRun +} diff --git a/src/lib/state.ts b/src/lib/state.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ba4dc1d1cc29c73d43e792d076c2bd7115de4ef --- /dev/null +++ b/src/lib/state.ts @@ -0,0 +1,25 @@ +import type { ModelsResponse } from "~/services/copilot/get-models" + +export interface State { + githubToken?: string + copilotToken?: string + + accountType: string + models?: ModelsResponse + vsCodeVersion?: string + + manualApprove: boolean + rateLimitWait: boolean + showToken: boolean + + // Rate limiting configuration + rateLimitSeconds?: number + lastRequestTimestamp?: number +} + +export const state: State = { + accountType: "individual", + manualApprove: false, + rateLimitWait: false, + showToken: false, +} diff --git a/src/lib/token.ts b/src/lib/token.ts new file mode 100644 index 0000000000000000000000000000000000000000..fc8d2785f580282ce67ac9cc82c73369a09792fc --- /dev/null +++ b/src/lib/token.ts @@ -0,0 +1,95 @@ +import consola from "consola" +import fs from "node:fs/promises" + +import { PATHS } from "~/lib/paths" +import { getCopilotToken } from "~/services/github/get-copilot-token" +import { getDeviceCode } from "~/services/github/get-device-code" +import { getGitHubUser } from "~/services/github/get-user" +import { pollAccessToken } from "~/services/github/poll-access-token" + +import { HTTPError } from "./error" +import { state } from "./state" + +const readGithubToken = () => fs.readFile(PATHS.GITHUB_TOKEN_PATH, "utf8") + +const writeGithubToken = (token: string) => + fs.writeFile(PATHS.GITHUB_TOKEN_PATH, token) + +export const setupCopilotToken = async () => { + const { token, refresh_in } = await getCopilotToken() + state.copilotToken = token + + // Display the Copilot token to the screen + consola.debug("GitHub Copilot Token fetched successfully!") + if (state.showToken) { + consola.info("Copilot token:", token) + } + + const refreshInterval = (refresh_in - 60) * 1000 + setInterval(async () => { + consola.debug("Refreshing Copilot token") + try { + const { token } = await getCopilotToken() + state.copilotToken = token + consola.debug("Copilot token refreshed") + if (state.showToken) { + consola.info("Refreshed Copilot token:", token) + } + } catch (error) { + consola.error("Failed to refresh Copilot token:", error) + throw error + } + }, refreshInterval) +} + +interface SetupGitHubTokenOptions { + force?: boolean +} + +export async function setupGitHubToken( + options?: SetupGitHubTokenOptions, +): Promise { + try { + const githubToken = await readGithubToken() + + if (githubToken && !options?.force) { + state.githubToken = githubToken + if (state.showToken) { + consola.info("GitHub token:", githubToken) + } + await logUser() + + return + } + + consola.info("Not logged in, getting new access token") + const response = await getDeviceCode() + consola.debug("Device code response:", response) + + consola.info( + `Please enter the code "${response.user_code}" in ${response.verification_uri}`, + ) + + const token = await pollAccessToken(response) + await writeGithubToken(token) + state.githubToken = token + + if (state.showToken) { + consola.info("GitHub token:", token) + } + await logUser() + } catch (error) { + if (error instanceof HTTPError) { + consola.error("Failed to get GitHub token:", await error.response.json()) + throw error + } + + consola.error("Failed to get GitHub token:", error) + throw error + } +} + +async function logUser() { + const user = await getGitHubUser() + consola.info(`Logged in as ${user.login}`) +} diff --git a/src/lib/tokenizer.ts b/src/lib/tokenizer.ts new file mode 100644 index 0000000000000000000000000000000000000000..8c3eda73689c95d9618064a802e541ebb93913b8 --- /dev/null +++ b/src/lib/tokenizer.ts @@ -0,0 +1,348 @@ +import type { + ChatCompletionsPayload, + ContentPart, + Message, + Tool, + ToolCall, +} from "~/services/copilot/create-chat-completions" +import type { Model } from "~/services/copilot/get-models" + +// Encoder type mapping +const ENCODING_MAP = { + o200k_base: () => import("gpt-tokenizer/encoding/o200k_base"), + cl100k_base: () => import("gpt-tokenizer/encoding/cl100k_base"), + p50k_base: () => import("gpt-tokenizer/encoding/p50k_base"), + p50k_edit: () => import("gpt-tokenizer/encoding/p50k_edit"), + r50k_base: () => import("gpt-tokenizer/encoding/r50k_base"), +} as const + +type SupportedEncoding = keyof typeof ENCODING_MAP + +// Define encoder interface +interface Encoder { + encode: (text: string) => Array +} + +// Cache loaded encoders to avoid repeated imports +const encodingCache = new Map() + +/** + * Calculate tokens for tool calls + */ +const calculateToolCallsTokens = ( + toolCalls: Array, + encoder: Encoder, + constants: ReturnType, +): number => { + let tokens = 0 + for (const toolCall of toolCalls) { + tokens += constants.funcInit + tokens += encoder.encode(JSON.stringify(toolCall)).length + } + tokens += constants.funcEnd + return tokens +} + +/** + * Calculate tokens for content parts + */ +const calculateContentPartsTokens = ( + contentParts: Array, + encoder: Encoder, +): number => { + let tokens = 0 + for (const part of contentParts) { + if (part.type === "image_url") { + tokens += encoder.encode(part.image_url.url).length + 85 + } else if (part.text) { + tokens += encoder.encode(part.text).length + } + } + return tokens +} + +/** + * Calculate tokens for a single message + */ +const calculateMessageTokens = ( + message: Message, + encoder: Encoder, + constants: ReturnType, +): number => { + const tokensPerMessage = 3 + const tokensPerName = 1 + let tokens = tokensPerMessage + for (const [key, value] of Object.entries(message)) { + if (typeof value === "string") { + tokens += encoder.encode(value).length + } + if (key === "name") { + tokens += tokensPerName + } + if (key === "tool_calls") { + tokens += calculateToolCallsTokens( + value as Array, + encoder, + constants, + ) + } + if (key === "content" && Array.isArray(value)) { + tokens += calculateContentPartsTokens( + value as Array, + encoder, + ) + } + } + return tokens +} + +/** + * Calculate tokens using custom algorithm + */ +const calculateTokens = ( + messages: Array, + encoder: Encoder, + constants: ReturnType, +): number => { + if (messages.length === 0) { + return 0 + } + let numTokens = 0 + for (const message of messages) { + numTokens += calculateMessageTokens(message, encoder, constants) + } + // every reply is primed with <|start|>assistant<|message|> + numTokens += 3 + return numTokens +} + +/** + * Get the corresponding encoder module based on encoding type + */ +const getEncodeChatFunction = async (encoding: string): Promise => { + if (encodingCache.has(encoding)) { + const cached = encodingCache.get(encoding) + if (cached) { + return cached + } + } + + const supportedEncoding = encoding as SupportedEncoding + if (!(supportedEncoding in ENCODING_MAP)) { + const fallbackModule = (await ENCODING_MAP.o200k_base()) as Encoder + encodingCache.set(encoding, fallbackModule) + return fallbackModule + } + + const encodingModule = (await ENCODING_MAP[supportedEncoding]()) as Encoder + encodingCache.set(encoding, encodingModule) + return encodingModule +} + +/** + * Get tokenizer type from model information + */ +export const getTokenizerFromModel = (model: Model): string => { + return model.capabilities.tokenizer || "o200k_base" +} + +/** + * Get model-specific constants for token calculation + */ +const getModelConstants = (model: Model) => { + return model.id === "gpt-3.5-turbo" || model.id === "gpt-4" ? + { + funcInit: 10, + propInit: 3, + propKey: 3, + enumInit: -3, + enumItem: 3, + funcEnd: 12, + } + : { + funcInit: 7, + propInit: 3, + propKey: 3, + enumInit: -3, + enumItem: 3, + funcEnd: 12, + } +} + +/** + * Calculate tokens for a single parameter + */ +const calculateParameterTokens = ( + key: string, + prop: unknown, + context: { + encoder: Encoder + constants: ReturnType + }, +): number => { + const { encoder, constants } = context + let tokens = constants.propKey + + // Early return if prop is not an object + if (typeof prop !== "object" || prop === null) { + return tokens + } + + // Type assertion for parameter properties + const param = prop as { + type?: string + description?: string + enum?: Array + [key: string]: unknown + } + + const paramName = key + const paramType = param.type || "string" + let paramDesc = param.description || "" + + // Handle enum values + if (param.enum && Array.isArray(param.enum)) { + tokens += constants.enumInit + for (const item of param.enum) { + tokens += constants.enumItem + tokens += encoder.encode(String(item)).length + } + } + + // Clean up description + if (paramDesc.endsWith(".")) { + paramDesc = paramDesc.slice(0, -1) + } + + // Encode the main parameter line + const line = `${paramName}:${paramType}:${paramDesc}` + tokens += encoder.encode(line).length + + // Handle additional properties (excluding standard ones) + const excludedKeys = new Set(["type", "description", "enum"]) + for (const propertyName of Object.keys(param)) { + if (!excludedKeys.has(propertyName)) { + const propertyValue = param[propertyName] + const propertyText = + typeof propertyValue === "string" ? propertyValue : ( + JSON.stringify(propertyValue) + ) + tokens += encoder.encode(`${propertyName}:${propertyText}`).length + } + } + + return tokens +} + +/** + * Calculate tokens for function parameters + */ +const calculateParametersTokens = ( + parameters: unknown, + encoder: Encoder, + constants: ReturnType, +): number => { + if (!parameters || typeof parameters !== "object") { + return 0 + } + + const params = parameters as Record + let tokens = 0 + + for (const [key, value] of Object.entries(params)) { + if (key === "properties") { + const properties = value as Record + if (Object.keys(properties).length > 0) { + tokens += constants.propInit + for (const propKey of Object.keys(properties)) { + tokens += calculateParameterTokens(propKey, properties[propKey], { + encoder, + constants, + }) + } + } + } else { + const paramText = + typeof value === "string" ? value : JSON.stringify(value) + tokens += encoder.encode(`${key}:${paramText}`).length + } + } + + return tokens +} + +/** + * Calculate tokens for a single tool + */ +const calculateToolTokens = ( + tool: Tool, + encoder: Encoder, + constants: ReturnType, +): number => { + let tokens = constants.funcInit + const func = tool.function + const fName = func.name + let fDesc = func.description || "" + if (fDesc.endsWith(".")) { + fDesc = fDesc.slice(0, -1) + } + const line = fName + ":" + fDesc + tokens += encoder.encode(line).length + if ( + typeof func.parameters === "object" // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + && func.parameters !== null + ) { + tokens += calculateParametersTokens(func.parameters, encoder, constants) + } + return tokens +} + +/** + * Calculate token count for tools based on model + */ +export const numTokensForTools = ( + tools: Array, + encoder: Encoder, + constants: ReturnType, +): number => { + let funcTokenCount = 0 + for (const tool of tools) { + funcTokenCount += calculateToolTokens(tool, encoder, constants) + } + funcTokenCount += constants.funcEnd + return funcTokenCount +} + +/** + * Calculate the token count of messages, supporting multiple GPT encoders + */ +export const getTokenCount = async ( + payload: ChatCompletionsPayload, + model: Model, +): Promise<{ input: number; output: number }> => { + // Get tokenizer string + const tokenizer = getTokenizerFromModel(model) + + // Get corresponding encoder module + const encoder = await getEncodeChatFunction(tokenizer) + + const simplifiedMessages = payload.messages + const inputMessages = simplifiedMessages.filter( + (msg) => msg.role !== "assistant", + ) + const outputMessages = simplifiedMessages.filter( + (msg) => msg.role === "assistant", + ) + + const constants = getModelConstants(model) + let inputTokens = calculateTokens(inputMessages, encoder, constants) + if (payload.tools && payload.tools.length > 0) { + inputTokens += numTokensForTools(payload.tools, encoder, constants) + } + const outputTokens = calculateTokens(outputMessages, encoder, constants) + + return { + input: inputTokens, + output: outputTokens, + } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..cc80be667127ecca9c0092e29ccb4d509be0b894 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,26 @@ +import consola from "consola" + +import { getModels } from "~/services/copilot/get-models" +import { getVSCodeVersion } from "~/services/get-vscode-version" + +import { state } from "./state" + +export const sleep = (ms: number) => + new Promise((resolve) => { + setTimeout(resolve, ms) + }) + +export const isNullish = (value: unknown): value is null | undefined => + value === null || value === undefined + +export async function cacheModels(): Promise { + const models = await getModels() + state.models = models +} + +export const cacheVSCodeVersion = async () => { + const response = await getVSCodeVersion() + state.vsCodeVersion = response + + consola.info(`Using VSCode version: ${response}`) +} diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000000000000000000000000000000000000..4f6ca784ba9dad767d4a42136bc6e1c6759fd431 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,19 @@ +#!/usr/bin/env node + +import { defineCommand, runMain } from "citty" + +import { auth } from "./auth" +import { checkUsage } from "./check-usage" +import { debug } from "./debug" +import { start } from "./start" + +const main = defineCommand({ + meta: { + name: "copilot-api", + description: + "A wrapper around GitHub Copilot API to make it OpenAI compatible, making it usable for other tools.", + }, + subCommands: { auth, start, "check-usage": checkUsage, debug }, +}) + +await runMain(main) diff --git a/src/routes/chat-completions/handler.ts b/src/routes/chat-completions/handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..04a5ae9edd4943b4d246ac49453c7152b534be05 --- /dev/null +++ b/src/routes/chat-completions/handler.ts @@ -0,0 +1,68 @@ +import type { Context } from "hono" + +import consola from "consola" +import { streamSSE, type SSEMessage } from "hono/streaming" + +import { awaitApproval } from "~/lib/approval" +import { checkRateLimit } from "~/lib/rate-limit" +import { state } from "~/lib/state" +import { getTokenCount } from "~/lib/tokenizer" +import { isNullish } from "~/lib/utils" +import { + createChatCompletions, + type ChatCompletionResponse, + type ChatCompletionsPayload, +} from "~/services/copilot/create-chat-completions" + +export async function handleCompletion(c: Context) { + await checkRateLimit(state) + + let payload = await c.req.json() + consola.debug("Request payload:", JSON.stringify(payload).slice(-400)) + + // Find the selected model + const selectedModel = state.models?.data.find( + (model) => model.id === payload.model, + ) + + // Calculate and display token count + try { + if (selectedModel) { + const tokenCount = await getTokenCount(payload, selectedModel) + consola.info("Current token count:", tokenCount) + } else { + consola.warn("No model selected, skipping token count calculation") + } + } catch (error) { + consola.warn("Failed to calculate token count:", error) + } + + if (state.manualApprove) await awaitApproval() + + if (isNullish(payload.max_tokens)) { + payload = { + ...payload, + max_tokens: selectedModel?.capabilities.limits.max_output_tokens, + } + consola.debug("Set max_tokens to:", JSON.stringify(payload.max_tokens)) + } + + const response = await createChatCompletions(payload) + + if (isNonStreaming(response)) { + consola.debug("Non-streaming response:", JSON.stringify(response)) + return c.json(response) + } + + consola.debug("Streaming response") + return streamSSE(c, async (stream) => { + for await (const chunk of response) { + consola.debug("Streaming chunk:", JSON.stringify(chunk)) + await stream.writeSSE(chunk as SSEMessage) + } + }) +} + +const isNonStreaming = ( + response: Awaited>, +): response is ChatCompletionResponse => Object.hasOwn(response, "choices") diff --git a/src/routes/chat-completions/route.ts b/src/routes/chat-completions/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..996de4a06418d4594fa96a3ab33ada7f810908f8 --- /dev/null +++ b/src/routes/chat-completions/route.ts @@ -0,0 +1,15 @@ +import { Hono } from "hono" + +import { forwardError } from "~/lib/error" + +import { handleCompletion } from "./handler" + +export const completionRoutes = new Hono() + +completionRoutes.post("/", async (c) => { + try { + return await handleCompletion(c) + } catch (error) { + return await forwardError(c, error) + } +}) diff --git a/src/routes/embeddings/route.ts b/src/routes/embeddings/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..4c4fc7b8a38736142c5c0aeb436f80e10068bb67 --- /dev/null +++ b/src/routes/embeddings/route.ts @@ -0,0 +1,20 @@ +import { Hono } from "hono" + +import { forwardError } from "~/lib/error" +import { + createEmbeddings, + type EmbeddingRequest, +} from "~/services/copilot/create-embeddings" + +export const embeddingRoutes = new Hono() + +embeddingRoutes.post("/", async (c) => { + try { + const paylod = await c.req.json() + const response = await createEmbeddings(paylod) + + return c.json(response) + } catch (error) { + return await forwardError(c, error) + } +}) diff --git a/src/routes/messages/anthropic-types.ts b/src/routes/messages/anthropic-types.ts new file mode 100644 index 0000000000000000000000000000000000000000..881fffcc8df5cd54bc4e3a3a2472a7a193301ccd --- /dev/null +++ b/src/routes/messages/anthropic-types.ts @@ -0,0 +1,206 @@ +// Anthropic API Types + +export interface AnthropicMessagesPayload { + model: string + messages: Array + max_tokens: number + system?: string | Array + metadata?: { + user_id?: string + } + stop_sequences?: Array + stream?: boolean + temperature?: number + top_p?: number + top_k?: number + tools?: Array + tool_choice?: { + type: "auto" | "any" | "tool" | "none" + name?: string + } + thinking?: { + type: "enabled" + budget_tokens?: number + } + service_tier?: "auto" | "standard_only" +} + +export interface AnthropicTextBlock { + type: "text" + text: string +} + +export interface AnthropicImageBlock { + type: "image" + source: { + type: "base64" + media_type: "image/jpeg" | "image/png" | "image/gif" | "image/webp" + data: string + } +} + +export interface AnthropicToolResultBlock { + type: "tool_result" + tool_use_id: string + content: string + is_error?: boolean +} + +export interface AnthropicToolUseBlock { + type: "tool_use" + id: string + name: string + input: Record +} + +export interface AnthropicThinkingBlock { + type: "thinking" + thinking: string +} + +export type AnthropicUserContentBlock = + | AnthropicTextBlock + | AnthropicImageBlock + | AnthropicToolResultBlock + +export type AnthropicAssistantContentBlock = + | AnthropicTextBlock + | AnthropicToolUseBlock + | AnthropicThinkingBlock + +export interface AnthropicUserMessage { + role: "user" + content: string | Array +} + +export interface AnthropicAssistantMessage { + role: "assistant" + content: string | Array +} + +export type AnthropicMessage = AnthropicUserMessage | AnthropicAssistantMessage + +export interface AnthropicTool { + name: string + description?: string + input_schema: Record +} + +export interface AnthropicResponse { + id: string + type: "message" + role: "assistant" + content: Array + model: string + stop_reason: + | "end_turn" + | "max_tokens" + | "stop_sequence" + | "tool_use" + | "pause_turn" + | "refusal" + | null + stop_sequence: string | null + usage: { + input_tokens: number + output_tokens: number + cache_creation_input_tokens?: number + cache_read_input_tokens?: number + service_tier?: "standard" | "priority" | "batch" + } +} + +export type AnthropicResponseContentBlock = AnthropicAssistantContentBlock + +// Anthropic Stream Event Types +export interface AnthropicMessageStartEvent { + type: "message_start" + message: Omit< + AnthropicResponse, + "content" | "stop_reason" | "stop_sequence" + > & { + content: [] + stop_reason: null + stop_sequence: null + } +} + +export interface AnthropicContentBlockStartEvent { + type: "content_block_start" + index: number + content_block: + | { type: "text"; text: string } + | (Omit & { + input: Record + }) + | { type: "thinking"; thinking: string } +} + +export interface AnthropicContentBlockDeltaEvent { + type: "content_block_delta" + index: number + delta: + | { type: "text_delta"; text: string } + | { type: "input_json_delta"; partial_json: string } + | { type: "thinking_delta"; thinking: string } + | { type: "signature_delta"; signature: string } +} + +export interface AnthropicContentBlockStopEvent { + type: "content_block_stop" + index: number +} + +export interface AnthropicMessageDeltaEvent { + type: "message_delta" + delta: { + stop_reason?: AnthropicResponse["stop_reason"] + stop_sequence?: string | null + } + usage?: { + input_tokens?: number + output_tokens: number + cache_creation_input_tokens?: number + cache_read_input_tokens?: number + } +} + +export interface AnthropicMessageStopEvent { + type: "message_stop" +} + +export interface AnthropicPingEvent { + type: "ping" +} + +export interface AnthropicErrorEvent { + type: "error" + error: { + type: string + message: string + } +} + +export type AnthropicStreamEventData = + | AnthropicMessageStartEvent + | AnthropicContentBlockStartEvent + | AnthropicContentBlockDeltaEvent + | AnthropicContentBlockStopEvent + | AnthropicMessageDeltaEvent + | AnthropicMessageStopEvent + | AnthropicPingEvent + | AnthropicErrorEvent + +// State for streaming translation +export interface AnthropicStreamState { + messageStartSent: boolean + contentBlockIndex: number + contentBlockOpen: boolean + toolCalls: { + [openAIToolIndex: number]: { + id: string + name: string + anthropicBlockIndex: number + } + } +} diff --git a/src/routes/messages/count-tokens-handler.ts b/src/routes/messages/count-tokens-handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..2ec849cb87bc8121af6072974e32f33c1d35e557 --- /dev/null +++ b/src/routes/messages/count-tokens-handler.ts @@ -0,0 +1,70 @@ +import type { Context } from "hono" + +import consola from "consola" + +import { state } from "~/lib/state" +import { getTokenCount } from "~/lib/tokenizer" + +import { type AnthropicMessagesPayload } from "./anthropic-types" +import { translateToOpenAI } from "./non-stream-translation" + +/** + * Handles token counting for Anthropic messages + */ +export async function handleCountTokens(c: Context) { + try { + const anthropicBeta = c.req.header("anthropic-beta") + + const anthropicPayload = await c.req.json() + + const openAIPayload = translateToOpenAI(anthropicPayload) + + const selectedModel = state.models?.data.find( + (model) => model.id === anthropicPayload.model, + ) + + if (!selectedModel) { + consola.warn("Model not found, returning default token count") + return c.json({ + input_tokens: 1, + }) + } + + const tokenCount = await getTokenCount(openAIPayload, selectedModel) + + if (anthropicPayload.tools && anthropicPayload.tools.length > 0) { + let mcpToolExist = false + if (anthropicBeta?.startsWith("claude-code")) { + mcpToolExist = anthropicPayload.tools.some((tool) => + tool.name.startsWith("mcp__"), + ) + } + if (!mcpToolExist) { + if (anthropicPayload.model.startsWith("claude")) { + // https://docs.anthropic.com/en/docs/agents-and-tools/tool-use/overview#pricing + tokenCount.input = tokenCount.input + 346 + } else if (anthropicPayload.model.startsWith("grok")) { + tokenCount.input = tokenCount.input + 480 + } + } + } + + let finalTokenCount = tokenCount.input + tokenCount.output + if (anthropicPayload.model.startsWith("claude")) { + finalTokenCount = Math.round(finalTokenCount * 1.15) + } else if (anthropicPayload.model.startsWith("grok")) { + finalTokenCount = Math.round(finalTokenCount * 1.03) + } + + consola.info("Token count:", finalTokenCount) + + return c.json({ + input_tokens: finalTokenCount, + }) + } catch (error) { + consola.error("Error counting tokens:", error) + return c.json({ + input_tokens: 1, + }) + } +} diff --git a/src/routes/messages/handler.ts b/src/routes/messages/handler.ts new file mode 100644 index 0000000000000000000000000000000000000000..85dbf624379aa992073b635c597ca84caec07cf6 --- /dev/null +++ b/src/routes/messages/handler.ts @@ -0,0 +1,91 @@ +import type { Context } from "hono" + +import consola from "consola" +import { streamSSE } from "hono/streaming" + +import { awaitApproval } from "~/lib/approval" +import { checkRateLimit } from "~/lib/rate-limit" +import { state } from "~/lib/state" +import { + createChatCompletions, + type ChatCompletionChunk, + type ChatCompletionResponse, +} from "~/services/copilot/create-chat-completions" + +import { + type AnthropicMessagesPayload, + type AnthropicStreamState, +} from "./anthropic-types" +import { + translateToAnthropic, + translateToOpenAI, +} from "./non-stream-translation" +import { translateChunkToAnthropicEvents } from "./stream-translation" + +export async function handleCompletion(c: Context) { + await checkRateLimit(state) + + const anthropicPayload = await c.req.json() + consola.debug("Anthropic request payload:", JSON.stringify(anthropicPayload)) + + const openAIPayload = translateToOpenAI(anthropicPayload) + consola.debug( + "Translated OpenAI request payload:", + JSON.stringify(openAIPayload), + ) + + if (state.manualApprove) { + await awaitApproval() + } + + const response = await createChatCompletions(openAIPayload) + + if (isNonStreaming(response)) { + consola.debug( + "Non-streaming response from Copilot:", + JSON.stringify(response).slice(-400), + ) + const anthropicResponse = translateToAnthropic(response) + consola.debug( + "Translated Anthropic response:", + JSON.stringify(anthropicResponse), + ) + return c.json(anthropicResponse) + } + + consola.debug("Streaming response from Copilot") + return streamSSE(c, async (stream) => { + const streamState: AnthropicStreamState = { + messageStartSent: false, + contentBlockIndex: 0, + contentBlockOpen: false, + toolCalls: {}, + } + + for await (const rawEvent of response) { + consola.debug("Copilot raw stream event:", JSON.stringify(rawEvent)) + if (rawEvent.data === "[DONE]") { + break + } + + if (!rawEvent.data) { + continue + } + + const chunk = JSON.parse(rawEvent.data) as ChatCompletionChunk + const events = translateChunkToAnthropicEvents(chunk, streamState) + + for (const event of events) { + consola.debug("Translated Anthropic event:", JSON.stringify(event)) + await stream.writeSSE({ + event: event.type, + data: JSON.stringify(event), + }) + } + } + }) +} + +const isNonStreaming = ( + response: Awaited>, +): response is ChatCompletionResponse => Object.hasOwn(response, "choices") diff --git a/src/routes/messages/non-stream-translation.ts b/src/routes/messages/non-stream-translation.ts new file mode 100644 index 0000000000000000000000000000000000000000..dc41e638251012e800b9d30dad85bf6deb201fe0 --- /dev/null +++ b/src/routes/messages/non-stream-translation.ts @@ -0,0 +1,357 @@ +import { + type ChatCompletionResponse, + type ChatCompletionsPayload, + type ContentPart, + type Message, + type TextPart, + type Tool, + type ToolCall, +} from "~/services/copilot/create-chat-completions" + +import { + type AnthropicAssistantContentBlock, + type AnthropicAssistantMessage, + type AnthropicMessage, + type AnthropicMessagesPayload, + type AnthropicResponse, + type AnthropicTextBlock, + type AnthropicThinkingBlock, + type AnthropicTool, + type AnthropicToolResultBlock, + type AnthropicToolUseBlock, + type AnthropicUserContentBlock, + type AnthropicUserMessage, +} from "./anthropic-types" +import { mapOpenAIStopReasonToAnthropic } from "./utils" + +// Payload translation + +export function translateToOpenAI( + payload: AnthropicMessagesPayload, +): ChatCompletionsPayload { + return { + model: translateModelName(payload.model), + messages: translateAnthropicMessagesToOpenAI( + payload.messages, + payload.system, + ), + max_tokens: payload.max_tokens, + stop: payload.stop_sequences, + stream: payload.stream, + temperature: payload.temperature, + top_p: payload.top_p, + user: payload.metadata?.user_id, + tools: translateAnthropicToolsToOpenAI(payload.tools), + tool_choice: translateAnthropicToolChoiceToOpenAI(payload.tool_choice), + } +} + +function translateModelName(model: string): string { + // Subagent requests use a specific model number which Copilot doesn't support + if (model.startsWith("claude-sonnet-4-")) { + return model.replace(/^claude-sonnet-4-.*/, "claude-sonnet-4") + } else if (model.startsWith("claude-opus-")) { + return model.replace(/^claude-opus-4-.*/, "claude-opus-4") + } + return model +} + +function translateAnthropicMessagesToOpenAI( + anthropicMessages: Array, + system: string | Array | undefined, +): Array { + const systemMessages = handleSystemPrompt(system) + + const otherMessages = anthropicMessages.flatMap((message) => + message.role === "user" ? + handleUserMessage(message) + : handleAssistantMessage(message), + ) + + return [...systemMessages, ...otherMessages] +} + +function handleSystemPrompt( + system: string | Array | undefined, +): Array { + if (!system) { + return [] + } + + if (typeof system === "string") { + return [{ role: "system", content: system }] + } else { + const systemText = system.map((block) => block.text).join("\n\n") + return [{ role: "system", content: systemText }] + } +} + +function handleUserMessage(message: AnthropicUserMessage): Array { + const newMessages: Array = [] + + if (Array.isArray(message.content)) { + const toolResultBlocks = message.content.filter( + (block): block is AnthropicToolResultBlock => + block.type === "tool_result", + ) + const otherBlocks = message.content.filter( + (block) => block.type !== "tool_result", + ) + + // Tool results must come first to maintain protocol: tool_use -> tool_result -> user + for (const block of toolResultBlocks) { + newMessages.push({ + role: "tool", + tool_call_id: block.tool_use_id, + content: mapContent(block.content), + }) + } + + if (otherBlocks.length > 0) { + newMessages.push({ + role: "user", + content: mapContent(otherBlocks), + }) + } + } else { + newMessages.push({ + role: "user", + content: mapContent(message.content), + }) + } + + return newMessages +} + +function handleAssistantMessage( + message: AnthropicAssistantMessage, +): Array { + if (!Array.isArray(message.content)) { + return [ + { + role: "assistant", + content: mapContent(message.content), + }, + ] + } + + const toolUseBlocks = message.content.filter( + (block): block is AnthropicToolUseBlock => block.type === "tool_use", + ) + + const textBlocks = message.content.filter( + (block): block is AnthropicTextBlock => block.type === "text", + ) + + const thinkingBlocks = message.content.filter( + (block): block is AnthropicThinkingBlock => block.type === "thinking", + ) + + // Combine text and thinking blocks, as OpenAI doesn't have separate thinking blocks + const allTextContent = [ + ...textBlocks.map((b) => b.text), + ...thinkingBlocks.map((b) => b.thinking), + ].join("\n\n") + + return toolUseBlocks.length > 0 ? + [ + { + role: "assistant", + content: allTextContent || null, + tool_calls: toolUseBlocks.map((toolUse) => ({ + id: toolUse.id, + type: "function", + function: { + name: toolUse.name, + arguments: JSON.stringify(toolUse.input), + }, + })), + }, + ] + : [ + { + role: "assistant", + content: mapContent(message.content), + }, + ] +} + +function mapContent( + content: + | string + | Array, +): string | Array | null { + if (typeof content === "string") { + return content + } + if (!Array.isArray(content)) { + return null + } + + const hasImage = content.some((block) => block.type === "image") + if (!hasImage) { + return content + .filter( + (block): block is AnthropicTextBlock | AnthropicThinkingBlock => + block.type === "text" || block.type === "thinking", + ) + .map((block) => (block.type === "text" ? block.text : block.thinking)) + .join("\n\n") + } + + const contentParts: Array = [] + for (const block of content) { + switch (block.type) { + case "text": { + contentParts.push({ type: "text", text: block.text }) + + break + } + case "thinking": { + contentParts.push({ type: "text", text: block.thinking }) + + break + } + case "image": { + contentParts.push({ + type: "image_url", + image_url: { + url: `data:${block.source.media_type};base64,${block.source.data}`, + }, + }) + + break + } + // No default + } + } + return contentParts +} + +function translateAnthropicToolsToOpenAI( + anthropicTools: Array | undefined, +): Array | undefined { + if (!anthropicTools) { + return undefined + } + return anthropicTools.map((tool) => ({ + type: "function", + function: { + name: tool.name, + description: tool.description, + parameters: tool.input_schema, + }, + })) +} + +function translateAnthropicToolChoiceToOpenAI( + anthropicToolChoice: AnthropicMessagesPayload["tool_choice"], +): ChatCompletionsPayload["tool_choice"] { + if (!anthropicToolChoice) { + return undefined + } + + switch (anthropicToolChoice.type) { + case "auto": { + return "auto" + } + case "any": { + return "required" + } + case "tool": { + if (anthropicToolChoice.name) { + return { + type: "function", + function: { name: anthropicToolChoice.name }, + } + } + return undefined + } + case "none": { + return "none" + } + default: { + return undefined + } + } +} + +// Response translation + +export function translateToAnthropic( + response: ChatCompletionResponse, +): AnthropicResponse { + // Merge content from all choices + const allTextBlocks: Array = [] + const allToolUseBlocks: Array = [] + let stopReason: "stop" | "length" | "tool_calls" | "content_filter" | null = + null // default + stopReason = response.choices[0]?.finish_reason ?? stopReason + + // Process all choices to extract text and tool use blocks + for (const choice of response.choices) { + const textBlocks = getAnthropicTextBlocks(choice.message.content) + const toolUseBlocks = getAnthropicToolUseBlocks(choice.message.tool_calls) + + allTextBlocks.push(...textBlocks) + allToolUseBlocks.push(...toolUseBlocks) + + // Use the finish_reason from the first choice, or prioritize tool_calls + if (choice.finish_reason === "tool_calls" || stopReason === "stop") { + stopReason = choice.finish_reason + } + } + + // Note: GitHub Copilot doesn't generate thinking blocks, so we don't include them in responses + + return { + id: response.id, + type: "message", + role: "assistant", + model: response.model, + content: [...allTextBlocks, ...allToolUseBlocks], + stop_reason: mapOpenAIStopReasonToAnthropic(stopReason), + stop_sequence: null, + usage: { + input_tokens: + (response.usage?.prompt_tokens ?? 0) + - (response.usage?.prompt_tokens_details?.cached_tokens ?? 0), + output_tokens: response.usage?.completion_tokens ?? 0, + ...(response.usage?.prompt_tokens_details?.cached_tokens + !== undefined && { + cache_read_input_tokens: + response.usage.prompt_tokens_details.cached_tokens, + }), + }, + } +} + +function getAnthropicTextBlocks( + messageContent: Message["content"], +): Array { + if (typeof messageContent === "string") { + return [{ type: "text", text: messageContent }] + } + + if (Array.isArray(messageContent)) { + return messageContent + .filter((part): part is TextPart => part.type === "text") + .map((part) => ({ type: "text", text: part.text })) + } + + return [] +} + +function getAnthropicToolUseBlocks( + toolCalls: Array | undefined, +): Array { + if (!toolCalls) { + return [] + } + return toolCalls.map((toolCall) => ({ + type: "tool_use", + id: toolCall.id, + name: toolCall.function.name, + input: JSON.parse(toolCall.function.arguments) as Record, + })) +} diff --git a/src/routes/messages/route.ts b/src/routes/messages/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..ef72d802eb7a008e5497bec6da19c32b0d65e5db --- /dev/null +++ b/src/routes/messages/route.ts @@ -0,0 +1,24 @@ +import { Hono } from "hono" + +import { forwardError } from "~/lib/error" + +import { handleCountTokens } from "./count-tokens-handler" +import { handleCompletion } from "./handler" + +export const messageRoutes = new Hono() + +messageRoutes.post("/", async (c) => { + try { + return await handleCompletion(c) + } catch (error) { + return await forwardError(c, error) + } +}) + +messageRoutes.post("/count_tokens", async (c) => { + try { + return await handleCountTokens(c) + } catch (error) { + return await forwardError(c, error) + } +}) diff --git a/src/routes/messages/stream-translation.ts b/src/routes/messages/stream-translation.ts new file mode 100644 index 0000000000000000000000000000000000000000..55094448fe3874ace7c75dd7fbfe4f1a3b23eb97 --- /dev/null +++ b/src/routes/messages/stream-translation.ts @@ -0,0 +1,190 @@ +import { type ChatCompletionChunk } from "~/services/copilot/create-chat-completions" + +import { + type AnthropicStreamEventData, + type AnthropicStreamState, +} from "./anthropic-types" +import { mapOpenAIStopReasonToAnthropic } from "./utils" + +function isToolBlockOpen(state: AnthropicStreamState): boolean { + if (!state.contentBlockOpen) { + return false + } + // Check if the current block index corresponds to any known tool call + return Object.values(state.toolCalls).some( + (tc) => tc.anthropicBlockIndex === state.contentBlockIndex, + ) +} + +// eslint-disable-next-line max-lines-per-function, complexity +export function translateChunkToAnthropicEvents( + chunk: ChatCompletionChunk, + state: AnthropicStreamState, +): Array { + const events: Array = [] + + if (chunk.choices.length === 0) { + return events + } + + const choice = chunk.choices[0] + const { delta } = choice + + if (!state.messageStartSent) { + events.push({ + type: "message_start", + message: { + id: chunk.id, + type: "message", + role: "assistant", + content: [], + model: chunk.model, + stop_reason: null, + stop_sequence: null, + usage: { + input_tokens: + (chunk.usage?.prompt_tokens ?? 0) + - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0), + output_tokens: 0, // Will be updated in message_delta when finished + ...(chunk.usage?.prompt_tokens_details?.cached_tokens + !== undefined && { + cache_read_input_tokens: + chunk.usage.prompt_tokens_details.cached_tokens, + }), + }, + }, + }) + state.messageStartSent = true + } + + if (delta.content) { + if (isToolBlockOpen(state)) { + // A tool block was open, so close it before starting a text block. + events.push({ + type: "content_block_stop", + index: state.contentBlockIndex, + }) + state.contentBlockIndex++ + state.contentBlockOpen = false + } + + if (!state.contentBlockOpen) { + events.push({ + type: "content_block_start", + index: state.contentBlockIndex, + content_block: { + type: "text", + text: "", + }, + }) + state.contentBlockOpen = true + } + + events.push({ + type: "content_block_delta", + index: state.contentBlockIndex, + delta: { + type: "text_delta", + text: delta.content, + }, + }) + } + + if (delta.tool_calls) { + for (const toolCall of delta.tool_calls) { + if (toolCall.id && toolCall.function?.name) { + // New tool call starting. + if (state.contentBlockOpen) { + // Close any previously open block. + events.push({ + type: "content_block_stop", + index: state.contentBlockIndex, + }) + state.contentBlockIndex++ + state.contentBlockOpen = false + } + + const anthropicBlockIndex = state.contentBlockIndex + state.toolCalls[toolCall.index] = { + id: toolCall.id, + name: toolCall.function.name, + anthropicBlockIndex, + } + + events.push({ + type: "content_block_start", + index: anthropicBlockIndex, + content_block: { + type: "tool_use", + id: toolCall.id, + name: toolCall.function.name, + input: {}, + }, + }) + state.contentBlockOpen = true + } + + if (toolCall.function?.arguments) { + const toolCallInfo = state.toolCalls[toolCall.index] + // Tool call can still be empty + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (toolCallInfo) { + events.push({ + type: "content_block_delta", + index: toolCallInfo.anthropicBlockIndex, + delta: { + type: "input_json_delta", + partial_json: toolCall.function.arguments, + }, + }) + } + } + } + } + + if (choice.finish_reason) { + if (state.contentBlockOpen) { + events.push({ + type: "content_block_stop", + index: state.contentBlockIndex, + }) + state.contentBlockOpen = false + } + + events.push( + { + type: "message_delta", + delta: { + stop_reason: mapOpenAIStopReasonToAnthropic(choice.finish_reason), + stop_sequence: null, + }, + usage: { + input_tokens: + (chunk.usage?.prompt_tokens ?? 0) + - (chunk.usage?.prompt_tokens_details?.cached_tokens ?? 0), + output_tokens: chunk.usage?.completion_tokens ?? 0, + ...(chunk.usage?.prompt_tokens_details?.cached_tokens + !== undefined && { + cache_read_input_tokens: + chunk.usage.prompt_tokens_details.cached_tokens, + }), + }, + }, + { + type: "message_stop", + }, + ) + } + + return events +} + +export function translateErrorToAnthropicErrorEvent(): AnthropicStreamEventData { + return { + type: "error", + error: { + type: "api_error", + message: "An unexpected error occurred during streaming.", + }, + } +} diff --git a/src/routes/messages/utils.ts b/src/routes/messages/utils.ts new file mode 100644 index 0000000000000000000000000000000000000000..d0febfc9dadda34f3ff99479e2d78d26b53bb962 --- /dev/null +++ b/src/routes/messages/utils.ts @@ -0,0 +1,16 @@ +import { type AnthropicResponse } from "./anthropic-types" + +export function mapOpenAIStopReasonToAnthropic( + finishReason: "stop" | "length" | "tool_calls" | "content_filter" | null, +): AnthropicResponse["stop_reason"] { + if (finishReason === null) { + return null + } + const stopReasonMap = { + stop: "end_turn", + length: "max_tokens", + tool_calls: "tool_use", + content_filter: "end_turn", + } as const + return stopReasonMap[finishReason] +} diff --git a/src/routes/models/route.ts b/src/routes/models/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..5254e2af7fcd94325f375de6fd4342948fbde568 --- /dev/null +++ b/src/routes/models/route.ts @@ -0,0 +1,34 @@ +import { Hono } from "hono" + +import { forwardError } from "~/lib/error" +import { state } from "~/lib/state" +import { cacheModels } from "~/lib/utils" + +export const modelRoutes = new Hono() + +modelRoutes.get("/", async (c) => { + try { + if (!state.models) { + // This should be handled by startup logic, but as a fallback. + await cacheModels() + } + + const models = state.models?.data.map((model) => ({ + id: model.id, + object: "model", + type: "model", + created: 0, // No date available from source + created_at: new Date(0).toISOString(), // No date available from source + owned_by: model.vendor, + display_name: model.name, + })) + + return c.json({ + object: "list", + data: models, + has_more: false, + }) + } catch (error) { + return await forwardError(c, error) + } +}) diff --git a/src/routes/token/route.ts b/src/routes/token/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..dd0456d9a332107353bafee52f2dd63b62789ac0 --- /dev/null +++ b/src/routes/token/route.ts @@ -0,0 +1,16 @@ +import { Hono } from "hono" + +import { state } from "~/lib/state" + +export const tokenRoute = new Hono() + +tokenRoute.get("/", (c) => { + try { + return c.json({ + token: state.copilotToken, + }) + } catch (error) { + console.error("Error fetching token:", error) + return c.json({ error: "Failed to fetch token", token: null }, 500) + } +}) diff --git a/src/routes/usage/route.ts b/src/routes/usage/route.ts new file mode 100644 index 0000000000000000000000000000000000000000..3e94732365c929e60bde0fcc0f8c1d8368a2b965 --- /dev/null +++ b/src/routes/usage/route.ts @@ -0,0 +1,15 @@ +import { Hono } from "hono" + +import { getCopilotUsage } from "~/services/github/get-copilot-usage" + +export const usageRoute = new Hono() + +usageRoute.get("/", async (c) => { + try { + const usage = await getCopilotUsage() + return c.json(usage) + } catch (error) { + console.error("Error fetching Copilot usage:", error) + return c.json({ error: "Failed to fetch Copilot usage" }, 500) + } +}) diff --git a/src/server.ts b/src/server.ts new file mode 100644 index 0000000000000000000000000000000000000000..462a278f38a411cceb5d5a58b5191044d9ac9e63 --- /dev/null +++ b/src/server.ts @@ -0,0 +1,31 @@ +import { Hono } from "hono" +import { cors } from "hono/cors" +import { logger } from "hono/logger" + +import { completionRoutes } from "./routes/chat-completions/route" +import { embeddingRoutes } from "./routes/embeddings/route" +import { messageRoutes } from "./routes/messages/route" +import { modelRoutes } from "./routes/models/route" +import { tokenRoute } from "./routes/token/route" +import { usageRoute } from "./routes/usage/route" + +export const server = new Hono() + +server.use(logger()) +server.use(cors()) + +server.get("/", (c) => c.text("Server running")) + +server.route("/chat/completions", completionRoutes) +server.route("/models", modelRoutes) +server.route("/embeddings", embeddingRoutes) +server.route("/usage", usageRoute) +server.route("/token", tokenRoute) + +// Compatibility with tools that expect v1/ prefix +server.route("/v1/chat/completions", completionRoutes) +server.route("/v1/models", modelRoutes) +server.route("/v1/embeddings", embeddingRoutes) + +// Anthropic compatible endpoints +server.route("/v1/messages", messageRoutes) diff --git a/src/services/copilot/create-chat-completions.ts b/src/services/copilot/create-chat-completions.ts new file mode 100644 index 0000000000000000000000000000000000000000..8534151daa3c0cdc6a87358182506fbb13af38aa --- /dev/null +++ b/src/services/copilot/create-chat-completions.ts @@ -0,0 +1,193 @@ +import consola from "consola" +import { events } from "fetch-event-stream" + +import { copilotHeaders, copilotBaseUrl } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export const createChatCompletions = async ( + payload: ChatCompletionsPayload, +) => { + if (!state.copilotToken) throw new Error("Copilot token not found") + + const enableVision = payload.messages.some( + (x) => + typeof x.content !== "string" + && x.content?.some((x) => x.type === "image_url"), + ) + + // Agent/user check for X-Initiator header + // Determine if any message is from an agent ("assistant" or "tool") + const isAgentCall = payload.messages.some((msg) => + ["assistant", "tool"].includes(msg.role), + ) + + // Build headers and add X-Initiator + const headers: Record = { + ...copilotHeaders(state, enableVision), + "X-Initiator": isAgentCall ? "agent" : "user", + } + + const response = await fetch(`${copilotBaseUrl(state)}/chat/completions`, { + method: "POST", + headers, + body: JSON.stringify(payload), + }) + + if (!response.ok) { + consola.error("Failed to create chat completions", response) + throw new HTTPError("Failed to create chat completions", response) + } + + if (payload.stream) { + return events(response) + } + + return (await response.json()) as ChatCompletionResponse +} + +// Streaming types + +export interface ChatCompletionChunk { + id: string + object: "chat.completion.chunk" + created: number + model: string + choices: Array + system_fingerprint?: string + usage?: { + prompt_tokens: number + completion_tokens: number + total_tokens: number + prompt_tokens_details?: { + cached_tokens: number + } + completion_tokens_details?: { + accepted_prediction_tokens: number + rejected_prediction_tokens: number + } + } +} + +interface Delta { + content?: string | null + role?: "user" | "assistant" | "system" | "tool" + tool_calls?: Array<{ + index: number + id?: string + type?: "function" + function?: { + name?: string + arguments?: string + } + }> +} + +interface Choice { + index: number + delta: Delta + finish_reason: "stop" | "length" | "tool_calls" | "content_filter" | null + logprobs: object | null +} + +// Non-streaming types + +export interface ChatCompletionResponse { + id: string + object: "chat.completion" + created: number + model: string + choices: Array + system_fingerprint?: string + usage?: { + prompt_tokens: number + completion_tokens: number + total_tokens: number + prompt_tokens_details?: { + cached_tokens: number + } + } +} + +interface ResponseMessage { + role: "assistant" + content: string | null + tool_calls?: Array +} + +interface ChoiceNonStreaming { + index: number + message: ResponseMessage + logprobs: object | null + finish_reason: "stop" | "length" | "tool_calls" | "content_filter" +} + +// Payload types + +export interface ChatCompletionsPayload { + messages: Array + model: string + temperature?: number | null + top_p?: number | null + max_tokens?: number | null + stop?: string | Array | null + n?: number | null + stream?: boolean | null + + frequency_penalty?: number | null + presence_penalty?: number | null + logit_bias?: Record | null + logprobs?: boolean | null + response_format?: { type: "json_object" } | null + seed?: number | null + tools?: Array | null + tool_choice?: + | "none" + | "auto" + | "required" + | { type: "function"; function: { name: string } } + | null + user?: string | null +} + +export interface Tool { + type: "function" + function: { + name: string + description?: string + parameters: Record + } +} + +export interface Message { + role: "user" | "assistant" | "system" | "tool" | "developer" + content: string | Array | null + + name?: string + tool_calls?: Array + tool_call_id?: string +} + +export interface ToolCall { + id: string + type: "function" + function: { + name: string + arguments: string + } +} + +export type ContentPart = TextPart | ImagePart + +export interface TextPart { + type: "text" + text: string +} + +export interface ImagePart { + type: "image_url" + image_url: { + url: string + detail?: "low" | "high" | "auto" + } +} diff --git a/src/services/copilot/create-embeddings.ts b/src/services/copilot/create-embeddings.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2ad5c233bd014aa5559d12dfc7902442a32840d --- /dev/null +++ b/src/services/copilot/create-embeddings.ts @@ -0,0 +1,38 @@ +import { copilotHeaders, copilotBaseUrl } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export const createEmbeddings = async (payload: EmbeddingRequest) => { + if (!state.copilotToken) throw new Error("Copilot token not found") + + const response = await fetch(`${copilotBaseUrl(state)}/embeddings`, { + method: "POST", + headers: copilotHeaders(state), + body: JSON.stringify(payload), + }) + + if (!response.ok) throw new HTTPError("Failed to create embeddings", response) + + return (await response.json()) as EmbeddingResponse +} + +export interface EmbeddingRequest { + input: string | Array + model: string +} + +export interface Embedding { + object: string + embedding: Array + index: number +} + +export interface EmbeddingResponse { + object: string + data: Array + model: string + usage: { + prompt_tokens: number + total_tokens: number + } +} diff --git a/src/services/copilot/get-models.ts b/src/services/copilot/get-models.ts new file mode 100644 index 0000000000000000000000000000000000000000..3cfa30af0b27cad3f84031b9c3d9202c6f4a9d5e --- /dev/null +++ b/src/services/copilot/get-models.ts @@ -0,0 +1,55 @@ +import { copilotBaseUrl, copilotHeaders } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export const getModels = async () => { + const response = await fetch(`${copilotBaseUrl(state)}/models`, { + headers: copilotHeaders(state), + }) + + if (!response.ok) throw new HTTPError("Failed to get models", response) + + return (await response.json()) as ModelsResponse +} + +export interface ModelsResponse { + data: Array + object: string +} + +interface ModelLimits { + max_context_window_tokens?: number + max_output_tokens?: number + max_prompt_tokens?: number + max_inputs?: number +} + +interface ModelSupports { + tool_calls?: boolean + parallel_tool_calls?: boolean + dimensions?: boolean +} + +interface ModelCapabilities { + family: string + limits: ModelLimits + object: string + supports: ModelSupports + tokenizer: string + type: string +} + +export interface Model { + capabilities: ModelCapabilities + id: string + model_picker_enabled: boolean + name: string + object: string + preview: boolean + vendor: string + version: string + policy?: { + state: string + terms: string + } +} diff --git a/src/services/get-vscode-version.ts b/src/services/get-vscode-version.ts new file mode 100644 index 0000000000000000000000000000000000000000..6078f09b5583b837a2b01d5e1d44042dc5a7fa2f --- /dev/null +++ b/src/services/get-vscode-version.ts @@ -0,0 +1,33 @@ +const FALLBACK = "1.104.3" + +export async function getVSCodeVersion() { + const controller = new AbortController() + const timeout = setTimeout(() => { + controller.abort() + }, 5000) + + try { + const response = await fetch( + "https://aur.archlinux.org/cgit/aur.git/plain/PKGBUILD?h=visual-studio-code-bin", + { + signal: controller.signal, + }, + ) + + const pkgbuild = await response.text() + const pkgverRegex = /pkgver=([0-9.]+)/ + const match = pkgbuild.match(pkgverRegex) + + if (match) { + return match[1] + } + + return FALLBACK + } catch { + return FALLBACK + } finally { + clearTimeout(timeout) + } +} + +await getVSCodeVersion() diff --git a/src/services/github/get-copilot-token.ts b/src/services/github/get-copilot-token.ts new file mode 100644 index 0000000000000000000000000000000000000000..98744bab17bbf36d87eef7d68b348d2200c56ce2 --- /dev/null +++ b/src/services/github/get-copilot-token.ts @@ -0,0 +1,23 @@ +import { GITHUB_API_BASE_URL, githubHeaders } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export const getCopilotToken = async () => { + const response = await fetch( + `${GITHUB_API_BASE_URL}/copilot_internal/v2/token`, + { + headers: githubHeaders(state), + }, + ) + + if (!response.ok) throw new HTTPError("Failed to get Copilot token", response) + + return (await response.json()) as GetCopilotTokenResponse +} + +// Trimmed for the sake of simplicity +interface GetCopilotTokenResponse { + expires_at: number + refresh_in: number + token: string +} diff --git a/src/services/github/get-copilot-usage.ts b/src/services/github/get-copilot-usage.ts new file mode 100644 index 0000000000000000000000000000000000000000..6cdd8bc107d83f3ac5177d1fbeb71c96d3e12433 --- /dev/null +++ b/src/services/github/get-copilot-usage.ts @@ -0,0 +1,45 @@ +import { GITHUB_API_BASE_URL, githubHeaders } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export const getCopilotUsage = async (): Promise => { + const response = await fetch(`${GITHUB_API_BASE_URL}/copilot_internal/user`, { + headers: githubHeaders(state), + }) + + if (!response.ok) { + throw new HTTPError("Failed to get Copilot usage", response) + } + + return (await response.json()) as CopilotUsageResponse +} + +export interface QuotaDetail { + entitlement: number + overage_count: number + overage_permitted: boolean + percent_remaining: number + quota_id: string + quota_remaining: number + remaining: number + unlimited: boolean +} + +interface QuotaSnapshots { + chat: QuotaDetail + completions: QuotaDetail + premium_interactions: QuotaDetail +} + +interface CopilotUsageResponse { + access_type_sku: string + analytics_tracking_id: string + assigned_date: string + can_signup_for_limited: boolean + chat_enabled: boolean + copilot_plan: string + organization_login_list: Array + organization_list: Array + quota_reset_date: string + quota_snapshots: QuotaSnapshots +} diff --git a/src/services/github/get-device-code.ts b/src/services/github/get-device-code.ts new file mode 100644 index 0000000000000000000000000000000000000000..cf35f4ec93f9b128ad5d57a337b0bfb78ebc9fea --- /dev/null +++ b/src/services/github/get-device-code.ts @@ -0,0 +1,30 @@ +import { + GITHUB_APP_SCOPES, + GITHUB_BASE_URL, + GITHUB_CLIENT_ID, + standardHeaders, +} from "~/lib/api-config" +import { HTTPError } from "~/lib/error" + +export async function getDeviceCode(): Promise { + const response = await fetch(`${GITHUB_BASE_URL}/login/device/code`, { + method: "POST", + headers: standardHeaders(), + body: JSON.stringify({ + client_id: GITHUB_CLIENT_ID, + scope: GITHUB_APP_SCOPES, + }), + }) + + if (!response.ok) throw new HTTPError("Failed to get device code", response) + + return (await response.json()) as DeviceCodeResponse +} + +export interface DeviceCodeResponse { + device_code: string + user_code: string + verification_uri: string + expires_in: number + interval: number +} diff --git a/src/services/github/get-user.ts b/src/services/github/get-user.ts new file mode 100644 index 0000000000000000000000000000000000000000..23e1b1c1c253165ccce9fbceb0c46202cbe10cd8 --- /dev/null +++ b/src/services/github/get-user.ts @@ -0,0 +1,21 @@ +import { GITHUB_API_BASE_URL, standardHeaders } from "~/lib/api-config" +import { HTTPError } from "~/lib/error" +import { state } from "~/lib/state" + +export async function getGitHubUser() { + const response = await fetch(`${GITHUB_API_BASE_URL}/user`, { + headers: { + authorization: `token ${state.githubToken}`, + ...standardHeaders(), + }, + }) + + if (!response.ok) throw new HTTPError("Failed to get GitHub user", response) + + return (await response.json()) as GithubUserResponse +} + +// Trimmed for the sake of simplicity +interface GithubUserResponse { + login: string +} diff --git a/src/services/github/poll-access-token.ts b/src/services/github/poll-access-token.ts new file mode 100644 index 0000000000000000000000000000000000000000..4639ee0dcf79d0b2f8b98a5ad710d175364b0fd7 --- /dev/null +++ b/src/services/github/poll-access-token.ts @@ -0,0 +1,58 @@ +import consola from "consola" + +import { + GITHUB_BASE_URL, + GITHUB_CLIENT_ID, + standardHeaders, +} from "~/lib/api-config" +import { sleep } from "~/lib/utils" + +import type { DeviceCodeResponse } from "./get-device-code" + +export async function pollAccessToken( + deviceCode: DeviceCodeResponse, +): Promise { + // Interval is in seconds, we need to multiply by 1000 to get milliseconds + // I'm also adding another second, just to be safe + const sleepDuration = (deviceCode.interval + 1) * 1000 + consola.debug(`Polling access token with interval of ${sleepDuration}ms`) + + while (true) { + const response = await fetch( + `${GITHUB_BASE_URL}/login/oauth/access_token`, + { + method: "POST", + headers: standardHeaders(), + body: JSON.stringify({ + client_id: GITHUB_CLIENT_ID, + device_code: deviceCode.device_code, + grant_type: "urn:ietf:params:oauth:grant-type:device_code", + }), + }, + ) + + if (!response.ok) { + await sleep(sleepDuration) + consola.error("Failed to poll access token:", await response.text()) + + continue + } + + const json = await response.json() + consola.debug("Polling access token response:", json) + + const { access_token } = json as AccessTokenResponse + + if (access_token) { + return access_token + } else { + await sleep(sleepDuration) + } + } +} + +interface AccessTokenResponse { + access_token: string + token_type: string + scope: string +} diff --git a/src/start.ts b/src/start.ts new file mode 100644 index 0000000000000000000000000000000000000000..14abbbdffba2e55d641034ec3b9a82af81d56894 --- /dev/null +++ b/src/start.ts @@ -0,0 +1,207 @@ +#!/usr/bin/env node + +import { defineCommand } from "citty" +import clipboard from "clipboardy" +import consola from "consola" +import { serve, type ServerHandler } from "srvx" +import invariant from "tiny-invariant" + +import { ensurePaths } from "./lib/paths" +import { initProxyFromEnv } from "./lib/proxy" +import { generateEnvScript } from "./lib/shell" +import { state } from "./lib/state" +import { setupCopilotToken, setupGitHubToken } from "./lib/token" +import { cacheModels, cacheVSCodeVersion } from "./lib/utils" +import { server } from "./server" + +interface RunServerOptions { + port: number + verbose: boolean + accountType: string + manual: boolean + rateLimit?: number + rateLimitWait: boolean + githubToken?: string + claudeCode: boolean + showToken: boolean + proxyEnv: boolean +} + +export async function runServer(options: RunServerOptions): Promise { + if (options.proxyEnv) { + initProxyFromEnv() + } + + if (options.verbose) { + consola.level = 5 + consola.info("Verbose logging enabled") + } + + state.accountType = options.accountType + if (options.accountType !== "individual") { + consola.info(`Using ${options.accountType} plan GitHub account`) + } + + state.manualApprove = options.manual + state.rateLimitSeconds = options.rateLimit + state.rateLimitWait = options.rateLimitWait + state.showToken = options.showToken + + await ensurePaths() + await cacheVSCodeVersion() + + if (options.githubToken) { + state.githubToken = options.githubToken + consola.info("Using provided GitHub token") + } else { + await setupGitHubToken() + } + + await setupCopilotToken() + await cacheModels() + + consola.info( + `Available models: \n${state.models?.data.map((model) => `- ${model.id}`).join("\n")}`, + ) + + const serverUrl = `http://localhost:${options.port}` + + if (options.claudeCode) { + invariant(state.models, "Models should be loaded by now") + + const selectedModel = await consola.prompt( + "Select a model to use with Claude Code", + { + type: "select", + options: state.models.data.map((model) => model.id), + }, + ) + + const selectedSmallModel = await consola.prompt( + "Select a small model to use with Claude Code", + { + type: "select", + options: state.models.data.map((model) => model.id), + }, + ) + + const command = generateEnvScript( + { + ANTHROPIC_BASE_URL: serverUrl, + ANTHROPIC_AUTH_TOKEN: "dummy", + ANTHROPIC_MODEL: selectedModel, + ANTHROPIC_DEFAULT_SONNET_MODEL: selectedModel, + ANTHROPIC_SMALL_FAST_MODEL: selectedSmallModel, + ANTHROPIC_DEFAULT_HAIKU_MODEL: selectedSmallModel, + DISABLE_NON_ESSENTIAL_MODEL_CALLS: "1", + CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: "1", + }, + "claude", + ) + + try { + clipboard.writeSync(command) + consola.success("Copied Claude Code command to clipboard!") + } catch { + consola.warn( + "Failed to copy to clipboard. Here is the Claude Code command:", + ) + consola.log(command) + } + } + + consola.box( + `🌐 Usage Viewer: https://ericc-ch.github.io/copilot-api?endpoint=${serverUrl}/usage`, + ) + + serve({ + fetch: server.fetch as ServerHandler, + port: options.port, + }) +} + +export const start = defineCommand({ + meta: { + name: "start", + description: "Start the Copilot API server", + }, + args: { + port: { + alias: "p", + type: "string", + default: "4141", + description: "Port to listen on", + }, + verbose: { + alias: "v", + type: "boolean", + default: false, + description: "Enable verbose logging", + }, + "account-type": { + alias: "a", + type: "string", + default: "individual", + description: "Account type to use (individual, business, enterprise)", + }, + manual: { + type: "boolean", + default: false, + description: "Enable manual request approval", + }, + "rate-limit": { + alias: "r", + type: "string", + description: "Rate limit in seconds between requests", + }, + wait: { + alias: "w", + type: "boolean", + default: false, + description: + "Wait instead of error when rate limit is hit. Has no effect if rate limit is not set", + }, + "github-token": { + alias: "g", + type: "string", + description: + "Provide GitHub token directly (must be generated using the `auth` subcommand)", + }, + "claude-code": { + alias: "c", + type: "boolean", + default: false, + description: + "Generate a command to launch Claude Code with Copilot API config", + }, + "show-token": { + type: "boolean", + default: false, + description: "Show GitHub and Copilot tokens on fetch and refresh", + }, + "proxy-env": { + type: "boolean", + default: false, + description: "Initialize proxy from environment variables", + }, + }, + run({ args }) { + const rateLimitRaw = args["rate-limit"] + const rateLimit = + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + rateLimitRaw === undefined ? undefined : Number.parseInt(rateLimitRaw, 10) + + return runServer({ + port: Number.parseInt(args.port, 10), + verbose: args.verbose, + accountType: args["account-type"], + manual: args.manual, + rateLimit, + rateLimitWait: args.wait, + githubToken: args["github-token"], + claudeCode: args["claude-code"], + showToken: args["show-token"], + proxyEnv: args["proxy-env"], + }) + }, +}) diff --git a/start.bat b/start.bat new file mode 100644 index 0000000000000000000000000000000000000000..1a0f8cb83e01313278b807fed7dc918ce9d74c17 --- /dev/null +++ b/start.bat @@ -0,0 +1,20 @@ +@echo off +echo ================================================ +echo GitHub Copilot API Server with Usage Viewer +echo ================================================ +echo. + +if not exist node_modules ( + echo Installing dependencies... + bun install + echo. +) + +echo Starting server... +echo The usage viewer page will open automatically after the server starts +echo. + +start "" "https://ericc-ch.github.io/copilot-api?endpoint=http://localhost:4141/usage" +bun run dev + +pause diff --git a/tests/anthropic-request.test.ts b/tests/anthropic-request.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..06c6637788e9084f8fb5d5fa037bc660aedb4f54 --- /dev/null +++ b/tests/anthropic-request.test.ts @@ -0,0 +1,313 @@ +import { describe, test, expect } from "bun:test" +import { z } from "zod" + +import type { AnthropicMessagesPayload } from "~/routes/messages/anthropic-types" + +import { translateToOpenAI } from "../src/routes/messages/non-stream-translation" + +// Zod schema for a single message in the chat completion request. +const messageSchema = z.object({ + role: z.enum([ + "system", + "user", + "assistant", + "tool", + "function", + "developer", + ]), + content: z.union([z.string(), z.object({}), z.array(z.any())]), + name: z.string().optional(), + tool_calls: z.array(z.any()).optional(), + tool_call_id: z.string().optional(), +}) + +// Zod schema for the entire chat completion request payload. +// This is derived from the openapi.documented.yml specification. +const chatCompletionRequestSchema = z.object({ + messages: z.array(messageSchema).min(1, "Messages array cannot be empty."), + model: z.string(), + frequency_penalty: z.number().min(-2).max(2).optional().nullable(), + logit_bias: z.record(z.string(), z.number()).optional().nullable(), + logprobs: z.boolean().optional().nullable(), + top_logprobs: z.number().int().min(0).max(20).optional().nullable(), + max_tokens: z.number().int().optional().nullable(), + n: z.number().int().min(1).max(128).optional().nullable(), + presence_penalty: z.number().min(-2).max(2).optional().nullable(), + response_format: z + .object({ + type: z.enum(["text", "json_object", "json_schema"]), + json_schema: z.object({}).optional(), + }) + .optional(), + seed: z.number().int().optional().nullable(), + stop: z + .union([z.string(), z.array(z.string())]) + .optional() + .nullable(), + stream: z.boolean().optional().nullable(), + temperature: z.number().min(0).max(2).optional().nullable(), + top_p: z.number().min(0).max(1).optional().nullable(), + tools: z.array(z.any()).optional(), + tool_choice: z.union([z.string(), z.object({})]).optional(), + user: z.string().optional(), +}) + +/** + * Validates if a request payload conforms to the OpenAI Chat Completion v1 shape using Zod. + * @param payload The request payload to validate. + * @returns True if the payload is valid, false otherwise. + */ +function isValidChatCompletionRequest(payload: unknown): boolean { + const result = chatCompletionRequestSchema.safeParse(payload) + return result.success +} + +describe("Anthropic to OpenAI translation logic", () => { + test("should translate minimal Anthropic payload to valid OpenAI payload", () => { + const anthropicPayload: AnthropicMessagesPayload = { + model: "gpt-4o", + messages: [{ role: "user", content: "Hello!" }], + max_tokens: 0, + } + + const openAIPayload = translateToOpenAI(anthropicPayload) + expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) + }) + + test("should translate comprehensive Anthropic payload to valid OpenAI payload", () => { + const anthropicPayload: AnthropicMessagesPayload = { + model: "gpt-4o", + system: "You are a helpful assistant.", + messages: [ + { role: "user", content: "What is the weather like in Boston?" }, + { + role: "assistant", + content: "The weather in Boston is sunny and 75°F.", + }, + ], + temperature: 0.7, + max_tokens: 150, + top_p: 1, + stream: false, + metadata: { user_id: "user-123" }, + tools: [ + { + name: "getWeather", + description: "Gets weather info", + input_schema: { location: { type: "string" } }, + }, + ], + tool_choice: { type: "auto" }, + } + const openAIPayload = translateToOpenAI(anthropicPayload) + expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) + }) + + test("should handle missing fields gracefully", () => { + const anthropicPayload: AnthropicMessagesPayload = { + model: "gpt-4o", + messages: [{ role: "user", content: "Hello!" }], + max_tokens: 0, + } + const openAIPayload = translateToOpenAI(anthropicPayload) + expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) + }) + + test("should handle invalid types in Anthropic payload", () => { + const anthropicPayload = { + model: "gpt-4o", + messages: [{ role: "user", content: "Hello!" }], + temperature: "hot", // Should be a number + } + // @ts-expect-error intended to be invalid + const openAIPayload = translateToOpenAI(anthropicPayload) + // Should fail validation + expect(isValidChatCompletionRequest(openAIPayload)).toBe(false) + }) + + test("should handle thinking blocks in assistant messages", () => { + const anthropicPayload: AnthropicMessagesPayload = { + model: "claude-3-5-sonnet-20241022", + messages: [ + { role: "user", content: "What is 2+2?" }, + { + role: "assistant", + content: [ + { + type: "thinking", + thinking: "Let me think about this simple math problem...", + }, + { type: "text", text: "2+2 equals 4." }, + ], + }, + ], + max_tokens: 100, + } + const openAIPayload = translateToOpenAI(anthropicPayload) + expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) + + // Check that thinking content is combined with text content + const assistantMessage = openAIPayload.messages.find( + (m) => m.role === "assistant", + ) + expect(assistantMessage?.content).toContain( + "Let me think about this simple math problem...", + ) + expect(assistantMessage?.content).toContain("2+2 equals 4.") + }) + + test("should handle thinking blocks with tool calls", () => { + const anthropicPayload: AnthropicMessagesPayload = { + model: "claude-3-5-sonnet-20241022", + messages: [ + { role: "user", content: "What's the weather?" }, + { + role: "assistant", + content: [ + { + type: "thinking", + thinking: + "I need to call the weather API to get current weather information.", + }, + { type: "text", text: "I'll check the weather for you." }, + { + type: "tool_use", + id: "call_123", + name: "get_weather", + input: { location: "New York" }, + }, + ], + }, + ], + max_tokens: 100, + } + const openAIPayload = translateToOpenAI(anthropicPayload) + expect(isValidChatCompletionRequest(openAIPayload)).toBe(true) + + // Check that thinking content is included in the message content + const assistantMessage = openAIPayload.messages.find( + (m) => m.role === "assistant", + ) + expect(assistantMessage?.content).toContain( + "I need to call the weather API", + ) + expect(assistantMessage?.content).toContain( + "I'll check the weather for you.", + ) + expect(assistantMessage?.tool_calls).toHaveLength(1) + expect(assistantMessage?.tool_calls?.[0].function.name).toBe("get_weather") + }) +}) + +describe("OpenAI Chat Completion v1 Request Payload Validation with Zod", () => { + test("should return true for a minimal valid request payload", () => { + const validPayload = { + model: "gpt-4o", + messages: [{ role: "user", content: "Hello!" }], + } + expect(isValidChatCompletionRequest(validPayload)).toBe(true) + }) + + test("should return true for a comprehensive valid request payload", () => { + const validPayload = { + model: "gpt-4o", + messages: [ + { role: "system", content: "You are a helpful assistant." }, + { role: "user", content: "What is the weather like in Boston?" }, + ], + temperature: 0.7, + max_tokens: 150, + top_p: 1, + frequency_penalty: 0, + presence_penalty: 0, + stream: false, + n: 1, + } + expect(isValidChatCompletionRequest(validPayload)).toBe(true) + }) + + test('should return false if the "model" field is missing', () => { + const invalidPayload = { + messages: [{ role: "user", content: "Hello!" }], + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test('should return false if the "messages" field is missing', () => { + const invalidPayload = { + model: "gpt-4o", + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test('should return false if the "messages" array is empty', () => { + const invalidPayload = { + model: "gpt-4o", + messages: [], + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test('should return false if "model" is not a string', () => { + const invalidPayload = { + model: 12345, + messages: [{ role: "user", content: "Hello!" }], + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test('should return false if "messages" is not an array', () => { + const invalidPayload = { + model: "gpt-4o", + messages: { role: "user", content: "Hello!" }, + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test('should return false if a message in the "messages" array is missing a "role"', () => { + const invalidPayload = { + model: "gpt-4o", + messages: [{ content: "Hello!" }], + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test('should return false if a message in the "messages" array is missing "content"', () => { + const invalidPayload = { + model: "gpt-4o", + messages: [{ role: "user" }], + } + // Note: Zod considers 'undefined' as missing, so this will fail as expected. + const result = chatCompletionRequestSchema.safeParse(invalidPayload) + expect(result.success).toBe(false) + }) + + test('should return false if a message has an invalid "role"', () => { + const invalidPayload = { + model: "gpt-4o", + messages: [{ role: "customer", content: "Hello!" }], + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test("should return false if an optional field has an incorrect type", () => { + const invalidPayload = { + model: "gpt-4o", + messages: [{ role: "user", content: "Hello!" }], + temperature: "hot", // Should be a number + } + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test("should return false for a completely empty object", () => { + const invalidPayload = {} + expect(isValidChatCompletionRequest(invalidPayload)).toBe(false) + }) + + test("should return false for null or non-object payloads", () => { + expect(isValidChatCompletionRequest(null)).toBe(false) + expect(isValidChatCompletionRequest(undefined)).toBe(false) + expect(isValidChatCompletionRequest("a string")).toBe(false) + expect(isValidChatCompletionRequest(123)).toBe(false) + }) +}) diff --git a/tests/anthropic-response.test.ts b/tests/anthropic-response.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..ecd71aacc083127066683e3dbd4bdcb887b283ca --- /dev/null +++ b/tests/anthropic-response.test.ts @@ -0,0 +1,365 @@ +import { describe, test, expect } from "bun:test" +import { z } from "zod" + +import type { + ChatCompletionChunk, + ChatCompletionResponse, +} from "~/services/copilot/create-chat-completions" + +import { type AnthropicStreamState } from "~/routes/messages/anthropic-types" +import { translateToAnthropic } from "~/routes/messages/non-stream-translation" +import { translateChunkToAnthropicEvents } from "~/routes/messages/stream-translation" + +const anthropicUsageSchema = z.object({ + input_tokens: z.number().int(), + output_tokens: z.number().int(), +}) + +const anthropicContentBlockTextSchema = z.object({ + type: z.literal("text"), + text: z.string(), +}) + +const anthropicContentBlockToolUseSchema = z.object({ + type: z.literal("tool_use"), + id: z.string(), + name: z.string(), + input: z.record(z.string(), z.any()), +}) + +const anthropicMessageResponseSchema = z.object({ + id: z.string(), + type: z.literal("message"), + role: z.literal("assistant"), + content: z.array( + z.union([ + anthropicContentBlockTextSchema, + anthropicContentBlockToolUseSchema, + ]), + ), + model: z.string(), + stop_reason: z.enum(["end_turn", "max_tokens", "stop_sequence", "tool_use"]), + stop_sequence: z.string().nullable(), + usage: anthropicUsageSchema, +}) + +/** + * Validates if a response payload conforms to the Anthropic Message shape. + * @param payload The response payload to validate. + * @returns True if the payload is valid, false otherwise. + */ +function isValidAnthropicResponse(payload: unknown): boolean { + return anthropicMessageResponseSchema.safeParse(payload).success +} + +const anthropicStreamEventSchema = z.looseObject({ + type: z.enum([ + "message_start", + "content_block_start", + "content_block_delta", + "content_block_stop", + "message_delta", + "message_stop", + ]), +}) + +function isValidAnthropicStreamEvent(payload: unknown): boolean { + return anthropicStreamEventSchema.safeParse(payload).success +} + +describe("OpenAI to Anthropic Non-Streaming Response Translation", () => { + test("should translate a simple text response correctly", () => { + const openAIResponse: ChatCompletionResponse = { + id: "chatcmpl-123", + object: "chat.completion", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: "Hello! How can I help you today?", + }, + finish_reason: "stop", + logprobs: null, + }, + ], + usage: { + prompt_tokens: 9, + completion_tokens: 12, + total_tokens: 21, + }, + } + + const anthropicResponse = translateToAnthropic(openAIResponse) + + expect(isValidAnthropicResponse(anthropicResponse)).toBe(true) + + expect(anthropicResponse.id).toBe("chatcmpl-123") + expect(anthropicResponse.stop_reason).toBe("end_turn") + expect(anthropicResponse.usage.input_tokens).toBe(9) + expect(anthropicResponse.content[0].type).toBe("text") + if (anthropicResponse.content[0].type === "text") { + expect(anthropicResponse.content[0].text).toBe( + "Hello! How can I help you today?", + ) + } else { + throw new Error("Expected text block") + } + }) + + test("should translate a response with tool calls", () => { + const openAIResponse: ChatCompletionResponse = { + id: "chatcmpl-456", + object: "chat.completion", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_abc", + type: "function", + function: { + name: "get_current_weather", + arguments: '{"location": "Boston, MA"}', + }, + }, + ], + }, + finish_reason: "tool_calls", + logprobs: null, + }, + ], + usage: { + prompt_tokens: 30, + completion_tokens: 20, + total_tokens: 50, + }, + } + + const anthropicResponse = translateToAnthropic(openAIResponse) + + expect(isValidAnthropicResponse(anthropicResponse)).toBe(true) + + expect(anthropicResponse.stop_reason).toBe("tool_use") + expect(anthropicResponse.content[0].type).toBe("tool_use") + if (anthropicResponse.content[0].type === "tool_use") { + expect(anthropicResponse.content[0].id).toBe("call_abc") + expect(anthropicResponse.content[0].name).toBe("get_current_weather") + expect(anthropicResponse.content[0].input).toEqual({ + location: "Boston, MA", + }) + } else { + throw new Error("Expected tool_use block") + } + }) + + test("should translate a response stopped due to length", () => { + const openAIResponse: ChatCompletionResponse = { + id: "chatcmpl-789", + object: "chat.completion", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + message: { + role: "assistant", + content: "This is a very long response that was cut off...", + }, + finish_reason: "length", + logprobs: null, + }, + ], + usage: { + prompt_tokens: 10, + completion_tokens: 2048, + total_tokens: 2058, + }, + } + + const anthropicResponse = translateToAnthropic(openAIResponse) + + expect(isValidAnthropicResponse(anthropicResponse)).toBe(true) + expect(anthropicResponse.stop_reason).toBe("max_tokens") + }) +}) + +describe("OpenAI to Anthropic Streaming Response Translation", () => { + test("should translate a simple text stream correctly", () => { + const openAIStream: Array = [ + { + id: "cmpl-1", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { role: "assistant" }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-1", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { content: "Hello" }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-1", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { content: " there" }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-1", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { index: 0, delta: {}, finish_reason: "stop", logprobs: null }, + ], + }, + ] + + const streamState: AnthropicStreamState = { + messageStartSent: false, + contentBlockIndex: 0, + contentBlockOpen: false, + toolCalls: {}, + } + const translatedStream = openAIStream.flatMap((chunk) => + translateChunkToAnthropicEvents(chunk, streamState), + ) + + for (const event of translatedStream) { + expect(isValidAnthropicStreamEvent(event)).toBe(true) + } + }) + + test("should translate a stream with tool calls", () => { + const openAIStream: Array = [ + { + id: "cmpl-2", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { role: "assistant" }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-2", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { + tool_calls: [ + { + index: 0, + id: "call_xyz", + type: "function", + function: { name: "get_weather", arguments: "" }, + }, + ], + }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-2", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { + tool_calls: [{ index: 0, function: { arguments: '{"loc' } }], + }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-2", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { + index: 0, + delta: { + tool_calls: [ + { index: 0, function: { arguments: 'ation": "Paris"}' } }, + ], + }, + finish_reason: null, + logprobs: null, + }, + ], + }, + { + id: "cmpl-2", + object: "chat.completion.chunk", + created: 1677652288, + model: "gpt-4o-2024-05-13", + choices: [ + { index: 0, delta: {}, finish_reason: "tool_calls", logprobs: null }, + ], + }, + ] + + // Streaming translation requires state + const streamState: AnthropicStreamState = { + messageStartSent: false, + contentBlockIndex: 0, + contentBlockOpen: false, + toolCalls: {}, + } + const translatedStream = openAIStream.flatMap((chunk) => + translateChunkToAnthropicEvents(chunk, streamState), + ) + + // These tests will fail until the stub is implemented + for (const event of translatedStream) { + expect(isValidAnthropicStreamEvent(event)).toBe(true) + } + }) +}) diff --git a/tests/create-chat-completions.test.ts b/tests/create-chat-completions.test.ts new file mode 100644 index 0000000000000000000000000000000000000000..d18e741aa6067a7263d22e55ce78effc76e92b60 --- /dev/null +++ b/tests/create-chat-completions.test.ts @@ -0,0 +1,56 @@ +import { test, expect, mock } from "bun:test" + +import type { ChatCompletionsPayload } from "../src/services/copilot/create-chat-completions" + +import { state } from "../src/lib/state" +import { createChatCompletions } from "../src/services/copilot/create-chat-completions" + +// Mock state +state.copilotToken = "test-token" +state.vsCodeVersion = "1.0.0" +state.accountType = "individual" + +// Helper to mock fetch +const fetchMock = mock( + (_url: string, opts: { headers: Record }) => { + return { + ok: true, + json: () => ({ id: "123", object: "chat.completion", choices: [] }), + headers: opts.headers, + } + }, +) +// @ts-expect-error - Mock fetch doesn't implement all fetch properties +;(globalThis as unknown as { fetch: typeof fetch }).fetch = fetchMock + +test("sets X-Initiator to agent if tool/assistant present", async () => { + const payload: ChatCompletionsPayload = { + messages: [ + { role: "user", content: "hi" }, + { role: "tool", content: "tool call" }, + ], + model: "gpt-test", + } + await createChatCompletions(payload) + expect(fetchMock).toHaveBeenCalled() + const headers = ( + fetchMock.mock.calls[0][1] as { headers: Record } + ).headers + expect(headers["X-Initiator"]).toBe("agent") +}) + +test("sets X-Initiator to user if only user present", async () => { + const payload: ChatCompletionsPayload = { + messages: [ + { role: "user", content: "hi" }, + { role: "user", content: "hello again" }, + ], + model: "gpt-test", + } + await createChatCompletions(payload) + expect(fetchMock).toHaveBeenCalled() + const headers = ( + fetchMock.mock.calls[1][1] as { headers: Record } + ).headers + expect(headers["X-Initiator"]).toBe("user") +}) diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..8ff821caf15daeaa8e3480f5db37d979aa3ca207 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext"], + "module": "ESNext", + "skipLibCheck": true, + + "allowJs": true, + "moduleResolution": "Bundler", + "moduleDetection": "force", + "erasableSyntaxOnly": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "baseUrl": ".", + "paths": { + "~/*": ["./src/*"] + } + } +} diff --git a/tsdown.config.ts b/tsdown.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..60ef1cdcc4ee19c95be6c1a7766e58137d33831e --- /dev/null +++ b/tsdown.config.ts @@ -0,0 +1,17 @@ +import { defineConfig } from "tsdown" + +export default defineConfig({ + entry: ["src/main.ts"], + + format: ["esm"], + target: "es2022", + platform: "node", + + sourcemap: true, + clean: true, + removeNodeProtocol: false, + + env: { + NODE_ENV: "production", + }, +})