@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,54 @@
1
+ function toContentString(value) {
2
+ if (typeof value === 'string') {
3
+ return value;
4
+ }
5
+ return JSON.stringify(value);
6
+ }
7
+ export function convertPrompt(prompt) {
8
+ const messages = [];
9
+ for (const message of prompt) {
10
+ if (message.role === 'system') {
11
+ messages.push({ role: 5, content: message.content });
12
+ continue;
13
+ }
14
+ if (message.role === 'user') {
15
+ const text = message.content
16
+ .filter((part) => part.type === 'text')
17
+ .map((part) => part.text)
18
+ .join('');
19
+ messages.push({ role: 1, content: text });
20
+ continue;
21
+ }
22
+ if (message.role === 'assistant') {
23
+ for (const part of message.content) {
24
+ if (part.type === 'text') {
25
+ messages.push({ role: 2, content: part.text });
26
+ continue;
27
+ }
28
+ if (part.type === 'tool-call') {
29
+ messages.push({
30
+ role: 2,
31
+ content: '',
32
+ metadata: {
33
+ toolCallId: part.toolCallId,
34
+ toolName: part.toolName,
35
+ toolArgsJson: JSON.stringify(part.args),
36
+ },
37
+ });
38
+ }
39
+ }
40
+ continue;
41
+ }
42
+ // Tool result messages - use refCallId to reference the original tool call
43
+ for (const part of message.content) {
44
+ messages.push({
45
+ role: 4,
46
+ content: toContentString(part.result),
47
+ metadata: {
48
+ refCallId: part.toolCallId, // This links back to the tool call
49
+ },
50
+ });
51
+ }
52
+ }
53
+ return messages;
54
+ }
@@ -0,0 +1,93 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { convertPrompt } from './prompt-converter.js';
3
+ describe('convertPrompt', () => {
4
+ it('system maps to role 5', () => {
5
+ const prompt = [{ role: 'system', content: 'Keep answers concise.' }];
6
+ const result = convertPrompt(prompt);
7
+ expect(result).toEqual([{ role: 5, content: 'Keep answers concise.' }]);
8
+ });
9
+ it('tool-call converts assistant tool calls and preserves id/name/args', () => {
10
+ const prompt = [
11
+ {
12
+ role: 'assistant',
13
+ content: [
14
+ { type: 'text', text: 'Let me call a tool.' },
15
+ {
16
+ type: 'tool-call',
17
+ toolCallId: 'call_1',
18
+ toolName: 'searchDocs',
19
+ args: { query: 'prompt converter', topK: 3 },
20
+ },
21
+ ],
22
+ },
23
+ ];
24
+ const result = convertPrompt(prompt);
25
+ expect(result).toHaveLength(2);
26
+ expect(result[0]).toEqual({ role: 2, content: 'Let me call a tool.' });
27
+ expect(result[1]).toEqual({
28
+ role: 2,
29
+ content: '',
30
+ metadata: {
31
+ toolCallId: 'call_1',
32
+ toolName: 'searchDocs',
33
+ toolArgsJson: '{"query":"prompt converter","topK":3}',
34
+ },
35
+ });
36
+ });
37
+ it('multi-turn preserves ordering across mixed roles', () => {
38
+ const prompt = [
39
+ { role: 'system', content: 'System instruction' },
40
+ {
41
+ role: 'user',
42
+ content: [
43
+ { type: 'text', text: 'Find usage examples.' },
44
+ { type: 'file', data: 'ZmFrZQ==', mediaType: 'image/png' },
45
+ ],
46
+ },
47
+ {
48
+ role: 'assistant',
49
+ content: [
50
+ { type: 'text', text: 'I will call a tool now.' },
51
+ { type: 'tool-call', toolCallId: 'call_2', toolName: 'searchDocs', args: { q: 'usage examples' } },
52
+ ],
53
+ },
54
+ {
55
+ role: 'tool',
56
+ content: [
57
+ {
58
+ type: 'tool-result',
59
+ toolCallId: 'call_2',
60
+ toolName: 'searchDocs',
61
+ result: { hits: ['a.ts', 'b.ts'] },
62
+ },
63
+ ],
64
+ },
65
+ { role: 'assistant', content: [{ type: 'text', text: 'Done.' }] },
66
+ ];
67
+ const snapshot = JSON.parse(JSON.stringify(prompt));
68
+ const result = convertPrompt(prompt);
69
+ expect(result).toEqual([
70
+ { role: 5, content: 'System instruction' },
71
+ { role: 1, content: 'Find usage examples.' },
72
+ { role: 2, content: 'I will call a tool now.' },
73
+ {
74
+ role: 2,
75
+ content: '',
76
+ metadata: {
77
+ toolCallId: 'call_2',
78
+ toolName: 'searchDocs',
79
+ toolArgsJson: '{"q":"usage examples"}',
80
+ },
81
+ },
82
+ {
83
+ role: 4,
84
+ content: '{"hits":["a.ts","b.ts"]}',
85
+ metadata: {
86
+ refCallId: 'call_2',
87
+ },
88
+ },
89
+ { role: 2, content: 'Done.' },
90
+ ]);
91
+ expect(prompt).toEqual(snapshot);
92
+ });
93
+ });
@@ -0,0 +1,230 @@
1
+ import { gunzipSync } from 'node:zlib';
2
+ import { extractStrings } from '../protocol/protobuf.js';
3
+ const TOOL_CALL_PREFIX = '[TOOL_CALLS]';
4
+ const ARGS_PREFIX = '[ARGS]';
5
+ function pushText(parts, text) {
6
+ if (text.length > 0) {
7
+ parts.push({ type: 'text', text });
8
+ }
9
+ }
10
+ function extractAnswerText(args) {
11
+ if (typeof args === 'string') {
12
+ return args;
13
+ }
14
+ if (args != null && typeof args === 'object') {
15
+ if ('answer' in args && typeof args.answer === 'string') {
16
+ return args.answer;
17
+ }
18
+ if ('text' in args && typeof args.text === 'string') {
19
+ return args.text;
20
+ }
21
+ }
22
+ return JSON.stringify(args);
23
+ }
24
+ function parseStringEnd(value, startIndex) {
25
+ let index = startIndex + 1;
26
+ let escaping = false;
27
+ while (index < value.length) {
28
+ const char = value[index];
29
+ if (escaping) {
30
+ escaping = false;
31
+ }
32
+ else if (char === '\\') {
33
+ escaping = true;
34
+ }
35
+ else if (char === '"') {
36
+ return index + 1;
37
+ }
38
+ index += 1;
39
+ }
40
+ return null;
41
+ }
42
+ function parseBalancedEnd(value, startIndex) {
43
+ const stack = [value[startIndex] === '{' ? '}' : ']'];
44
+ let index = startIndex + 1;
45
+ let inString = false;
46
+ let escaping = false;
47
+ while (index < value.length) {
48
+ const char = value[index];
49
+ if (inString) {
50
+ if (escaping) {
51
+ escaping = false;
52
+ }
53
+ else if (char === '\\') {
54
+ escaping = true;
55
+ }
56
+ else if (char === '"') {
57
+ inString = false;
58
+ }
59
+ index += 1;
60
+ continue;
61
+ }
62
+ if (char === '"') {
63
+ inString = true;
64
+ index += 1;
65
+ continue;
66
+ }
67
+ if (char === '{') {
68
+ stack.push('}');
69
+ index += 1;
70
+ continue;
71
+ }
72
+ if (char === '[') {
73
+ stack.push(']');
74
+ index += 1;
75
+ continue;
76
+ }
77
+ if (char === '}' || char === ']') {
78
+ const expected = stack[stack.length - 1];
79
+ if (expected !== char) {
80
+ return null;
81
+ }
82
+ stack.pop();
83
+ index += 1;
84
+ if (stack.length === 0) {
85
+ return index;
86
+ }
87
+ continue;
88
+ }
89
+ index += 1;
90
+ }
91
+ return null;
92
+ }
93
+ function parsePrimitiveEnd(value, startIndex) {
94
+ let index = startIndex;
95
+ while (index < value.length) {
96
+ const char = value[index];
97
+ if (char === ' ' || char === '\n' || char === '\r' || char === '\t') {
98
+ break;
99
+ }
100
+ index += 1;
101
+ }
102
+ return index;
103
+ }
104
+ function parseJsonValue(value, startIndex) {
105
+ let jsonStart = startIndex;
106
+ while (jsonStart < value.length) {
107
+ const char = value[jsonStart];
108
+ if (char !== ' ' && char !== '\n' && char !== '\r' && char !== '\t') {
109
+ break;
110
+ }
111
+ jsonStart += 1;
112
+ }
113
+ if (jsonStart >= value.length) {
114
+ return null;
115
+ }
116
+ const firstChar = value[jsonStart];
117
+ let endIndex;
118
+ if (firstChar === '{' || firstChar === '[') {
119
+ endIndex = parseBalancedEnd(value, jsonStart);
120
+ }
121
+ else if (firstChar === '"') {
122
+ endIndex = parseStringEnd(value, jsonStart);
123
+ }
124
+ else {
125
+ endIndex = parsePrimitiveEnd(value, jsonStart);
126
+ }
127
+ if (endIndex == null || endIndex <= jsonStart) {
128
+ return null;
129
+ }
130
+ const rawJson = value.slice(jsonStart, endIndex);
131
+ try {
132
+ return {
133
+ parsed: JSON.parse(rawJson),
134
+ endIndex,
135
+ };
136
+ }
137
+ catch {
138
+ return null;
139
+ }
140
+ }
141
+ function hasControlChars(value) {
142
+ for (const char of value) {
143
+ const code = char.charCodeAt(0);
144
+ if (code <= 8 || code === 11 || code === 12 || (code >= 14 && code <= 31)) {
145
+ return true;
146
+ }
147
+ }
148
+ return false;
149
+ }
150
+ function maybeGunzip(buffer) {
151
+ if (buffer.length < 2) {
152
+ return buffer;
153
+ }
154
+ if (buffer.readUInt8(0) !== 0x1f || buffer.readUInt8(1) !== 0x8b) {
155
+ return buffer;
156
+ }
157
+ try {
158
+ return gunzipSync(buffer);
159
+ }
160
+ catch {
161
+ return buffer;
162
+ }
163
+ }
164
+ function isLikelyMetadata(value) {
165
+ return /^[A-Za-z0-9._:-]{1,32}$/.test(value);
166
+ }
167
+ function pickBestExtractedText(values) {
168
+ const markerValues = values.filter((value) => value.includes(TOOL_CALL_PREFIX) || value.includes(ARGS_PREFIX));
169
+ if (markerValues.length > 0) {
170
+ return markerValues.join('');
171
+ }
172
+ const nonMetadata = values.filter((value) => !isLikelyMetadata(value));
173
+ const candidates = nonMetadata.length > 0 ? nonMetadata : values;
174
+ return candidates.reduce((best, current) => (current.length > best.length ? current : best), candidates[0] ?? '');
175
+ }
176
+ function decodeResponseText(buffer) {
177
+ const source = maybeGunzip(buffer);
178
+ const raw = source.toString('utf8');
179
+ if (!hasControlChars(raw) && !raw.includes('\ufffd')) {
180
+ return raw;
181
+ }
182
+ const extracted = extractStrings(source).filter((value) => value.length > 0 && !hasControlChars(value));
183
+ if (extracted.length === 0) {
184
+ return raw;
185
+ }
186
+ return pickBestExtractedText(extracted);
187
+ }
188
+ export function convertResponse(buffer) {
189
+ const responseText = decodeResponseText(buffer);
190
+ const parts = [];
191
+ let cursor = 0;
192
+ let toolCallCount = 0;
193
+ while (cursor < responseText.length) {
194
+ const markerStart = responseText.indexOf(TOOL_CALL_PREFIX, cursor);
195
+ if (markerStart === -1) {
196
+ pushText(parts, responseText.slice(cursor));
197
+ break;
198
+ }
199
+ pushText(parts, responseText.slice(cursor, markerStart));
200
+ const toolNameStart = markerStart + TOOL_CALL_PREFIX.length;
201
+ const argsStart = responseText.indexOf(ARGS_PREFIX, toolNameStart);
202
+ if (argsStart === -1) {
203
+ pushText(parts, responseText.slice(markerStart));
204
+ break;
205
+ }
206
+ const toolName = responseText.slice(toolNameStart, argsStart);
207
+ const parsedArgs = parseJsonValue(responseText, argsStart + ARGS_PREFIX.length);
208
+ if (parsedArgs == null) {
209
+ const nextMarker = responseText.indexOf(TOOL_CALL_PREFIX, markerStart + TOOL_CALL_PREFIX.length);
210
+ const malformedEnd = nextMarker === -1 ? responseText.length : nextMarker;
211
+ pushText(parts, responseText.slice(markerStart, malformedEnd));
212
+ cursor = malformedEnd;
213
+ continue;
214
+ }
215
+ if (toolName === 'answer') {
216
+ pushText(parts, extractAnswerText(parsedArgs.parsed));
217
+ }
218
+ else {
219
+ toolCallCount += 1;
220
+ parts.push({
221
+ type: 'tool-call',
222
+ toolCallId: `toolcall_${toolCallCount}`,
223
+ toolName,
224
+ args: parsedArgs.parsed,
225
+ });
226
+ }
227
+ cursor = parsedArgs.endIndex;
228
+ }
229
+ return parts;
230
+ }
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { gzipSync } from 'node:zlib';
3
+ import { ProtobufEncoder } from '../protocol/protobuf.js';
4
+ import { convertResponse } from './response-converter.js';
5
+ describe('convertResponse', () => {
6
+ it('text - returns plain text part for plain response', () => {
7
+ const result = convertResponse(Buffer.from('plain response text', 'utf8'));
8
+ expect(result).toEqual([{ type: 'text', text: 'plain response text' }]);
9
+ });
10
+ it('tool-call - parses tool markers and preserves surrounding text', () => {
11
+ const input = 'Before [TOOL_CALLS]searchDocs[ARGS]{"query":"prompt converter"} between [TOOL_CALLS]answer[ARGS]{"answer":"final answer"} after';
12
+ const result = convertResponse(Buffer.from(input, 'utf8'));
13
+ expect(result).toEqual([
14
+ { type: 'text', text: 'Before ' },
15
+ {
16
+ type: 'tool-call',
17
+ toolCallId: 'toolcall_1',
18
+ toolName: 'searchDocs',
19
+ args: { query: 'prompt converter' },
20
+ },
21
+ { type: 'text', text: ' between ' },
22
+ { type: 'text', text: 'final answer' },
23
+ { type: 'text', text: ' after' },
24
+ ]);
25
+ });
26
+ it('malformed - invalid marker json remains text and does not throw', () => {
27
+ const input = 'prefix [TOOL_CALLS]searchDocs[ARGS]{"query": nope} suffix';
28
+ expect(() => convertResponse(Buffer.from(input, 'utf8'))).not.toThrow();
29
+ const result = convertResponse(Buffer.from(input, 'utf8'));
30
+ const combinedText = result
31
+ .filter((part) => part.type === 'text')
32
+ .map((part) => part.text)
33
+ .join('');
34
+ expect(result.every((part) => part.type === 'text')).toBe(true);
35
+ expect(combinedText).toBe(input);
36
+ });
37
+ it('protobuf payload - extracts clean utf8 text without binary mojibake prefix', () => {
38
+ const payload = new ProtobufEncoder();
39
+ payload.writeVarint(1, 150);
40
+ payload.writeString(2, '你好,TypeScript');
41
+ const result = convertResponse(payload.toBuffer());
42
+ expect(result).toEqual([{ type: 'text', text: '你好,TypeScript' }]);
43
+ });
44
+ it('protobuf payload - still parses tool-call markers from extracted strings', () => {
45
+ const payload = new ProtobufEncoder();
46
+ payload.writeVarint(1, 150);
47
+ payload.writeString(2, '[TOOL_CALLS]answer[ARGS]{"answer":"final answer"}');
48
+ const result = convertResponse(payload.toBuffer());
49
+ expect(result).toEqual([{ type: 'text', text: 'final answer' }]);
50
+ });
51
+ it('protobuf payload - ignores metadata strings and keeps main text field', () => {
52
+ const payload = new ProtobufEncoder();
53
+ payload.writeString(1, 'meta');
54
+ payload.writeString(2, '你好,TypeScript');
55
+ const result = convertResponse(payload.toBuffer());
56
+ expect(result).toEqual([{ type: 'text', text: '你好,TypeScript' }]);
57
+ });
58
+ it('gzip payload - decompresses before decoding text', () => {
59
+ const compressed = gzipSync(Buffer.from('hello from gzip', 'utf8'));
60
+ const result = convertResponse(compressed);
61
+ expect(result).toEqual([{ type: 'text', text: 'hello from gzip' }]);
62
+ });
63
+ });
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ export {};
@@ -0,0 +1,3 @@
1
+ export * from './types/index.js';
2
+ export { createWindsurfProvider, windsurf } from './provider.js';
3
+ export { default } from './provider.js';