@ngotrnghia1811/opencode-windsurf-auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,39 @@
1
+ // credentials.ts — load the Windsurf JWT from ~/.local/share/devin/credentials.toml
2
+ //
3
+ // The file format is simple TOML:
4
+ // windsurf_api_key = "devin-session-token$eyJhbG..."
5
+ //
6
+ // We read the file, find the windsurf_api_key line, extract the value,
7
+ // and return the bare JWT (without the "devin-session-token$" prefix).
8
+ // Returns null if the file or key is absent.
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ const CREDS_PATH = join(homedir(), ".local/share/devin/credentials.toml");
12
+ const PREFIX = "devin-session-token$";
13
+ export async function loadWindsurfJwt() {
14
+ const file = Bun.file(CREDS_PATH);
15
+ const exists = await file.exists();
16
+ if (!exists)
17
+ return null;
18
+ const text = await file.text();
19
+ for (const line of text.split("\n")) {
20
+ const trimmed = line.trim();
21
+ if (!trimmed.startsWith("windsurf_api_key"))
22
+ continue;
23
+ const eq = trimmed.indexOf("=");
24
+ if (eq === -1)
25
+ continue;
26
+ let value = trimmed.slice(eq + 1).trim();
27
+ // strip quotes
28
+ if ((value.startsWith('"') && value.endsWith('"')) ||
29
+ (value.startsWith("'") && value.endsWith("'"))) {
30
+ value = value.slice(1, -1);
31
+ }
32
+ // strip the devin-session-token$ prefix if present
33
+ if (value.startsWith(PREFIX)) {
34
+ return value.slice(PREFIX.length);
35
+ }
36
+ return value;
37
+ }
38
+ return null;
39
+ }
@@ -0,0 +1,8 @@
1
+ import type { Hooks, PluginInput } from "@opencode-ai/plugin";
2
+ export { createWindsurf } from "./windsurf-provider.js";
3
+ declare function WindsurfPlugin(_input: PluginInput): Promise<Hooks>;
4
+ declare const _default: {
5
+ id: string;
6
+ server: typeof WindsurfPlugin;
7
+ };
8
+ export default _default;
package/dist/index.js ADDED
@@ -0,0 +1,49 @@
1
+ // index.ts — opencode-windsurf-auth package entry point
2
+ //
3
+ // Dual-export contract (see MIGRATION_PLAN.md §1 for the loader trace):
4
+ //
5
+ // 1. Named export `createWindsurf` — picked up by the Provider SDK loader
6
+ // (`provider/provider.ts` line 1740: scans `Object.keys(mod)` for the
7
+ // first key starting with `"create"`).
8
+ // 2. Default export (V1 plugin format) — `{ id, server }` object picked
9
+ // up by the plugin loader (`plugin/shared.ts:readV1Plugin` looks at
10
+ // `mod.default` for an object with a `server` function). The V1 path
11
+ // matches first, so the legacy fallback (`getLegacyPlugins`) never
12
+ // iterates `Object.values(mod)` and never accidentally treats
13
+ // `createWindsurf` as a plugin function.
14
+ //
15
+ // These two consumers never conflict because they look at different
16
+ // slots on the module namespace object.
17
+ import { WINDSURF_MODELS } from "./models";
18
+ // ── Provider SDK export ─────────────────────────────────────────────────
19
+ // Re-export from windsurf-provider.ts so the provider SDK loader finds it.
20
+ export { createWindsurf } from "./windsurf-provider.js";
21
+ // ── Plugin Hooks (V1 format) ───────────────────────────────────────────
22
+ async function WindsurfPlugin(_input) {
23
+ return {
24
+ provider: {
25
+ id: "windsurf-devin-provider",
26
+ async models(_provider, _ctx) {
27
+ return WINDSURF_MODELS;
28
+ },
29
+ },
30
+ auth: {
31
+ provider: "windsurf-devin-provider",
32
+ methods: [
33
+ {
34
+ type: "api",
35
+ label: "Devin CLI (devin /login required once)",
36
+ },
37
+ ],
38
+ loader: async () => ({}),
39
+ },
40
+ };
41
+ }
42
+ // V1 plugin format: default export is an object with `id` + `server`.
43
+ // The plugin loader detects this shape in `detect` mode and calls
44
+ // `default.server(input, options)`. The `id` field satisfies
45
+ // `resolvePluginId` (required for file:// plugins).
46
+ export default {
47
+ id: "windsurf-auth",
48
+ server: WindsurfPlugin,
49
+ };
@@ -0,0 +1,2 @@
1
+ import type { Model } from "@opencode-ai/sdk/v2";
2
+ export declare const WINDSURF_MODELS: Record<string, Model>;
package/dist/models.js ADDED
@@ -0,0 +1,206 @@
1
+ // ── api.npm source constant ────────────────────────────────────────────
2
+ // Default: npm package name (used when package is published).
3
+ // Local dev override: set WINDSURF_AUTH_LOCAL=1 to use the local dist path
4
+ // (e.g. `export WINDSURF_AUTH_LOCAL=1` before running opencode dev build).
5
+ const PROVIDER_NPM = () => {
6
+ if (typeof process !== "undefined" && process.env["WINDSURF_AUTH_LOCAL"] === "1") {
7
+ return "file:///Users/nghiango-mbp/opencode-learn/opencode-windsurf-auth/dist/index.js";
8
+ }
9
+ return "@ngotrnghia1811/opencode-windsurf-auth";
10
+ };
11
+ const BASE_MODEL = {
12
+ providerID: "windsurf-devin-provider",
13
+ family: "cascade",
14
+ capabilities: {
15
+ temperature: false,
16
+ reasoning: false,
17
+ attachment: false,
18
+ toolcall: true,
19
+ input: { text: true, audio: false, image: false, video: false, pdf: false },
20
+ output: { text: true, audio: false, image: false, video: false, pdf: false },
21
+ interleaved: false,
22
+ },
23
+ cost: {
24
+ input: 0,
25
+ output: 0,
26
+ cache: { read: 0, write: 0 },
27
+ },
28
+ limit: {
29
+ context: 128_000,
30
+ output: 8_192,
31
+ },
32
+ status: "active",
33
+ options: {},
34
+ headers: {},
35
+ release_date: "2024-01-01",
36
+ };
37
+ const windsurfModel = (key, name, apiId, inputCost, outputCost, reasoning = false) => ({
38
+ ...BASE_MODEL,
39
+ id: `windsurf/${key}`,
40
+ name: `Windsurf ${name}`,
41
+ capabilities: { ...BASE_MODEL.capabilities, reasoning },
42
+ cost: { input: inputCost, output: outputCost, cache: { read: 0, write: 0 } },
43
+ api: { id: apiId, url: "", npm: PROVIDER_NPM() },
44
+ });
45
+ export const WINDSURF_MODELS = {
46
+ // ── Windsurf native models ──────────────────────────────────────────────
47
+ adaptive: windsurfModel("adaptive", "Adaptive", "adaptive", 0.5, 2),
48
+ "arena-fast": windsurfModel("arena-fast", "Fast Arena", "arena-fast", 0.1, 0.5),
49
+ "arena-mixed": windsurfModel("arena-mixed", "Hybrid Arena", "arena-mixed", 1, 5),
50
+ "arena-smart": windsurfModel("arena-smart", "Frontier Arena", "arena-smart", 3, 15),
51
+ // ── Anthropic ───────────────────────────────────────────────────────────
52
+ "claude-opus-4-1": windsurfModel("claude-opus-4-1", "Claude Opus 4.1", "MODEL_CLAUDE_4_1_OPUS", 5, 25),
53
+ "claude-opus-4-1-thinking": windsurfModel("claude-opus-4-1-thinking", "Claude Opus 4.1 Thinking", "MODEL_CLAUDE_4_1_OPUS_THINKING", 5, 25, true),
54
+ "claude-opus-4-5": windsurfModel("claude-opus-4-5", "Claude Opus 4.5", "MODEL_CLAUDE_4_5_OPUS", 5, 25),
55
+ "claude-opus-4-5-thinking": windsurfModel("claude-opus-4-5-thinking", "Claude Opus 4.5 Thinking", "MODEL_CLAUDE_4_5_OPUS_THINKING", 5, 25, true),
56
+ "claude-opus-4-6": windsurfModel("claude-opus-4-6", "Claude Opus 4.6", "claude-opus-4-6", 5, 25),
57
+ "claude-opus-4-6-1m": windsurfModel("claude-opus-4-6-1m", "Claude Opus 4.6 1M", "claude-opus-4-6-1m", 5, 25),
58
+ "claude-opus-4-6-fast": windsurfModel("claude-opus-4-6-fast", "Claude Opus 4.6 Fast", "claude-opus-4-6-fast", 30, 150),
59
+ "claude-opus-4-6-thinking": windsurfModel("claude-opus-4-6-thinking", "Claude Opus 4.6 Thinking", "claude-opus-4-6-thinking", 5, 25, true),
60
+ "claude-opus-4-6-thinking-1m": windsurfModel("claude-opus-4-6-thinking-1m", "Claude Opus 4.6 Thinking 1M", "claude-opus-4-6-thinking-1m", 5, 25, true),
61
+ "claude-opus-4-6-thinking-fast": windsurfModel("claude-opus-4-6-thinking-fast", "Claude Opus 4.6 Thinking Fast", "claude-opus-4-6-thinking-fast", 30, 150, true),
62
+ "claude-opus-4-7-high": windsurfModel("claude-opus-4-7-high", "Claude Opus 4.7 High", "claude-opus-4-7-high", 5, 25),
63
+ "claude-opus-4-7-high-fast": windsurfModel("claude-opus-4-7-high-fast", "Claude Opus 4.7 High Fast", "claude-opus-4-7-high-fast", 30, 150),
64
+ "claude-opus-4-7-low": windsurfModel("claude-opus-4-7-low", "Claude Opus 4.7 Low", "claude-opus-4-7-low", 5, 25),
65
+ "claude-opus-4-7-low-fast": windsurfModel("claude-opus-4-7-low-fast", "Claude Opus 4.7 Low Fast", "claude-opus-4-7-low-fast", 30, 150),
66
+ "claude-opus-4-7-max": windsurfModel("claude-opus-4-7-max", "Claude Opus 4.7 Max", "claude-opus-4-7-max", 5, 25),
67
+ "claude-opus-4-7-max-fast": windsurfModel("claude-opus-4-7-max-fast", "Claude Opus 4.7 Max Fast", "claude-opus-4-7-max-fast", 30, 150),
68
+ "claude-opus-4-7-medium": windsurfModel("claude-opus-4-7-medium", "Claude Opus 4.7 Medium", "claude-opus-4-7-medium", 5, 25),
69
+ "claude-opus-4-7-medium-fast": windsurfModel("claude-opus-4-7-medium-fast", "Claude Opus 4.7 Medium Fast", "claude-opus-4-7-medium-fast", 30, 150),
70
+ "claude-opus-4-7-xhigh": windsurfModel("claude-opus-4-7-xhigh", "Claude Opus 4.7 XHigh", "claude-opus-4-7-xhigh", 5, 25),
71
+ "claude-opus-4-7-xhigh-fast": windsurfModel("claude-opus-4-7-xhigh-fast", "Claude Opus 4.7 XHigh Fast", "claude-opus-4-7-xhigh-fast", 30, 150),
72
+ "claude-opus-4-8-high": windsurfModel("claude-opus-4-8-high", "Claude Opus 4.8 High", "claude-opus-4-8-high", 5, 25),
73
+ "claude-opus-4-8-high-fast": windsurfModel("claude-opus-4-8-high-fast", "Claude Opus 4.8 High Fast", "claude-opus-4-8-high-fast", 10, 50),
74
+ "claude-opus-4-8-low": windsurfModel("claude-opus-4-8-low", "Claude Opus 4.8 Low", "claude-opus-4-8-low", 5, 25),
75
+ "claude-opus-4-8-low-fast": windsurfModel("claude-opus-4-8-low-fast", "Claude Opus 4.8 Low Fast", "claude-opus-4-8-low-fast", 10, 50),
76
+ "claude-opus-4-8-max": windsurfModel("claude-opus-4-8-max", "Claude Opus 4.8 Max", "claude-opus-4-8-max", 5, 25),
77
+ "claude-opus-4-8-max-fast": windsurfModel("claude-opus-4-8-max-fast", "Claude Opus 4.8 Max Fast", "claude-opus-4-8-max-fast", 10, 50),
78
+ "claude-opus-4-8-medium": windsurfModel("claude-opus-4-8-medium", "Claude Opus 4.8 Medium", "claude-opus-4-8-medium", 5, 25),
79
+ "claude-opus-4-8-medium-fast": windsurfModel("claude-opus-4-8-medium-fast", "Claude Opus 4.8 Medium Fast", "claude-opus-4-8-medium-fast", 10, 50),
80
+ "claude-opus-4-8-xhigh": windsurfModel("claude-opus-4-8-xhigh", "Claude Opus 4.8 XHigh", "claude-opus-4-8-xhigh", 5, 25),
81
+ "claude-opus-4-8-xhigh-fast": windsurfModel("claude-opus-4-8-xhigh-fast", "Claude Opus 4.8 XHigh Fast", "claude-opus-4-8-xhigh-fast", 10, 50),
82
+ "claude-sonnet-4": windsurfModel("claude-sonnet-4", "Claude Sonnet 4", "MODEL_CLAUDE_4_SONNET", 3, 15),
83
+ "claude-sonnet-4-thinking": windsurfModel("claude-sonnet-4-thinking", "Claude Sonnet 4 Thinking", "MODEL_CLAUDE_4_SONNET_THINKING", 3, 15, true),
84
+ "claude-sonnet-4-5": windsurfModel("claude-sonnet-4-5", "Claude Sonnet 4.5", "MODEL_PRIVATE_2", 3, 15),
85
+ "claude-sonnet-4-5-thinking": windsurfModel("claude-sonnet-4-5-thinking", "Claude Sonnet 4.5 Thinking", "MODEL_PRIVATE_3", 3, 15, true),
86
+ "claude-sonnet-4-6": windsurfModel("claude-sonnet-4-6", "Claude Sonnet 4.6", "claude-sonnet-4-6", 3, 15),
87
+ "claude-sonnet-4-6-1m": windsurfModel("claude-sonnet-4-6-1m", "Claude Sonnet 4.6 1M", "claude-sonnet-4-6-1m", 3, 15),
88
+ "claude-sonnet-4-6-thinking": windsurfModel("claude-sonnet-4-6-thinking", "Claude Sonnet 4.6 Thinking", "claude-sonnet-4-6-thinking", 3, 15, true),
89
+ "claude-sonnet-4-6-thinking-1m": windsurfModel("claude-sonnet-4-6-thinking-1m", "Claude Sonnet 4.6 Thinking 1M", "claude-sonnet-4-6-thinking-1m", 3, 15, true),
90
+ "claude-haiku-4-5": windsurfModel("claude-haiku-4-5", "Claude Haiku 4.5", "MODEL_PRIVATE_11", 1, 5),
91
+ "opus-4-7-review": windsurfModel("opus-4-7-review", "Opus 4.7 Review", "opus-4-7-review", 5, 25),
92
+ // ── OpenAI ──────────────────────────────────────────────────────────────
93
+ "gpt-4o": windsurfModel("gpt-4o", "GPT-4o", "MODEL_CHAT_GPT_4O_2024_08_06", 2.5, 10),
94
+ "gpt-4-1": windsurfModel("gpt-4-1", "GPT-4.1", "MODEL_CHAT_GPT_4_1_2025_04_14", 2, 8),
95
+ o3: windsurfModel("o3", "o3", "MODEL_CHAT_O3", 2, 8),
96
+ "o3-high": windsurfModel("o3-high", "o3 High Reasoning", "MODEL_CHAT_O3_HIGH", 2, 8),
97
+ "gpt-5-codex": windsurfModel("gpt-5-codex", "GPT-5-Codex", "MODEL_CHAT_GPT_5_CODEX", 1.25, 10),
98
+ "gpt-5-codex-private": windsurfModel("gpt-5-codex-private", "GPT-5-Codex", "MODEL_PRIVATE_5", 1.25, 10),
99
+ "gpt-5-low": windsurfModel("gpt-5-low", "GPT-5 Low Thinking", "MODEL_PRIVATE_6", 1.25, 10),
100
+ "gpt-5-medium": windsurfModel("gpt-5-medium", "GPT-5 Medium Thinking", "MODEL_PRIVATE_7", 1.25, 10),
101
+ "gpt-5-high": windsurfModel("gpt-5-high", "GPT-5 High Thinking", "MODEL_PRIVATE_8", 1.25, 10),
102
+ // ── GPT-5.1 ─────────────────────────────────────────────────────────────
103
+ "gpt-5-1-none": windsurfModel("gpt-5-1-none", "GPT-5.1 No Thinking", "MODEL_PRIVATE_12", 1.25, 10),
104
+ "gpt-5-1-low": windsurfModel("gpt-5-1-low", "GPT-5.1 Low Thinking", "MODEL_PRIVATE_13", 1.25, 10),
105
+ "gpt-5-1-medium": windsurfModel("gpt-5-1-medium", "GPT-5.1 Medium Thinking", "MODEL_PRIVATE_14", 1.25, 10),
106
+ "gpt-5-1-high": windsurfModel("gpt-5-1-high", "GPT-5.1 High Thinking", "MODEL_PRIVATE_15", 1.25, 10),
107
+ "gpt-5-1-none-fast": windsurfModel("gpt-5-1-none-fast", "GPT-5.1 No Thinking Fast", "MODEL_PRIVATE_20", 2.5, 20),
108
+ "gpt-5-1-low-fast": windsurfModel("gpt-5-1-low-fast", "GPT-5.1 Low Thinking Fast", "MODEL_PRIVATE_21", 2.5, 20),
109
+ "gpt-5-1-medium-fast": windsurfModel("gpt-5-1-medium-fast", "GPT-5.1 Medium Thinking Fast", "MODEL_PRIVATE_22", 2.5, 20),
110
+ "gpt-5-1-high-fast": windsurfModel("gpt-5-1-high-fast", "GPT-5.1 High Thinking Fast", "MODEL_PRIVATE_23", 2.5, 20),
111
+ // ── GPT-5.1 Codex ───────────────────────────────────────────────────────
112
+ "gpt-5-1-codex-low": windsurfModel("gpt-5-1-codex-low", "GPT-5.1-Codex Low", "MODEL_GPT_5_1_CODEX_LOW", 1.25, 10),
113
+ "gpt-5-1-codex-max-high": windsurfModel("gpt-5-1-codex-max-high", "GPT-5.1-Codex Max High", "MODEL_GPT_5_1_CODEX_MAX_HIGH", 1.25, 10),
114
+ "gpt-5-1-codex-max-low": windsurfModel("gpt-5-1-codex-max-low", "GPT-5.1-Codex Max Low", "MODEL_GPT_5_1_CODEX_MAX_LOW", 1.25, 10),
115
+ "gpt-5-1-codex-max-medium": windsurfModel("gpt-5-1-codex-max-medium", "GPT-5.1-Codex Max Medium", "MODEL_GPT_5_1_CODEX_MAX_MEDIUM", 1.25, 10),
116
+ "gpt-5-1-codex-mini-low": windsurfModel("gpt-5-1-codex-mini-low", "GPT-5.1-Codex-Mini Low", "MODEL_GPT_5_1_CODEX_MINI_LOW", 0.25, 2),
117
+ "gpt-5-1-codex-mini": windsurfModel("gpt-5-1-codex-mini", "GPT-5.1-Codex-Mini", "MODEL_PRIVATE_19", 0.25, 2),
118
+ "gpt-5-1-codex-medium": windsurfModel("gpt-5-1-codex-medium", "GPT-5.1-Codex Medium", "MODEL_PRIVATE_9", 0.25, 2),
119
+ // ── GPT-5.2 ─────────────────────────────────────────────────────────────
120
+ "gpt-5-2-none": windsurfModel("gpt-5-2-none", "GPT-5.2 No Thinking", "MODEL_GPT_5_2_NONE", 1.75, 14),
121
+ "gpt-5-2-none-fast": windsurfModel("gpt-5-2-none-fast", "GPT-5.2 No Thinking Fast", "MODEL_GPT_5_2_NONE_PRIORITY", 3.5, 28),
122
+ "gpt-5-2-low": windsurfModel("gpt-5-2-low", "GPT-5.2 Low Thinking", "MODEL_GPT_5_2_LOW", 1.75, 14),
123
+ "gpt-5-2-low-fast": windsurfModel("gpt-5-2-low-fast", "GPT-5.2 Low Thinking Fast", "MODEL_GPT_5_2_LOW_PRIORITY", 3.5, 28),
124
+ "gpt-5-2-medium": windsurfModel("gpt-5-2-medium", "GPT-5.2 Medium Thinking", "MODEL_GPT_5_2_MEDIUM", 1.75, 14),
125
+ "gpt-5-2-medium-fast": windsurfModel("gpt-5-2-medium-fast", "GPT-5.2 Medium Thinking Fast", "MODEL_GPT_5_2_MEDIUM_PRIORITY", 3.5, 28),
126
+ "gpt-5-2-high": windsurfModel("gpt-5-2-high", "GPT-5.2 High Thinking", "MODEL_GPT_5_2_HIGH", 1.75, 14),
127
+ "gpt-5-2-high-fast": windsurfModel("gpt-5-2-high-fast", "GPT-5.2 High Thinking Fast", "MODEL_GPT_5_2_HIGH_PRIORITY", 3.5, 28),
128
+ "gpt-5-2-xhigh": windsurfModel("gpt-5-2-xhigh", "GPT-5.2 XHigh Thinking", "MODEL_GPT_5_2_XHIGH", 1.75, 14),
129
+ "gpt-5-2-xhigh-fast": windsurfModel("gpt-5-2-xhigh-fast", "GPT-5.2 XHigh Thinking Fast", "MODEL_GPT_5_2_XHIGH_PRIORITY", 3.5, 28),
130
+ // ── GPT-5.2 Codex ───────────────────────────────────────────────────────
131
+ "gpt-5-2-codex-low": windsurfModel("gpt-5-2-codex-low", "GPT-5.2-Codex Low", "MODEL_GPT_5_2_CODEX_LOW", 1.75, 14),
132
+ "gpt-5-2-codex-low-fast": windsurfModel("gpt-5-2-codex-low-fast", "GPT-5.2-Codex Low Fast", "MODEL_GPT_5_2_CODEX_LOW_PRIORITY", 3.5, 28),
133
+ "gpt-5-2-codex-medium": windsurfModel("gpt-5-2-codex-medium", "GPT-5.2-Codex Medium", "MODEL_GPT_5_2_CODEX_MEDIUM", 1.75, 14),
134
+ "gpt-5-2-codex-medium-fast": windsurfModel("gpt-5-2-codex-medium-fast", "GPT-5.2-Codex Medium Fast", "MODEL_GPT_5_2_CODEX_MEDIUM_PRIORITY", 3.5, 28),
135
+ "gpt-5-2-codex-high": windsurfModel("gpt-5-2-codex-high", "GPT-5.2-Codex High", "MODEL_GPT_5_2_CODEX_HIGH", 1.75, 14),
136
+ "gpt-5-2-codex-high-fast": windsurfModel("gpt-5-2-codex-high-fast", "GPT-5.2-Codex High Fast", "MODEL_GPT_5_2_CODEX_HIGH_PRIORITY", 3.5, 28),
137
+ "gpt-5-2-codex-xhigh": windsurfModel("gpt-5-2-codex-xhigh", "GPT-5.2-Codex XHigh", "MODEL_GPT_5_2_CODEX_XHIGH", 1.75, 14),
138
+ "gpt-5-2-codex-xhigh-fast": windsurfModel("gpt-5-2-codex-xhigh-fast", "GPT-5.2-Codex XHigh Fast", "MODEL_GPT_5_2_CODEX_XHIGH_PRIORITY", 3.5, 28),
139
+ // ── GPT-5.3 Codex ───────────────────────────────────────────────────────
140
+ "gpt-5-3-codex-low": windsurfModel("gpt-5-3-codex-low", "GPT-5.3-Codex Low", "gpt-5-3-codex-low", 1.75, 14),
141
+ "gpt-5-3-codex-low-fast": windsurfModel("gpt-5-3-codex-low-fast", "GPT-5.3-Codex Low Fast", "gpt-5-3-codex-low-priority", 3.5, 28),
142
+ "gpt-5-3-codex-medium": windsurfModel("gpt-5-3-codex-medium", "GPT-5.3-Codex Medium", "gpt-5-3-codex-medium", 1.75, 14),
143
+ "gpt-5-3-codex-medium-fast": windsurfModel("gpt-5-3-codex-medium-fast", "GPT-5.3-Codex Medium Fast", "gpt-5-3-codex-medium-priority", 3.5, 28),
144
+ "gpt-5-3-codex-high": windsurfModel("gpt-5-3-codex-high", "GPT-5.3-Codex High", "gpt-5-3-codex-high", 1.75, 14),
145
+ "gpt-5-3-codex-high-fast": windsurfModel("gpt-5-3-codex-high-fast", "GPT-5.3-Codex High Fast", "gpt-5-3-codex-high-priority", 3.5, 28),
146
+ "gpt-5-3-codex-xhigh": windsurfModel("gpt-5-3-codex-xhigh", "GPT-5.3-Codex X-High", "gpt-5-3-codex-xhigh", 1.75, 14),
147
+ "gpt-5-3-codex-xhigh-fast": windsurfModel("gpt-5-3-codex-xhigh-fast", "GPT-5.3-Codex XHigh Fast", "gpt-5-3-codex-xhigh-priority", 3.5, 28),
148
+ "gpt-5-3-codex-spark-medium": windsurfModel("gpt-5-3-codex-spark-medium", "GPT-5.3-Codex Spark Medium", "gpt-5-3-codex-spark-medium", 1.75, 14),
149
+ // ── GPT-5.4 ─────────────────────────────────────────────────────────────
150
+ "gpt-5-4-none": windsurfModel("gpt-5-4-none", "GPT-5.4 No Thinking", "gpt-5-4-none", 2.5, 15),
151
+ "gpt-5-4-none-fast": windsurfModel("gpt-5-4-none-fast", "GPT-5.4 No Thinking Fast", "gpt-5-4-none-priority", 5, 30),
152
+ "gpt-5-4-low": windsurfModel("gpt-5-4-low", "GPT-5.4 Low Thinking", "gpt-5-4-low", 2.5, 15),
153
+ "gpt-5-4-low-fast": windsurfModel("gpt-5-4-low-fast", "GPT-5.4 Low Thinking Fast", "gpt-5-4-low-priority", 5, 30),
154
+ "gpt-5-4-medium": windsurfModel("gpt-5-4-medium", "GPT-5.4 Medium Thinking", "gpt-5-4-medium", 2.5, 15),
155
+ "gpt-5-4-medium-fast": windsurfModel("gpt-5-4-medium-fast", "GPT-5.4 Medium Thinking Fast", "gpt-5-4-medium-priority", 5, 30),
156
+ "gpt-5-4-high": windsurfModel("gpt-5-4-high", "GPT-5.4 High Thinking", "gpt-5-4-high", 2.5, 15),
157
+ "gpt-5-4-high-fast": windsurfModel("gpt-5-4-high-fast", "GPT-5.4 High Thinking Fast", "gpt-5-4-high-priority", 5, 30),
158
+ "gpt-5-4-xhigh": windsurfModel("gpt-5-4-xhigh", "GPT-5.4 XHigh Thinking", "gpt-5-4-xhigh", 2.5, 15),
159
+ "gpt-5-4-xhigh-fast": windsurfModel("gpt-5-4-xhigh-fast", "GPT-5.4 XHigh Thinking Fast", "gpt-5-4-xhigh-priority", 5, 30),
160
+ // ── GPT-5.4 Mini ────────────────────────────────────────────────────────
161
+ "gpt-5-4-mini-low": windsurfModel("gpt-5-4-mini-low", "GPT-5.4 Mini Low Thinking", "gpt-5-4-mini-low", 0.75, 4.5),
162
+ "gpt-5-4-mini-medium": windsurfModel("gpt-5-4-mini-medium", "GPT-5.4 Mini Medium Thinking", "gpt-5-4-mini-medium", 0.75, 4.5),
163
+ "gpt-5-4-mini-high": windsurfModel("gpt-5-4-mini-high", "GPT-5.4 Mini High Thinking", "gpt-5-4-mini-high", 0.75, 4.5),
164
+ "gpt-5-4-mini-xhigh": windsurfModel("gpt-5-4-mini-xhigh", "GPT-5.4 Mini XHigh Thinking", "gpt-5-4-mini-xhigh", 0.75, 4.5),
165
+ // ── GPT-5.5 ─────────────────────────────────────────────────────────────
166
+ "gpt-5-5-none": windsurfModel("gpt-5-5-none", "GPT-5.5 No Thinking", "gpt-5-5-none", 2.5, 15),
167
+ "gpt-5-5-none-fast": windsurfModel("gpt-5-5-none-fast", "GPT-5.5 No Thinking Fast", "gpt-5-5-none-priority", 6.25, 37.5),
168
+ "gpt-5-5-low": windsurfModel("gpt-5-5-low", "GPT-5.5 Low Thinking", "gpt-5-5-low", 2.5, 15),
169
+ "gpt-5-5-low-fast": windsurfModel("gpt-5-5-low-fast", "GPT-5.5 Low Thinking Fast", "gpt-5-5-low-priority", 6.25, 37.5),
170
+ "gpt-5-5-medium": windsurfModel("gpt-5-5-medium", "GPT-5.5 Medium Thinking", "gpt-5-5-medium", 2.5, 15),
171
+ "gpt-5-5-medium-fast": windsurfModel("gpt-5-5-medium-fast", "GPT-5.5 Medium Thinking Fast", "gpt-5-5-medium-priority", 6.25, 37.5),
172
+ "gpt-5-5-high": windsurfModel("gpt-5-5-high", "GPT-5.5 High Thinking", "gpt-5-5-high", 2.5, 15),
173
+ "gpt-5-5-high-fast": windsurfModel("gpt-5-5-high-fast", "GPT-5.5 High Thinking Fast", "gpt-5-5-high-priority", 6.25, 37.5),
174
+ "gpt-5-5-xhigh": windsurfModel("gpt-5-5-xhigh", "GPT-5.5 XHigh Thinking", "gpt-5-5-xhigh", 2.5, 15),
175
+ "gpt-5-5-xhigh-fast": windsurfModel("gpt-5-5-xhigh-fast", "GPT-5.5 XHigh Thinking Fast", "gpt-5-5-xhigh-priority", 6.25, 37.5),
176
+ "gpt-5-5-review": windsurfModel("gpt-5-5-review", "GPT 5.5 Review", "gpt-5-5-review", 2.5, 15),
177
+ // ── GPT-OSS ─────────────────────────────────────────────────────────────
178
+ "gpt-oss-120b": windsurfModel("gpt-oss-120b", "GPT-OSS 120B Medium Thinking", "MODEL_GPT_OSS_120B", 0.15, 0.6),
179
+ // ── Google ──────────────────────────────────────────────────────────────
180
+ "gemini-2-5-pro": windsurfModel("gemini-2-5-pro", "Gemini 2.5 Pro", "MODEL_GOOGLE_GEMINI_2_5_PRO", 1.25, 10),
181
+ "gemini-3-0-flash-high": windsurfModel("gemini-3-0-flash-high", "Gemini 3 Flash High", "MODEL_GOOGLE_GEMINI_3_0_FLASH_HIGH", 0.5, 3),
182
+ "gemini-3-0-flash-low": windsurfModel("gemini-3-0-flash-low", "Gemini 3 Flash Low", "MODEL_GOOGLE_GEMINI_3_0_FLASH_LOW", 0.5, 3),
183
+ "gemini-3-0-flash-medium": windsurfModel("gemini-3-0-flash-medium", "Gemini 3 Flash Medium", "MODEL_GOOGLE_GEMINI_3_0_FLASH_MEDIUM", 0.5, 3),
184
+ "gemini-3-0-flash-minimal": windsurfModel("gemini-3-0-flash-minimal", "Gemini 3 Flash Minimal", "MODEL_GOOGLE_GEMINI_3_0_FLASH_MINIMAL", 0.5, 3),
185
+ "gemini-3-1-pro-high": windsurfModel("gemini-3-1-pro-high", "Gemini 3.1 Pro High Thinking", "gemini-3-1-pro-high", 2, 12),
186
+ "gemini-3-1-pro-low": windsurfModel("gemini-3-1-pro-low", "Gemini 3.1 Pro Low Thinking", "gemini-3-1-pro-low", 2, 12),
187
+ // ── xAI ─────────────────────────────────────────────────────────────────
188
+ "xai-grok-3": windsurfModel("xai-grok-3", "xAI Grok-3", "MODEL_XAI_GROK_3", 3, 15),
189
+ "xai-grok-3-mini-thinking": windsurfModel("xai-grok-3-mini-thinking", "xAI Grok-3 mini Thinking", "MODEL_XAI_GROK_3_MINI_REASONING", 0.3, 0.5, true),
190
+ "grok-code-fast-1": windsurfModel("grok-code-fast-1", "Grok Code Fast 1", "MODEL_PRIVATE_4", 0.2, 1.5),
191
+ // ── DeepSeek ────────────────────────────────────────────────────────────
192
+ "deepseek-v4": windsurfModel("deepseek-v4", "DeepSeek V4", "deepseek-v4", 1.74, 3.48),
193
+ // ── Kimi ────────────────────────────────────────────────────────────────
194
+ "kimi-k2-5": windsurfModel("kimi-k2-5", "Kimi K2.5", "kimi-k2-5", 0.6, 3),
195
+ "kimi-k2-6": windsurfModel("kimi-k2-6", "Kimi K2.6", "kimi-k2-6", 0.95, 4),
196
+ // ── GLM ─────────────────────────────────────────────────────────────────
197
+ "glm-5-1": windsurfModel("glm-5-1", "GLM-5.1", "glm-5-1", 1.4, 4.4),
198
+ // ── MiniMax ─────────────────────────────────────────────────────────────
199
+ "minimax-m2-5": windsurfModel("minimax-m2-5", "Minimax M2.5", "minimax-m2-5", 0.3, 1.2),
200
+ // ── SWE ─────────────────────────────────────────────────────────────────
201
+ "swe-1-5": windsurfModel("swe-1-5", "SWE-1.5 Fast", "MODEL_SWE_1_5", 0.3, 1.5),
202
+ "swe-1-5-slow": windsurfModel("swe-1-5-slow", "SWE-1.5", "MODEL_SWE_1_5_SLOW", 0, 0),
203
+ "swe-1-6": windsurfModel("swe-1-6", "SWE-1.6", "swe-1-6", 0, 0),
204
+ "swe-1-6-fast": windsurfModel("swe-1-6-fast", "SWE-1.6 Fast", "swe-1-6-fast", 0.3, 1.5),
205
+ "swe-check": windsurfModel("swe-check", "SWE-check", "swe-check", 0, 0),
206
+ };
@@ -0,0 +1,21 @@
1
+ export declare function encodeVarint(value: number): Uint8Array;
2
+ export declare function encodeVarintField(field: number, value: number): Uint8Array;
3
+ export declare function encodeFixed64Field(field: number, value: number): Uint8Array;
4
+ export declare function encodeBytesField(field: number, bytes: Uint8Array): Uint8Array;
5
+ export declare function encodeStringField(field: number, str: string): Uint8Array;
6
+ export declare function encodeMessageField(field: number, msg: Uint8Array): Uint8Array;
7
+ /**
8
+ * Wrap a protobuf body into a Connect unary request frame:
9
+ * 1 flag byte (0x00) + 4-byte big-endian length + body.
10
+ */
11
+ export declare function encodeConnectFrame(body: Uint8Array): Uint8Array;
12
+ export declare const CONNECT_FLAG_EOS = 2;
13
+ /**
14
+ * Stream parser: yields { flag, body } for each Connect frame in the
15
+ * accumulated buffer. Call repeatedly as bytes arrive. Call with empty
16
+ * accumulator + new chunk; returns { frames, remainder } for incremental use.
17
+ */
18
+ export declare function parseConnectFrames(buf: Uint8Array): Array<{
19
+ flag: number;
20
+ body: Uint8Array;
21
+ }>;
package/dist/proto.js ADDED
@@ -0,0 +1,100 @@
1
+ // proto.ts — minimal protobuf wire encode helpers + Connect framing
2
+ //
3
+ // Mirror of the Python research decoders:
4
+ // opencode-windsurf-auth/research/decode_request.py
5
+ // opencode-windsurf-auth/research/connect_decode.py
6
+ //
7
+ // Wire types: 0=varint, 1=fixed64, 2=length-delimited, 5=fixed32
8
+ // tag = (field_number << 3) | wire_type
9
+ // ---------------------------------------------------------------------------
10
+ // Varint encode
11
+ // ---------------------------------------------------------------------------
12
+ export function encodeVarint(value) {
13
+ const bytes = [];
14
+ let v = value;
15
+ while (v >= 0x80) {
16
+ bytes.push((v & 0x7f) | 0x80);
17
+ v >>>= 7;
18
+ }
19
+ bytes.push(v & 0x7f);
20
+ return new Uint8Array(bytes);
21
+ }
22
+ export function encodeVarintField(field, value) {
23
+ const tag = encodeVarint((field << 3) | 0); // wire type 0
24
+ const val = encodeVarint(value);
25
+ return concat(tag, val);
26
+ }
27
+ export function encodeFixed64Field(field, value) {
28
+ const tag = encodeVarint((field << 3) | 1); // wire type 1
29
+ const buf = new ArrayBuffer(8);
30
+ const view = new DataView(buf);
31
+ view.setFloat64(0, value, true); // little-endian
32
+ return concat(tag, new Uint8Array(buf));
33
+ }
34
+ export function encodeBytesField(field, bytes) {
35
+ const tag = encodeVarint((field << 3) | 2); // wire type 2
36
+ const len = encodeVarint(bytes.byteLength);
37
+ return concat(tag, len, bytes);
38
+ }
39
+ export function encodeStringField(field, str) {
40
+ return encodeBytesField(field, new TextEncoder().encode(str));
41
+ }
42
+ // encodeMessageField wraps encoded sub-message bytes into a length-delimited field
43
+ export function encodeMessageField(field, msg) {
44
+ return encodeBytesField(field, msg);
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Helpers
48
+ // ---------------------------------------------------------------------------
49
+ function concat(...arrays) {
50
+ const total = arrays.reduce((s, a) => s + a.byteLength, 0);
51
+ const result = new Uint8Array(total);
52
+ let offset = 0;
53
+ for (const a of arrays) {
54
+ result.set(a, offset);
55
+ offset += a.byteLength;
56
+ }
57
+ return result;
58
+ }
59
+ // ---------------------------------------------------------------------------
60
+ // Connect framing
61
+ // ---------------------------------------------------------------------------
62
+ /**
63
+ * Wrap a protobuf body into a Connect unary request frame:
64
+ * 1 flag byte (0x00) + 4-byte big-endian length + body.
65
+ */
66
+ export function encodeConnectFrame(body) {
67
+ const header = new Uint8Array(5);
68
+ header[0] = 0x00; // no flags
69
+ const len = body.byteLength;
70
+ header[1] = (len >>> 24) & 0xff;
71
+ header[2] = (len >>> 16) & 0xff;
72
+ header[3] = (len >>> 8) & 0xff;
73
+ header[4] = len & 0xff;
74
+ return concat(header, body);
75
+ }
76
+ // Response frame flags
77
+ export const CONNECT_FLAG_EOS = 0x02;
78
+ /**
79
+ * Stream parser: yields { flag, body } for each Connect frame in the
80
+ * accumulated buffer. Call repeatedly as bytes arrive. Call with empty
81
+ * accumulator + new chunk; returns { frames, remainder } for incremental use.
82
+ */
83
+ export function parseConnectFrames(buf) {
84
+ const frames = [];
85
+ let i = 0;
86
+ while (i + 5 <= buf.byteLength) {
87
+ const flag = buf[i];
88
+ const bodyLen = ((buf[i + 1] << 24) |
89
+ (buf[i + 2] << 16) |
90
+ (buf[i + 3] << 8) |
91
+ buf[i + 4]) >>>
92
+ 0;
93
+ if (i + 5 + bodyLen > buf.byteLength)
94
+ break; // not enough data yet
95
+ const body = buf.slice(i + 5, i + 5 + bodyLen);
96
+ frames.push({ flag, body });
97
+ i += 5 + bodyLen;
98
+ }
99
+ return frames;
100
+ }
@@ -0,0 +1,22 @@
1
+ type ProxyEvent = {
2
+ type: "reasoning";
3
+ text: string;
4
+ } | {
5
+ type: "text";
6
+ text: string;
7
+ } | {
8
+ type: "finish";
9
+ model?: string;
10
+ input_tokens?: number;
11
+ output_tokens?: number;
12
+ msg_id?: string;
13
+ };
14
+ type StreamResult = {
15
+ ok: true;
16
+ events: AsyncGenerator<ProxyEvent>;
17
+ cleanup: () => Promise<void>;
18
+ } | {
19
+ ok: false;
20
+ };
21
+ export declare function launchProxyStream(modelId: string, prompt: string): Promise<StreamResult>;
22
+ export {};
@@ -0,0 +1,151 @@
1
+ import { spawn } from "bun";
2
+ import { resolve } from "node:path";
3
+ // ── Constants ──────────────────────────────────────────────────────────────
4
+ const RESEARCH_DIR = "/Users/nghiango-mbp/opencode-learn/opencode-windsurf-auth/research";
5
+ const THINKING_PROXY_PY = resolve(RESEARCH_DIR, "thinking_proxy.py");
6
+ const MITMPROXY_CA = resolve(process.env.HOME ?? "/tmp", ".mitmproxy/mitmproxy-ca-cert.pem");
7
+ const PROXY_STREAM_TIMEOUT_MS = 180_000;
8
+ // ── Main entry: launch proxy + devin, return an event generator ────────────
9
+ export async function launchProxyStream(modelId, prompt) {
10
+ const mitmdump = Bun.which("mitmdump");
11
+ if (!mitmdump)
12
+ return { ok: false };
13
+ const devinPath = Bun.which("devin");
14
+ if (!devinPath)
15
+ throw new Error("devin CLI not found — run `devin /login` first");
16
+ const port = await findFreePort();
17
+ const sinkPath = `/tmp/windsurf_thinking_${port}.jsonl`;
18
+ // Remove stale sink from previous run (if any)
19
+ try {
20
+ await Bun.file(sinkPath).delete();
21
+ }
22
+ catch { }
23
+ // ── 1. Start mitmdump proxy ───────────────────────────────────────────
24
+ const proxyProc = spawn({
25
+ cmd: [mitmdump, "-q", "-p", String(port), "-s", THINKING_PROXY_PY],
26
+ env: { ...Bun.env, WINDSURF_THINKING_SINK: sinkPath },
27
+ stdout: "pipe",
28
+ stderr: "pipe",
29
+ });
30
+ // Wait for proxy to be ready
31
+ let proxyReady = false;
32
+ for (let i = 0; i < 50; i++) {
33
+ try {
34
+ const resp = await fetch(`http://127.0.0.1:${port}/`, { signal: AbortSignal.timeout(200) });
35
+ await resp.text();
36
+ proxyReady = true;
37
+ break;
38
+ }
39
+ catch {
40
+ await new Promise((r) => setTimeout(r, 100));
41
+ }
42
+ }
43
+ if (!proxyReady) {
44
+ proxyProc.kill();
45
+ await proxyProc.exited.catch(() => { });
46
+ try {
47
+ await Bun.file(sinkPath).delete();
48
+ }
49
+ catch { }
50
+ return { ok: false };
51
+ }
52
+ // ── 2. Spawn devin through the proxy ──────────────────────────────────
53
+ const proxyUrl = `http://127.0.0.1:${port}`;
54
+ const devinProc = spawn({
55
+ cmd: [devinPath, "--permission-mode", "bypass", "--model", modelId, "-p", "--", prompt],
56
+ env: {
57
+ ...Bun.env,
58
+ HTTPS_PROXY: proxyUrl,
59
+ HTTP_PROXY: proxyUrl,
60
+ SSL_CERT_FILE: MITMPROXY_CA,
61
+ },
62
+ stdout: "pipe",
63
+ stderr: "pipe",
64
+ });
65
+ // ── 3. Build generator + cleanup ─────────────────────────────────────
66
+ const abort = new AbortController();
67
+ const events = tailSink(sinkPath, abort.signal, devinProc, PROXY_STREAM_TIMEOUT_MS);
68
+ const cleanup = async () => {
69
+ abort.abort();
70
+ devinProc.kill();
71
+ proxyProc.kill();
72
+ await devinProc.exited.catch(() => { });
73
+ await proxyProc.exited.catch(() => { });
74
+ try {
75
+ await Bun.file(sinkPath).delete();
76
+ }
77
+ catch { }
78
+ };
79
+ return { ok: true, events, cleanup };
80
+ }
81
+ // ── Port allocation ────────────────────────────────────────────────────────
82
+ async function findFreePort() {
83
+ const server = Bun.listen({ port: 0, hostname: "127.0.0.1", socket: { data() { } } });
84
+ const port = server.port;
85
+ server.stop();
86
+ return port;
87
+ }
88
+ // ── Tail a growing NDJSON file, yielding parsed events ────────────────────
89
+ async function* tailSink(path, signal, devinProc, deadlineMs) {
90
+ let offset = 0;
91
+ let devinExited = false;
92
+ const deadline = deadlineMs ? Date.now() + deadlineMs : undefined;
93
+ if (devinProc) {
94
+ devinProc.exited
95
+ .then(() => {
96
+ devinExited = true;
97
+ })
98
+ .catch(() => {
99
+ devinExited = true;
100
+ });
101
+ }
102
+ while (!signal.aborted) {
103
+ if (deadline && Date.now() >= deadline)
104
+ return;
105
+ const file = Bun.file(path);
106
+ const exists = await file.exists();
107
+ if (!exists) {
108
+ if (devinExited)
109
+ return;
110
+ await delay(100, signal);
111
+ continue;
112
+ }
113
+ const text = await file.text();
114
+ const chunk = text.slice(offset);
115
+ if (chunk.length > 0) {
116
+ offset = text.length;
117
+ for (const line of chunk.split("\n")) {
118
+ if (!line)
119
+ continue;
120
+ const event = parseJsonSafe(line);
121
+ if (!event)
122
+ continue;
123
+ yield event;
124
+ if (event.type === "finish")
125
+ return;
126
+ }
127
+ }
128
+ if (devinExited)
129
+ return;
130
+ await delay(80, signal);
131
+ }
132
+ }
133
+ // ── Helpers ────────────────────────────────────────────────────────────────
134
+ function delay(ms, signal) {
135
+ return new Promise((resolve) => {
136
+ if (signal?.aborted) {
137
+ resolve();
138
+ return;
139
+ }
140
+ const t = setTimeout(resolve, ms);
141
+ signal?.addEventListener("abort", () => { clearTimeout(t); resolve(); }, { once: true });
142
+ });
143
+ }
144
+ function parseJsonSafe(text) {
145
+ try {
146
+ return JSON.parse(text);
147
+ }
148
+ catch {
149
+ return null;
150
+ }
151
+ }
@@ -0,0 +1,16 @@
1
+ import type { LanguageModelV3, LanguageModelV3CallOptions, LanguageModelV3GenerateResult, LanguageModelV3StreamResult } from "@ai-sdk/provider";
2
+ export declare class WindsurfLanguageModel implements LanguageModelV3 {
3
+ readonly specificationVersion: "v3";
4
+ readonly provider: string;
5
+ readonly modelId: string;
6
+ readonly supportedUrls: Record<string, RegExp[]>;
7
+ constructor(modelId: string);
8
+ doGenerate(options: LanguageModelV3CallOptions): Promise<LanguageModelV3GenerateResult>;
9
+ doStream(options: LanguageModelV3CallOptions): Promise<LanguageModelV3StreamResult>;
10
+ }
11
+ export declare function createWindsurf(opts: {
12
+ name: string;
13
+ }): {
14
+ languageModel(modelId: string): LanguageModelV3;
15
+ };
16
+ export * as WindsurfProvider from ".";