Skip to content

rsvedant/opencode-windsurf-auth

Repository files navigation

opencode-windsurf-auth

npm version npm beta npm downloads License: MIT

Use Windsurf models in opencode: Claude, GPT, Gemini, Kimi, DeepSeek, SWE-1.6, and more with your existing Windsurf subscription. All via Windsurf OAuth, no app required.

Install

Option A - one-line wizard (recommended)

curl -fsSL https://raw.githubusercontent.com/rsvedant/opencode-windsurf-auth/master/install.sh | bash

The wizard backs up your existing ~/.config/opencode/opencode.json, merges in the plugin entry + a curated 7-model provider.windsurf block (additive, your other settings are untouched), then launches opencode auth login --provider windsurf so you can sign in.

Flags:

Flag Effect
--no-login Skip the sign-in step (run opencode auth login --provider windsurf later)
--force Overwrite an existing provider.windsurf block (default: keep what's there)
--help Show usage and exit

Pass them through the pipe:

curl -fsSL https://raw.githubusercontent.com/rsvedant/opencode-windsurf-auth/master/install.sh | bash -s -- --no-login

Option B - manual

Paste this into ~/.config/opencode/opencode.json:

{
  "$schema": "https://opencode.ai/config.json",
  "plugin": ["opencode-windsurf-auth@beta"],
  "provider": {
    "windsurf": {
      "name": "Cognition (Windsurf)",
      "npm": "@ai-sdk/openai-compatible",
      "options": { "baseURL": "http://127.0.0.1:42100/v1" },
      "models": {
        "claude-opus-4.7": {
          "name": "Claude Opus 4.7",
          "limit": { "context": 1000000, "output": 128000 },
          "attachment": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] },
          "variants": {
            "low": {}, "medium": {}, "high": {}, "xhigh": {}, "max": {},
            "low-fast": {}, "medium-fast": {}, "high-fast": {}, "xhigh-fast": {}, "max-fast": {}
          }
        },
        "gpt-5.5": {
          "name": "GPT 5.5",
          "limit": { "context": 1050000, "output": 128000 },
          "attachment": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] },
          "variants": {
            "none": {}, "low": {}, "medium": {}, "high": {}, "xhigh": {},
            "none-priority": {}, "low-priority": {}, "medium-priority": {}, "high-priority": {}, "xhigh-priority": {}
          }
        },
        "kimi-k2.6": {
          "name": "Kimi K2.6",
          "limit": { "context": 262144, "output": 262144 },
          "attachment": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] }
        },
        "gemini-3.5-flash": {
          "name": "Gemini 3.5 Flash",
          "limit": { "context": 1048576, "output": 65536 },
          "attachment": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] },
          "variants": { "minimal": {}, "low": {}, "medium": {}, "high": {} }
        },
        "claude-opus-4.6": {
          "name": "Claude Opus 4.6",
          "limit": { "context": 1000000, "output": 128000 },
          "attachment": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] },
          "variants": { "thinking": {}, "1m": {}, "thinking-1m": {}, "fast": {}, "thinking-fast": {} }
        },
        "swe-1.6": {
          "name": "SWE 1.6",
          "limit": { "context": 1000000, "output": 128000 },
          "attachment": true,
          "modalities": { "input": ["text", "image"], "output": ["text"] },
          "variants": { "fast": {}, "fast-low": {}, "fast-medium": {}, "fast-high": {} }
        },
        "deepseek-v4": {
          "name": "DeepSeek V4",
          "limit": { "context": 1000000, "output": 384000 }
        }
      }
    }
  }
}

Sign in:

opencode auth login --provider windsurf
# → browser opens; sign in with your Windsurf account
# → credential is saved automatically

Want the full catalog (94 models, all variants)? Copy opencode_config_example.json verbatim.

Use

opencode run --model=windsurf/swe-1.6 "hi"
opencode run --model=windsurf/claude-opus-4.7:high "what does this codebase do?"
opencode run --model=windsurf/kimi-k2.6 -f screenshot.png -- "describe this image"

Variants pass through as model suffixes (:low, :high, :thinking, etc.) — same syntax Windsurf's own clients use.

Image attachments

Models with "attachment": true in your config accept image content parts via opencode's -f <path> flag (and the TUI's paste/drag-drop). The 7-model curated set above marks six as image-capable (everything except deepseek-v4). The full 94-model catalog in opencode_config_example.json flags 55 models as image-capable based on per-model verification against models.dev. Append custom models without the attachment flag and opencode automatically blocks image attachment in the UI.

Sign in / sign out

opencode auth login --provider windsurf   # browser-based, recommended
opencode auth logout windsurf             # clears the credential

# Headless / SSH / no-browser fallback:
npx opencode-windsurf-auth login --manual
npx opencode-windsurf-auth whoami         # show signed-in account
npx opencode-windsurf-auth status         # show credentials path + version

Credentials are stored mode 0600 at the XDG-config location opencode itself uses, on every platform:

  • Linux + macOS + Windows: ~/.config/opencode-windsurf-auth/credentials.json

(opencode doesn't honor %APPDATA% on Windows either, it follows XDG conventions everywhere — so the plugin's credentials sit next to opencode's own config.)

How it works

opencode loads the plugin from npm via its own cache. The plugin binds a Bearer-gated local proxy at 127.0.0.1:42100, translates OpenAI-shaped chat requests into Cognition's Connect-RPC GetChatMessage wire format, and streams the response back as OpenAI SSE. Tool calls, MCP servers, reasoning deltas, token usage, image attachments — all wired through. No language_server runs. Auth uses a loopback OAuth callback on a random ephemeral port; the long-lived api_key from RegisterUser is then exchanged for a short-lived user_jwt on every chat. For the wire-protocol details see docs/CASCADE_PROTOCOL.md.

Troubleshooting

Port 42100 is already in use

Another process holds 127.0.0.1:42100. Find and stop it:

lsof -nP -iTCP:42100 -sTCP:LISTEN
kill -9 <PID>

The plugin refuses to silently adopt a foreign listener on that port because a squatter could otherwise capture your prompts. Re-run opencode auth login after killing the squatter.

Sign-in browser didn't open / headless host

The CLI fallback prints a URL you can click manually:

npx opencode-windsurf-auth login --manual

Or paste the URL it shows into any browser on a machine that can reach your headless host (the loopback callback won't work cross-host, so use --manual for fully-headless setups).

Model says "I can't read images"

Only models with "attachment": true in your opencode.json will receive image content from opencode. If you added a custom model and want image attachments, add:

"attachment": true,
"modalities": { "input": ["text", "image"], "output": ["text"] }

(Adding the flag to a model that doesn't actually support vision will still let opencode attach the image, but the model will respond with "I can't read images" — that's the model talking, not the plugin.)

"Cognition (Windsurf)" doesn't show up in opencode auth login

Confirm the plugin entry is present:

jq '.plugin' ~/.config/opencode/opencode.json
# Should contain "opencode-windsurf-auth@beta"

Then nuke opencode's plugin cache so it re-resolves from npm:

rm -rf ~/.cache/opencode/packages/opencode-windsurf-auth@beta
opencode auth login

Project layout

src/
├── plugin.ts                # Proxy server + opencode hooks (auth + chat.params)
├── cli.ts                   # `opencode-windsurf-auth` standalone CLI
├── cloud-direct/            # Cognition Connect-RPC wire client
│   ├── chat.ts              # GetChatMessage stream + SSE adapter
│   ├── wire.ts              # Proto + Connect framing
│   ├── auth.ts              # GetUserJwt mint + cache
│   └── metadata.ts          # Metadata proto builder
├── oauth/                   # OAuth flow + credentials.json
│   ├── login.ts             # Loopback + manual-paste sign-in
│   ├── register-user.ts     # POST register.windsurf.com → api_key
│   └── storage.ts           # O_EXCL-locked, mode-0600 atomic write
└── plugin/
    ├── credentials-resolver.ts
    └── models.ts            # 110+ canonical model IDs + variant resolver

Development

git clone https://github.com/rsvedant/opencode-windsurf-auth.git
cd opencode-windsurf-auth
bun install
bun run typecheck
bun run build
bun test

Further reading

  • docs/CASCADE_PROTOCOL.md — Windsurf 2.x wire-format notes (why RawGetChatMessage is dead, why model UIDs are now strings, required metadata fields, etc.)

License

MIT

About

Enable Opencode to authenticate via Windsurf's OAuth, so you can access models like Claude Opus 4.7 & GPT-5.5 with your Windsurf subscription.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors