@bxb1337/windsurf-fast-context 1.0.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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +337 -0
  3. package/dist/auth/api-key.d.ts +2 -0
  4. package/dist/auth/api-key.test.d.ts +1 -0
  5. package/dist/auth/jwt-manager.d.ts +18 -0
  6. package/dist/auth/jwt-manager.test.d.ts +1 -0
  7. package/dist/cjs/auth/api-key.js +10 -0
  8. package/dist/cjs/auth/api-key.test.js +29 -0
  9. package/dist/cjs/auth/jwt-manager.js +94 -0
  10. package/dist/cjs/auth/jwt-manager.test.js +99 -0
  11. package/dist/cjs/conversion/prompt-converter.js +57 -0
  12. package/dist/cjs/conversion/prompt-converter.test.js +95 -0
  13. package/dist/cjs/conversion/response-converter.js +233 -0
  14. package/dist/cjs/conversion/response-converter.test.js +65 -0
  15. package/dist/cjs/index.js +23 -0
  16. package/dist/cjs/model/devstral-language-model.js +399 -0
  17. package/dist/cjs/model/devstral-language-model.test.js +410 -0
  18. package/dist/cjs/package.json +3 -0
  19. package/dist/cjs/protocol/connect-frame.js +40 -0
  20. package/dist/cjs/protocol/connect-frame.test.js +36 -0
  21. package/dist/cjs/protocol/protobuf.js +114 -0
  22. package/dist/cjs/protocol/protobuf.test.js +58 -0
  23. package/dist/cjs/provider.js +13 -0
  24. package/dist/cjs/provider.test.js +61 -0
  25. package/dist/cjs/transport/http.js +83 -0
  26. package/dist/cjs/transport/http.test.js +196 -0
  27. package/dist/cjs/types/index.js +2 -0
  28. package/dist/conversion/prompt-converter.d.ts +49 -0
  29. package/dist/conversion/prompt-converter.test.d.ts +1 -0
  30. package/dist/conversion/response-converter.d.ts +12 -0
  31. package/dist/conversion/response-converter.test.d.ts +1 -0
  32. package/dist/esm/auth/api-key.js +7 -0
  33. package/dist/esm/auth/api-key.test.js +27 -0
  34. package/dist/esm/auth/jwt-manager.js +90 -0
  35. package/dist/esm/auth/jwt-manager.test.js +97 -0
  36. package/dist/esm/conversion/prompt-converter.js +54 -0
  37. package/dist/esm/conversion/prompt-converter.test.js +93 -0
  38. package/dist/esm/conversion/response-converter.js +230 -0
  39. package/dist/esm/conversion/response-converter.test.js +63 -0
  40. package/dist/esm/dist/cjs/index.js +3 -0
  41. package/dist/esm/index.js +3 -0
  42. package/dist/esm/model/devstral-language-model.js +395 -0
  43. package/dist/esm/model/devstral-language-model.test.js +408 -0
  44. package/dist/esm/protocol/connect-frame.js +36 -0
  45. package/dist/esm/protocol/connect-frame.test.js +34 -0
  46. package/dist/esm/protocol/protobuf.js +108 -0
  47. package/dist/esm/protocol/protobuf.test.js +56 -0
  48. package/dist/esm/provider.js +9 -0
  49. package/dist/esm/provider.test.js +59 -0
  50. package/dist/esm/scripts/postbuild.js +10 -0
  51. package/dist/esm/src/index.js +1 -0
  52. package/dist/esm/transport/http.js +78 -0
  53. package/dist/esm/transport/http.test.js +194 -0
  54. package/dist/esm/types/index.js +1 -0
  55. package/dist/esm/vitest.config.js +6 -0
  56. package/dist/index.cjs +2 -0
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.js +1 -0
  59. package/dist/model/devstral-language-model.d.ts +118 -0
  60. package/dist/model/devstral-language-model.test.d.ts +1 -0
  61. package/dist/protocol/connect-frame.d.ts +10 -0
  62. package/dist/protocol/connect-frame.test.d.ts +1 -0
  63. package/dist/protocol/protobuf.d.ts +11 -0
  64. package/dist/protocol/protobuf.test.d.ts +1 -0
  65. package/dist/provider.d.ts +5 -0
  66. package/dist/provider.test.d.ts +1 -0
  67. package/dist/transport/http.d.ts +22 -0
  68. package/dist/transport/http.test.d.ts +1 -0
  69. package/dist/types/index.d.ts +37 -0
  70. package/package.json +51 -0
@@ -0,0 +1,399 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DevstralLanguageModel = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_os_1 = require("node:os");
6
+ const api_key_js_1 = require("../auth/api-key.js");
7
+ const jwt_manager_js_1 = require("../auth/jwt-manager.js");
8
+ const prompt_converter_js_1 = require("../conversion/prompt-converter.js");
9
+ const response_converter_js_1 = require("../conversion/response-converter.js");
10
+ const connect_frame_js_1 = require("../protocol/connect-frame.js");
11
+ const protobuf_js_1 = require("../protocol/protobuf.js");
12
+ const http_js_1 = require("../transport/http.js");
13
+ const DEFAULT_BASE_URL = 'https://server.self-serve.windsurf.com';
14
+ const DEFAULT_MODEL_ID = 'MODEL_SWE_1_6_FAST';
15
+ const API_SERVICE_PATH = '/exa.api_server_pb.ApiServerService';
16
+ const DEVSTRAL_STREAM_PATH = '/GetDevstralStream';
17
+ const CONNECT_TIMEOUT_MS = '30000';
18
+ const WS_APP = 'windsurf';
19
+ const WS_APP_VER = process.env.WS_APP_VER ?? '1.48.2';
20
+ const WS_LS_VER = process.env.WS_LS_VER ?? '1.9544.35';
21
+ const SENTRY_PUBLIC_KEY = 'b813f73488da69eedec534dba1029111';
22
+ const CONNECT_USER_AGENT = 'connect-go/1.18.1 (go1.25.5)';
23
+ class DevstralLanguageModel {
24
+ specificationVersion = 'v3';
25
+ provider = 'windsurf';
26
+ modelId;
27
+ supportedUrls = {};
28
+ apiKey;
29
+ baseURL;
30
+ headers;
31
+ transport;
32
+ jwtManager;
33
+ constructor(options = {}) {
34
+ this.apiKey = (0, api_key_js_1.resolveApiKey)(options);
35
+ this.baseURL = trimTrailingSlash(options.baseURL ?? DEFAULT_BASE_URL);
36
+ this.modelId = options.modelId ?? DEFAULT_MODEL_ID;
37
+ this.headers = options.headers ?? {};
38
+ this.transport = options.transport ?? new http_js_1.DevstralTransport({ fetch: options.fetch });
39
+ this.jwtManager =
40
+ options.jwtManager ??
41
+ new jwt_manager_js_1.JwtManager({
42
+ fetch: options.fetch,
43
+ authBase: `${this.baseURL}/exa.auth_pb.AuthService`,
44
+ });
45
+ }
46
+ async doGenerate(options) {
47
+ const jwt = await this.jwtManager.getJwt(this.apiKey);
48
+ const messages = (0, prompt_converter_js_1.convertPrompt)(options.prompt);
49
+ const requestPayload = buildGenerateRequest({
50
+ apiKey: this.apiKey,
51
+ jwt,
52
+ messages,
53
+ tools: options.tools,
54
+ });
55
+ // connectFrameEncode now handles gzip compression internally (default compress=true)
56
+ const requestFrame = (0, connect_frame_js_1.connectFrameEncode)(requestPayload);
57
+ const headers = createConnectHeaders(this.headers);
58
+ const responseFrame = await this.transport.postUnary(`${this.baseURL}${API_SERVICE_PATH}${DEVSTRAL_STREAM_PATH}`, requestFrame, headers);
59
+ const responsePayloads = (0, connect_frame_js_1.connectFrameDecode)(responseFrame);
60
+ const payloads = responsePayloads.length > 0 ? responsePayloads : [responseFrame];
61
+ const content = payloads.flatMap((payload) => toV3Content((0, response_converter_js_1.convertResponse)(payload)));
62
+ return {
63
+ content,
64
+ finishReason: { unified: 'stop', raw: 'stop' },
65
+ usage: emptyUsage(),
66
+ warnings: [],
67
+ };
68
+ }
69
+ async doStream(options) {
70
+ const jwt = await this.jwtManager.getJwt(this.apiKey);
71
+ const messages = (0, prompt_converter_js_1.convertPrompt)(options.prompt);
72
+ const requestPayload = buildGenerateRequest({
73
+ apiKey: this.apiKey,
74
+ jwt,
75
+ messages,
76
+ tools: options.tools,
77
+ });
78
+ // connectFrameEncode now handles gzip compression internally (default compress=true)
79
+ const requestFrame = (0, connect_frame_js_1.connectFrameEncode)(requestPayload);
80
+ const headers = createConnectHeaders(this.headers);
81
+ const byteStream = await this.transport.postStreaming(`${this.baseURL}${API_SERVICE_PATH}${DEVSTRAL_STREAM_PATH}`, requestFrame, headers, options.abortSignal);
82
+ return {
83
+ stream: new ReadableStream({
84
+ start: async (controller) => {
85
+ const reader = byteStream.getReader();
86
+ const abortHandler = () => {
87
+ safeClose(controller);
88
+ };
89
+ options.abortSignal?.addEventListener('abort', abortHandler, { once: true });
90
+ let textSegmentId = null;
91
+ let textSegmentCounter = 0;
92
+ let pending = Buffer.alloc(0);
93
+ const closeTextSegment = () => {
94
+ if (textSegmentId == null) {
95
+ return;
96
+ }
97
+ safeEnqueue(controller, {
98
+ type: 'text-end',
99
+ id: textSegmentId,
100
+ });
101
+ textSegmentId = null;
102
+ };
103
+ try {
104
+ safeEnqueue(controller, { type: 'stream-start', warnings: [] });
105
+ safeEnqueue(controller, { type: 'response-metadata', modelId: this.modelId });
106
+ while (!isAborted(options.abortSignal)) {
107
+ const next = await reader.read();
108
+ if (next.done) {
109
+ break;
110
+ }
111
+ if (next.value.byteLength === 0) {
112
+ continue;
113
+ }
114
+ pending = Buffer.concat([pending, Buffer.from(next.value)]);
115
+ while (true) {
116
+ const frameResult = readNextConnectFrame(pending);
117
+ if (frameResult == null) {
118
+ break;
119
+ }
120
+ pending = frameResult.rest;
121
+ const contentParts = toV3Content((0, response_converter_js_1.convertResponse)(frameResult.payload));
122
+ for (const part of contentParts) {
123
+ if (isAborted(options.abortSignal)) {
124
+ safeClose(controller);
125
+ return;
126
+ }
127
+ if (part.type === 'text') {
128
+ if (textSegmentId == null) {
129
+ textSegmentCounter += 1;
130
+ textSegmentId = `text_${textSegmentCounter}`;
131
+ safeEnqueue(controller, { type: 'text-start', id: textSegmentId });
132
+ }
133
+ safeEnqueue(controller, {
134
+ type: 'text-delta',
135
+ id: textSegmentId,
136
+ delta: part.text,
137
+ });
138
+ continue;
139
+ }
140
+ closeTextSegment();
141
+ safeEnqueue(controller, {
142
+ type: 'tool-input-start',
143
+ toolCallId: part.toolCallId,
144
+ toolName: part.toolName,
145
+ });
146
+ safeEnqueue(controller, {
147
+ type: 'tool-input-delta',
148
+ toolCallId: part.toolCallId,
149
+ delta: part.args,
150
+ });
151
+ safeEnqueue(controller, {
152
+ type: 'tool-input-end',
153
+ toolCallId: part.toolCallId,
154
+ });
155
+ safeEnqueue(controller, part);
156
+ }
157
+ }
158
+ }
159
+ if (isAborted(options.abortSignal)) {
160
+ safeClose(controller);
161
+ return;
162
+ }
163
+ closeTextSegment();
164
+ safeEnqueue(controller, {
165
+ type: 'finish',
166
+ finishReason: { unified: 'stop', raw: 'stop' },
167
+ usage: emptyUsage(),
168
+ });
169
+ safeClose(controller);
170
+ }
171
+ catch (error) {
172
+ if (!isAborted(options.abortSignal)) {
173
+ closeTextSegment();
174
+ safeEnqueue(controller, {
175
+ type: 'error',
176
+ error,
177
+ });
178
+ safeEnqueue(controller, {
179
+ type: 'finish',
180
+ finishReason: { unified: 'stop', raw: 'stop' },
181
+ usage: emptyUsage(),
182
+ });
183
+ }
184
+ safeClose(controller);
185
+ }
186
+ finally {
187
+ options.abortSignal?.removeEventListener('abort', abortHandler);
188
+ reader.releaseLock();
189
+ }
190
+ },
191
+ }),
192
+ };
193
+ }
194
+ }
195
+ exports.DevstralLanguageModel = DevstralLanguageModel;
196
+ const CONNECT_FRAME_HEADER_BYTES = 5;
197
+ function readNextConnectFrame(buffer) {
198
+ if (buffer.length < CONNECT_FRAME_HEADER_BYTES) {
199
+ return null;
200
+ }
201
+ const payloadLength = buffer.readUInt32BE(1);
202
+ const frameLength = CONNECT_FRAME_HEADER_BYTES + payloadLength;
203
+ if (buffer.length < frameLength) {
204
+ return null;
205
+ }
206
+ const frame = buffer.subarray(0, frameLength);
207
+ const decoded = (0, connect_frame_js_1.connectFrameDecode)(frame);
208
+ return {
209
+ payload: decoded[0] ?? Buffer.alloc(0),
210
+ rest: buffer.subarray(frameLength),
211
+ };
212
+ }
213
+ function safeEnqueue(controller, part) {
214
+ if (isControllerClosed(controller)) {
215
+ return;
216
+ }
217
+ controller.enqueue(part);
218
+ }
219
+ function safeClose(controller) {
220
+ if (isControllerClosed(controller)) {
221
+ return;
222
+ }
223
+ controller.close();
224
+ }
225
+ function isControllerClosed(controller) {
226
+ try {
227
+ controller.desiredSize;
228
+ return false;
229
+ }
230
+ catch {
231
+ return true;
232
+ }
233
+ }
234
+ function isAborted(signal) {
235
+ return signal?.aborted === true;
236
+ }
237
+ function emptyUsage() {
238
+ return {
239
+ inputTokens: {
240
+ total: undefined,
241
+ noCache: undefined,
242
+ cacheRead: undefined,
243
+ cacheWrite: undefined,
244
+ },
245
+ outputTokens: {
246
+ total: undefined,
247
+ text: undefined,
248
+ reasoning: undefined,
249
+ },
250
+ };
251
+ }
252
+ function toV3Content(parts) {
253
+ return parts.map((part) => {
254
+ if (part.type === 'tool-call') {
255
+ return {
256
+ type: 'tool-call',
257
+ toolCallType: 'function',
258
+ toolCallId: part.toolCallId,
259
+ toolName: part.toolName,
260
+ args: JSON.stringify(part.args),
261
+ };
262
+ }
263
+ return part;
264
+ });
265
+ }
266
+ function buildGenerateRequest(input) {
267
+ const request = new protobuf_js_1.ProtobufEncoder();
268
+ request.writeMessage(1, buildMetadata(input.apiKey, input.jwt));
269
+ for (const message of input.messages) {
270
+ request.writeMessage(2, buildMessage(message));
271
+ }
272
+ // Tools are sent as a single JSON string at field 3 (not repeated messages)
273
+ if (input.tools && Object.keys(input.tools).length > 0) {
274
+ const toolsArray = Object.entries(input.tools).map(([name, tool]) => ({
275
+ type: 'function',
276
+ function: {
277
+ name,
278
+ description: tool.description ?? '',
279
+ parameters: tool.parameters ?? {},
280
+ },
281
+ }));
282
+ request.writeString(3, JSON.stringify(toolsArray));
283
+ }
284
+ return request.toBuffer();
285
+ }
286
+ /**
287
+ * Build metadata protobuf with correct field numbers matching the reference implementation.
288
+ *
289
+ * Field mapping:
290
+ * 1: WS_APP ("windsurf")
291
+ * 2: WS_APP_VER ("1.48.2")
292
+ * 3: apiKey
293
+ * 4: locale ("zh-cn")
294
+ * 5: systemInfo JSON
295
+ * 7: WS_LS_VER ("1.9544.35")
296
+ * 8: cpuInfo JSON
297
+ * 12: WS_APP ("windsurf")
298
+ * 21: jwt
299
+ * 30: bytes [0x00, 0x01]
300
+ */
301
+ function buildMetadata(apiKey, jwt) {
302
+ const plat = (0, node_os_1.platform)();
303
+ const systemInfo = {
304
+ Os: plat,
305
+ Arch: (0, node_os_1.arch)(),
306
+ Release: (0, node_os_1.release)(),
307
+ Version: (0, node_os_1.version)(),
308
+ Machine: (0, node_os_1.arch)(),
309
+ Nodename: (0, node_os_1.hostname)(),
310
+ Sysname: plat === 'darwin' ? 'Darwin' : plat === 'win32' ? 'Windows_NT' : 'Linux',
311
+ ProductVersion: '',
312
+ };
313
+ const cpuList = (0, node_os_1.cpus)();
314
+ const ncpu = cpuList.length || 4;
315
+ const cpuInfo = {
316
+ NumSockets: 1,
317
+ NumCores: ncpu,
318
+ NumThreads: ncpu,
319
+ VendorID: '',
320
+ Family: '0',
321
+ Model: '0',
322
+ ModelName: cpuList[0]?.model || 'Unknown',
323
+ Memory: (0, node_os_1.totalmem)(),
324
+ };
325
+ const metadata = new protobuf_js_1.ProtobufEncoder();
326
+ metadata.writeString(1, WS_APP);
327
+ metadata.writeString(2, WS_APP_VER);
328
+ metadata.writeString(3, apiKey);
329
+ metadata.writeString(4, 'zh-cn');
330
+ metadata.writeString(5, JSON.stringify(systemInfo));
331
+ metadata.writeString(7, WS_LS_VER);
332
+ metadata.writeString(8, JSON.stringify(cpuInfo));
333
+ metadata.writeString(12, WS_APP);
334
+ metadata.writeString(21, jwt);
335
+ metadata.writeBytes(30, Buffer.from([0x00, 0x01]));
336
+ return metadata;
337
+ }
338
+ /**
339
+ * Build message protobuf with correct field numbers matching the reference implementation.
340
+ *
341
+ * Field mapping:
342
+ * 2: role (1=user, 2=assistant, 4=tool_result, 5=system)
343
+ * 3: content
344
+ * 6: toolCall (nested message with fields 1=callId, 2=name, 3=argsJson)
345
+ * 7: refCallId
346
+ */
347
+ function buildMessage(message) {
348
+ const encoded = new protobuf_js_1.ProtobufEncoder();
349
+ // Role at field 2 (not 1)
350
+ encoded.writeVarint(2, message.role);
351
+ // Content at field 3 (not 2)
352
+ encoded.writeString(3, message.content);
353
+ const toolCallId = getMetadataString(message, 'toolCallId');
354
+ const toolName = getMetadataString(message, 'toolName');
355
+ const toolArgsJson = getMetadataString(message, 'toolArgsJson');
356
+ if (toolCallId && toolName && toolArgsJson) {
357
+ // Tool call as nested message at field 6
358
+ const toolCall = new protobuf_js_1.ProtobufEncoder();
359
+ toolCall.writeString(1, toolCallId);
360
+ toolCall.writeString(2, toolName);
361
+ toolCall.writeString(3, toolArgsJson);
362
+ encoded.writeMessage(6, toolCall);
363
+ }
364
+ const refCallId = getMetadataString(message, 'refCallId');
365
+ if (refCallId) {
366
+ encoded.writeString(7, refCallId);
367
+ }
368
+ return encoded;
369
+ }
370
+ function getMetadataString(message, key) {
371
+ const value = message.metadata?.[key];
372
+ return typeof value === 'string' ? value : null;
373
+ }
374
+ /**
375
+ * Create Connect-RPC headers matching the reference implementation.
376
+ * Note: Authorization header is NOT included - JWT is in metadata field 21.
377
+ */
378
+ function createConnectHeaders(headers) {
379
+ const traceId = (0, node_crypto_1.randomUUID)().replace(/-/g, '');
380
+ const spanId = (0, node_crypto_1.randomUUID)().replace(/-/g, '').slice(0, 16);
381
+ return new Headers({
382
+ ...headers,
383
+ 'Content-Type': 'application/connect+proto',
384
+ 'Connect-Protocol-Version': '1',
385
+ 'Connect-Timeout-Ms': CONNECT_TIMEOUT_MS,
386
+ 'Connect-Accept-Encoding': 'gzip',
387
+ 'Connect-Content-Encoding': 'gzip',
388
+ 'Accept-Encoding': 'identity',
389
+ 'User-Agent': CONNECT_USER_AGENT,
390
+ Baggage: `sentry-release=language-server-windsurf@${WS_LS_VER},` +
391
+ 'sentry-environment=stable,sentry-sampled=false,' +
392
+ `sentry-trace_id=${traceId},` +
393
+ `sentry-public_key=${SENTRY_PUBLIC_KEY}`,
394
+ 'Sentry-Trace': `${traceId}-${spanId}-0`,
395
+ });
396
+ }
397
+ function trimTrailingSlash(value) {
398
+ return value.endsWith('/') ? value.slice(0, -1) : value;
399
+ }