@ai-ide-bridge/cli 1.0.5 → 1.1.1

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.
Files changed (140) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/dist/commands/configure.js +78 -10
  3. package/dist/commands/daemon.d.ts +1 -0
  4. package/dist/commands/daemon.js +107 -13
  5. package/dist/commands/doctor.js +1 -1
  6. package/dist/commands/init.js +70 -5
  7. package/dist/commands/login.d.ts +1 -0
  8. package/dist/commands/login.js +62 -0
  9. package/dist/commands/logout.d.ts +1 -0
  10. package/dist/commands/logout.js +12 -0
  11. package/dist/commands/start.js +4 -4
  12. package/dist/core/config.d.ts +4 -0
  13. package/dist/core/config.js +43 -0
  14. package/dist/core/daemon-session.d.ts +14 -0
  15. package/dist/core/daemon-session.js +179 -0
  16. package/dist/core/daemon.d.ts +16 -0
  17. package/dist/core/daemon.js +168 -0
  18. package/dist/core/formatter.d.ts +3 -0
  19. package/dist/core/formatter.js +44 -0
  20. package/dist/core/index.d.ts +9 -0
  21. package/dist/core/index.js +9 -0
  22. package/dist/core/parser.d.ts +164 -0
  23. package/dist/core/parser.js +37 -0
  24. package/dist/core/registry.d.ts +16 -0
  25. package/dist/core/registry.js +53 -0
  26. package/dist/core/server.d.ts +19 -0
  27. package/dist/core/server.js +185 -0
  28. package/dist/core/session.d.ts +11 -0
  29. package/dist/core/session.js +39 -0
  30. package/dist/core/types.d.ts +166 -0
  31. package/dist/core/types.js +44 -0
  32. package/dist/index.js +22 -5
  33. package/dist/oauth/device-flow.d.ts +12 -0
  34. package/dist/oauth/device-flow.js +93 -0
  35. package/dist/oauth/flow.d.ts +11 -0
  36. package/dist/oauth/flow.js +75 -0
  37. package/dist/oauth/index.d.ts +6 -0
  38. package/dist/oauth/index.js +5 -0
  39. package/dist/oauth/lifecycle.d.ts +13 -0
  40. package/dist/oauth/lifecycle.js +56 -0
  41. package/dist/oauth/providers.d.ts +2 -0
  42. package/dist/oauth/providers.js +19 -0
  43. package/dist/oauth/storage-file.d.ts +2 -0
  44. package/dist/oauth/storage-file.js +68 -0
  45. package/dist/oauth/storage.d.ts +2 -0
  46. package/dist/oauth/storage.js +4 -0
  47. package/dist/oauth/types.d.ts +44 -0
  48. package/dist/oauth/types.js +1 -0
  49. package/dist/plugins/copilot/auth.d.ts +7 -0
  50. package/dist/plugins/copilot/auth.js +30 -0
  51. package/dist/plugins/copilot/index.d.ts +5 -0
  52. package/dist/plugins/copilot/index.js +4 -0
  53. package/dist/plugins/copilot/plugin.d.ts +8 -0
  54. package/dist/plugins/copilot/plugin.js +29 -0
  55. package/dist/plugins/copilot/session.d.ts +8 -0
  56. package/dist/plugins/copilot/session.js +115 -0
  57. package/dist/plugins/copilot/tools.d.ts +10 -0
  58. package/dist/plugins/copilot/tools.js +10 -0
  59. package/dist/plugins/copilot/types.d.ts +15 -0
  60. package/dist/plugins/copilot/types.js +27 -0
  61. package/dist/plugins/cursor/index.d.ts +2 -0
  62. package/dist/plugins/cursor/index.js +2 -0
  63. package/dist/plugins/cursor/plugin.d.ts +8 -0
  64. package/dist/plugins/cursor/plugin.js +36 -0
  65. package/dist/plugins/cursor/session.d.ts +11 -0
  66. package/dist/plugins/cursor/session.js +69 -0
  67. package/dist/plugins/cursor/tools.d.ts +11 -0
  68. package/dist/plugins/cursor/tools.js +13 -0
  69. package/dist/plugins/windsurf/auth.d.ts +3 -0
  70. package/dist/plugins/windsurf/auth.js +20 -0
  71. package/dist/plugins/windsurf/daemon.d.ts +6 -0
  72. package/dist/plugins/windsurf/daemon.js +16 -0
  73. package/dist/plugins/windsurf/index.d.ts +5 -0
  74. package/dist/plugins/windsurf/index.js +4 -0
  75. package/dist/plugins/windsurf/models.d.ts +2 -0
  76. package/dist/plugins/windsurf/models.js +42 -0
  77. package/dist/plugins/windsurf/plugin.d.ts +8 -0
  78. package/dist/plugins/windsurf/plugin.js +31 -0
  79. package/dist/plugins/windsurf/session.d.ts +5 -0
  80. package/dist/plugins/windsurf/session.js +6 -0
  81. package/dist/plugins/windsurf/tools.d.ts +3 -0
  82. package/dist/plugins/windsurf/tools.js +10 -0
  83. package/dist/plugins/windsurf/types.d.ts +22 -0
  84. package/dist/plugins/windsurf/types.js +1 -0
  85. package/dist/utils/config.d.ts +1 -1
  86. package/dist/utils/config.js +1 -1
  87. package/dist/utils/opencode.d.ts +3 -1
  88. package/dist/utils/opencode.js +3 -3
  89. package/dist/utils/platform.d.ts +1 -0
  90. package/dist/utils/platform.js +3 -0
  91. package/package.json +3 -5
  92. package/src/commands/configure.ts +107 -12
  93. package/src/commands/daemon.ts +112 -13
  94. package/src/commands/doctor.ts +1 -1
  95. package/src/commands/init.ts +72 -5
  96. package/src/commands/login.ts +98 -0
  97. package/src/commands/logout.ts +15 -0
  98. package/src/commands/start.ts +4 -4
  99. package/src/core/config.ts +45 -0
  100. package/src/core/daemon-session.ts +199 -0
  101. package/src/core/daemon.ts +206 -0
  102. package/src/core/formatter.ts +56 -0
  103. package/src/core/index.ts +9 -0
  104. package/src/core/parser.ts +47 -0
  105. package/src/core/registry.ts +62 -0
  106. package/src/core/server.ts +211 -0
  107. package/src/core/session.ts +54 -0
  108. package/src/core/types.ts +100 -0
  109. package/src/index.ts +22 -4
  110. package/src/oauth/device-flow.ts +111 -0
  111. package/src/oauth/flow.ts +94 -0
  112. package/src/oauth/index.ts +6 -0
  113. package/src/oauth/lifecycle.ts +77 -0
  114. package/src/oauth/providers.ts +21 -0
  115. package/src/oauth/storage-file.ts +77 -0
  116. package/src/oauth/storage.ts +6 -0
  117. package/src/oauth/types.ts +50 -0
  118. package/src/plugins/copilot/auth.ts +39 -0
  119. package/src/plugins/copilot/index.ts +5 -0
  120. package/src/plugins/copilot/plugin.ts +31 -0
  121. package/src/plugins/copilot/session.ts +130 -0
  122. package/src/plugins/copilot/tools.ts +21 -0
  123. package/src/plugins/copilot/types.ts +43 -0
  124. package/src/plugins/cursor/index.ts +2 -0
  125. package/src/plugins/cursor/plugin.ts +37 -0
  126. package/src/plugins/cursor/session.ts +78 -0
  127. package/src/plugins/cursor/tools.ts +25 -0
  128. package/src/plugins/windsurf/auth.ts +23 -0
  129. package/src/plugins/windsurf/daemon.ts +24 -0
  130. package/src/plugins/windsurf/index.ts +5 -0
  131. package/src/plugins/windsurf/models.ts +44 -0
  132. package/src/plugins/windsurf/plugin.ts +34 -0
  133. package/src/plugins/windsurf/session.ts +8 -0
  134. package/src/plugins/windsurf/tools.ts +13 -0
  135. package/src/plugins/windsurf/types.ts +24 -0
  136. package/src/utils/config.ts +1 -1
  137. package/src/utils/opencode.ts +4 -3
  138. package/src/utils/platform.ts +3 -0
  139. package/test/configure.test.ts +19 -4
  140. package/test/daemon.test.ts +224 -0
@@ -0,0 +1,130 @@
1
+ import type { BridgeSession, Message, ToolDefinition, StreamChunk } from '../../core/index.js';
2
+ import { translateTools } from './tools.js';
3
+
4
+ const COPILOT_API_BASE = 'https://api.github.com';
5
+
6
+ export class CopilotBridgeSession implements BridgeSession {
7
+ private token: string;
8
+ private modelId: string;
9
+
10
+ constructor(token: string, modelId: string) {
11
+ this.token = token;
12
+ this.modelId = modelId;
13
+ }
14
+
15
+ async *send(messages: Message[], tools?: ToolDefinition[]): AsyncIterable<StreamChunk> {
16
+ const body = {
17
+ model: this.modelId,
18
+ messages: messages.map((m) => ({
19
+ role: m.role,
20
+ content: m.content ?? '',
21
+ ...(m.tool_calls && { tool_calls: m.tool_calls }),
22
+ ...(m.tool_call_id && { tool_call_id: m.tool_call_id }),
23
+ })),
24
+ ...(tools && { tools: translateTools(tools) }),
25
+ stream: true,
26
+ };
27
+
28
+ try {
29
+ const response = await fetch(`${COPILOT_API_BASE}/copilot_internal/chat/completions`, {
30
+ method: 'POST',
31
+ headers: {
32
+ Authorization: `Bearer ${this.token}`,
33
+ 'Content-Type': 'application/json',
34
+ Accept: 'text/event-stream',
35
+ },
36
+ body: JSON.stringify(body),
37
+ });
38
+
39
+ if (!response.ok) {
40
+ const errorText = await response.text();
41
+ yield {
42
+ type: 'error',
43
+ content: `Copilot API error: ${response.status} ${errorText}`,
44
+ finishReason: 'error',
45
+ };
46
+ return;
47
+ }
48
+
49
+ const reader = response.body?.getReader();
50
+ if (!reader) {
51
+ yield { type: 'error', content: 'No response body', finishReason: 'error' };
52
+ return;
53
+ }
54
+
55
+ const decoder = new TextDecoder();
56
+ let buffer = '';
57
+ let finished = false;
58
+
59
+ while (true) {
60
+ const { done, value } = await reader.read();
61
+ if (done) break;
62
+
63
+ buffer += decoder.decode(value, { stream: true });
64
+ const lines = buffer.split(/\r?\n/);
65
+ buffer = lines.pop() ?? '';
66
+
67
+ for (const line of lines) {
68
+ const trimmed = line.trim();
69
+ if (!trimmed || !trimmed.startsWith('data:')) continue;
70
+
71
+ const data = trimmed.slice(5).trim();
72
+ if (data === '[DONE]') {
73
+ if (!finished) {
74
+ yield { type: 'done', finishReason: 'stop' };
75
+ }
76
+ return;
77
+ }
78
+
79
+ try {
80
+ const parsed = JSON.parse(data);
81
+ const delta = parsed.choices?.[0]?.delta;
82
+
83
+ if (delta?.content) {
84
+ yield { type: 'text', content: delta.content };
85
+ }
86
+
87
+ if (delta?.tool_calls) {
88
+ for (const tc of delta.tool_calls) {
89
+ yield {
90
+ type: 'tool_call',
91
+ toolCall: {
92
+ id: tc.id ?? '',
93
+ name: tc.function?.name ?? '',
94
+ arguments: tc.function?.arguments ?? '',
95
+ },
96
+ };
97
+ }
98
+ }
99
+
100
+ if (parsed.choices?.[0]?.finish_reason && !finished) {
101
+ const reason = parsed.choices[0].finish_reason;
102
+ const finishReason =
103
+ reason === 'stop'
104
+ ? 'stop'
105
+ : reason === 'tool_calls'
106
+ ? 'tool_calls'
107
+ : reason === 'length'
108
+ ? 'length'
109
+ : 'error';
110
+ yield {
111
+ type: 'done',
112
+ finishReason,
113
+ };
114
+ finished = true;
115
+ }
116
+ } catch {
117
+ // Skip malformed SSE data
118
+ }
119
+ }
120
+ }
121
+ } catch (e) {
122
+ const msg = e instanceof Error ? e.message : String(e);
123
+ yield { type: 'error', content: msg, finishReason: 'error' };
124
+ }
125
+ }
126
+
127
+ async dispose(): Promise<void> {
128
+ // No persistent connections to dispose
129
+ }
130
+ }
@@ -0,0 +1,21 @@
1
+ import type { ToolDefinition } from '../../core/index.js';
2
+
3
+ export interface CopilotTool {
4
+ type: 'function';
5
+ function: {
6
+ name: string;
7
+ description?: string;
8
+ parameters: Record<string, unknown>;
9
+ };
10
+ }
11
+
12
+ export function translateTools(tools: ToolDefinition[]): CopilotTool[] {
13
+ return tools.map((tool) => ({
14
+ type: 'function' as const,
15
+ function: {
16
+ name: tool.function.name,
17
+ description: tool.function.description,
18
+ parameters: tool.function.parameters as Record<string, unknown>,
19
+ },
20
+ }));
21
+ }
@@ -0,0 +1,43 @@
1
+ export interface CopilotModel {
2
+ id: string;
3
+ name: string;
4
+ capabilities: {
5
+ streaming: boolean;
6
+ tools: boolean;
7
+ vision?: boolean;
8
+ };
9
+ }
10
+
11
+ export const COPILOT_MODELS: CopilotModel[] = [
12
+ {
13
+ id: 'gpt-4-copilot',
14
+ name: 'GPT-4 (Copilot)',
15
+ capabilities: { streaming: true, tools: true },
16
+ },
17
+ {
18
+ id: 'gpt-4o-copilot',
19
+ name: 'GPT-4o (Copilot)',
20
+ capabilities: { streaming: true, tools: true },
21
+ },
22
+ {
23
+ id: 'claude-3.5-sonnet-copilot',
24
+ name: 'Claude 3.5 Sonnet (Copilot)',
25
+ capabilities: { streaming: true, tools: true },
26
+ },
27
+ {
28
+ id: 'o1-copilot',
29
+ name: 'o1 (Copilot)',
30
+ capabilities: { streaming: true, tools: true },
31
+ },
32
+ {
33
+ id: 'o1-mini-copilot',
34
+ name: 'o1-mini (Copilot)',
35
+ capabilities: { streaming: true, tools: false },
36
+ },
37
+ ];
38
+
39
+ export interface CopilotConfig {
40
+ COPILOT_TOKEN?: string;
41
+ COPILOT_OAUTH_TOKEN?: string;
42
+ COPILOT_OAUTH_REFRESH_TOKEN?: string;
43
+ }
@@ -0,0 +1,2 @@
1
+ export { CursorBridgePlugin } from './plugin.js';
2
+ export { CursorBridgeSession } from './session.js';
@@ -0,0 +1,37 @@
1
+ import { Cursor } from '@cursor/sdk';
2
+ import type { BridgePlugin, BridgeSession, ModelInfo } from '../../core/index.js';
3
+ import { CursorBridgeSession } from './session.js';
4
+
5
+ export class CursorBridgePlugin implements BridgePlugin {
6
+ name = 'cursor';
7
+ version = '2.0.0';
8
+
9
+ async authenticate(config: Record<string, string>): Promise<boolean> {
10
+ const apiKey = config.CURSOR_API_KEY;
11
+ if (!apiKey) return false;
12
+ try {
13
+ await Cursor.me({ apiKey });
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ async listModels(config: Record<string, string>): Promise<ModelInfo[]> {
21
+ const apiKey = config.CURSOR_API_KEY;
22
+ if (!apiKey) throw new Error('Missing CURSOR_API_KEY');
23
+ const models = await Cursor.models.list({ apiKey });
24
+ return models.map((m) => ({
25
+ id: m.id,
26
+ name: m.id,
27
+ capabilities: { streaming: true, tools: true },
28
+ }));
29
+ }
30
+
31
+ async createSession(config: Record<string, string>, model: string): Promise<BridgeSession> {
32
+ const apiKey = config.CURSOR_API_KEY;
33
+ if (!apiKey) throw new Error('Missing CURSOR_API_KEY');
34
+ const cwd = config.CURSOR_OPENCODE_BRIDGE_CWD ?? process.cwd();
35
+ return new CursorBridgeSession(apiKey, model, cwd);
36
+ }
37
+ }
@@ -0,0 +1,78 @@
1
+ import { Agent } from '@cursor/sdk';
2
+ import type { SDKAgent, SendOptions } from '@cursor/sdk';
3
+ import type { BridgeSession, Message, ToolDefinition, StreamChunk } from '../../core/index.js';
4
+ import { translateTools } from './tools.js';
5
+
6
+ export class CursorBridgeSession implements BridgeSession {
7
+ private agent: SDKAgent | null = null;
8
+ private apiKey: string;
9
+ private modelId: string;
10
+ private cwd: string;
11
+
12
+ constructor(apiKey: string, modelId: string, cwd: string = process.cwd()) {
13
+ this.apiKey = apiKey;
14
+ this.modelId = modelId;
15
+ this.cwd = cwd;
16
+ }
17
+
18
+ async *send(messages: Message[], tools?: ToolDefinition[]): AsyncIterable<StreamChunk> {
19
+ const prompt = this.buildPrompt(messages);
20
+ const cursorTools = tools ? translateTools(tools) : undefined;
21
+
22
+ try {
23
+ this.agent = await Agent.create({
24
+ apiKey: this.apiKey,
25
+ model: { id: this.modelId },
26
+ local: { cwd: this.cwd, settingSources: [] },
27
+ });
28
+
29
+ const sendOptions: SendOptions = {
30
+ model: { id: this.modelId },
31
+ onDelta: ({ update }) => {
32
+ if (update.type === 'text-delta' && 'text' in update && update.text) {
33
+ // onDelta is synchronous callback, we buffer and yield in the loop
34
+ }
35
+ },
36
+ };
37
+
38
+ const run = await this.agent.send(prompt, sendOptions);
39
+
40
+ const result = await run.wait();
41
+ if (result.status === 'error' || result.status === 'cancelled') {
42
+ yield {
43
+ type: 'error',
44
+ content: `Agent run ${result.status}: ${result.result ?? 'no details'}`,
45
+ finishReason: 'error',
46
+ };
47
+ return;
48
+ }
49
+
50
+ yield { type: 'text', content: result.result ?? '', finishReason: 'stop' };
51
+ } catch (e) {
52
+ const msg = e instanceof Error ? e.message : String(e);
53
+ yield { type: 'error', content: msg, finishReason: 'error' };
54
+ }
55
+ }
56
+
57
+ async dispose(): Promise<void> {
58
+ if (this.agent) {
59
+ try {
60
+ await this.agent[Symbol.asyncDispose]();
61
+ } catch {
62
+ // Ignore dispose errors
63
+ }
64
+ this.agent = null;
65
+ }
66
+ }
67
+
68
+ private buildPrompt(messages: Message[]): string {
69
+ const blocks: string[] = [];
70
+ for (const m of messages) {
71
+ const text = typeof m.content === 'string' ? m.content : '';
72
+ if (!text) continue;
73
+ const label = m.role === 'tool' ? `tool (${m.tool_call_id ?? m.name ?? 'result'})` : m.role;
74
+ blocks.push(`[${label}]\n${text}`);
75
+ }
76
+ return `\nFollow this conversation transcript and reply as the assistant.\n\n${blocks.join('\n\n---\n\n')}\n`;
77
+ }
78
+ }
@@ -0,0 +1,25 @@
1
+ import type { ToolDefinition } from '../../core/index.js';
2
+
3
+ export interface CursorTool {
4
+ type: 'function';
5
+ function: {
6
+ name: string;
7
+ description?: string;
8
+ parameters: Record<string, unknown>;
9
+ };
10
+ }
11
+
12
+ export function translateTools(tools: ToolDefinition[]): CursorTool[] {
13
+ return tools.map((tool) => ({
14
+ type: 'function' as const,
15
+ function: {
16
+ name: tool.function.name,
17
+ description: tool.function.description,
18
+ parameters: tool.function.parameters as Record<string, unknown>,
19
+ },
20
+ }));
21
+ }
22
+
23
+ export function translateToolResult(toolCallId: string, result: string): string {
24
+ return `[tool result for ${toolCallId}]\n${result}`;
25
+ }
@@ -0,0 +1,23 @@
1
+ import type { WindsurfConfig } from './types.js';
2
+
3
+ const WINDSURF_API_BASE = 'https://server.codeium.com';
4
+
5
+ export function getToken(config: WindsurfConfig): string | null {
6
+ return config.WINDSURF_TOKEN ?? config.WINDSURF_OAUTH_TOKEN ?? null;
7
+ }
8
+
9
+ export async function validateToken(token: string): Promise<boolean> {
10
+ try {
11
+ const response = await fetch(`${WINDSURF_API_BASE}/api/v1/validate_token`, {
12
+ method: 'POST',
13
+ headers: {
14
+ 'Content-Type': 'application/json',
15
+ Authorization: `Bearer ${token}`,
16
+ },
17
+ body: JSON.stringify({ token }),
18
+ });
19
+ return response.ok;
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
@@ -0,0 +1,24 @@
1
+ import { createDaemonManager, type DaemonManager } from '../../core/index.js';
2
+ import { homedir, platform, arch } from 'node:os';
3
+ import { join } from 'node:path';
4
+
5
+ const DEFAULT_KNOWN_PATHS: string[] = [
6
+ join('/Applications', 'Windsurf.app', 'Contents', 'Resources', 'language_server'),
7
+ join('/usr', 'local', 'bin', 'language_server'),
8
+ ];
9
+
10
+ interface WindsurfDaemonOptions {
11
+ knownPaths?: string[];
12
+ }
13
+
14
+ export function createWindsurfDaemon(options: WindsurfDaemonOptions = {}): DaemonManager {
15
+ const knownPaths = options.knownPaths ?? DEFAULT_KNOWN_PATHS;
16
+
17
+ return createDaemonManager({
18
+ binaryName: 'language_server',
19
+ downloadUrl: `https://server.codeium.com/language_server/latest/{platform}/{arch}`,
20
+ checksum: 'PLACEHOLDER_SHA256',
21
+ knownPaths,
22
+ envVar: 'WINDSURF_LANGUAGE_SERVER_PATH',
23
+ });
24
+ }
@@ -0,0 +1,5 @@
1
+ export { WindsurfBridgePlugin } from './plugin.js';
2
+ export { WINDSURF_MODELS } from './models.js';
3
+ export { createWindsurfDaemon } from './daemon.js';
4
+ export { WindsurfBridgeSession } from './session.js';
5
+ export type { WindsurfModel, WindsurfConfig } from './types.js';
@@ -0,0 +1,44 @@
1
+ import type { WindsurfModel } from './types.js';
2
+
3
+ export const WINDSURF_MODELS: WindsurfModel[] = [
4
+ {
5
+ id: 'claude-4.5-sonnet',
6
+ name: 'Claude 4.5 Sonnet (Windsurf)',
7
+ capabilities: { streaming: true, tools: true },
8
+ },
9
+ {
10
+ id: 'claude-4.5-opus',
11
+ name: 'Claude 4.5 Opus (Windsurf)',
12
+ capabilities: { streaming: true, tools: true },
13
+ },
14
+ {
15
+ id: 'gpt-5.2',
16
+ name: 'GPT-5.2 (Windsurf)',
17
+ capabilities: { streaming: true, tools: true },
18
+ },
19
+ {
20
+ id: 'gpt-5.2-codex',
21
+ name: 'GPT-5.2 Codex (Windsurf)',
22
+ capabilities: { streaming: true, tools: true },
23
+ },
24
+ {
25
+ id: 'gpt-4o',
26
+ name: 'GPT-4o (Windsurf)',
27
+ capabilities: { streaming: true, tools: true },
28
+ },
29
+ {
30
+ id: 'gemini-3.0-pro',
31
+ name: 'Gemini 3.0 Pro (Windsurf)',
32
+ capabilities: { streaming: true, tools: true },
33
+ },
34
+ {
35
+ id: 'gemini-3.0-flash',
36
+ name: 'Gemini 3.0 Flash (Windsurf)',
37
+ capabilities: { streaming: true, tools: true },
38
+ },
39
+ {
40
+ id: 'swe-1.5',
41
+ name: 'SWE-1.5 (Windsurf)',
42
+ capabilities: { streaming: true, tools: true },
43
+ },
44
+ ];
@@ -0,0 +1,34 @@
1
+ import type { BridgePlugin, BridgeSession, ModelInfo } from '../../core/index.js';
2
+ import { WindsurfBridgeSession } from './session.js';
3
+ import { WINDSURF_MODELS } from './models.js';
4
+ import type { WindsurfConfig } from './types.js';
5
+ import { getToken, validateToken } from './auth.js';
6
+ import { createWindsurfDaemon } from './daemon.js';
7
+
8
+ export class WindsurfBridgePlugin implements BridgePlugin {
9
+ name = 'windsurf';
10
+ version = '2.0.0';
11
+
12
+ async authenticate(config: Record<string, string>): Promise<boolean> {
13
+ const token = getToken(config as WindsurfConfig);
14
+ if (!token) return false;
15
+ return validateToken(token);
16
+ }
17
+
18
+ async listModels(config: Record<string, string>): Promise<ModelInfo[]> {
19
+ const token = getToken(config as WindsurfConfig);
20
+ if (!token) throw new Error('Missing WINDSURF_TOKEN');
21
+ return WINDSURF_MODELS.map((m) => ({
22
+ id: m.id,
23
+ name: m.name,
24
+ capabilities: m.capabilities,
25
+ }));
26
+ }
27
+
28
+ async createSession(config: Record<string, string>, model: string): Promise<BridgeSession> {
29
+ const token = getToken(config as WindsurfConfig);
30
+ if (!token) throw new Error('Missing WINDSURF_TOKEN');
31
+ const daemon = createWindsurfDaemon();
32
+ return new WindsurfBridgeSession(daemon, token, model);
33
+ }
34
+ }
@@ -0,0 +1,8 @@
1
+ import { DaemonBridgeSession } from '../../core/index.js';
2
+ import type { DaemonManager } from '../../core/index.js';
3
+
4
+ export class WindsurfBridgeSession extends DaemonBridgeSession {
5
+ constructor(daemon: DaemonManager, token: string, model: string, cwd: string = process.cwd()) {
6
+ super(daemon, token, model, cwd);
7
+ }
8
+ }
@@ -0,0 +1,13 @@
1
+ import type { ToolDefinition } from '../../core/index.js';
2
+ import type { WindsurfTool } from './types.js';
3
+
4
+ export function translateTools(tools: ToolDefinition[]): WindsurfTool[] {
5
+ return tools.map((tool) => ({
6
+ type: 'function' as const,
7
+ function: {
8
+ name: tool.function.name,
9
+ description: tool.function.description,
10
+ parameters: tool.function.parameters as Record<string, unknown>,
11
+ },
12
+ }));
13
+ }
@@ -0,0 +1,24 @@
1
+ export interface WindsurfModel {
2
+ id: string;
3
+ name: string;
4
+ capabilities: {
5
+ streaming: boolean;
6
+ tools: boolean;
7
+ vision?: boolean;
8
+ };
9
+ }
10
+
11
+ export interface WindsurfConfig {
12
+ WINDSURF_TOKEN?: string;
13
+ WINDSURF_OAUTH_TOKEN?: string;
14
+ WINDSURF_LANGUAGE_SERVER_PATH?: string;
15
+ }
16
+
17
+ export interface WindsurfTool {
18
+ type: 'function';
19
+ function: {
20
+ name: string;
21
+ description?: string;
22
+ parameters: Record<string, unknown>;
23
+ };
24
+ }
@@ -1,4 +1,4 @@
1
- import { loadConfig, saveConfig, BridgeConfig } from '@ai-ide-bridge/core';
1
+ import { loadConfig, saveConfig, BridgeConfig } from '../core/index.js';
2
2
 
3
3
  export function readConfig(): BridgeConfig {
4
4
  return loadConfig();
@@ -18,8 +18,9 @@ export function findOpencodeConfig(): string | null {
18
18
  export function injectProvider(
19
19
  configPath: string,
20
20
  providerId: string,
21
- modelId: string,
21
+ models: Record<string, { name: string }>,
22
22
  port: number,
23
+ defaultModelId: string,
23
24
  ): void {
24
25
  const raw = fs.readFileSync(configPath, 'utf8');
25
26
  let config: any;
@@ -36,8 +37,8 @@ export function injectProvider(
36
37
  apiKey: 'bridge-local',
37
38
  baseURL: `http://127.0.0.1:${port}/v1`,
38
39
  },
39
- models: { [modelId]: { name: modelId } },
40
+ models,
40
41
  };
41
- config.model = `${providerId}/${modelId}`;
42
+ config.model = `${providerId}/${defaultModelId}`;
42
43
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
43
44
  }
@@ -0,0 +1,3 @@
1
+ export function getPlatform(): NodeJS.Platform {
2
+ return process.platform;
3
+ }
@@ -12,17 +12,32 @@ describe('opencode utils', () => {
12
12
  fs.writeFileSync(tmpFile, '{}');
13
13
  });
14
14
 
15
- it('injects provider into empty config', () => {
16
- injectProvider(tmpFile, 'test-provider', 'test-model', 3849);
15
+ it('injects provider with single model into empty config', () => {
16
+ const models = { 'cursor/composer-2': { name: 'cursor/composer-2' } };
17
+ injectProvider(tmpFile, 'test-provider', models, 3849, 'cursor/composer-2');
17
18
  const config = JSON.parse(fs.readFileSync(tmpFile, 'utf8'));
18
19
  expect(config.provider['test-provider']).toBeDefined();
19
20
  expect(config.provider['test-provider'].options.baseURL).toBe('http://127.0.0.1:3849/v1');
20
- expect(config.model).toBe('test-provider/test-model');
21
+ expect(config.provider['test-provider'].models['cursor/composer-2']).toBeDefined();
22
+ expect(config.model).toBe('test-provider/cursor/composer-2');
23
+ });
24
+
25
+ it('injects provider with multiple models', () => {
26
+ const models = {
27
+ 'cursor/composer-2': { name: 'cursor/composer-2' },
28
+ 'cursor/gpt-4o': { name: 'cursor/gpt-4o' },
29
+ };
30
+ injectProvider(tmpFile, 'test-provider', models, 3849, 'cursor/gpt-4o');
31
+ const config = JSON.parse(fs.readFileSync(tmpFile, 'utf8'));
32
+ expect(config.provider['test-provider'].models['cursor/composer-2']).toBeDefined();
33
+ expect(config.provider['test-provider'].models['cursor/gpt-4o']).toBeDefined();
34
+ expect(config.model).toBe('test-provider/cursor/gpt-4o');
21
35
  });
22
36
 
23
37
  it('preserves existing config fields', () => {
24
38
  fs.writeFileSync(tmpFile, JSON.stringify({ existing: 'value' }));
25
- injectProvider(tmpFile, 'test-provider', 'test-model', 3849);
39
+ const models = { 'cursor/composer-2': { name: 'cursor/composer-2' } };
40
+ injectProvider(tmpFile, 'test-provider', models, 3849, 'cursor/composer-2');
26
41
  const config = JSON.parse(fs.readFileSync(tmpFile, 'utf8'));
27
42
  expect(config.existing).toBe('value');
28
43
  expect(config.provider['test-provider']).toBeDefined();