dwgx/WindsurfAPI
dwgx/WindsurfAPI has 3,123 GitHub stars and 635 forks. GitHot measured +1 stars in 24 hours. Last checked 2026-11-04 15:33 UTC. Last push 2026-11-01.
Turn Windsurf / Devin Desktop's 100+ AI models (Claude, GPT, Gemini, DeepSeek, Kimi, GLM, SWE) into OpenAI-, Anthropic- & Gemini-compatible APIs. Zero-dependency self-hosted reverse proxy for Claude Code, Cline & Cursor. 把 Windsurf/Devin 云端 100+ 模型变成三套兼容 API。
Measured star growth
- in 24h
- +1
- in 7 days
- Update delayed
- in 15 days
- Update delayed
- in 1 month
- Update delayed
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
README.en.md
Original
WindsurfAPI · DevinAPI
Turn Windsurf / Devin's 100+ AI models (Claude, GPT, Gemini, DeepSeek, Kimi, GLM, SWE…) into OpenAI / Anthropic / Gemini standard APIs. Zero npm runtime dependencies.
History Ledger · Every commit laid out: 1387 commits, 196 releases, 82 PRs, 180 issues (the ledger's own frozen scope — its numbers stop at v3.9.34 / 2026-09-15) — timeline, contributor analysis, Git tree (vertical / horizontal / ring), and a full account of every mistake along the way. Open the interactive ledger (zero-dependency, pure vanilla).
·
中文/简体中文
Notice
If you haven't starred and followed: commercial use, resale, paid deployment, hosting as a backend for public services, or reselling as a relay service is strictly prohibited. If you have starred and followed: go ahead, I'll look the other way.
The code itself is MIT-licensed (see LICENSE); the above is the author's personal stance.
Turns Windsurf (formerly Codeium, now Devin Desktop)'s AI models into four standard, compatible APIs:
POST /v1/chat/completions— OpenAI Compatible for any OpenAI SDK.POST /v1/completions— OpenAI legacy Completions (non-stream;promptbecomes one user turn — stream via chat).POST /v1/responses— OpenAI Responses Compatible (plusGET/DELETE /v1/responses/{id}to retrieve or drop a stored response — these need an identity header, see below).POST /v1/messages— Anthropic Compatible for direct connection with Claude Code / Cline / Cursor.POST /v1beta/models/*— Gemini Compatible for direct Gemini SDK use.
Auth transport: prefer the
Authorization: Bearer <API_KEY>header, or Gemini'sx-goog-api-keyheader.?key=is accepted for Gemini-native clients, but it puts the key in the URL, where reverse proxies, access logs, browser history and packet captures can retain it. The bundlednginx.conflogs only$uri(the query string is dropped); proxies you add are yours to configure.?key=is supported but deprecated — see docs/QUERY-KEY-DEPRECATION.md for the removal plan and the header migration.
100+ Models: Claude 4.5/4.6/Opus 4.7/5 · GPT-5/5.1/5.2/5.4/5.5/5.6-Luna series · Gemini 2.5/3.0/3.1 · Grok · Qwen · Kimi K2.x · GLM 4.7/5/5.1/5.2 · MiniMax · SWE 1.5/1.6/1.7/2 · Arena, etc. Zero npm dependencies, pure Node.js.
Keywords: Windsurf reverse proxy · Devin API · Claude Code proxy · Cursor mirror · free Claude/GPT/Gemini · Codeium API · OpenAI-compatible endpoint · self-hosted LLM gateway
How it works · Quick start · Client setup · Env switches · All docs · 中文
Project Scope and Usage Boundaries
WindsurfAPI is an independent, open-source, self-hosted protocol-compatibility gateway for connecting Windsurf / Devin account capabilities that the deployer owns or is authorized to use to OpenAI-, Anthropic-, and Gemini-compatible clients.
Usage Model
- Bring Your Own Account (BYO-account): the deployer supplies and controls their own upstream accounts and credentials.
- The project itself does not provide, sell, rent, share, or bundle any Windsurf / Devin accounts, subscriptions, or credentials.
- Multi-account support is for account sets owned or authorized for management by the same deployer — for example failover, health checks, and rate-limit isolation; it is not a public shared account pool.
- The project aims at protocol interoperability, self-hosted client compatibility, and engineering research; it is not intended for subscription resale, cross-user account sharing, bypassing upstream quotas, or circumventing access controls.
- Deployers are responsible for confirming that their accounts, subscriptions, and specific usage comply with the applicable upstream terms of service and applicable law.
Independent Project Statement
WindsurfAPI is an independent open-source project and is not affiliated with, authorized by, or endorsed by Cognition, Windsurf, Devin, OpenAI, Anthropic, Google, or any other model provider, unless explicitly stated otherwise.
The project code is released under the MIT License. The MIT License grants rights to this project's code; it does not grant access to any third-party service, model, account, or subscription.
This section only sets the boundaries; the rest of this document covers what it actually does.
What is it doing?
flowchart LR
subgraph clients["Your clients"]
A["OpenAI SDK<br/>curl / frontend"]
B["Claude Code<br/>Cline · Cursor"]
C["Gemini SDK"]
end
subgraph gw["WindsurfAPI (this service · port 3003)"]
direction TB
R["Protocol translation<br/>OpenAI ↔ Anthropic ↔ Gemini"]
P["Account pool<br/>round-robin · rate-limit isolation · failover · breaker"]
N["Identity neutralisation<br/>strips the upstream Windsurf identity"]
R --- P
R --- N
end
LS["Language Server<br/>(Windsurf binary)"]
UP["Windsurf cloud<br/>server.self-serve.windsurf.com"]
DC["Devin cloud<br/>(DEVIN_CONNECT path)"]
A -- "/v1/chat/completions" --> R
B -- "/v1/messages" --> R
C -- "/v1beta/models/*" --> R
R -- "gRPC" --> LS
LS -- "HTTPS" --> UP
R -. "HTTPS (optional direct)" .-> DC
classDef gwStyle fill:#1f6feb22,stroke:#1f6feb,stroke-width:2px
classDef upStyle fill:#8957e522,stroke:#8957e5
class gw gwStyle
class UP,DC upStyle
What it does:
- An HTTP service (port 3003) exposing both OpenAI and Anthropic APIs simultaneously.
- Translates requests into Windsurf's internal gRPC protocol and sends them to the Windsurf cloud via a local Language Server.
- Manages an account pool with automatic round-robin, rate limiting, and failover.
- Strips the upstream Windsurf identity before returning, making the model identify as "I am Claude Opus 4.6, developed by Anthropic."
How to use with Claude Code / Cline / Cursor
The model itself does not operate on files — file operations are executed locally by the IDE Agent client (Claude Code, Cline, etc.):
sequenceDiagram
actor U as You
participant CC as Claude Code
participant WA as WindsurfAPI
participant WC as Windsurf Cloud
U->>CC: "Help me fix a bug"
CC->>WA: POST /v1/messages<br>messages + tools + system
WA->>WC: Package into Cascade request
WC-->>WA: Model thinks → returns<br>tool_use(edit_file)
WA-->>CC: Anthropic SSE<br>content_block=tool_use
CC->>CC: Execute edit_file() locally<br>(Read/write local files)
CC->>WA: Send tool_result
WA->>WC: Continue conversation...
loop Conversation Loop
WC-->>WA: Response
WA-->>CC: SSE stream
end
CC-->>U: Final answer
Key Point: WindsurfAPI is only responsible for passing tool_use / tool_result. The client CLI is what actually modifies the files.
Quick Start
Runtime: Node.js 22.13+ (24.x is the release gate). Node 20 reached EOL on 2026-04-30 and is unsupported; local Windsurf account import needs the unflagged
node:sqlitefrom 22.13, and earlier 22.x returnssqlite_unavailable.
One-Click Deployment
git clone https://github.com/dwgx/WindsurfAPI.git
cd WindsurfAPI
bash setup.sh # Create directories · Set permissions · Generate .env
node src/index.js
Dashboard: http://YOUR_IP:3003/dashboard
Docker Deployment
cp .env.example .env
# Empty API_KEY / DASHBOARD_PASSWORD is fail-closed (compose binds 0.0.0.0).
# Compose defaults to DEVIN_CONNECT=1 — no Language Server, no LS auto-download.
# LS install on first boot only runs if you turn DEVIN_CONNECT off (Cascade).
docker compose up -d --build
docker compose logs -f
Default mounts:
./.docker-data/data: persistedaccounts.json,proxy.json,stats.json,runtime-config.json,model-access.json, andlogs/./.docker-data/opt/windsurf: Language Server binary and its data directory./.docker-data/tmp/windsurf-workspace: temporary workspace
If you want a different persistence location, set DATA_DIR in .env. The Docker setup defaults it to /data.
One-Command Docker Update
Docker deployments use deploy/update.sh: pull the new image → recreate only windsurf-api → wait for /health → roll back to the previous image automatically on failure. Data volumes are never touched, and no docker.sock mount or Watchtower is required.
bash deploy/update.sh # pull :latest and update
bash deploy/update.sh --tag 3.9.52 # pin a release
bash deploy/update.sh --build # rebuild from this checkout (local/fork images)
bash deploy/update.sh --dry-run # print the plan; change nothing
bash deploy/update.sh --rollback # restore the image from before the last update
Full walkthrough, health gate, rollback and troubleshooting: docs/DOCKER-UPDATE.md.
The
update.shbelow is for git/PM2 deployments. Docker deployments usedeploy/update.sh.
One-Click Update
To pull the latest fixes after deployment, just run one command:
cd ~/WindsurfAPI && bash update.sh
update.sh does: git pull → updates the LS binary via install-ls.sh → stops PM2 → kills any residual process on port 3003 → restarts → health check.
If you are using our public instances (skiapi.dev, etc.), you don't need to do anything; we've already pushed the updates.
Manual Installation
git clone https://github.com/dwgx/WindsurfAPI.git
cd WindsurfAPI
# Language Server binary — auto-detects Linux/macOS, one-click download + chmod
bash install-ls.sh
# Download chain (manifest-driven, fail-closed):
# 1. Read https://windsurf-stable.codeium.com/api/versions
# 2. Pick the newest pinned stable release in ls-pins.json (currently 3.10.67)
# and require the manifest's commit + archive sha256 to match the pin
# 3. Download the desktop archive → verify archive sha256 → extract the LS →
# verify the inner binary sha256
# Any mismatch exits non-zero; there is no silent fallback to the old mirror.
#
# Print the plan without downloading (recommended before the first install):
# bash install-ls.sh --dry-run
#
# Pin a version / override the source:
# bash install-ls.sh --version 3.10.67
# WINDSURFAPI_LS_VERSION=3.10.67 bash install-ls.sh
# WINDSURFAPI_LS_MANIFEST_URL=<url|local path> bash install-ls.sh
# WINDSURFAPI_LS_PINS=/path/to/ls-pins.json bash install-ls.sh
#
# Roll back to the pinned 3.5.17 (explicit dwgx/windsurf-ls-release v3.5.17 tag):
# bash install-ls.sh --rollback
# same as: bash install-ls.sh --version 3.5.17 --allow-mirror
#
# Default install paths:
# Linux x64: /opt/windsurf/language_server_linux_x64
# macOS Apple Silicon: $HOME/.windsurf/language_server_macos_arm
# macOS Intel: $HOME/.windsurf/language_server_macos_x64
# Linux arm64: no upstream archive; install a desktop binary with --file.
# Or use a local binary you already have:
# bash install-ls.sh /path/to/language_server_linux_x64
# Or specify a custom URL (prefer --sha256; without it the installer warns):
# bash install-ls.sh --url https://example.com/language_server_linux_x64 --sha256 <64-hex>
# ⚠️ Platform not covered by the upstream archive / want a different source?
# Upstream only ships linux-x64, darwin-arm64 and darwin-x64 desktop archives.
# For other platforms, copy the LS binary out of the Windsurf desktop app:
#
# macOS: "$HOME/Library/Application Support/Windsurf/resources/app/extensions/windsurf/bin/language_server_macos_arm"
# Linux: "$HOME/.windsurf/bin/language_server_linux_x64"
# or /opt/Windsurf/resources/app/extensions/windsurf/bin/language_server_linux_x64
# Windows: %APPDATA%\Windsurf\bin\language_server_windows_x64.exe
#
# # Install from the local desktop copy:
# bash install-ls.sh /path/to/language_server_linux_x64
#
# Note: swapping the LS binary does not change /v1/models.
# The catalog is fetched by the proxy over HTTPS (GetCascadeModelConfigs /
# GetCliModelConfigs). ideVersion is hardcoded in src/windsurf-api.js and is
# not read from the binary — so the listing depends on what upstream grants
# this account. A missing new model is an entitlement gap, not a stale file.
cat > .env << 'EOF'
PORT=3003
# Empty API_KEY is fail-closed (401) even on localhost.
# Local open access: WINDSURFAPI_ALLOW_UNAUTHENTICATED=1 and HOST=127.0.0.1
API_KEY=
DEFAULT_MODEL=claude-sonnet-4.6
MAX_TOKENS=8192
LOG_LEVEL=info
LS_BINARY_PATH=/opt/windsurf/language_server_linux_x64
LS_DATA_DIR=/opt/windsurf/data
LS_PORT=42100
# Empty DASHBOARD_PASSWORD is fail-closed. Local open panel: DASHBOARD_ALLOW_NO_AUTH=1
DASHBOARD_PASSWORD=
EOF
# For a local macOS run, use the LS_BINARY_PATH printed by install-ls.sh
# and set LS_DATA_DIR to a user-writable path such as /Users/you/.windsurf/data.
# Note: Inline comments are supported in .env for unquoted values:
# PORT=3003 # Service port
# Quoted values preserve everything inside the quotes.
node src/index.js
Add Accounts
After the service is running, you need to add Windsurf accounts. There are three ways:
Method 1: Dashboard One-Click Login (Recommended)
Open http://YOUR_IP:3003/dashboard → Login to get token → Click Sign in with Google or Sign in with GitHub (OAuth popup) or fill in email/password directly. All methods will automatically add the account to the pool.
Method 2: Token (Works with any login method)
Go to windsurf.com/show-auth-token to copy your token:
curl -X POST http://localhost:3003/auth/login \
-H "Content-Type: application/json" \
-d '{"token": "YOUR_TOKEN"}'
Method 3: Batch
curl -X POST http://localhost:3003/auth/login \
-H "Content-Type: application/json" \
-d '{"accounts": [{"token": "t1"}, {"token": "t2"}]}'
Usage Examples
OpenAI Format (Python / JS / curl)
from openai import OpenAI
client = OpenAI(base_url="http://YOUR_IP:3003/v1", api_key="YOUR_API_KEY")
r = client.chat.completions.create(
model="claude-sonnet-4.6",
messages=[{"role": "user", "content": "Hello"}]
)
print(r.choices[0].message.content)
Anthropic Format (Directly with Claude Code)
export ANTHROPIC_BASE_URL=http://YOUR_IP:3003
export ANTHROPIC_API_KEY=YOUR_API_KEY
claude # Use Claude Code as usual
# Raw curl test
curl http://localhost:3003/v1/messages \
-H "Authorization: Bearer YOUR_KEY" \
-H "anthropic-version: 2023-06-01" \
-d '{"model":"claude-opus-4.6","max_tokens":100,"messages":[{"role":"user","content":"Hello"}]}'
Cline / Cursor / Aider
In your client's settings for Custom OpenAI Compatible:
- Base URL:
http://YOUR_IP:3003/v1 - API Key: YOUR_API_KEY
- Model: Choose any supported model.
Cursor users: Cursor's client-side whitelist blocks model names containing
claude(the request never reaches the backend). Use these aliases instead:
Type in Cursor Actual model opus-4.6claude-opus-4.6 sonnet-4.6claude-sonnet-4.6 opus-4.7claude-opus-4-7-medium ws-opusclaude-opus-4.6 ws-sonnetclaude-sonnet-4.6 GPT / Gemini / DeepSeek models are not affected by Cursor's filter — use their original names.
Environment Variables
| Variable | Default | Description |
|---|---|---|
PORT |
3003 |
Service port |
API_KEY |
empty | Caller key. Empty is fail-closed (401) even on localhost. Local open access requires WINDSURFAPI_ALLOW_UNAUTHENTICATED=1 on a local bind. |
WINDSURFAPI_ALLOW_UNAUTHENTICATED |
off | Allow empty API_KEY on a local bind. Default off. Ignored on a public bind. |
DATA_DIR |
project root | Directory for persisted JSON state and logs/. Docker deployments should usually use /data. |
CODEIUM_API_KEY |
empty | Direct API key from Windsurf (alternative to token-based auth). |
CODEIUM_AUTH_TOKEN |
empty | Token from windsurf.com/show-auth-token. |
CODEIUM_EMAIL |
empty | Email for Windsurf account authentication. |
CODEIUM_PASSWORD |
empty | Password for Windsurf account authentication. |
CODEIUM_API_URL |
https://server.self-serve.windsurf.com |
Windsurf cloud API endpoint. |
DEFAULT_MODEL |
claude-sonnet-4.6 |
The model to use if model is not specified. Must be a name the active backend can resolve. Unmapped Connect names return 400 model_not_found unless WINDSURFAPI_STRICT_MODEL=0, which degrades to a free selector the live catalog actually exposes — and still returns 400 when there is none, instead of falling back to swe-1-6-slow, which the catalog no longer carries. |
MAX_TOKENS |
8192 |
Default maximum number of response tokens. |
LOG_LEVEL |
info |
Console level: debug / info / warn / error. JSONL files and Dashboard still receive all levels. |
LOG_RETENTION_DAYS |
0 (off) |
Application JSONL retention in UTC calendar days including today; e.g. 14. Cleanup at startup and hourly. Restart after changes. |
LOG_MAX_FILE_MB |
0 (off) |
MiB limit for newly written log files; e.g. 20. Rotate between complete JSONL records. |
LOG_MAX_TOTAL_MB |
0 (off) |
Combined app/error log MiB budget; e.g. 500. Evict oldest dated segments, including today's if necessary. Oversized records skip disk persistence. See disk-log policy. |
WAPI_ACCOUNTS_ONLY |
empty (all) | Per-instance account isolation: comma-separated emails or account ids; only matching accounts are loaded from accounts.json, case-insensitively. A non-blank value with no valid tokens fails closed (loads no accounts). Use it to pin each port of a multi-port deployment to its own account(s) when they share one accounts.json |
WINDSURFAPI_IGNORE_CLOUD_FILTER |
0 |
On the Cascade transport, after per-account cloud catalogs sync, pool listings show their union and routing enforces each selected account's catalog. Set to 1 to restore the full static catalog. Missing, empty, or failed catalog syncs fail open. DEVIN_CONNECT uses its separate selector catalog. |
LS_BINARY_PATH |
/opt/windsurf/language_server_linux_x64 |
Path to the LS binary. |
LS_PORT |
42100 |
LS gRPC port. |
LS_DATA_DIR |
Linux: /opt/windsurf/data; macOS: ~/.windsurf/data |
Per-proxy LS data directory root. |
LS_MAX_INSTANCES |
adaptive, max 20 |
Maximum LS pool size. The adaptive default reserves at least one non-default proxy slot on small hosts. |
LS_SPAWN_MIN_AVAILABLE_BYTES |
700MB |
Minimum available memory required before starting a new non-default LS. |
LS_PREWARM_DEFAULT |
1 |
Set to 0 to skip startup default-LS prewarm and start LS lazily on first request. Useful for low-memory, all-proxy pools. |
LS_PREWARM_PROXIES |
0 |
Set to 1 to prewarm all proxy LS instances on startup. Scheduled probes and predictive prewarm only reuse idle resident LS instances. |
LS_PREWARM_ON_ACCOUNT_ADD |
0 |
Set to 1 to prewarm LS immediately after dashboard/import/OAuth account add. Default avoids memory spikes during bulk import. |
WINDSURFAPI_NATIVE_TOOL_BRIDGE |
empty | Lab/remote-execution opt-in only. all_mapped enables native bridge only when every declared function tool is allowlisted and mappable; 1 enables partition mode for mapped subsets. Do not treat this as a general fix for local IDE tools. |
WINDSURFAPI_NATIVE_TOOL_BRIDGE_TOOLS |
Bash/shell_command/run_command families |
Tool allowlist for native bridge. The default intentionally contains only the command path. Read / Grep / Glob / WebSearch / WebFetch require explicit allowlisting plus model/account/API-key gates and are still protocol-lab scope, not production defaults. |
WINDSURFAPI_NATIVE_TOOL_BRIDGE_MODELS / PROVIDERS / ROUTES / CALLERS / ACCOUNTS / API_KEYS |
empty | Optional native-bridge gray gates. Empty means unrestricted; when set, the request must match. ACCOUNTS accepts upstream account id/email. API_KEYS matches caller API keys without passing plaintext tokens into chat logic. |
WINDSURFAPI_NATIVE_TOOL_BRIDGE_OFF |
empty | Set to 1 to force native bridge off. |
WINDSURFAPI_SPECIAL_AGENT_BACKEND |
empty | Optional lab-only special-agent backend. Set devin-cli to test swe-1.6, swe-1.6-fast, adaptive, and arena-* through Devin CLI instead of direct Cascade. This is not a normal catalog-model fix. |
DEVIN_CLI_PATH |
devin |
Devin CLI executable path. Docker/macOS deployments must install or mount it themselves. |
DEVIN_CLI_MODE |
print |
print uses conservative devin -p; acp is an experimental ACP stdio backend using upstream Windsurf account-pool apiKeys. |
DEVIN_MAX_PROCS |
1 |
Maximum concurrent Devin CLI processes. |
DASHBOARD_PASSWORD |
empty | Dashboard password. Empty is fail-closed even on localhost. Local open panel requires DASHBOARD_ALLOW_NO_AUTH=1. |
ALLOW_PRIVATE_PROXY_HOSTS |
empty | Set to 1 to allow private/internal IPs (e.g., 192.168.x.x, 10.x.x.x) in proxy tests and login. Leave empty to only allow public addresses (default). |
CASCADE_REUSE_STRICT |
0 |
Set to 1 for strict conversation reuse mode (waits for same fingerprint). |
CASCADE_REUSE_STRICT_RETRY_MS |
60000 |
Retry delay in ms for strict reuse mode. |
CASCADE_REUSE_HASH_SYSTEM |
1 |
System messages are hashed into the conversation reuse fingerprint by default. Set to 0 to opt out — raises the reuse hit rate for callers whose system prompt drifts every turn (Claude Code with cwd snapshots), at the cost of reuse isolation: two requests with different system prompts can then share one pooled conversation. |
CASCADE_REUSE_BY_CALLER |
0 |
Set to 1 to enable caller-based fallback reuse. When fingerprint misses, falls back to the latest cascade for the same caller+model. Best for single-user Claude Code setups. |
CASCADE_POOL_MAX |
500 |
Max conversation pool entries. Set to 1–5 for single-user setups to minimize resource usage. |
STICKY_SESSION_ENABLED |
0 |
Set to 1 to pin each conversation to one upstream account. Strongly recommended on DEVIN_CONNECT: upstream prompt caches are per-account and a cache write costs ~5.6× a read (devin-connect.js 17.8%-of-miss calibration), so without pinning every turn rotates accounts and re-writes the whole context. Requires a per-user signal on the caller (user / safety_identifier / prompt_cache_key / Claude Code metadata.user_id); single-user self-hosts without one set WINDSURFAPI_SINGLE_TENANT_CACHE=1. Observe via the sticky field of /dashboard/api/connect-metrics. |
STICKY_SESSION_TTL_MS |
1800000 |
Binding TTL (30 min); active conversations auto-renew each turn. |
STICKY_SESSION_MAX |
10000 |
Binding table cap, LRU-evicted. |
RESPONSE_STORE_ENABLED |
1 |
Responses API server-side conversation state. With it on, previous_response_id continues a conversation (the client sends only the new turn); set 0 and such requests get a 400, as do GET/DELETE /v1/responses/{id}. Scoped by callerKey. Retrieval and deletion use the same scope as chaining: another caller's id always 404s, without revealing whether it exists. This row used to say "tenants cannot read each other's conversations", which overstated where the guarantee comes from — the scope is not a secret. Behind one shared API key the callerKey is api:{hash(apiKey)}:user:{hash(body.user)}, and user is typically an email or account id, i.e. guessable. What actually prevents a cross-read is the 90 bits of entropy in the response id (resp_ + a dash-stripped UUIDv4 truncated to 24 hex chars — 96 bits of width less the version/variant bits that fall inside the slice). Measured: with the scope entirely correct but the id wrong, the lookup still returns not_found. So the isolation does hold, but it holds because you have to hit a 90-bit id — not because the scope separates tenants. Do not treat user as an access control. Retrieval/deletion carry no request body, so the identity signal rides a header: GET /v1/responses/{id} with x-response-prompt-cache-key: <the value you sent on POST>. All six scope signals work: user / prompt_cache_key / safety_identifier / conversation / conversation_id / session_id. Header names replace underscores with hyphens (x-response-conversation-id); the query fallback accepts either spelling (?conversation_id= and ?conversation-id=, and likewise for the other multi-word signals). It must match the value used when the response was created, otherwise 404. Send ONE scope signal and send it consistently — supplying several different scope signals folds them all into a single identity, so adding one changes the derived key (pre-existing behaviour, not specific to the query channel). Query strings are logged by reverse proxies, CDNs and browser history and user often contains PII — prefer the header. |
RESPONSE_STORE_TTL_MS |
3600000 |
An idle timeout (1 hour), not a retention ceiling. It measures time since last access, and every successful GET refreshes that — so on its own, periodic reads keep an entry alive indefinitely. The absolute bound is the next row. |
RESPONSE_STORE_MAX_AGE_MS |
86400000 |
The absolute retention bound (24 hours), measured from when the entry was created and never refreshed by reads. The default sits well above a long agent session rather than close to the idle timeout: dropping a running loop's context mid-flight is a worse failure than retaining it longer, and total memory is already bounded by the byte and count caps. |
RESPONSE_STORE_MAX |
2000 |
Max stored conversations, LRU-evicted with a per-tenant fair share. |
RESPONSE_STORE_MAX_BYTES |
128m |
Total byte budget for stored conversations (b/k/kb/m/mb/g/gb). The count caps bound cardinality, not memory — a realistic agent conversation measures ~167KB, so 2000 entries is ~327MB. Eviction triggers on whichever limit binds first. |
DEVIN_CONNECT_IMAGE_TAG |
10 |
DEVIN_CONNECT image-field tag. Defaults to repeated field #10, independently verified by the extracted Devin schema and a native SWE-1.7 capture; set 0 to stop emitting images. |
DEVIN_CONNECT_COLLAPSE_SYSTEM |
0 |
Set 1 to wrap system content in <system>...</system> and merge it, in order, into the next user message, avoiding the stricter upstream policy path on field #2. Default off; when tools require a non-empty field #2, only the existing benign placeholder remains there. |
DEVIN_CONNECT_REPLAY_SIGNED_THINKING |
0 |
Set 1 to replay signed Claude thinking from /v1/messages history natively as #11 thinking + #12 signature (tool-call and plain assistant turns), so the model can read its earlier reasoning inside a tool loop and after a new user message. Needs DEVIN_CONNECT_SIGNATURE_TAG=10 so clients receive the signature at all. The upstream verifies the signature; only an untouched block is replayed. Details in docs/ENV-SWITCHES.md. |
DEVIN_CONNECT_CATALOG_TTL_MS |
300000 |
Successful DEVIN_CONNECT live-catalog TTL (5 minutes by default, 10-second minimum). Accounts sync independently; failed or empty responses keep the last-known-good catalog. |
The full list lives in .env.example; the table above covers the common ones.
Enabling images / vision
The DEVIN_CONNECT backend now emits inline images using the native Devin wire by default:
# 10 is already the default; use only for an emergency rollback
DEVIN_CONNECT_IMAGE_TAG=0
Tag 10 has two independent sources of truth: the extracted Devin schema declares
ChatMessagePrompt.images as repeated #10 and ImageData as base64_data #1 /
mime_type #2; a native SWE-1.7 request attaches the image directly to a source=USER
message, receives modelUid=swe-1-7, and correctly identifies the macOS Dock, Sketch, QQ,
and WPS. The gateway therefore no longer invents an assistant read tool call, a synthetic
tool result, or a top-level read ToolDef, and it no longer rejects vision from a model merely
because its name starts with swe-.
Ordinary user images remain on their original user message; multiple images are repeated on that
same message. Images in a native tool result remain source=TOOL_RESULT and preserve the caller's
tool_call_id #7. Live catalog synchronization also decodes
ClientModelConfig.supports_images #5 and exposes supports_images through /v1/models when
upstream explicitly supplies true or false. A missing field remains unknown rather than being
invented as false. Models carrying upstream disabled #4 do not enter the live catalog.
DEVIN_CONNECT_IMAGE_INNER_TAGS can still override the inner base64,mime tags (default 1,2)
as an emergency calibration surface for a future wire change. Remote http(s):// image URLs are
now downloaded by streamChat before the proto is assembled, through the same bounded
fetchImageUrl the Cascade path uses (8 s total budget, 5 MB cap, image/* allowlist, and a
private/loopback rejection on every redirect hop), then inlined as data URLs in #10. A failed,
oversized, or wrong-type fetch logs a warning (host only — never a possibly signed full URL) and
drops just that image; the text still goes upstream. With DEVIN_CONNECT_IMAGE_TAG=0 nothing is
downloaded. Alternatively, explicitly enable DEVIN_ACP_VISION=1 to route vision through a
locally installed Devin CLI ACP transport.
Remote images are https-only by default: plaintext http:// bytes can be replaced by any
middlebox on the path, and those bytes are inlined verbatim into the model's context (#10 /
Cascade image blocks). A rejected fetch drops just that image and still sends the text, logging a
warning with the host only (never the full URL, which may carry a signed query).
WINDSURFAPI_ALLOW_HTTP_IMAGES=1 re-allows plaintext http for public hosts only (both http
and https); the default or any other value accepts https only. Private/internal/loopback targets
stay blocked regardless of the switch — the SSRF policy is separate and not relaxable (including
per-hop DNS resolutions), so it cannot be used for a trusted LAN mirror. The check runs on every
fetchImageUrl hop, so an https → http redirect is rejected too.
Dashboard Features
Open http://YOUR_IP:3003/dashboard:
| Panel | Features |
|---|---|
| Overview | Runtime status · Account pool · LS health · Success rate |
| Login/Get Token | Google / GitHub OAuth one-click login · Email/password login · Test Proxy button (tests egress IP) |
| Account Management | Add / Delete / Disable · Detect subscription level · Check balance · Ban models via blacklist |
| Model Control | Global model whitelist/blacklist |
| Proxy Config | Global or per-account HTTP / SOCKS5 proxy |
| Logs | Real-time SSE streaming · Filter by level · turns=N chars=M diagnostics per turn |
| Stats & Analytics | Time range 6h / 24h / 72h · Per-account dimensions · p50 / p95 latency |
| ACU & Usage | Org-scoped ACU limit · cycle usage · daily burn-down · per-product breakdown · overage balance in USD |
| Experimental | Cascade conversation reuse · Model Identity Injection (custom prompt per vendor) |
The ACU & Usage panel reads app.devin.ai/{org}/billing/* once per organization, so members sharing an org render one ledger instead of one per account. Truth rules: max_acu_limit: null means unlimited (never zero), an absent cycle total is unknown (—), and overage_credits is a signed USD balance. A negative balance is an overdrawn on-demand balance: it renders as -$0.10 (overdrawn) and is never clamped to zero or hidden.
Supported Models
100+ static models in the main catalog plus dynamic cloud-side models added at startup via mergeCloudModels. On the Cascade transport, after per-account cloud catalogs sync, GET /v1/models and the Dashboard show the union available across active accounts, while routing applies the selected account's own catalog. DEVIN_CONNECT remains governed by its separate selector catalog. The full static catalog remains available on the GitHub Pages model catalog (auto-generated from src/models.js).
Claude (Anthropic) — 80 models
claude-3.5-sonnet / 3.7-sonnet / thinking · claude-4-sonnet / opus / thinking · claude-4.1-opus · claude-4.5-haiku / sonnet / opus · claude-sonnet-4.6 (incl. 1m / thinking / thinking-1m) · claude-opus-4.6 / thinking · claude-opus-4.7-medium · claude-opus-4.8 series (low / medium / high / xhigh / max + fast) · claude-5-fable / claude-sonnet-5 / claude-opus-5 series (low / medium / high / xhigh / max; opus-5 incl. fast) · claude-fable-5.1 / claude-sonnet-5.5 / claude-opus-5.5 series (low / medium / high / xhigh / max)
GPT (OpenAI) — 118 models
gpt-4o · gpt-4.1 · gpt-5 series (incl. medium / high / codex) · gpt-5.1 series (base / low / medium / high + fast + codex, all 6 variants) · gpt-5.2 series (none / low / medium / high / xhigh + fast + codex) · gpt-5.4 series (base / mini × low/medium/high/xhigh) · gpt-5.5 series (none / low / medium / high / xhigh + fast) · gpt-5.6-luna series (none / low / medium / high / xhigh / max) · gpt-5.6-sol / terra series (none / low / medium / high / xhigh / max) · gpt-6 family (sol / luna: none / low / medium / high / xhigh / max; astra and gpt-6.1-sol: low / medium / high / xhigh / max) · o3 series (base / mini / pro) · o4-mini
Gemini (Google) — 26 models
gemini-2.5-pro / flash · gemini-3.0-pro / flash (minimal / low / medium / high — 4 reasoning levels) · gemini-3.1-pro (low / high) · gemini-3.6-flash (minimal / low / medium / high) · gemini-3.7 / 3.8-flash (low / medium / high) · gemini-4-argon (low / medium / high)
Open source / Chinese providers
Kimi: kimi-k2 / k2.5 / k2-6 / k2-7 / k3 · GLM: glm-4.7 / 5 / 5.1 / 5.2 / 5.3 · Qwen: qwen-3 · DeepSeek: deepseek-v4 / v4.1-flash · Grok: grok-3 / grok-3-mini-thinking / grok-code-fast-1 / grok-4.5 / 4.6 / 4.7 · MiniMax: minimax-m2.5 · Nemotron: nemotron-3-ultra · Inkling: inkling
Windsurf in-house + Arena
swe-1.5 / 1.5-fast / 1.6 / 1.6-fast / 1.7 / 1.7-lightning · arena-fast · arena-smart
DEVIN_CONNECT model spellings: the dotted
claude-sonnet-4.6is a deliberate alias that resolves to the upstreamclaude-sonnet-4-6-thinkingselector (thinking variant, credit 6). For the non-thinking base, ask for the catalog selectorclaude-sonnet-4-6explicitly (credit 4). Both spellings aremapped:true— the alias is a curated default, not a downgrade.Free-account entitlements typically include
gemini-2.5-flash,glm-4.7/glm-5/5.1,kimi-k2/k2.5/k2-6,qwen-3and similar open-source models; Claude family, GPT family, and Opus / thinking variants require Pro. Each account's exact list shows up in the dashboard.Tool-calling reliability (measured v2.0.82+): Claude family is the most reliable (their training covered prompt-level tool protocols); GLM-4.7 / Kimi-K2.5 work for most cases via NLU fallback + optional retry-with-correction; GLM-5.1 is unreliable on the cascade backend (it often returns empty responses, no narration to recover from); GPT family is also limited because the cascade upstream doesn't carry
tools[]schema. For Claude Code / Cline / Codex doing local tool calls, preferclaude-haiku-4.5orclaude-sonnet-4.6.
Language-Following for CJK Users
The service automatically detects Chinese, Japanese, or Korean characters in your messages and injects a language-following hint to ensure the model responds in the same language. This fixes the issue where Claude Code's large English system prompt would override the communication language.
Architecture Highlights
- Zero npm dependencies Everything uses
node:*built-ins · Protobuf is handcrafted (src/proto.js) · image codecs vendored (src/vendor/, BSD-3 jpeg-js + an original pure-Node PNG decoder) · download and run. - Account Pool + LS Pool Each independent proxy gets its own LS instance, no mixing.
- NO_TOOL Mode
planner_mode=3disables Cascade's built-in tool loop to prevent/tmp/windsurf-workspace/path leakage. - Three-layer sanitization LS built-in tool result filtering ·
<tool_call>text parsing · Output path cleaning. - Real token counting Fetches real
inputTokens/outputTokens/cacheRead/cacheWritefromCortexStepMetadata.model_usage. On DEVIN_CONNECT,prompt_tokensis the full input (fresh + cache_read + cache_write), with the split onprompt_tokens_details.cached_tokensandcache_creation_input_tokens; on Cascade,prompt_tokenscarries fresh + cache_read while cache_write lands intotal_tokensandcache_creation_input_tokens.
PM2 Deployment
npm install -g pm2
pm2 start src/index.js --name windsurf-api
pm2 save && pm2 startup
Do not use pm2 restart (it can create zombie processes). Use the one-click update script bash update.sh.
Firewall
# Ubuntu
ufw allow 3003/tcp
# CentOS
firewall-cmd --add-port=3003/tcp --permanent && firewall-cmd --reload
Remember to open port 3003 in your cloud provider's security group.
Having Issues? Start Here
Search by symptom — no need to read the whole FAQ. This is a protocol-conversion gateway, so the first step of any investigation is always to figure out which layer is broken: the client, this gateway, or the upstream.
flowchart TD
S{"Symptom?"} --> A["Request never arrives<br/>connection refused / timeout"]
S --> B["401 / 403 returned"]
S --> C["Replies, but tools not called"]
S --> D["Replies, but content is wrong<br/>lost context / thinking leaked in"]
S --> E["All accounts down<br/>rate-limited / unavailable"]
A --> A1["1. Is the service alive?<br/>curl :3003/v1/models"]
A1 --> A2["2. Is port 3003 open in the firewall?<br/>see the Firewall section"]
A2 --> A3["3. Raising a timeout in .env won't help<br/>see the context deadline entry"]
B --> B1["Don't mix up the two key layers:<br/>caller key ≠ upstream account"]
B1 --> B2["Gemini clients use<br/>x-goog-api-key or ?key="]
C --> C1["Check ToolRoute[...] in the logs first<br/>it lists why tools were filtered/downgraded"]
C1 --> C2["Then check for server-side tools<br/>the translation layer drops unimplemented kinds"]
D --> D1["Lost context → should you use<br/>the /v1/responses chain?"]
D1 --> D2["Thinking leaked in → enable LEAK_TRACE<br/>and capture boundary logs"]
E --> E1["First separate: account-limited<br/>or IP-level cooldown?"]
E1 --> E2["See the 'All accounts<br/>temporarily rate-limited' entry"]
classDef sym fill:#8957e522,stroke:#8957e5
classDef act fill:#1f6feb22,stroke:#1f6feb
class A,B,C,D,E sym
class A1,A2,A3,B1,B2,C1,C2,D1,D2,E1,E2 act
The two easiest to trip over:
| Symptom | Real cause |
|---|---|
Raised the timeout in .env but context deadline exceeded is still there |
That timeout lives at a different layer. See the FAQ entry with the same name below |
| "All accounts rate-limited" right after start, suspecting the proxy is broken | Most likely an IP-level cooldown — not an account problem, not a proxy problem |
When tracing a request chain, send a real request and read the response — don't just read the code. This is a protocol-conversion gateway with two key layers and four egress paths; reasoning from the source alone leads you astray.
FAQ
Q: Login fails with "Invalid email or password" A: You probably signed up for Windsurf using Google/GitHub, which means your account doesn't have a password. The Dashboard's login panel now directly supports one-click login via Google / GitHub OAuth.
Q: The model says "I cannot operate on the file system"
A: This is a chat API, not an IDE agent. To have the model actually modify files, use a client CLI like Claude Code / Cline / Cursor / Aider and point their API base URL to this service. The model will produce tool_use, the client executes it locally, and sends the tool_result back. The diagram above shows the detailed flow.
Q: Context is lost / The model forgets previous parts of the conversation
A: Multi-account round-robin will not lose context — every request repackages the full history and sends it to Cascade. The real reason is usually a relay layer (like new-api) not passing the full messages[] array. Check turns=N in the Dashboard logs: if it's a multi-turn conversation but turns=1, then a layer before you has already dropped the history.
Q: Long prompts are timing out
A: This has been fixed. Cold stall detection is adaptive to input length (30s base, +5s per 1500 characters) and is capped by CASCADE_MAX_WAIT_MS (default 600s) — the old fixed 90s cap was removed on 2026-04-20.
Q: Can I use Claude Code?
A: Yes. export ANTHROPIC_BASE_URL=http://YOUR_API + export ANTHROPIC_API_KEY=YOUR_KEY. /v1/messages supports the full suite: system, tools, tool_use, tool_result, stream, and multi-turn, all tested and working.
Q: What models can free accounts use?
A: Mostly gemini-2.5-flash, glm-4.7 / 5 / 5.1, kimi-k2 / k2.5 / k2-6, qwen-3 (open-source series). Claude family, GPT family, and Opus / Max / -thinking variants need Pro entitlement. The dashboard shows each account's entitled list, and model_not_entitled error responses include an available_in_pool field with the names you can switch to. The static cold-start floor is gemini-2.5-flash only; everything else is discovered per account (GetUserStatus allowlist + a successful call), so a fresh account showing one model is expected — run Probe in the dashboard to fill it in.
Q: Are tool calls reliable on free accounts?
A: Depends on the model. Claude family is rock-solid (also free-account-entitled when available). GLM-4.7 / Kimi-K2.5 work in most cases via NLU recovery + WINDSURFAPI_NLU_RETRY=1 retry-with-correction. GLM-5.1 is unreliable on the cascade backend (frequent empty responses) — proxy can't fix this. GPT family is similarly limited by the cascade protocol layer not passing tools[] schema. For Claude Code / Cline / Codex doing local file/shell ops prefer claude-haiku-4.5 or claude-sonnet-4.6.
Q: The client shows "tools not being called" — how do I debug this?
A: First look for ToolRoute[...] in the logs. It lists the tools your client declared, the effective tools after tool_choice filtering, mapped/unmapped tools for the native bridge, the preamble downgrade tier, and the reason (tool_choice_none / forced_tool_not_declared / preamble_compacted / native_bridge_*). Server-side tools on /v1/messages and /v1/responses (Anthropic advisor/code_execution, OpenAI file_search/mcp/computer_use) are dropped at the translation layer when the proxy doesn't implement them; those aren't ordinary function tools, and their absence doesn't mean WindsurfAPI can execute them on the client's behalf. The native bridge is also not a "local IDE tool fix" switch: the default safe path is prompt/tool emulation with local client-side execution; the native bridge makes the remote Windsurf workspace execute Cascade built-in tools and is only meant for low-traffic experiments behind a model/account/API-key gate.
Q: 31 trial accounts go unavailable after a few hundred calls
A: Likely the model is a weekly-quota variant — claude-opus-4-7-max / gpt-5.5-xhigh / claude-opus-4-8-xhigh etc. cap at 5 calls per week per account, so 31 accounts × 5 ≈ 150 calls hit the wall fast. Switch to claude-sonnet-4.6 / claude-haiku-4.5 (daily quotas are much wider). Verify with docker logs windsurfapi-windsurf-api-1 | grep rate_limit — the per-account cooldown reason is in the log.
Q: "All accounts temporarily rate-limited" / IP-level cooldown — is the proxy broken?
A: Usually not. Windsurf's upstream applies cooldowns to dense bursts from the same egress IP + same model, so accounts sharing one egress IP get limited together. WindsurfAPI stops burning accounts and returns 429 + Retry-After; since v2.0.140 the wait time reflects the upstream's real value (e.g. Resets in: 27m12s) instead of a fixed 30-second hint. The fixes are lowering concurrency, switching to gentler models, binding accounts to different egress IPs, or waiting for the upstream cooldown to expire.
Q: Is the free tier locally limited to 1 request per minute?
A: No. The local free-tier RPM defaults to 10/min. The 1/min you're seeing (or recovery after a while) is usually Windsurf upstream's dynamic free-tier throttling or a model entitlement limit. Check account status and the available-model list in the Dashboard; when you request a model you're not entitled to, the error's available_in_pool field lists what the current pool can use.
Q: Can context deadline exceeded / Client.Timeout be fixed by raising the .env timeout?
A: No. Long thinking / long output is cut at roughly 236-243 seconds — that's the Windsurf provider/Cascade single-stream window. WindsurfAPI marks it upstream_deadline_exceeded / windsurf_provider_deadline and discards the half-finished Cascade reuse trajectory so the next round's context doesn't get corrupted. The only real mitigations are splitting the task, lowering reasoning/max output, or switching to a faster model.
Contributors
Huge thanks to the following folks who sent pull requests or systematically audited the code:
- @dd373156 — PR #1 Fixed the Pro tier model-merge logic: the hardcoded table wasn't picking up dynamically-fetched cloud models, so Pro accounts couldn't see newly-released models in Cursor / Cherry Studio.
- @colin1112a — PR #13
A single-shot audit that flagged 15 security / concurrency / resource bugs: XSS escaping, shell injection, OOM guards, auth route placement, gRPC double-callback, LS pool race, HTTP/2 frame size caps, and more. On top of this we later added a JS-level
escJsAttr, coalesced concurrentensureLscalls via_pending, released pooled sessions on LS exit, and fixed 6 more issues surfaced by a follow-up Antigravity audit. - @baily-zhang — PR #36 + PR #45 Core Cascade reuse fixes: stableTurns fingerprinting (#36) solved 0% hit rate; trajectory offset tracking (#45) eliminated context bloat during multi-turn reuse.
- @aict666 — PR #44 Fixed inferTier demoting Pro/Trial accounts to free after every chat call, preserving the authoritative tier from GetUserStatus.
- @smeinecke — PR #43 Full Dashboard i18n: 14 commits covering Chinese/English translations, I18n system, and check-i18n.js validation tool.
- @you922 — PR #162 + PR #163 Built sticky session from scratch (callerKey + modelKey → accountId binding) + LS crash auto-restart with exponential backoff. Also provided root-cause analysis of SectionOverrideConfig tool-call suppression in #164.
- @Fermiz — PR #181 Cascade reuse optimization for single-user setups (skip unnecessary rotation) + HTTPS proxy layer + configurable conversation-pool size.
- @linqichenggg — PR #175 Cross-platform LS path alignment: unified binary paths, data dirs, and install scripts across Windows / macOS / Linux.
- @lauvww — PR #182 Rewrote dashboard batch import parser: supports JSON / CSV / plain text mixed paste with auto-detection.
- @ucloudnb666 — PR #184 Added Astraflow as a third-party provider.
- @datfooldive — PR #173 Dashboard UI overhaul: unified component styles, improved card layouts and responsive design.
- @The-five-stooges — PR #188 Sticky session streaming-path fix + body.user multi-user isolation + stickyNoFallback / stickyBindByUserOnly toggles.
- @andya1lan — PR #192
Routed
update.shLS binary updates throughinstall-ls.sh, aligned the WindsurfAPI release / public LS mirror / Exafunction source chain, and fixed macOSgrep -Pcompatibility. - @MatrixNeoKozak — PR #195
Made malformed JSON sent to the dashboard API return HTTP 400 instead of a 200 response with
ok:false, so UI and automation callers can rely on status-code semantics for bad request bodies. - @brandonedley — PR #201 Added GLM 5.2 and Kimi K2.7 catalog entries while keeping README, README.en, package metadata, generated model docs checks, and catalog tests in sync.
- @forrinzhao — PR #219
Traced the Devin content-policy block that killed every request from a Feishu codex bot to one sentence in codex's
apply_patchtool description:FREEFORM tool, so do not wrap the patch in JSON.Live-bisected 7/7 deterministic, with the decisive two-fragment A/B — rewriting only one fragment still blocks, both must change. The finding exposed a deeper structural gap: the tool-description preamble is injected afterneutralizeClientIdentity, so any trigger phrase arriving via a tool description bypassed the entire a1-a6 defense line on the native path. The landed fix moves neutralization after preamble injection and adds the two rewrites as rule(a7)in the existing sequence (reusing the main switch and test harness), so it now covers every client and every trigger. Also adopted hisresponses.jsfix: Codex sends bare{role, content}input items withouttype:'message', which were silently dropped and left upstream with an empty messages array → UPSTREAM_INTERNAL. - @warelik — PR #224 #225 #226 #227 #228 #229
Six PRs in one round, across the account pool, rate limiting, identity neutralization, and protocol conformance. The 429 reset-window bug was two links of one chain: the upstream's 3h reset window was dropped in transport (both error paths destructured only
{code, message}), and a model-scoped cooldown is structurally invisible to pool selection viagetApiKey(modelKey=null)— fixing either alone has no observable effect, so a just-429'd account got re-picked within seconds and hammered upstream into a hard lockout (#224). Client disconnects are no longer treated as account faults: abort is now recognized uniformly (undici and AbortController disagree on shape), and no exit path penalizes the error budget, burns quota on failover, or writes to a dead socket (#225). Grok CLI'sYou are Grok … released by xAIself-ID trips the content policy — added to the a1-a5 rule family, plus whole-block stripping of<executing_actions_with_care>(#227). Thinking blocks now omitsignatureinstead of sending"", which strict clients reject (#228). Responses usage gainedinput/output_tokens_details, which pairs exactly with the DEVIN_CONNECT cache wire-tag calibration (#229). Also a zero-dependency pair-chain session-continuity module (600 lines when it landed, 858 today) deriving a stablesession_idfrom the conversation's own completed request/response pairs — opt-in, byte-identical when off (#226). - @warelik — PR #216 + PR #215
Neutralized the upstream MCP-gate: server.codeium.com fingerprints tool descriptions and rejected 8 of Cursor's 21 tools with permission_denied. The fix replaces the native #10 ToolDef top-level description with the tool name and recursively strips
descriptionannotations from the parameter schema (preserving structure and parameters literally nameddescription), then injects a description-only preamble into the system prompt to restore tool-selection context — 21/21 tools pass (#216). Also fixed Node 20+ IPv6 Happy Eyeballs ETIMEDOUT by disabling autoSelectFamily and switching local HTTP/2 to 127.0.0.1 (#215).
Want to be on this list? Open an issue or a pull request. The dashboard's Credits panel on the left is the fuller list: it renders from contributors.json — currently 36 contributors across 68 entries, each with a weight tier and a mechanism note — while the list above is a hand-picked subset of it.
License
MIT License. See LICENSE.
Release and Secret Boundary
Release automation may publish Docker images and GitHub Releases. The current release is v3.141.592658; the repository carries 228 git tags and 218 release-note files (2026-10-30). Every release from v3.10.0 attaches a SHA256SUMS manifest covering the four published assets (Windows exe/zip, macOS arm64 binary/zip) and signs the four assets and the manifest with cosign keyless signing (GitHub OIDC; no private key or signing secret in the repository). From v3.141.592657 the Docker image digest (image-digest.txt) and the source-OTA provenance statement (release-attestation.json) are also each signed with a .sigstore.json bundle. Each signed blob ships a single .sigstore.json bundle (signature, Fulcio certificate and Rekor inclusion proof in one file). The signature proves the bytes were produced by this repository's release workflow for a v* tag; it does not prove human review or a reproducible build, and anyone who can rewrite the repository/tag can re-sign. Verify before running a download: node scripts/verify-release-assets.mjs <download-dir> (requires cosign), or follow docs/RELEASE-SIGNING.md to verify SHA256SUMS.sigstore.json with cosign verify-blob and then run sha256sum -c SHA256SUMS (shasum -a 256 -c on macOS; Get-FileHash on Windows). The manifest uses the flat asset names, so all files can sit in one directory; v3.9.53 recorded dist-windows/ / dist-macos/ paths, and releases before v3.10.0 are unsigned. The Docker image is signed by digest and its digest is published as image-digest.txt; the dashboard's Docker update resolves the tag through the daemon and refuses to pull or deploy unless it equals that signed digest. The git self-update path does not download binaries, but it verifies the release's release-attestation.json (tag→commit/tree and every asset's SHA-256, cosign-signed); WINDSURFAPI_UPDATE_ATTESTATION=warn is the default (missing material logs a warning), and required refuses. See docs/RELEASE-SIGNING.md for the trust boundary and the OTA checks. Keep tokens, API keys, cookies, and provider credentials out of issues, pull requests, logs, and committed config. If a report needs authentication details, share only redacted metadata and reproduction steps.
Explore related projects
affaan-m/ECC
The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.
297,334 starsNousResearch/hermes-agent
The agent that grows with you
260,622 starsmultica-ai/andrej-karpathy-skills
A single CLAUDE.md file to improve Claude Code behavior, derived from Andrej Karpathy's observations on LLM coding pitfalls.
229,556 starsfirecrawl/firecrawl
Supercharge your AI agents with data from the web and beyond. Building the library for superintelligence. 🔥
198,458 stars