@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
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # @bxb1337/windsurf-fast-context
2
+
3
+ An AI SDK V3 compatible provider for Windsurf's Devstral code search API. This provider exposes tool calls for code search operations, allowing tools like OpenCode to execute them.
4
+
5
+ **Important**: This provider exposes tool calls but does **not** execute them. Tool execution is delegated to the caller (e.g., OpenCode, your application).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @bxb1337/windsurf-fast-context
11
+ # or
12
+ pnpm add @bxb1337/windsurf-fast-context
13
+ # or
14
+ yarn add @bxb1337/windsurf-fast-context
15
+ ```
16
+
17
+ ### Peer Dependencies
18
+
19
+ This package requires `ai` as a peer dependency:
20
+
21
+ ```bash
22
+ npm install ai
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```typescript
28
+ import { createWindsurfProvider } from '@bxb1337/windsurf-fast-context';
29
+ import { generateText } from 'ai';
30
+
31
+ // Create the provider with your API key
32
+ const windsurf = createWindsurfProvider({
33
+ apiKey: process.env.WINDSURF_API_KEY,
34
+ });
35
+
36
+ // Use with AI SDK
37
+ const result = await generateText({
38
+ model: windsurf('MODEL_SWE_1_6_FAST'),
39
+ prompt: 'Find authentication logic in the codebase',
40
+ tools: {
41
+ ripgrep: {
42
+ description: 'Search for patterns in files',
43
+ parameters: {
44
+ type: 'object',
45
+ properties: {
46
+ pattern: { type: 'string' },
47
+ path: { type: 'string' },
48
+ },
49
+ required: ['pattern'],
50
+ },
51
+ },
52
+ readfile: {
53
+ description: 'Read file contents',
54
+ parameters: {
55
+ type: 'object',
56
+ properties: {
57
+ path: { type: 'string' },
58
+ },
59
+ required: ['path'],
60
+ },
61
+ },
62
+ },
63
+ });
64
+
65
+ // Tool calls are exposed for you to execute
66
+ for (const part of result.content) {
67
+ if (part.type === 'tool-call') {
68
+ console.log(`Tool: ${part.toolName}`);
69
+ console.log(`Args: ${JSON.stringify(part.args)}`);
70
+ // Execute the tool yourself...
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## API Reference
76
+
77
+ ### `createWindsurfProvider(options?)`
78
+
79
+ Creates a Windsurf provider factory function.
80
+
81
+ ```typescript
82
+ import { createWindsurfProvider } from '@bxb1337/windsurf-fast-context';
83
+
84
+ const windsurf = createWindsurfProvider({
85
+ apiKey: 'your-api-key',
86
+ baseURL: 'https://custom-endpoint.com',
87
+ headers: { 'X-Custom': 'value' },
88
+ });
89
+ ```
90
+
91
+ #### Options
92
+
93
+ | Option | Type | Description |
94
+ |--------|------|-------------|
95
+ | `apiKey` | `string` | Windsurf API key. Falls back to `WINDSURF_API_KEY` environment variable. Required. |
96
+ | `baseURL` | `string` | Custom API endpoint. Default: `https://server.self-serve.windsurf.com` |
97
+ | `headers` | `Record<string, string>` | Custom headers to send with each request. |
98
+ | `fetch` | `FetchFn` | Custom fetch function for testing or proxying. |
99
+ | `generateId` | `() => string` | Custom ID generator for tool calls. |
100
+
101
+ #### Returns
102
+
103
+ A function that accepts a model ID and returns a `LanguageModelV3` instance:
104
+
105
+ ```typescript
106
+ const model = windsurf('MODEL_SWE_1_6_FAST');
107
+ ```
108
+
109
+ ### `windsurf` (named export)
110
+
111
+ A pre-configured provider instance that reads the API key from `WINDSURF_API_KEY`:
112
+
113
+ ```typescript
114
+ import { windsurf } from '@bxb1337/windsurf-fast-context';
115
+
116
+ const model = windsurf('MODEL_SWE_1_6_FAST');
117
+ ```
118
+
119
+ ### Default Export
120
+
121
+ The default export is the `windsurf` provider:
122
+
123
+ ```typescript
124
+ import windsurf from '@bxb1337/windsurf-fast-context';
125
+
126
+ const model = windsurf('MODEL_SWE_1_6_FAST');
127
+ ```
128
+
129
+ ### Supported Models
130
+
131
+ | Model ID | Description |
132
+ |----------|-------------|
133
+ | `MODEL_SWE_1_6_FAST` | Fast variant for quick responses |
134
+ | `MODEL_SWE_1_6` | Standard variant with more capability |
135
+
136
+ Custom model IDs are also accepted as strings.
137
+
138
+ ## Configuration
139
+
140
+ ### Environment Variable
141
+
142
+ Set your API key via environment variable:
143
+
144
+ ```bash
145
+ export WINDSURF_API_KEY="your-api-key"
146
+ ```
147
+
148
+ Then use the default export:
149
+
150
+ ```typescript
151
+ import windsurf from '@bxb1337/windsurf-fast-context';
152
+
153
+ // Reads WINDSURF_API_KEY automatically
154
+ const model = windsurf('MODEL_SWE_1_6_FAST');
155
+ ```
156
+
157
+ ### OpenCode Integration
158
+
159
+ Add to your `opencode.json`:
160
+
161
+ ```json
162
+ {
163
+ "$schema": "https://opencode.ai/config.json",
164
+ "provider": {
165
+ "windsurf": {
166
+ "npm": "@bxb1337/windsurf-fast-context",
167
+ "name": "Windsurf Devstral",
168
+ "options": {
169
+ "apiKey": "your-api-key",
170
+ "baseURL": "https://server.self-serve.windsurf.com"
171
+ },
172
+ "models": {
173
+ "MODEL_SWE_1_6_FAST": {
174
+ "name": "Devstral Fast",
175
+ "limit": {
176
+ "context": 128000,
177
+ "output": 8192
178
+ }
179
+ }
180
+ }
181
+ }
182
+ }
183
+ }
184
+ ```
185
+
186
+ ### Custom Fetch for Testing
187
+
188
+ ```typescript
189
+ import { createWindsurfProvider } from '@bxb1337/windsurf-fast-context';
190
+
191
+ const mockFetch = async (url: string | URL | Request, init?: RequestInit) => {
192
+ // Return mock responses for testing
193
+ return new Response(JSON.stringify({ result: 'mocked' }), {
194
+ headers: { 'Content-Type': 'application/json' },
195
+ });
196
+ };
197
+
198
+ const windsurf = createWindsurfProvider({
199
+ apiKey: 'test-key',
200
+ fetch: mockFetch,
201
+ });
202
+ ```
203
+
204
+ ## Examples
205
+
206
+ ### Basic Code Search
207
+
208
+ ```typescript
209
+ import windsurf from '@bxb1337/windsurf-fast-context';
210
+ import { generateText } from 'ai';
211
+
212
+ const result = await generateText({
213
+ model: windsurf('MODEL_SWE_1_6_FAST'),
214
+ prompt: 'Search for TODO comments in the codebase',
215
+ tools: {
216
+ ripgrep: {
217
+ description: 'Search files using regex patterns',
218
+ parameters: {
219
+ type: 'object',
220
+ properties: {
221
+ pattern: { type: 'string', description: 'Regex pattern to search' },
222
+ path: { type: 'string', description: 'Directory to search in' },
223
+ },
224
+ required: ['pattern'],
225
+ },
226
+ },
227
+ },
228
+ });
229
+
230
+ console.log(result.text);
231
+ ```
232
+
233
+ ### Streaming Responses
234
+
235
+ ```typescript
236
+ import windsurf from '@bxb1337/windsurf-fast-context';
237
+ import { streamText } from 'ai';
238
+
239
+ const stream = await streamText({
240
+ model: windsurf('MODEL_SWE_1_6_FAST'),
241
+ prompt: 'Analyze the project structure',
242
+ tools: {
243
+ tree: {
244
+ description: 'List directory tree',
245
+ parameters: {
246
+ type: 'object',
247
+ properties: {
248
+ path: { type: 'string' },
249
+ depth: { type: 'number' },
250
+ },
251
+ required: ['path'],
252
+ },
253
+ },
254
+ },
255
+ });
256
+
257
+ for await (const chunk of stream.textStream) {
258
+ process.stdout.write(chunk);
259
+ }
260
+ ```
261
+
262
+ ### Multi-turn Conversation
263
+
264
+ ```typescript
265
+ import windsurf from '@bxb1337/windsurf-fast-context';
266
+ import { generateText } from 'ai';
267
+
268
+ const result = await generateText({
269
+ model: windsurf('MODEL_SWE_1_6_FAST'),
270
+ messages: [
271
+ { role: 'system', content: 'You are a code search assistant.' },
272
+ { role: 'user', content: 'Find all API routes' },
273
+ { role: 'assistant', content: 'I found routes in src/routes/' },
274
+ { role: 'user', content: 'Show me the auth routes' },
275
+ ],
276
+ tools: {
277
+ glob: {
278
+ description: 'Find files matching a pattern',
279
+ parameters: {
280
+ type: 'object',
281
+ properties: {
282
+ pattern: { type: 'string' },
283
+ },
284
+ required: ['pattern'],
285
+ },
286
+ },
287
+ },
288
+ });
289
+ ```
290
+
291
+ ## Troubleshooting
292
+
293
+ ### "WINDSURF_API_KEY is required"
294
+
295
+ The API key was not provided. Either:
296
+ 1. Pass `apiKey` to `createWindsurfProvider()`
297
+ 2. Set the `WINDSURF_API_KEY` environment variable
298
+
299
+ ### Authentication Errors
300
+
301
+ If you see authentication failures:
302
+ 1. Verify your API key is valid and not expired
303
+ 2. Check that the key starts with the expected prefix
304
+ 3. Ensure no extra whitespace in the environment variable
305
+
306
+ ### Tool Calls Not Executed
307
+
308
+ This is expected behavior. This provider exposes tool calls for you to execute. The `restricted_exec` and `answer` tools are returned as `tool-call` content parts. Your application is responsible for executing them.
309
+
310
+ ### Integration Tests Skipped
311
+
312
+ Integration tests are gated by `WINDSURF_API_KEY`. To run them:
313
+
314
+ ```bash
315
+ export WINDSURF_API_KEY="your-api-key"
316
+ pnpm test test/integration/
317
+ ```
318
+
319
+ ### Custom Endpoint Issues
320
+
321
+ When using `baseURL`, ensure:
322
+ 1. The URL includes the protocol (`https://`)
323
+ 2. No trailing slash
324
+ 3. The endpoint is accessible from your network
325
+
326
+ ## What This Package Does NOT Do
327
+
328
+ Per the design scope:
329
+
330
+ - **No built-in tool execution**: Tools like `rg`, `readfile`, `tree`, `ls`, `glob` are exposed as tool calls, not executed
331
+ - **No MCP server**: This is an AI SDK provider, not an MCP server implementation
332
+ - **No local key extraction**: API keys must be provided explicitly via constructor or environment variable
333
+ - **No Chat/Completions API compatibility**: This is an AI SDK V3 provider, not an OpenAI-compatible API
334
+
335
+ ## License
336
+
337
+ MIT
@@ -0,0 +1,2 @@
1
+ import type { WindsurfProviderOptions } from '../types';
2
+ export declare function resolveApiKey(options?: WindsurfProviderOptions): string;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ type FetchLike = (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
2
+ export declare const AUTH_BASE = "https://server.self-serve.windsurf.com/exa.auth_pb.AuthService";
3
+ export interface JwtManagerOptions {
4
+ fetch?: FetchLike;
5
+ authBase?: string;
6
+ now?: () => number;
7
+ }
8
+ export declare class JwtManager {
9
+ private readonly fetchFn;
10
+ private readonly authBase;
11
+ private readonly now;
12
+ private readonly cache;
13
+ private readonly inFlight;
14
+ constructor(options?: JwtManagerOptions);
15
+ getJwt(apiKey: string): Promise<string>;
16
+ private fetchJwt;
17
+ }
18
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveApiKey = resolveApiKey;
4
+ function resolveApiKey(options) {
5
+ if (options?.apiKey)
6
+ return options.apiKey;
7
+ if (process.env.WINDSURF_API_KEY)
8
+ return process.env.WINDSURF_API_KEY;
9
+ throw new Error('WINDSURF_API_KEY is required');
10
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const api_key_1 = require("./api-key");
5
+ (0, vitest_1.describe)('api key resolver', () => {
6
+ const ORIGINAL = process.env.WINDSURF_API_KEY;
7
+ (0, vitest_1.afterEach)(() => {
8
+ // restore original env to avoid leakage
9
+ if (ORIGINAL === undefined) {
10
+ delete process.env.WINDSURF_API_KEY;
11
+ }
12
+ else {
13
+ process.env.WINDSURF_API_KEY = ORIGINAL;
14
+ }
15
+ });
16
+ (0, vitest_1.it)('constructor', () => {
17
+ const key = (0, api_key_1.resolveApiKey)({ apiKey: 'ctor-key' });
18
+ (0, vitest_1.expect)(key).toBe('ctor-key');
19
+ });
20
+ (0, vitest_1.it)('env', () => {
21
+ process.env.WINDSURF_API_KEY = 'test-key';
22
+ const key = (0, api_key_1.resolveApiKey)();
23
+ (0, vitest_1.expect)(key).toBe('test-key');
24
+ });
25
+ (0, vitest_1.it)('missing', () => {
26
+ delete process.env.WINDSURF_API_KEY;
27
+ (0, vitest_1.expect)(() => (0, api_key_1.resolveApiKey)()).toThrowError(new Error('WINDSURF_API_KEY is required'));
28
+ });
29
+ });
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.JwtManager = exports.AUTH_BASE = void 0;
4
+ const protobuf_js_1 = require("../protocol/protobuf.js");
5
+ exports.AUTH_BASE = 'https://server.self-serve.windsurf.com/exa.auth_pb.AuthService';
6
+ const WS_APP = 'windsurf';
7
+ const WS_APP_VER = process.env.WS_APP_VER ?? '1.48.2';
8
+ const WS_LS_VER = process.env.WS_LS_VER ?? '1.9544.35';
9
+ const JWT_PATTERN = /eyJ[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*\.[A-Za-z0-9_-]*/;
10
+ class JwtManager {
11
+ fetchFn;
12
+ authBase;
13
+ now;
14
+ cache = new Map();
15
+ inFlight = new Map();
16
+ constructor(options = {}) {
17
+ this.fetchFn = options.fetch ?? fetch;
18
+ this.authBase = options.authBase ?? exports.AUTH_BASE;
19
+ this.now = options.now ?? Date.now;
20
+ }
21
+ async getJwt(apiKey) {
22
+ if (!apiKey) {
23
+ throw new Error('API key is required');
24
+ }
25
+ const nowSeconds = Math.floor(this.now() / 1000);
26
+ const cached = this.cache.get(apiKey);
27
+ if (cached && cached.expiresAt > nowSeconds + 60) {
28
+ return cached.token;
29
+ }
30
+ const inFlight = this.inFlight.get(apiKey);
31
+ if (inFlight) {
32
+ return inFlight;
33
+ }
34
+ const pending = this.fetchJwt(apiKey)
35
+ .then((token) => {
36
+ const expiresAt = getJwtExp(token) || Math.floor(this.now() / 1000) + 3600;
37
+ this.cache.set(apiKey, { token, expiresAt });
38
+ return token;
39
+ })
40
+ .finally(() => {
41
+ this.inFlight.delete(apiKey);
42
+ });
43
+ this.inFlight.set(apiKey, pending);
44
+ return pending;
45
+ }
46
+ async fetchJwt(apiKey) {
47
+ const metadata = new protobuf_js_1.ProtobufEncoder();
48
+ metadata.writeString(1, WS_APP);
49
+ metadata.writeString(2, WS_APP_VER);
50
+ metadata.writeString(3, apiKey);
51
+ metadata.writeString(4, 'zh-cn');
52
+ metadata.writeString(7, WS_LS_VER);
53
+ metadata.writeString(12, WS_APP);
54
+ metadata.writeBytes(30, Buffer.from([0x00, 0x01]));
55
+ const requestBody = new protobuf_js_1.ProtobufEncoder();
56
+ requestBody.writeMessage(1, metadata);
57
+ const response = await this.fetchFn(`${this.authBase}/GetUserJwt`, {
58
+ method: 'POST',
59
+ headers: {
60
+ 'Content-Type': 'application/proto',
61
+ 'Connect-Protocol-Version': '1',
62
+ 'User-Agent': 'connect-go/1.18.1 (go1.25.5)',
63
+ },
64
+ body: requestBody.toBuffer(),
65
+ });
66
+ if (!response.ok) {
67
+ throw new Error(`HTTP ${response.status}`);
68
+ }
69
+ const bytes = Buffer.from(await response.arrayBuffer());
70
+ const token = extractJwt(bytes);
71
+ if (!token) {
72
+ throw new Error('Failed to extract JWT from GetUserJwt response');
73
+ }
74
+ return token;
75
+ }
76
+ }
77
+ exports.JwtManager = JwtManager;
78
+ function extractJwt(value) {
79
+ const match = value.toString('utf8').match(JWT_PATTERN);
80
+ return match?.[0] ?? null;
81
+ }
82
+ function getJwtExp(jwt) {
83
+ try {
84
+ const payloadPart = jwt.split('.')[1];
85
+ if (!payloadPart) {
86
+ return 0;
87
+ }
88
+ const payload = JSON.parse(Buffer.from(payloadPart, 'base64url').toString('utf8'));
89
+ return typeof payload.exp === 'number' ? payload.exp : 0;
90
+ }
91
+ catch {
92
+ return 0;
93
+ }
94
+ }
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const protobuf_js_1 = require("../protocol/protobuf.js");
5
+ const jwt_manager_js_1 = require("./jwt-manager.js");
6
+ function makeJwt(exp, tag) {
7
+ const header = Buffer.from(JSON.stringify({ alg: 'HS256', typ: 'JWT' })).toString('base64url');
8
+ const payload = Buffer.from(JSON.stringify({ exp, tag })).toString('base64url');
9
+ return `${header}.${payload}.signature`;
10
+ }
11
+ function makeJwtResponse(token) {
12
+ const encoder = new protobuf_js_1.ProtobufEncoder();
13
+ encoder.writeString(1, `prefix:${token}:suffix`);
14
+ return new Response(Uint8Array.from(encoder.toBuffer()), { status: 200 });
15
+ }
16
+ function bufferFromBody(body) {
17
+ if (body == null) {
18
+ return Buffer.alloc(0);
19
+ }
20
+ if (typeof body === 'string') {
21
+ return Buffer.from(body, 'utf8');
22
+ }
23
+ if (body instanceof ArrayBuffer) {
24
+ return Buffer.from(body);
25
+ }
26
+ if (ArrayBuffer.isView(body)) {
27
+ return Buffer.from(body.buffer, body.byteOffset, body.byteLength);
28
+ }
29
+ throw new Error(`Unsupported request body type: ${typeof body}`);
30
+ }
31
+ (0, vitest_1.describe)('jwt manager fetch', () => {
32
+ (0, vitest_1.it)('fetch exchanges api key to jwt and caches result', async () => {
33
+ const exp = 4_000_000_000;
34
+ const token = makeJwt(exp, 'first');
35
+ const calls = [];
36
+ const fakeFetch = async (input, init) => {
37
+ calls.push({ input, init });
38
+ return makeJwtResponse(token);
39
+ };
40
+ const manager = new jwt_manager_js_1.JwtManager({ fetch: fakeFetch, now: () => (exp - 3_600) * 1000 });
41
+ const jwt1 = await manager.getJwt('test-api-key');
42
+ const jwt2 = await manager.getJwt('test-api-key');
43
+ (0, vitest_1.expect)(jwt1).toBe(token);
44
+ (0, vitest_1.expect)(jwt2).toBe(token);
45
+ (0, vitest_1.expect)(calls).toHaveLength(1);
46
+ (0, vitest_1.expect)(calls[0]?.input).toBe(`${jwt_manager_js_1.AUTH_BASE}/GetUserJwt`);
47
+ (0, vitest_1.expect)(calls[0]?.init?.method).toBe('POST');
48
+ (0, vitest_1.expect)(new Headers(calls[0]?.init?.headers).get('content-type')).toBe('application/proto');
49
+ const requestBody = bufferFromBody(calls[0]?.init?.body);
50
+ (0, vitest_1.expect)(requestBody.length).toBeGreaterThan(0);
51
+ (0, vitest_1.expect)(requestBody.toString('utf8')).toContain('test-api-key');
52
+ });
53
+ });
54
+ (0, vitest_1.describe)('jwt manager expiry', () => {
55
+ (0, vitest_1.it)('expiry refreshes token when less than sixty seconds remain', async () => {
56
+ let nowMs = 0;
57
+ const token1 = makeJwt(10_000, 'first');
58
+ const token2 = makeJwt(20_000, 'second');
59
+ const queue = [makeJwtResponse(token1), makeJwtResponse(token2)];
60
+ let callCount = 0;
61
+ const fakeFetch = async () => {
62
+ callCount += 1;
63
+ const next = queue.shift();
64
+ if (!next) {
65
+ throw new Error('No fake fetch response queued');
66
+ }
67
+ return next;
68
+ };
69
+ const manager = new jwt_manager_js_1.JwtManager({ fetch: fakeFetch, now: () => nowMs });
70
+ const first = await manager.getJwt('exp-api-key');
71
+ nowMs = (10_000 - 59) * 1000;
72
+ const second = await manager.getJwt('exp-api-key');
73
+ (0, vitest_1.expect)(first).toBe(token1);
74
+ (0, vitest_1.expect)(second).toBe(token2);
75
+ (0, vitest_1.expect)(callCount).toBe(2);
76
+ });
77
+ });
78
+ (0, vitest_1.describe)('jwt manager concurrent', () => {
79
+ (0, vitest_1.it)('concurrent getJwt calls share one in-flight fetch', async () => {
80
+ const token = makeJwt(50_000, 'concurrent');
81
+ let fetchCalls = 0;
82
+ let resolveFetch;
83
+ const pending = new Promise((resolve) => {
84
+ resolveFetch = resolve;
85
+ });
86
+ const fakeFetch = async () => {
87
+ fetchCalls += 1;
88
+ return pending;
89
+ };
90
+ const manager = new jwt_manager_js_1.JwtManager({ fetch: fakeFetch, now: () => 0 });
91
+ const p1 = manager.getJwt('same-key');
92
+ const p2 = manager.getJwt('same-key');
93
+ resolveFetch(makeJwtResponse(token));
94
+ const [jwt1, jwt2] = await Promise.all([p1, p2]);
95
+ (0, vitest_1.expect)(fetchCalls).toBe(1);
96
+ (0, vitest_1.expect)(jwt1).toBe(token);
97
+ (0, vitest_1.expect)(jwt2).toBe(token);
98
+ });
99
+ });
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.convertPrompt = convertPrompt;
4
+ function toContentString(value) {
5
+ if (typeof value === 'string') {
6
+ return value;
7
+ }
8
+ return JSON.stringify(value);
9
+ }
10
+ function convertPrompt(prompt) {
11
+ const messages = [];
12
+ for (const message of prompt) {
13
+ if (message.role === 'system') {
14
+ messages.push({ role: 5, content: message.content });
15
+ continue;
16
+ }
17
+ if (message.role === 'user') {
18
+ const text = message.content
19
+ .filter((part) => part.type === 'text')
20
+ .map((part) => part.text)
21
+ .join('');
22
+ messages.push({ role: 1, content: text });
23
+ continue;
24
+ }
25
+ if (message.role === 'assistant') {
26
+ for (const part of message.content) {
27
+ if (part.type === 'text') {
28
+ messages.push({ role: 2, content: part.text });
29
+ continue;
30
+ }
31
+ if (part.type === 'tool-call') {
32
+ messages.push({
33
+ role: 2,
34
+ content: '',
35
+ metadata: {
36
+ toolCallId: part.toolCallId,
37
+ toolName: part.toolName,
38
+ toolArgsJson: JSON.stringify(part.args),
39
+ },
40
+ });
41
+ }
42
+ }
43
+ continue;
44
+ }
45
+ // Tool result messages - use refCallId to reference the original tool call
46
+ for (const part of message.content) {
47
+ messages.push({
48
+ role: 4,
49
+ content: toContentString(part.result),
50
+ metadata: {
51
+ refCallId: part.toolCallId, // This links back to the tool call
52
+ },
53
+ });
54
+ }
55
+ }
56
+ return messages;
57
+ }