diff --git a/README.md b/README.md index 0365d04..a9351e0 100644 --- a/README.md +++ b/README.md @@ -9,22 +9,18 @@ Opencode plugin for Windsurf/Codeium authentication - use Windsurf models in Ope ## Features -- OpenAI-compatible `/v1/chat/completions` interface with streaming SSE -- Automatic credential discovery (CSRF token, port, API key) -- Transparent REST↔gRPC translation over HTTP/2 -- Zero extra auth prompts when Windsurf is running -- Opencode tool-calling compatible: tools are planned via Windsurf inference but executed by Opencode (MCP/tool registry remains authoritative) - -## Overview - -This plugin enables Opencode users to access Windsurf/Codeium models by leveraging their existing Windsurf installation. It communicates directly with the **local Windsurf language server** via gRPC—no network traffic capture or OAuth flows required. +- **OpenAI-compatible** `/v1/chat/completions` interface with streaming SSE. +- **Automatic Discovery**: CSRF tokens, dynamic ports, and API keys are fetched directly from your running Windsurf process. +- **Node.js & Bun Support**: Runs in standard Node.js environments (like default OpenCode) or Bun. +- **Enterprise Ready**: Supports exclusive Enterprise models, regional deployments, and private model slots (Kimi, Minimax, etc.). +- **Dynamic Model Sync**: Automatically update your `opencode.json` with the models actually enabled for your account. +- **Transparent gRPC Translation**: Translates REST requests to Windsurf's internal gRPC protocol over HTTP/2. ## Prerequisites 1. **Windsurf IDE installed** - Download from [windsurf.com](https://windsurf.com) -2. **Windsurf running** - The plugin communicates with the local language server -3. **Logged into Windsurf** - Provides API key in `~/.codeium/config.json` -4. **Active Windsurf subscription** - Model access depends on your plan +2. **Windsurf running** - The plugin communicates with the local language server process. +3. **Logged into Windsurf** - Ensure you are signed in within the IDE. ## Installation @@ -32,13 +28,29 @@ This plugin enables Opencode users to access Windsurf/Codeium models by leveragi bun add opencode-windsurf-auth@beta ``` +## Model Synchronization + +Windsurf account permissions vary by tier (Free, Pro, Enterprise). To ensure you only see and use models enabled for your account, use the built-in sync tool: + +### Method 1: Via OpenCode (Recommended) +Run the login command and select **"Sync Models"**: +```bash +opencode login windsurf +``` + +### Method 2: Via CLI +Run the sync script directly from the plugin directory: +```bash +bun run sync-models +``` +This will scan your Windsurf configuration and update `~/.config/opencode/opencode.json` with the correct model IDs and human-readable labels. + ## Opencode Configuration -Add the following to your Opencode config (typically `~/.config/opencode/config.json`). The plugin starts a local proxy server on port 42100 (falls back to a random free port and updates `chat.params` automatically). The full model list with variants is in `opencode_config_example.json`; thinking vs non-thinking are separate models, while variants are only for performance tiers (low/high/xhigh/etc.). +Your `opencode.json` should point to the local proxy started by the plugin. Use the sync tool above to populate the `models` list automatically. ```json { - "$schema": "https://opencode.ai/config.json", "plugin": ["opencode-windsurf-auth@beta"], "provider": { "windsurf": { @@ -119,45 +131,22 @@ Keep Windsurf running and signed in—credentials are fetched live from the IDE ``` src/ -├── plugin.ts # Fetch interceptor that routes to Windsurf -├── constants.ts # gRPC service metadata -└── plugin/ - ├── auth.ts # Credential discovery - ├── grpc-client.ts # Streaming chat bridge - ├── models.ts # Model lookup tables - └── types.ts # Shared enums/types +├── plugin.ts # Proxy server & OpenCode hooks +├── plugin/ + ├── auth.ts # Process-based credential discovery (CSRF/Port) + ├── grpc-client.ts # Protobuf/gRPC communication logic + ├── models.ts # Internal ID to Enum mapping + └── types.ts # Protobuf Enums including PRIVATE slots +scripts/ +└── sync-models.ts # Data-driven model discovery script ``` -### How It Works - -1. **Credential Discovery**: Extracts CSRF token and port from the running `language_server_macos` process -2. **API Key**: Reads from `~/.codeium/config.json` -3. **gRPC Communication**: Sends requests to `localhost:{port}` using HTTP/2 gRPC protocol -4. **Response Transformation**: Converts gRPC responses to OpenAI-compatible SSE format (assistant/tool turns are not replayed back to Windsurf) -5. **Model Naming**: Sends both model enum and `chat_model_name` for fidelity with Windsurf’s expectations -6. **Tool Planning**: When `tools` are provided, we build a tool-calling prompt (with system messages) and ask Windsurf to produce `tool_calls`/final text. Tool execution and MCP tool registry stay on OpenCode’s side. - -### Supported Models (canonical names) - -**Claude**: `claude-3-opus`, `claude-3-sonnet`, `claude-3-haiku`, `claude-3.5-sonnet`, `claude-3.5-haiku`, `claude-3.7-sonnet`, `claude-3.7-sonnet-thinking`, `claude-4-opus`, `claude-4-opus-thinking`, `claude-4-sonnet`, `claude-4-sonnet-thinking`, `claude-4.1-opus`, `claude-4.1-opus-thinking`, `claude-4.5-sonnet`, `claude-4.5-sonnet-thinking`, `claude-4.5-opus`, `claude-4.5-opus-thinking`, `claude-code`. - -**OpenAI GPT**: `gpt-4`, `gpt-4-turbo`, `gpt-4o`, `gpt-4o-mini`, `gpt-4.1`, `gpt-4.1-mini`, `gpt-4.1-nano`, `gpt-5`, `gpt-5-nano`, `gpt-5-codex`, `gpt-5.1-codex-mini`, `gpt-5.1-codex`, `gpt-5.1-codex-max`, `gpt-5.2` (variants low/medium/high/xhigh + priority tiers). Non-thinking vs thinking are separate model IDs, not variants. - -**OpenAI O-series**: `o3`, `o3-mini`, `o3-low`, `o3-high`, `o3-pro`, `o3-pro-low`, `o3-pro-high`, `o4-mini`, `o4-mini-low`, `o4-mini-high`. - -**Gemini**: `gemini-2.0-flash`, `gemini-2.5-pro`, `gemini-2.5-flash`, `gemini-2.5-flash-thinking`, `gemini-2.5-flash-lite`, `gemini-3.0-pro` (variants: `minimal`, `low`, `medium`, `high`), `gemini-3.0-flash` (variants: `minimal`, `low`, `medium`, `high`). Thinking versions of Gemini 2.5 are separate models. - -**DeepSeek**: `deepseek-v3`, `deepseek-v3-2`, `deepseek-r1`, `deepseek-r1-fast`, `deepseek-r1-slow`. - -**Llama**: `llama-3.1-8b`, `llama-3.1-70b`, `llama-3.1-405b`, `llama-3.3-70b`, `llama-3.3-70b-r1`. - -**Qwen**: `qwen-2.5-7b`, `qwen-2.5-32b`, `qwen-2.5-72b`, `qwen-2.5-32b-r1`, `qwen-3-235b`, `qwen-3-coder-480b`, `qwen-3-coder-480b-fast`. - -**Grok (xAI)**: `grok-2`, `grok-3`, `grok-3-mini`, `grok-code-fast`. - -**Specialty & Proprietary**: `mistral-7b`, `kimi-k2`, `kimi-k2-thinking`, `glm-4.5`, `glm-4.5-fast`, `glm-4.6`, `glm-4.6-fast`, `glm-4.7`, `glm-4.7-fast`, `minimax-m2`, `minimax-m2.1`, `swe-1.5`, `swe-1.5-thinking`, `swe-1.5-slow`. +## How It Works -Aliases (e.g., `gpt-5.2-low-priority`) are also accepted. Variants live under `provider.windsurf.models[model].variants`; thinking/non-thinking are distinct models. +1. **Discovery**: The plugin finds the Windsurf process and extracts the CSRF token from its environment variables (`WINDSURF_CSRF_TOKEN`) and the dynamic port from its listener list. +2. **Proxy**: A local server (Node or Bun) starts to handle incoming OpenCode requests. +3. **Translation**: REST requests are converted to the internal gRPC format, including metadata like your API key and IDE version. +4. **Streaming**: Responses are streamed back in real-time using standard SSE. ## Development @@ -165,14 +154,14 @@ Aliases (e.g., `gpt-5.2-low-priority`) are also accepted. Variants live under `p # Install dependencies bun install -# Build +# Build (TypeScript to JS) bun run build -# Type check -bun run typecheck +# Synchronize models for testing +bun run sync-models -# Run tests -bun test +# Run live verification +bun run tests/live/verify-plugin.ts ``` ## Known Limitations diff --git a/bun.lock b/bun.lock index 7f726e9..c87a0fe 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "opencode-windsurf-auth", @@ -8,6 +9,7 @@ "@connectrpc/connect": "^2.0.0", "@connectrpc/connect-web": "^2.0.0", "proper-lockfile": "^4.1.2", + "sqlite3": "^5.1.7", "xdg-basedir": "^5.1.0", "zod": "^4.3.5", }, @@ -16,6 +18,7 @@ "@types/bun": "^1.3.6", "@types/node": "^25.0.0", "@types/proper-lockfile": "^4.1.4", + "@types/sqlite3": "^5.0.0", "typescript": "^5.7.0", }, "peerDependencies": { @@ -30,10 +33,18 @@ "@connectrpc/connect-web": ["@connectrpc/connect-web@2.1.1", "", { "peerDependencies": { "@bufbuild/protobuf": "^2.7.0", "@connectrpc/connect": "2.1.1" } }, "sha512-J8317Q2MaFRCT1jzVR1o06bZhDIBmU0UAzWx6xOIXzOq8+k71/+k7MUF7AwcBUX+34WIvbm5syRgC5HXQA8fOg=="], + "@gar/promisify": ["@gar/promisify@1.1.3", "", {}, "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw=="], + + "@npmcli/fs": ["@npmcli/fs@1.1.1", "", { "dependencies": { "@gar/promisify": "^1.0.1", "semver": "^7.3.5" } }, "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ=="], + + "@npmcli/move-file": ["@npmcli/move-file@1.1.2", "", { "dependencies": { "mkdirp": "^1.0.4", "rimraf": "^3.0.2" } }, "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg=="], + "@opencode-ai/plugin": ["@opencode-ai/plugin@1.1.21", "", { "dependencies": { "@opencode-ai/sdk": "1.1.21", "zod": "4.1.8" } }, "sha512-oAWVlKG7LACGFYawfdHGMN6e+6lyN6F+zPVncFUB99BrTl/TjELE5gTZwU7MalGpjwfU77yslBOZm4BXVAYGvw=="], "@opencode-ai/sdk": ["@opencode-ai/sdk@1.1.21", "", {}, "sha512-4M6lBjRPlPz99Rb5rS5ZqKrb0UDDxOT9VTG06JpNxvA7ynTd8C50ckc2NGzWtvjarmxfaAk1VeuBYN/cq2pIKQ=="], + "@tootallnate/once": ["@tootallnate/once@1.1.2", "", {}, "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw=="], + "@types/bun": ["@types/bun@1.3.6", "", { "dependencies": { "bun-types": "1.3.6" } }, "sha512-uWCv6FO/8LcpREhenN1d1b6fcspAB+cefwD7uti8C8VffIv0Um08TKMn98FynpTiU38+y2dUO55T11NgDt8VAA=="], "@types/node": ["@types/node@25.0.8", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg=="], @@ -42,28 +53,276 @@ "@types/retry": ["@types/retry@0.12.5", "", {}, "sha512-3xSjTp3v03X/lSQLkczaN9UIEwJMoMCA1+Nb5HfbJEQWogdeQIyVtTvxPXDQjZ5zws8rFQfVfRdz03ARihPJgw=="], + "@types/sqlite3": ["@types/sqlite3@5.1.0", "", { "dependencies": { "sqlite3": "*" } }, "sha512-w25Gd6OzcN0Sb6g/BO7cyee0ugkiLgonhgGYfG+H0W9Ub6PUsC2/4R+KXy2tc80faPIWO3Qytbvr8gP1fU4siA=="], + + "abbrev": ["abbrev@1.1.1", "", {}, "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="], + + "agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], + + "agentkeepalive": ["agentkeepalive@4.6.0", "", { "dependencies": { "humanize-ms": "^1.2.1" } }, "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ=="], + + "aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "aproba": ["aproba@2.1.0", "", {}, "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew=="], + + "are-we-there-yet": ["are-we-there-yet@3.0.1", "", { "dependencies": { "delegates": "^1.0.0", "readable-stream": "^3.6.0" } }, "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="], + + "bindings": ["bindings@1.5.0", "", { "dependencies": { "file-uri-to-path": "1.0.0" } }, "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ=="], + + "bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + + "brace-expansion": ["brace-expansion@1.1.13", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w=="], + + "buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], + "bun-types": ["bun-types@1.3.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-OlFwHcnNV99r//9v5IIOgQ9Uk37gZqrNMCcqEaExdkVq3Avwqok1bJFmvGMCkCE0FqzdY8VMOZpfpR3lwI+CsQ=="], + "cacache": ["cacache@15.3.0", "", { "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "glob": "^7.1.4", "infer-owner": "^1.0.4", "lru-cache": "^6.0.0", "minipass": "^3.1.1", "minipass-collect": "^1.0.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.2", "mkdirp": "^1.0.3", "p-map": "^4.0.0", "promise-inflight": "^1.0.1", "rimraf": "^3.0.2", "ssri": "^8.0.1", "tar": "^6.0.2", "unique-filename": "^1.1.1" } }, "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ=="], + + "chownr": ["chownr@2.0.0", "", {}, "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="], + + "clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="], + + "color-support": ["color-support@1.1.3", "", { "bin": { "color-support": "bin.js" } }, "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg=="], + + "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="], + + "console-control-strings": ["console-control-strings@1.1.0", "", {}, "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decompress-response": ["decompress-response@6.0.0", "", { "dependencies": { "mimic-response": "^3.1.0" } }, "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ=="], + + "deep-extend": ["deep-extend@0.6.0", "", {}, "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="], + + "delegates": ["delegates@1.0.0", "", {}, "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "encoding": ["encoding@0.1.13", "", { "dependencies": { "iconv-lite": "^0.6.2" } }, "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A=="], + + "end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="], + + "env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="], + + "err-code": ["err-code@2.0.3", "", {}, "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA=="], + + "expand-template": ["expand-template@2.0.3", "", {}, "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="], + + "file-uri-to-path": ["file-uri-to-path@1.0.0", "", {}, "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="], + + "fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="], + + "fs-minipass": ["fs-minipass@2.1.0", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg=="], + + "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="], + + "gauge": ["gauge@4.0.4", "", { "dependencies": { "aproba": "^1.0.3 || ^2.0.0", "color-support": "^1.1.3", "console-control-strings": "^1.1.0", "has-unicode": "^2.0.1", "signal-exit": "^3.0.7", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "wide-align": "^1.1.5" } }, "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg=="], + + "github-from-package": ["github-from-package@0.0.0", "", {}, "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw=="], + + "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + "has-unicode": ["has-unicode@2.0.1", "", {}, "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="], + + "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], + + "http-proxy-agent": ["http-proxy-agent@4.0.1", "", { "dependencies": { "@tootallnate/once": "1", "agent-base": "6", "debug": "4" } }, "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg=="], + + "https-proxy-agent": ["https-proxy-agent@5.0.1", "", { "dependencies": { "agent-base": "6", "debug": "4" } }, "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA=="], + + "humanize-ms": ["humanize-ms@1.2.1", "", { "dependencies": { "ms": "^2.0.0" } }, "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ=="], + + "iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="], + + "ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="], + + "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="], + + "indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="], + + "infer-owner": ["infer-owner@1.0.4", "", {}, "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A=="], + + "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="], + + "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + + "ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="], + + "ip-address": ["ip-address@10.1.0", "", {}, "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-lambda": ["is-lambda@1.0.1", "", {}, "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ=="], + + "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + + "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + + "make-fetch-happen": ["make-fetch-happen@9.1.0", "", { "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", "http-cache-semantics": "^4.1.0", "http-proxy-agent": "^4.0.1", "https-proxy-agent": "^5.0.0", "is-lambda": "^1.0.1", "lru-cache": "^6.0.0", "minipass": "^3.1.3", "minipass-collect": "^1.0.2", "minipass-fetch": "^1.3.2", "minipass-flush": "^1.0.5", "minipass-pipeline": "^1.2.4", "negotiator": "^0.6.2", "promise-retry": "^2.0.1", "socks-proxy-agent": "^6.0.0", "ssri": "^8.0.0" } }, "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg=="], + + "mimic-response": ["mimic-response@3.1.0", "", {}, "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="], + + "minimatch": ["minimatch@3.1.5", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "minipass": ["minipass@5.0.0", "", {}, "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ=="], + + "minipass-collect": ["minipass-collect@1.0.2", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA=="], + + "minipass-fetch": ["minipass-fetch@1.4.1", "", { "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", "minizlib": "^2.0.0" }, "optionalDependencies": { "encoding": "^0.1.12" } }, "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw=="], + + "minipass-flush": ["minipass-flush@1.0.7", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA=="], + + "minipass-pipeline": ["minipass-pipeline@1.2.4", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A=="], + + "minipass-sized": ["minipass-sized@1.0.3", "", { "dependencies": { "minipass": "^3.0.0" } }, "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g=="], + + "minizlib": ["minizlib@2.1.2", "", { "dependencies": { "minipass": "^3.0.0", "yallist": "^4.0.0" } }, "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg=="], + + "mkdirp": ["mkdirp@1.0.4", "", { "bin": { "mkdirp": "bin/cmd.js" } }, "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="], + + "mkdirp-classic": ["mkdirp-classic@0.5.3", "", {}, "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "napi-build-utils": ["napi-build-utils@2.0.0", "", {}, "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA=="], + + "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], + + "node-abi": ["node-abi@3.89.0", "", { "dependencies": { "semver": "^7.3.5" } }, "sha512-6u9UwL0HlAl21+agMN3YAMXcKByMqwGx+pq+P76vii5f7hTPtKDp08/H9py6DY+cfDw7kQNTGEj/rly3IgbNQA=="], + + "node-addon-api": ["node-addon-api@7.1.1", "", {}, "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="], + + "node-gyp": ["node-gyp@8.4.1", "", { "dependencies": { "env-paths": "^2.2.0", "glob": "^7.1.4", "graceful-fs": "^4.2.6", "make-fetch-happen": "^9.1.0", "nopt": "^5.0.0", "npmlog": "^6.0.0", "rimraf": "^3.0.2", "semver": "^7.3.5", "tar": "^6.1.2", "which": "^2.0.2" }, "bin": { "node-gyp": "bin/node-gyp.js" } }, "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w=="], + + "nopt": ["nopt@5.0.0", "", { "dependencies": { "abbrev": "1" }, "bin": { "nopt": "bin/nopt.js" } }, "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ=="], + + "npmlog": ["npmlog@6.0.2", "", { "dependencies": { "are-we-there-yet": "^3.0.0", "console-control-strings": "^1.1.0", "gauge": "^4.0.3", "set-blocking": "^2.0.0" } }, "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg=="], + + "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="], + + "p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="], + + "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="], + + "prebuild-install": ["prebuild-install@7.1.3", "", { "dependencies": { "detect-libc": "^2.0.0", "expand-template": "^2.0.3", "github-from-package": "0.0.0", "minimist": "^1.2.3", "mkdirp-classic": "^0.5.3", "napi-build-utils": "^2.0.0", "node-abi": "^3.3.0", "pump": "^3.0.0", "rc": "^1.2.7", "simple-get": "^4.0.0", "tar-fs": "^2.0.0", "tunnel-agent": "^0.6.0" }, "bin": { "prebuild-install": "bin.js" } }, "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug=="], + + "promise-inflight": ["promise-inflight@1.0.1", "", {}, "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g=="], + + "promise-retry": ["promise-retry@2.0.1", "", { "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" } }, "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g=="], + "proper-lockfile": ["proper-lockfile@4.1.2", "", { "dependencies": { "graceful-fs": "^4.2.4", "retry": "^0.12.0", "signal-exit": "^3.0.2" } }, "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA=="], + "pump": ["pump@3.0.4", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA=="], + + "rc": ["rc@1.2.8", "", { "dependencies": { "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" }, "bin": { "rc": "./cli.js" } }, "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw=="], + + "readable-stream": ["readable-stream@3.6.2", "", { "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", "util-deprecate": "^1.0.1" } }, "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA=="], + "retry": ["retry@0.12.0", "", {}, "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow=="], + "rimraf": ["rimraf@3.0.2", "", { "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" } }, "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA=="], + + "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="], + + "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + "signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="], + "simple-concat": ["simple-concat@1.0.1", "", {}, "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q=="], + + "simple-get": ["simple-get@4.0.1", "", { "dependencies": { "decompress-response": "^6.0.0", "once": "^1.3.1", "simple-concat": "^1.0.0" } }, "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA=="], + + "smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="], + + "socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="], + + "socks-proxy-agent": ["socks-proxy-agent@6.2.1", "", { "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.3", "socks": "^2.6.2" } }, "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ=="], + + "sqlite3": ["sqlite3@5.1.7", "", { "dependencies": { "bindings": "^1.5.0", "node-addon-api": "^7.0.0", "prebuild-install": "^7.1.1", "tar": "^6.1.11" }, "optionalDependencies": { "node-gyp": "8.x" } }, "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog=="], + + "ssri": ["ssri@8.0.1", "", { "dependencies": { "minipass": "^3.1.1" } }, "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "strip-json-comments": ["strip-json-comments@2.0.1", "", {}, "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ=="], + + "tar": ["tar@6.2.1", "", { "dependencies": { "chownr": "^2.0.0", "fs-minipass": "^2.0.0", "minipass": "^5.0.0", "minizlib": "^2.1.1", "mkdirp": "^1.0.3", "yallist": "^4.0.0" } }, "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A=="], + + "tar-fs": ["tar-fs@2.1.4", "", { "dependencies": { "chownr": "^1.1.1", "mkdirp-classic": "^0.5.2", "pump": "^3.0.0", "tar-stream": "^2.1.4" } }, "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ=="], + + "tar-stream": ["tar-stream@2.2.0", "", { "dependencies": { "bl": "^4.0.3", "end-of-stream": "^1.4.1", "fs-constants": "^1.0.0", "inherits": "^2.0.3", "readable-stream": "^3.1.1" } }, "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ=="], + + "tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="], + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], "undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="], + "unique-filename": ["unique-filename@1.1.1", "", { "dependencies": { "unique-slug": "^2.0.0" } }, "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ=="], + + "unique-slug": ["unique-slug@2.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4" } }, "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w=="], + + "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], + + "wide-align": ["wide-align@1.1.5", "", { "dependencies": { "string-width": "^1.0.2 || 2 || 3 || 4" } }, "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg=="], + + "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="], + "xdg-basedir": ["xdg-basedir@5.1.0", "", {}, "sha512-GCPAHLvrIH13+c0SuacwvRYj2SxJXQ4kaVTT5xgL3kPrz56XxkF21IGhjSE1+W0aw7gpBWRGXLCPnPby6lSpmQ=="], + "yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "zod": ["zod@4.3.5", "", {}, "sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g=="], "@opencode-ai/plugin/zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="], "bun-types/@types/node": ["@types/node@22.19.6", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-qm+G8HuG6hOHQigsi7VGuLjUVu6TtBo/F05zvX04Mw2uCg9Dv0Qxy3Qw7j41SidlTcl5D/5yg0SEZqOB+EqZnQ=="], + "cacache/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "fs-minipass/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "make-fetch-happen/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-collect/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-fetch/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-flush/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-pipeline/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minipass-sized/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "minizlib/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "ssri/minipass": ["minipass@3.3.6", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw=="], + + "tar-fs/chownr": ["chownr@1.1.4", "", {}, "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="], + "bun-types/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], } } diff --git a/package.json b/package.json index e3533df..2a3e8b6 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "test:unit": "bun test tests/unit", "test:request": "bun run tests/live/request.ts", "test:analyze": "bun run tests/live/analyze.ts", + "sync-models": "bun run scripts/sync-models.ts", "clean": "rm -rf dist" }, "keywords": [ @@ -40,6 +41,7 @@ "@connectrpc/connect-web": "^2.0.0", "@bufbuild/protobuf": "^2.0.0", "proper-lockfile": "^4.1.2", + "sqlite3": "^5.1.7", "xdg-basedir": "^5.1.0", "zod": "^4.3.5" }, @@ -48,6 +50,7 @@ "@types/bun": "^1.3.6", "@types/node": "^25.0.0", "@types/proper-lockfile": "^4.1.4", + "@types/sqlite3": "^5.0.0", "typescript": "^5.7.0" }, "peerDependencies": { diff --git a/scripts/sync-models.ts b/scripts/sync-models.ts new file mode 100644 index 0000000..3f76a4d --- /dev/null +++ b/scripts/sync-models.ts @@ -0,0 +1,98 @@ + +import { execSync } from 'child_process'; +import fs from 'fs'; +import path from 'path'; + +const DB_PATH = path.join(process.env.HOME || '', 'Library/Application Support/Windsurf/User/globalStorage/state.vscdb'); +const CONFIG_PATH = path.join(process.env.HOME || '', '.config/opencode/opencode.json'); + +/** + * Truly dynamic discovery that carves (Label, ID) pairs from the binary database. + * No hardcoded model names allowed. + */ +async function main() { + console.log("Starting 100% Dynamic Model Discovery..."); + + try { + if (!fs.existsSync(DB_PATH)) { + console.error("Windsurf database not found."); + return; + } + + // Extract the raw binary blob from the SQLite database + const configRaw = execSync(`sqlite3 "${DB_PATH}" "SELECT value FROM ItemTable WHERE key = 'windsurfConfigurations'"`).toString('utf8'); + if (!configRaw) { + console.error("windsurfConfigurations not found."); + return; + } + + const data = Buffer.from(configRaw, 'base64'); + const content = data.toString('latin1'); + const discovered: Record = {}; + + // Windsurf Protobuf Pattern for ClientModelConfig: + // Field 1 (Tag 0x0A): Label String + // ... metadata ... + // Field 22 (Tag 0xB2 0x01): Model UID String + + // We look for everything that looks like an internal ID first (Field 22) + // and then find the Label (Field 1) that immediately preceded it. + const idMatches = [...content.matchAll(/\xb2\x01([\x01-\x7f])([a-z0-9\-]{3,})/g)]; + + for (const match of idMatches) { + const modelId = match[2]; + const pos = match.index || 0; + + // Look back for the Label (starts with Tag 0x0A) + const lookback = content.slice(Math.max(0, pos - 250), pos); + + // Find strings that look like Labels: [Tag 0x0A][Length][Uppercase String] + const labelMatches = [...lookback.matchAll(/\x0a([\x01-\x7f])([A-Z][a-zA-Z0-9. ]{2,})/g)]; + + if (labelMatches.length > 0) { + // The label closest to the ID is usually the correct one + const label = labelMatches[labelMatches.length - 1][2].trim(); + + // Filter out generic IDE metadata + const metadata = ['No Thinking', 'Thinking', 'Fast Mode', 'Prompt Cache Retention', 'Reasoning Effort', 'Enterprise', 'Windsurf', 'Codeium', 'Cascade']; + if (metadata.includes(label)) continue; + + const cleanId = modelId.toLowerCase().replace(/_/g, '-'); + discovered[cleanId] = { + name: `${label} (Windsurf)`, + limit: { context: 200000, output: 8192 } + }; + } + } + + // Always ensure base standard models are present as fallbacks + const standard = ["gpt-4o", "claude-3-5-sonnet", "claude-3-7-sonnet"]; + for (const id of standard) { + if (!discovered[id]) { + discovered[id] = { + name: id.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()) + " (Windsurf)", + limit: { context: 200000, output: 8192 } + }; + } + } + + if (!fs.existsSync(CONFIG_PATH)) { + console.error(`OpenCode config not found at ${CONFIG_PATH}`); + return; + } + + const opencodeConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf8')); + opencodeConfig.provider.windsurf.models = discovered; + fs.writeFileSync(CONFIG_PATH, JSON.stringify(opencodeConfig, null, 2)); + + console.log(`\nSuccess! Dynamically synchronized ${Object.keys(discovered).length} models.`); + Object.entries(discovered).forEach(([id, meta]: [string, any]) => { + console.log(` - ${id.padEnd(35)} -> ${meta.name}`); + }); + + } catch (error) { + console.error("Discovery failed:", error); + } +} + +main(); diff --git a/src/plugin.ts b/src/plugin.ts index 8af1d3b..4e5db17 100644 --- a/src/plugin.ts +++ b/src/plugin.ts @@ -15,6 +15,7 @@ */ import * as crypto from 'crypto'; +import * as http from 'http'; import type { PluginInput, Hooks } from '@opencode-ai/plugin'; import { getCredentials, isWindsurfRunning, WindsurfCredentials } from './plugin/auth.js'; import { streamChatGenerator, ChatMessage } from './plugin/grpc-client.js'; @@ -732,7 +733,7 @@ async function ensureWindsurfProxyServer(): Promise { const bunAny = globalThis as any; if (typeof bunAny.Bun !== 'undefined' && typeof bunAny.Bun.serve === 'function') { - // Check if server already running on default port + // Bun runtime logic (original) try { const res = await fetch(`http://${WINDSURF_PROXY_HOST}:${WINDSURF_PROXY_DEFAULT_PORT}/health`).catch(() => null); if (res && res.ok) { @@ -740,17 +741,14 @@ async function ensureWindsurfProxyServer(): Promise { g[key].baseURL = baseURL; return baseURL; } - } catch { - // ignore - } + } catch { /* ignore */ } const startServer = (port: number) => { return bunAny.Bun.serve({ hostname: WINDSURF_PROXY_HOST, port, fetch: handler, - // Keep connections alive longer to allow slow/long chat streams - idleTimeout: 100, // seconds + idleTimeout: 100, }); }; @@ -760,24 +758,13 @@ async function ensureWindsurfProxyServer(): Promise { g[key].baseURL = baseURL; return baseURL; } catch (error) { - const code = (error as any)?.code; - if (code !== 'EADDRINUSE') { - throw error; - } - - // Port in use - check if it's our server - try { - const res = await fetch(`http://${WINDSURF_PROXY_HOST}:${WINDSURF_PROXY_DEFAULT_PORT}/health`).catch(() => null); - if (res && res.ok) { - const baseURL = `http://${WINDSURF_PROXY_HOST}:${WINDSURF_PROXY_DEFAULT_PORT}/v1`; - g[key].baseURL = baseURL; - return baseURL; - } - } catch { - // ignore + if ((error as any)?.code !== 'EADDRINUSE') throw error; + const res = await fetch(`http://${WINDSURF_PROXY_HOST}:${WINDSURF_PROXY_DEFAULT_PORT}/health`).catch(() => null); + if (res && res.ok) { + const baseURL = `http://${WINDSURF_PROXY_HOST}:${WINDSURF_PROXY_DEFAULT_PORT}/v1`; + g[key].baseURL = baseURL; + return baseURL; } - - // Fallback to random port const server = startServer(0); const baseURL = `http://${WINDSURF_PROXY_HOST}:${server.port}/v1`; g[key].baseURL = baseURL; @@ -785,7 +772,83 @@ async function ensureWindsurfProxyServer(): Promise { } } - throw new Error('Windsurf proxy server requires Bun runtime'); + // Node.js fallback + const startNodeServer = (port: number): Promise<{ port: number }> => { + return new Promise((resolve, reject) => { + const server = http.createServer(async (nodeReq, nodeRes) => { + try { + // Construct Fetch-like Request from Node request + const protocol = (nodeReq.socket as any).encrypted ? 'https' : 'http'; + const host = nodeReq.headers.host || `${WINDSURF_PROXY_HOST}:${port}`; + const url = new URL(nodeReq.url || '/', `${protocol}://${host}`); + + const chunks: Buffer[] = []; + for await (const chunk of nodeReq) { + chunks.push(chunk); + } + const body = chunks.length > 0 ? Buffer.concat(chunks) : undefined; + + const request = new Request(url.toString(), { + method: nodeReq.method, + headers: nodeReq.headers as any, + body: body, + }); + + const response = await handler(request); + + // Stream Response back to Node response + nodeRes.statusCode = response.status; + response.headers.forEach((value, key) => { + nodeRes.setHeader(key, value); + }); + + if (response.body) { + const reader = response.body.getReader(); + while (true) { + const { done, value } = await reader.read(); + if (done) break; + nodeRes.write(value); + } + } + nodeRes.end(); + } catch (err) { + console.error('[Windsurf Proxy] Request handler error:', err); + nodeRes.statusCode = 500; + nodeRes.end(JSON.stringify({ error: 'Internal Server Error', message: String(err) })); + } + }); + + server.on('error', (err: any) => { + if (port !== 0 && err.code === 'EADDRINUSE') { + reject(err); + } else { + console.error('[Windsurf Proxy] Server error:', err); + reject(err); + } + }); + + server.listen(port, WINDSURF_PROXY_HOST, () => { + const address = server.address(); + const actualPort = typeof address === 'string' ? port : (address?.port ?? port); + resolve({ port: actualPort }); + }); + }); + }; + + try { + const { port } = await startNodeServer(WINDSURF_PROXY_DEFAULT_PORT); + const baseURL = `http://${WINDSURF_PROXY_HOST}:${port}/v1`; + g[key].baseURL = baseURL; + return baseURL; + } catch (error) { + if ((error as any)?.code === 'EADDRINUSE') { + const { port } = await startNodeServer(0); + const baseURL = `http://${WINDSURF_PROXY_HOST}:${port}/v1`; + g[key].baseURL = baseURL; + return baseURL; + } + throw error; + } } // ============================================================================ @@ -811,7 +874,33 @@ export const createWindsurfPlugin = }, // No auth methods needed - we use Windsurf's existing auth - methods: [], + methods: [ + { + type: 'api', + label: 'Sync Models', + async authorize() { + console.log('Synchronizing Windsurf models...'); + try { + const { execSync } = await import('child_process'); + const path = await import('path'); + const { fileURLToPath } = await import('url'); + + // Find the root of the plugin package + const currentFile = fileURLToPath(import.meta.url); + const pluginRoot = path.join(path.dirname(currentFile), '..', '..'); + const scriptPath = path.join(pluginRoot, 'scripts', 'sync-models.ts'); + + console.log(`Executing sync script: ${scriptPath}`); + // Run bun install first to ensure sqlite3 exists in the cache folder, then run script + execSync(`cd ${pluginRoot} && bun install --silent && bun ${scriptPath}`, { stdio: 'inherit' }); + return { type: 'success', key: 'synced' }; + } catch (err) { + console.error('Failed to sync models:', err); + return { type: 'failed' }; + } + }, + }, + ], }, // Dynamic baseURL injection (key pattern from cursor-auth) diff --git a/src/plugin/auth.ts b/src/plugin/auth.ts index 04efc8c..8e15c9c 100644 --- a/src/plugin/auth.ts +++ b/src/plugin/auth.ts @@ -97,11 +97,11 @@ function getLanguageServerProcess(): string | null { ); return output; } else { - // Unix-like: use ps - const output = execSync( - `ps aux | grep ${pattern}`, - { encoding: 'utf8', timeout: 5000 } - ); + // Unix-like: use ps with eww to see environment on macOS + const cmd = process.platform === 'darwin' + ? `ps auxeww | grep ${pattern} | grep -v grep` + : `ps aux | grep ${pattern} | grep -v grep`; + const output = execSync(cmd, { encoding: 'utf8', timeout: 5000 }); return output; } } catch { @@ -122,9 +122,37 @@ export function getCSRFToken(): string { ); } - const match = processInfo.match(/--csrf_token\s+([a-f0-9-]+)/); - if (match?.[1]) { - return match[1]; + // 1. Try process arguments + const argMatch = processInfo.match(/--csrf_token\s+([a-f0-9-]+)/); + if (argMatch?.[1]) { + return argMatch[1]; + } + + // 2. Try environment variables (macOS/Linux) + if (process.platform !== 'win32') { + const envMatch = processInfo.match(/WINDSURF_CSRF_TOKEN=([a-f0-9-]+)/); + if (envMatch?.[1]) { + return envMatch[1]; + } + + // 3. Linux fallback: check /proc if ps aux didn't show it + if (process.platform === 'linux') { + try { + const pidMatch = processInfo.match(/^\s*\S+\s+(\d+)/); + if (pidMatch) { + const pid = pidMatch[1]; + const environ = fs.readFileSync(`/proc/${pid}/environ`, 'utf8'); + const lines = environ.split('\0'); + for (const line of lines) { + if (line.startsWith('WINDSURF_CSRF_TOKEN=')) { + return line.split('=')[1]; + } + } + } + } catch { + // Fall through + } + } } throw new WindsurfError( diff --git a/src/plugin/grpc-client.ts b/src/plugin/grpc-client.ts index 6a1b8be..050a8c9 100644 --- a/src/plugin/grpc-client.ts +++ b/src/plugin/grpc-client.ts @@ -655,17 +655,123 @@ export async function* streamChatGenerator( // Yield chunks as they arrive while (!done || chunkQueue.length > 0) { - if (chunkQueue.length > 0) { - yield chunkQueue.shift()!; - } else if (!done) { - await new Promise((resolve) => { - resolveWait = resolve; - }); - resolveWait = null; - } + if (chunkQueue.length > 0) { + yield chunkQueue.shift()!; + } else if (!done) { + await new Promise((resolve) => { + resolveWait = resolve; + }); + resolveWait = null; + } } if (error) { - throw error; + throw error; } -} + } + + /** + * Discovered model configuration from Windsurf + */ + export interface DiscoveredModel { + id: string; + name: string; + enumValue: number; + } + + /** + * Fetch available model configurations from Windsurf language server + */ + export async function getModels(credentials: WindsurfCredentials): Promise { + const { csrfToken, port, apiKey, version } = credentials; + + // GetModelStatusesRequest has Metadata in Field 1 + const metadata = encodeMetadata(apiKey, version); + const requestPayload = encodeMessage(1, metadata); + + // gRPC framing: 1 byte compression (0) + 4 bytes length + payload + const body = Buffer.alloc(5 + requestPayload.length); + body[0] = 0; + body.writeUInt32BE(requestPayload.length, 1); + Buffer.from(requestPayload).copy(body, 5); + + return new Promise((resolve, reject) => { + const client = http2.connect(`http://localhost:${port}`); + const chunks: Buffer[] = []; + + client.on('error', reject); + + const req = client.request({ + ':method': 'POST', + ':path': '/exa.language_server_pb.LanguageServerService/GetModelStatuses', + 'content-type': 'application/grpc', + 'te': 'trailers', + 'x-codeium-csrf-token': csrfToken, + }); + + req.on('data', (chunk: Buffer) => { + chunks.push(chunk); + }); + + req.on('end', () => { + client.close(); + const fullBuffer = Buffer.concat(chunks); + if (fullBuffer.length < 5) { + resolve([]); + return; + } + + const messageLength = fullBuffer.readUInt32BE(1); + const payload = fullBuffer.subarray(5, 5 + messageLength); + + try { + const models: DiscoveredModel[] = []; + let offset = 0; + + // Parse GetModelStatusesResponse + // Field 1: model_status_infos (repeated ModelStatusInfo) + while (offset < payload.length) { + const field = parseProtobufField(payload, offset); + if (!field) break; + offset += field.bytesConsumed; + + if (field.fieldNum === 1 && field.wireType === 2 && Buffer.isBuffer(field.value)) { + // Parse ModelStatusInfo + // Field 1: model (enum) + // Field 4: model_uid (string) + let infoOffset = 0; + let modelEnum = 0; + let modelUid = ''; + + const infoBuf = field.value; + while (infoOffset < infoBuf.length) { + const infoField = parseProtobufField(infoBuf, infoOffset); + if (!infoField) break; + infoOffset += infoField.bytesConsumed; + + if (infoField.fieldNum === 1 && infoField.wireType === 0) { + modelEnum = Number(infoField.value); + } else if (infoField.fieldNum === 4 && infoField.wireType === 2 && Buffer.isBuffer(infoField.value)) { + modelUid = infoField.value.toString('utf8'); + } + } + + if (modelEnum > 0) { + models.push({ + id: modelUid || `model-${modelEnum}`, + name: modelUid || `Model ${modelEnum}`, + enumValue: modelEnum + }); + } + } + } + resolve(models); + } catch (err) { + reject(err); + } + }); + + req.end(body); + }); + } + diff --git a/src/plugin/models.ts b/src/plugin/models.ts index 4b53fc3..43ea248 100644 --- a/src/plugin/models.ts +++ b/src/plugin/models.ts @@ -353,13 +353,9 @@ const MODEL_NAME_TO_ENUM: Record = { // GPT 5.2 variants 'gpt-5.2': ModelEnum.GPT_5_2_MEDIUM, - 'gpt-5-2': ModelEnum.GPT_5_2_MEDIUM, 'gpt-5.2-low': ModelEnum.GPT_5_2_LOW, - 'gpt-5-2-low': ModelEnum.GPT_5_2_LOW, 'gpt-5.2-high': ModelEnum.GPT_5_2_HIGH, - 'gpt-5-2-high': ModelEnum.GPT_5_2_HIGH, 'gpt-5.2-xhigh': ModelEnum.GPT_5_2_XHIGH, - 'gpt-5-2-xhigh': ModelEnum.GPT_5_2_XHIGH, 'gpt-5.2-priority': ModelEnum.GPT_5_2_MEDIUM_PRIORITY, 'gpt-5.2-low-priority': ModelEnum.GPT_5_2_LOW_PRIORITY, 'gpt-5.2-high-priority': ModelEnum.GPT_5_2_HIGH_PRIORITY, @@ -493,6 +489,38 @@ const MODEL_NAME_TO_ENUM: Record = { 'swe-1-5-thinking': ModelEnum.SWE_1_5_THINKING, 'swe-1.5-slow': ModelEnum.SWE_1_5_SLOW, 'swe-1-5-slow': ModelEnum.SWE_1_5_SLOW, + + // Generic Private slots for dynamic discovery + 'private-1': ModelEnum.MODEL_PRIVATE_1, + 'private-2': ModelEnum.MODEL_PRIVATE_2, + 'private-3': ModelEnum.MODEL_PRIVATE_3, + 'private-4': ModelEnum.MODEL_PRIVATE_4, + 'private-5': ModelEnum.MODEL_PRIVATE_5, + 'private-6': ModelEnum.MODEL_PRIVATE_6, + 'private-7': ModelEnum.MODEL_PRIVATE_7, + 'private-8': ModelEnum.MODEL_PRIVATE_8, + 'private-9': ModelEnum.MODEL_PRIVATE_9, + 'private-10': ModelEnum.MODEL_PRIVATE_10, + 'private-11': ModelEnum.MODEL_PRIVATE_11, + 'private-12': ModelEnum.MODEL_PRIVATE_12, + 'private-13': ModelEnum.MODEL_PRIVATE_13, + 'private-14': ModelEnum.MODEL_PRIVATE_14, + 'private-15': ModelEnum.MODEL_PRIVATE_15, + 'private-16': ModelEnum.MODEL_PRIVATE_16, + 'private-17': ModelEnum.MODEL_PRIVATE_17, + 'private-18': ModelEnum.MODEL_PRIVATE_18, + 'private-19': ModelEnum.MODEL_PRIVATE_19, + 'private-20': ModelEnum.MODEL_PRIVATE_20, + 'private-21': ModelEnum.MODEL_PRIVATE_21, + 'private-22': ModelEnum.MODEL_PRIVATE_22, + 'private-23': ModelEnum.MODEL_PRIVATE_23, + 'private-24': ModelEnum.MODEL_PRIVATE_24, + 'private-25': ModelEnum.MODEL_PRIVATE_25, + 'private-26': ModelEnum.MODEL_PRIVATE_26, + 'private-27': ModelEnum.MODEL_PRIVATE_27, + 'private-28': ModelEnum.MODEL_PRIVATE_28, + 'private-29': ModelEnum.MODEL_PRIVATE_29, + 'private-30': ModelEnum.MODEL_PRIVATE_30, }; /** @@ -611,6 +639,38 @@ const ENUM_TO_MODEL_NAME: Partial> = { [ModelEnum.SWE_1_5]: 'swe-1.5', [ModelEnum.SWE_1_5_THINKING]: 'swe-1.5-thinking', [ModelEnum.SWE_1_5_SLOW]: 'swe-1.5-slow', + + // Generic Private slots + [ModelEnum.MODEL_PRIVATE_1]: 'private-1', + [ModelEnum.MODEL_PRIVATE_2]: 'private-2', + [ModelEnum.MODEL_PRIVATE_3]: 'private-3', + [ModelEnum.MODEL_PRIVATE_4]: 'private-4', + [ModelEnum.MODEL_PRIVATE_5]: 'private-5', + [ModelEnum.MODEL_PRIVATE_6]: 'private-6', + [ModelEnum.MODEL_PRIVATE_7]: 'private-7', + [ModelEnum.MODEL_PRIVATE_8]: 'private-8', + [ModelEnum.MODEL_PRIVATE_9]: 'private-9', + [ModelEnum.MODEL_PRIVATE_10]: 'private-10', + [ModelEnum.MODEL_PRIVATE_11]: 'private-11', + [ModelEnum.MODEL_PRIVATE_12]: 'private-12', + [ModelEnum.MODEL_PRIVATE_13]: 'private-13', + [ModelEnum.MODEL_PRIVATE_14]: 'private-14', + [ModelEnum.MODEL_PRIVATE_15]: 'private-15', + [ModelEnum.MODEL_PRIVATE_16]: 'private-16', + [ModelEnum.MODEL_PRIVATE_17]: 'private-17', + [ModelEnum.MODEL_PRIVATE_18]: 'private-18', + [ModelEnum.MODEL_PRIVATE_19]: 'private-19', + [ModelEnum.MODEL_PRIVATE_20]: 'private-20', + [ModelEnum.MODEL_PRIVATE_21]: 'private-21', + [ModelEnum.MODEL_PRIVATE_22]: 'private-22', + [ModelEnum.MODEL_PRIVATE_23]: 'private-23', + [ModelEnum.MODEL_PRIVATE_24]: 'private-24', + [ModelEnum.MODEL_PRIVATE_25]: 'private-25', + [ModelEnum.MODEL_PRIVATE_26]: 'private-26', + [ModelEnum.MODEL_PRIVATE_27]: 'private-27', + [ModelEnum.MODEL_PRIVATE_28]: 'private-28', + [ModelEnum.MODEL_PRIVATE_29]: 'private-29', + [ModelEnum.MODEL_PRIVATE_30]: 'private-30', }; // ============================================================================ diff --git a/src/plugin/types.ts b/src/plugin/types.ts index 446a129..d554700 100644 --- a/src/plugin/types.ts +++ b/src/plugin/types.ts @@ -176,6 +176,46 @@ export const ModelEnum = { SWE_1_5_THINKING: 369, SWE_1_5_SLOW: 377, CLAUDE_4_5_SONNET_THINKING_1M: 371, + + // ============================================================================ + // Enterprise / Private Model Slots (Generic) + // These are used for dynamic discovery of account-specific models. + // ============================================================================ + MODEL_PRIVATE_1: 219, + MODEL_PRIVATE_2: 220, + MODEL_PRIVATE_3: 221, + MODEL_PRIVATE_4: 222, + MODEL_PRIVATE_5: 223, + MODEL_PRIVATE_6: 314, + MODEL_PRIVATE_7: 315, + MODEL_PRIVATE_8: 316, + MODEL_PRIVATE_9: 317, + MODEL_PRIVATE_10: 318, + MODEL_PRIVATE_11: 347, + MODEL_PRIVATE_12: 348, + MODEL_PRIVATE_13: 349, + MODEL_PRIVATE_14: 350, + MODEL_PRIVATE_15: 351, + MODEL_PRIVATE_16: 363, + MODEL_PRIVATE_17: 364, + MODEL_PRIVATE_18: 365, + MODEL_PRIVATE_19: 366, + MODEL_PRIVATE_20: 367, + MODEL_PRIVATE_21: 372, + MODEL_PRIVATE_22: 373, + MODEL_PRIVATE_23: 374, + MODEL_PRIVATE_24: 375, + MODEL_PRIVATE_25: 376, + MODEL_PRIVATE_26: 380, + MODEL_PRIVATE_27: 381, + MODEL_PRIVATE_28: 382, + MODEL_PRIVATE_29: 383, + MODEL_PRIVATE_30: 384, + + // ============================================================================ + // Additional Models + // ============================================================================ + GPT_OSS_120B: 326, } as const; export type ModelEnumValue = (typeof ModelEnum)[keyof typeof ModelEnum]; diff --git a/tests/live/verify-plugin.ts b/tests/live/verify-plugin.ts index 356abeb..28f001a 100644 --- a/tests/live/verify-plugin.ts +++ b/tests/live/verify-plugin.ts @@ -77,10 +77,10 @@ async function main() { process.exit(1); } - // Test 5: Test another model (Claude) + // Test 5: Test Claude 3.5 console.log('5. Testing with Claude 3.5 Sonnet...'); const messages2: ChatMessage[] = [ - { role: 'user', content: 'Reply with just: "Claude OK"' } + { role: 'user', content: 'Reply with just: "Claude 3.5 OK"' } ]; try { @@ -95,9 +95,35 @@ async function main() { chunks.push(chunk); process.stdout.write(chunk); } - console.log('\n OK: Claude streaming completed\n'); + console.log('\n OK: Claude 3.5 streaming completed\n'); } catch (error) { - console.error(`\n FAIL: ${error instanceof Error ? error.message : error}`); + console.log(`\n INFO: Claude 3.5 failed (might be deprecated): ${error instanceof Error ? error.message : error}`); + } + + // Add delay + await new Promise(r => setTimeout(r, 1000)); + + // Test 6: Test Claude 3.7 + console.log('6. Testing with Claude 3.7 Sonnet...'); + const messages3: ChatMessage[] = [ + { role: 'user', content: 'Reply with just: "Claude 3.7 OK"' } + ]; + + try { + const chunks: string[] = []; + const generator = streamChatGenerator(credentials, { + model: 'claude-3.7-sonnet', + messages: messages3, + }); + + process.stdout.write(' Response: '); + for await (const chunk of generator) { + chunks.push(chunk); + process.stdout.write(chunk); + } + console.log('\n OK: Claude 3.7 streaming completed\n'); + } catch (error) { + console.error(`\n FAIL: Claude 3.7 failed: ${error instanceof Error ? error.message : error}`); process.exit(1); }