@boostecom/provider 0.0.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.
- package/README.md +90 -0
- package/dist/index.cjs +2522 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +848 -0
- package/dist/index.d.ts +848 -0
- package/dist/index.js +2484 -0
- package/dist/index.js.map +1 -0
- package/docs/content/README.md +337 -0
- package/docs/content/agent-teams.mdx +324 -0
- package/docs/content/api.mdx +757 -0
- package/docs/content/best-practices.mdx +624 -0
- package/docs/content/examples.mdx +675 -0
- package/docs/content/guide.mdx +516 -0
- package/docs/content/index.mdx +99 -0
- package/docs/content/installation.mdx +246 -0
- package/docs/content/skills.mdx +548 -0
- package/docs/content/troubleshooting.mdx +588 -0
- package/docs/examples/README.md +499 -0
- package/docs/examples/abort-signal.ts +125 -0
- package/docs/examples/agent-teams.ts +122 -0
- package/docs/examples/basic-usage.ts +73 -0
- package/docs/examples/check-cli.ts +51 -0
- package/docs/examples/conversation-history.ts +69 -0
- package/docs/examples/custom-config.ts +90 -0
- package/docs/examples/generate-object-constraints.ts +209 -0
- package/docs/examples/generate-object.ts +211 -0
- package/docs/examples/hooks-callbacks.ts +63 -0
- package/docs/examples/images.ts +76 -0
- package/docs/examples/integration-test.ts +241 -0
- package/docs/examples/limitations.ts +150 -0
- package/docs/examples/logging-custom-logger.ts +99 -0
- package/docs/examples/logging-default.ts +55 -0
- package/docs/examples/logging-disabled.ts +74 -0
- package/docs/examples/logging-verbose.ts +64 -0
- package/docs/examples/long-running-tasks.ts +179 -0
- package/docs/examples/message-injection.ts +210 -0
- package/docs/examples/mid-stream-injection.ts +126 -0
- package/docs/examples/run-all-examples.sh +48 -0
- package/docs/examples/sdk-tools-callbacks.ts +49 -0
- package/docs/examples/skills-discovery.ts +144 -0
- package/docs/examples/skills-management.ts +140 -0
- package/docs/examples/stream-object.ts +80 -0
- package/docs/examples/streaming.ts +52 -0
- package/docs/examples/structured-output-repro.ts +227 -0
- package/docs/examples/tool-management.ts +215 -0
- package/docs/examples/tool-streaming.ts +132 -0
- package/docs/examples/zod4-compatibility-test.ts +290 -0
- package/docs/src/claude-code-language-model.test.ts +3883 -0
- package/docs/src/claude-code-language-model.ts +2586 -0
- package/docs/src/claude-code-provider.test.ts +97 -0
- package/docs/src/claude-code-provider.ts +179 -0
- package/docs/src/convert-to-claude-code-messages.images.test.ts +104 -0
- package/docs/src/convert-to-claude-code-messages.test.ts +193 -0
- package/docs/src/convert-to-claude-code-messages.ts +419 -0
- package/docs/src/errors.test.ts +213 -0
- package/docs/src/errors.ts +216 -0
- package/docs/src/index.test.ts +49 -0
- package/docs/src/index.ts +98 -0
- package/docs/src/logger.integration.test.ts +164 -0
- package/docs/src/logger.test.ts +184 -0
- package/docs/src/logger.ts +65 -0
- package/docs/src/map-claude-code-finish-reason.test.ts +120 -0
- package/docs/src/map-claude-code-finish-reason.ts +60 -0
- package/docs/src/mcp-helpers.test.ts +71 -0
- package/docs/src/mcp-helpers.ts +123 -0
- package/docs/src/message-injection.test.ts +460 -0
- package/docs/src/types.ts +447 -0
- package/docs/src/validation.test.ts +558 -0
- package/docs/src/validation.ts +360 -0
- package/package.json +124 -0
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
claudeCodeSettingsSchema,
|
|
4
|
+
validateModelId,
|
|
5
|
+
validateSettings,
|
|
6
|
+
validatePrompt,
|
|
7
|
+
validateSessionId,
|
|
8
|
+
} from './validation.js';
|
|
9
|
+
import * as fs from 'fs';
|
|
10
|
+
|
|
11
|
+
// Mock fs module
|
|
12
|
+
vi.mock('fs', () => ({
|
|
13
|
+
existsSync: vi.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
describe('claudeCodeSettingsSchema', () => {
|
|
17
|
+
it('should accept valid settings', () => {
|
|
18
|
+
const validSettings = {
|
|
19
|
+
pathToClaudeCodeExecutable: '/usr/bin/claude',
|
|
20
|
+
customSystemPrompt: 'You are helpful',
|
|
21
|
+
maxTurns: 10,
|
|
22
|
+
maxThinkingTokens: 50000,
|
|
23
|
+
executable: 'node',
|
|
24
|
+
executableArgs: ['--experimental'],
|
|
25
|
+
continue: true,
|
|
26
|
+
resume: 'session-123',
|
|
27
|
+
allowedTools: ['Read', 'Write'],
|
|
28
|
+
disallowedTools: ['Bash'],
|
|
29
|
+
betas: ['context-1m-2025-08-07'],
|
|
30
|
+
allowDangerouslySkipPermissions: true,
|
|
31
|
+
enableFileCheckpointing: true,
|
|
32
|
+
maxBudgetUsd: 2.5,
|
|
33
|
+
plugins: [{ type: 'local', path: './plugins/my-plugin' }],
|
|
34
|
+
resumeSessionAt: 'message-uuid',
|
|
35
|
+
sandbox: { enabled: true },
|
|
36
|
+
tools: ['Read', 'Write'],
|
|
37
|
+
verbose: true,
|
|
38
|
+
env: { BASH_DEFAULT_TIMEOUT_MS: '10' },
|
|
39
|
+
sdkOptions: { maxTurns: 3 },
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const result = claudeCodeSettingsSchema.safeParse(validSettings);
|
|
43
|
+
expect(result.success).toBe(true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should reject invalid maxTurns', () => {
|
|
47
|
+
const settings = { maxTurns: 0 };
|
|
48
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
49
|
+
expect(result.success).toBe(false);
|
|
50
|
+
if (!result.success) {
|
|
51
|
+
// Support both Zod v3 (errors) and v4 (issues)
|
|
52
|
+
const issues = (result.error as any).errors || result.error.issues;
|
|
53
|
+
// Support both v3 and v4 error message formats
|
|
54
|
+
expect(issues[0].message).toMatch(/greater than or equal to 1|Too small.*>=1/);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should reject invalid executable', () => {
|
|
59
|
+
const settings = { executable: 'python' as any };
|
|
60
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
61
|
+
expect(result.success).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should accept empty settings object', () => {
|
|
65
|
+
const result = claudeCodeSettingsSchema.safeParse({});
|
|
66
|
+
expect(result.success).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should reject unknown properties', () => {
|
|
70
|
+
const settings = { unknownProp: 'value' };
|
|
71
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
72
|
+
expect(result.success).toBe(false);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should accept env as a record of strings', () => {
|
|
76
|
+
const settings = { env: { PATH: '/usr/bin', FOO: 'bar' } };
|
|
77
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
78
|
+
expect(result.success).toBe(true);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should accept env values that are undefined', () => {
|
|
82
|
+
const settings = { env: { PATH: '/usr/bin', UNSET: undefined } };
|
|
83
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
84
|
+
expect(result.success).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('should reject env values that are not strings', () => {
|
|
88
|
+
const settings = { env: { NUM: 123 as any } };
|
|
89
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
90
|
+
expect(result.success).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should accept sessionId as a string', () => {
|
|
94
|
+
const settings = { sessionId: 'my-custom-session-id' };
|
|
95
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
96
|
+
expect(result.success).toBe(true);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should reject sessionId when not a string', () => {
|
|
100
|
+
const settings = { sessionId: 123 as any };
|
|
101
|
+
const result = claudeCodeSettingsSchema.safeParse(settings);
|
|
102
|
+
expect(result.success).toBe(false);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('should accept debug as a boolean', () => {
|
|
106
|
+
const result = claudeCodeSettingsSchema.safeParse({ debug: true });
|
|
107
|
+
expect(result.success).toBe(true);
|
|
108
|
+
const result2 = claudeCodeSettingsSchema.safeParse({ debug: false });
|
|
109
|
+
expect(result2.success).toBe(true);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('should reject debug when not a boolean', () => {
|
|
113
|
+
const result = claudeCodeSettingsSchema.safeParse({ debug: 'yes' as any });
|
|
114
|
+
expect(result.success).toBe(false);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should accept debugFile as a string', () => {
|
|
118
|
+
const result = claudeCodeSettingsSchema.safeParse({ debugFile: '/tmp/debug.log' });
|
|
119
|
+
expect(result.success).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('should reject debugFile when not a string', () => {
|
|
123
|
+
const result = claudeCodeSettingsSchema.safeParse({ debugFile: 42 as any });
|
|
124
|
+
expect(result.success).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe('validateModelId', () => {
|
|
129
|
+
it('should accept known models without warnings', () => {
|
|
130
|
+
expect(validateModelId('opus')).toBeUndefined();
|
|
131
|
+
expect(validateModelId('sonnet')).toBeUndefined();
|
|
132
|
+
expect(validateModelId('haiku')).toBeUndefined();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it('should warn about unknown models', () => {
|
|
136
|
+
const warning = validateModelId('gpt-4');
|
|
137
|
+
expect(warning).toContain("Unknown model ID: 'gpt-4'");
|
|
138
|
+
expect(warning).toContain('Known models are: opus, sonnet, haiku');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it('should throw error for empty model ID', () => {
|
|
142
|
+
expect(() => validateModelId('')).toThrow('Model ID cannot be empty');
|
|
143
|
+
expect(() => validateModelId(' ')).toThrow('Model ID cannot be empty');
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('should throw error for null/undefined model ID', () => {
|
|
147
|
+
expect(() => validateModelId(null as any)).toThrow('Model ID cannot be empty');
|
|
148
|
+
expect(() => validateModelId(undefined as any)).toThrow('Model ID cannot be empty');
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe('validateSettings', () => {
|
|
153
|
+
beforeEach(() => {
|
|
154
|
+
vi.mocked(fs.existsSync).mockReturnValue(true);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
afterEach(() => {
|
|
158
|
+
vi.clearAllMocks();
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should validate correct settings', () => {
|
|
162
|
+
const settings = {
|
|
163
|
+
maxTurns: 10,
|
|
164
|
+
maxThinkingTokens: 30000,
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const result = validateSettings(settings);
|
|
168
|
+
expect(result.valid).toBe(true);
|
|
169
|
+
expect(result.errors).toHaveLength(0);
|
|
170
|
+
expect(result.warnings).toHaveLength(0);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('should warn about high maxTurns', () => {
|
|
174
|
+
const settings = { maxTurns: 50 };
|
|
175
|
+
const result = validateSettings(settings);
|
|
176
|
+
|
|
177
|
+
expect(result.valid).toBe(true);
|
|
178
|
+
expect(result.warnings).toHaveLength(1);
|
|
179
|
+
expect(result.warnings[0]).toContain('High maxTurns value (50)');
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('should warn about very high maxThinkingTokens', () => {
|
|
183
|
+
const settings = { maxThinkingTokens: 80000 };
|
|
184
|
+
const result = validateSettings(settings);
|
|
185
|
+
|
|
186
|
+
expect(result.valid).toBe(true);
|
|
187
|
+
expect(result.warnings).toHaveLength(1);
|
|
188
|
+
expect(result.warnings[0]).toContain('Very high maxThinkingTokens (80000)');
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should warn when both allowedTools and disallowedTools are specified', () => {
|
|
192
|
+
const settings = {
|
|
193
|
+
allowedTools: ['Read'],
|
|
194
|
+
disallowedTools: ['Write'],
|
|
195
|
+
};
|
|
196
|
+
const result = validateSettings(settings);
|
|
197
|
+
|
|
198
|
+
expect(result.valid).toBe(true);
|
|
199
|
+
expect(result.warnings).toHaveLength(1);
|
|
200
|
+
expect(result.warnings[0]).toContain('Both allowedTools and disallowedTools are specified');
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should validate tool name formats', () => {
|
|
204
|
+
const settings = {
|
|
205
|
+
allowedTools: ['Read', 'Write', 'Bash(git log:*)', 'mcp__server__tool'],
|
|
206
|
+
disallowedTools: ['123invalid', '@#$bad'],
|
|
207
|
+
};
|
|
208
|
+
const result = validateSettings(settings);
|
|
209
|
+
|
|
210
|
+
expect(result.valid).toBe(true);
|
|
211
|
+
// The function also validates allowed tools, so we may get warnings for non-standard names
|
|
212
|
+
expect(result.warnings.length).toBeGreaterThanOrEqual(2);
|
|
213
|
+
// Check that we get warnings about unusual tool names
|
|
214
|
+
const toolWarnings = result.warnings.filter(
|
|
215
|
+
(w) => w.includes('Unusual') && w.includes('tool name format')
|
|
216
|
+
);
|
|
217
|
+
expect(toolWarnings.length).toBeGreaterThanOrEqual(2);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('should validate working directory exists', () => {
|
|
221
|
+
vi.mocked(fs.existsSync).mockReturnValue(false);
|
|
222
|
+
|
|
223
|
+
const settings = { cwd: '/nonexistent/path' };
|
|
224
|
+
const result = validateSettings(settings);
|
|
225
|
+
|
|
226
|
+
expect(result.valid).toBe(false);
|
|
227
|
+
expect(result.errors).toHaveLength(1);
|
|
228
|
+
expect(result.errors[0]).toContain('Working directory must exist');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should handle invalid settings type', () => {
|
|
232
|
+
const result = validateSettings('not an object' as any);
|
|
233
|
+
expect(result.valid).toBe(false);
|
|
234
|
+
expect(result.errors.length).toBeGreaterThan(0);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('should handle validation exceptions', () => {
|
|
238
|
+
vi.mocked(fs.existsSync).mockImplementation(() => {
|
|
239
|
+
throw new Error('FS error');
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
const settings = { cwd: '/some/path' };
|
|
243
|
+
const result = validateSettings(settings);
|
|
244
|
+
|
|
245
|
+
expect(result.valid).toBe(false);
|
|
246
|
+
expect(result.errors[0]).toContain('Validation error: FS error');
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it('should validate permissionMode values', () => {
|
|
250
|
+
// Valid permission modes (including delegate and dontAsk added in SDK 0.2.x)
|
|
251
|
+
const validModes = [
|
|
252
|
+
'default',
|
|
253
|
+
'acceptEdits',
|
|
254
|
+
'bypassPermissions',
|
|
255
|
+
'plan',
|
|
256
|
+
'delegate',
|
|
257
|
+
'dontAsk',
|
|
258
|
+
];
|
|
259
|
+
validModes.forEach((mode) => {
|
|
260
|
+
const result = validateSettings({ permissionMode: mode });
|
|
261
|
+
expect(result.valid).toBe(true);
|
|
262
|
+
expect(result.errors).toHaveLength(0);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// Invalid permission mode
|
|
266
|
+
const invalidResult = validateSettings({ permissionMode: 'invalid' });
|
|
267
|
+
expect(invalidResult.valid).toBe(false);
|
|
268
|
+
expect(invalidResult.errors[0]).toContain('permissionMode');
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
it('should validate mcpServers configuration', () => {
|
|
272
|
+
// Valid stdio server
|
|
273
|
+
const validStdio = {
|
|
274
|
+
mcpServers: {
|
|
275
|
+
filesystem: {
|
|
276
|
+
command: 'npx',
|
|
277
|
+
args: ['@modelcontextprotocol/server-filesystem'],
|
|
278
|
+
env: { PATH: '/usr/bin' },
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
expect(validateSettings(validStdio).valid).toBe(true);
|
|
283
|
+
|
|
284
|
+
// Valid stdio server without optional type field
|
|
285
|
+
const validStdioNoType = {
|
|
286
|
+
mcpServers: {
|
|
287
|
+
filesystem: {
|
|
288
|
+
command: 'npx',
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
expect(validateSettings(validStdioNoType).valid).toBe(true);
|
|
293
|
+
|
|
294
|
+
// Valid SSE server
|
|
295
|
+
const validSSE = {
|
|
296
|
+
mcpServers: {
|
|
297
|
+
apiServer: {
|
|
298
|
+
type: 'sse',
|
|
299
|
+
url: 'https://example.com/sse',
|
|
300
|
+
headers: { Authorization: 'Bearer token' },
|
|
301
|
+
},
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
expect(validateSettings(validSSE).valid).toBe(true);
|
|
305
|
+
|
|
306
|
+
// Valid HTTP server
|
|
307
|
+
const validHTTP = {
|
|
308
|
+
mcpServers: {
|
|
309
|
+
apiServer: {
|
|
310
|
+
type: 'http',
|
|
311
|
+
url: 'https://example.com/api',
|
|
312
|
+
headers: { Authorization: 'Bearer token' },
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
expect(validateSettings(validHTTP).valid).toBe(true);
|
|
317
|
+
|
|
318
|
+
// Invalid - missing required fields
|
|
319
|
+
const invalidMissingCommand = {
|
|
320
|
+
mcpServers: {
|
|
321
|
+
invalid: {
|
|
322
|
+
args: ['test'],
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
};
|
|
326
|
+
const result1 = validateSettings(invalidMissingCommand);
|
|
327
|
+
expect(result1.valid).toBe(false);
|
|
328
|
+
expect(result1.errors[0]).toContain('mcpServers');
|
|
329
|
+
|
|
330
|
+
// Invalid - SSE missing url
|
|
331
|
+
const invalidSSEMissingUrl = {
|
|
332
|
+
mcpServers: {
|
|
333
|
+
invalid: {
|
|
334
|
+
type: 'sse',
|
|
335
|
+
headers: { test: 'value' },
|
|
336
|
+
},
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
const result2 = validateSettings(invalidSSEMissingUrl);
|
|
340
|
+
expect(result2.valid).toBe(false);
|
|
341
|
+
expect(result2.errors[0]).toContain('mcpServers');
|
|
342
|
+
|
|
343
|
+
// Invalid - HTTP missing url
|
|
344
|
+
const invalidHTTPMissingUrl = {
|
|
345
|
+
mcpServers: {
|
|
346
|
+
invalid: {
|
|
347
|
+
type: 'http',
|
|
348
|
+
headers: { test: 'value' },
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
const result3 = validateSettings(invalidHTTPMissingUrl);
|
|
353
|
+
expect(result3.valid).toBe(false);
|
|
354
|
+
expect(result3.errors[0]).toContain('mcpServers');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('should validate hooks and canUseTool settings', () => {
|
|
358
|
+
// Valid canUseTool function
|
|
359
|
+
const valid1 = validateSettings({
|
|
360
|
+
canUseTool: async () => ({ behavior: 'allow', updatedInput: {} }),
|
|
361
|
+
});
|
|
362
|
+
expect(valid1.valid).toBe(true);
|
|
363
|
+
|
|
364
|
+
// Invalid canUseTool
|
|
365
|
+
const invalid1 = validateSettings({ canUseTool: 'not-a-function' as any });
|
|
366
|
+
expect(invalid1.valid).toBe(false);
|
|
367
|
+
expect(invalid1.errors[0]).toContain('canUseTool');
|
|
368
|
+
|
|
369
|
+
// Valid hooks
|
|
370
|
+
const validHooks = validateSettings({
|
|
371
|
+
hooks: { PreToolUse: [{ hooks: [async () => ({ continue: true })] }] },
|
|
372
|
+
});
|
|
373
|
+
expect(validHooks.valid).toBe(true);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should validate SDK MCP server configuration (type: sdk)', () => {
|
|
377
|
+
// Valid SDK server
|
|
378
|
+
const validSdk = {
|
|
379
|
+
mcpServers: {
|
|
380
|
+
custom: {
|
|
381
|
+
type: 'sdk',
|
|
382
|
+
name: 'local',
|
|
383
|
+
instance: {},
|
|
384
|
+
},
|
|
385
|
+
},
|
|
386
|
+
};
|
|
387
|
+
expect(validateSettings(validSdk).valid).toBe(true);
|
|
388
|
+
|
|
389
|
+
// Invalid - missing name
|
|
390
|
+
const invalidSdk = {
|
|
391
|
+
mcpServers: {
|
|
392
|
+
bad: {
|
|
393
|
+
type: 'sdk',
|
|
394
|
+
instance: {},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
} as any;
|
|
398
|
+
const res = validateSettings(invalidSdk);
|
|
399
|
+
expect(res.valid).toBe(false);
|
|
400
|
+
expect(res.errors[0]).toContain('mcpServers');
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
it('should validate persistSession option', () => {
|
|
404
|
+
// Valid boolean values
|
|
405
|
+
expect(validateSettings({ persistSession: true }).valid).toBe(true);
|
|
406
|
+
expect(validateSettings({ persistSession: false }).valid).toBe(true);
|
|
407
|
+
|
|
408
|
+
// Invalid - non-boolean
|
|
409
|
+
const invalidResult = validateSettings({ persistSession: 'true' as any });
|
|
410
|
+
expect(invalidResult.valid).toBe(false);
|
|
411
|
+
expect(invalidResult.errors[0]).toContain('persistSession');
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
it('should validate spawnClaudeCodeProcess option', () => {
|
|
415
|
+
// Valid function
|
|
416
|
+
const validResult = validateSettings({
|
|
417
|
+
spawnClaudeCodeProcess: () => ({
|
|
418
|
+
stdin: null,
|
|
419
|
+
stdout: null,
|
|
420
|
+
stderr: null,
|
|
421
|
+
kill: () => {},
|
|
422
|
+
}),
|
|
423
|
+
});
|
|
424
|
+
expect(validResult.valid).toBe(true);
|
|
425
|
+
|
|
426
|
+
// Invalid - non-function
|
|
427
|
+
const invalidResult = validateSettings({ spawnClaudeCodeProcess: 'not-a-function' as any });
|
|
428
|
+
expect(invalidResult.valid).toBe(false);
|
|
429
|
+
expect(invalidResult.errors[0]).toContain('spawnClaudeCodeProcess');
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('should validate agents with new SDK 0.2.x fields', () => {
|
|
433
|
+
// Valid agent with new fields
|
|
434
|
+
const validAgent = {
|
|
435
|
+
agents: {
|
|
436
|
+
'test-agent': {
|
|
437
|
+
description: 'A test agent',
|
|
438
|
+
prompt: 'You are a test agent',
|
|
439
|
+
tools: ['Read', 'Write'],
|
|
440
|
+
disallowedTools: ['Bash'],
|
|
441
|
+
mcpServers: ['my-server', { custom: { command: 'node', args: ['server.js'] } }],
|
|
442
|
+
criticalSystemReminder_EXPERIMENTAL: 'Remember to be careful',
|
|
443
|
+
},
|
|
444
|
+
},
|
|
445
|
+
};
|
|
446
|
+
expect(validateSettings(validAgent).valid).toBe(true);
|
|
447
|
+
|
|
448
|
+
// Valid agent without optional new fields
|
|
449
|
+
const minimalAgent = {
|
|
450
|
+
agents: {
|
|
451
|
+
minimal: {
|
|
452
|
+
description: 'Minimal agent',
|
|
453
|
+
prompt: 'You are minimal',
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
expect(validateSettings(minimalAgent).valid).toBe(true);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
describe('Skills configuration warnings', () => {
|
|
461
|
+
it('should warn when Skill is in allowedTools but settingSources is not set', () => {
|
|
462
|
+
const settings = {
|
|
463
|
+
allowedTools: ['Skill', 'Read'],
|
|
464
|
+
};
|
|
465
|
+
const result = validateSettings(settings);
|
|
466
|
+
|
|
467
|
+
expect(result.valid).toBe(true);
|
|
468
|
+
expect(result.warnings.some((w) => w.includes("allowedTools includes 'Skill'"))).toBe(true);
|
|
469
|
+
expect(result.warnings.some((w) => w.includes('settingSources is not set'))).toBe(true);
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('should not warn when Skill is in allowedTools and settingSources is set', () => {
|
|
473
|
+
const settings = {
|
|
474
|
+
allowedTools: ['Skill', 'Read'],
|
|
475
|
+
settingSources: ['user', 'project'] as const,
|
|
476
|
+
};
|
|
477
|
+
const result = validateSettings(settings);
|
|
478
|
+
|
|
479
|
+
expect(result.valid).toBe(true);
|
|
480
|
+
// Should not have the Skill warning
|
|
481
|
+
const skillWarnings = result.warnings.filter((w) =>
|
|
482
|
+
w.includes("allowedTools includes 'Skill'")
|
|
483
|
+
);
|
|
484
|
+
expect(skillWarnings).toHaveLength(0);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
it('should not warn when Skill is not in allowedTools', () => {
|
|
488
|
+
const settings = {
|
|
489
|
+
allowedTools: ['Read', 'Write'],
|
|
490
|
+
};
|
|
491
|
+
const result = validateSettings(settings);
|
|
492
|
+
|
|
493
|
+
expect(result.valid).toBe(true);
|
|
494
|
+
const skillWarnings = result.warnings.filter((w) => w.includes('Skill'));
|
|
495
|
+
expect(skillWarnings).toHaveLength(0);
|
|
496
|
+
});
|
|
497
|
+
});
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
describe('validatePrompt', () => {
|
|
501
|
+
it('should not warn for normal prompts', () => {
|
|
502
|
+
const normalPrompt = 'Write a function to calculate fibonacci numbers';
|
|
503
|
+
expect(validatePrompt(normalPrompt)).toBeUndefined();
|
|
504
|
+
|
|
505
|
+
const longButOkPrompt = 'a'.repeat(50000);
|
|
506
|
+
expect(validatePrompt(longButOkPrompt)).toBeUndefined();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it('should warn for very long prompts', () => {
|
|
510
|
+
const veryLongPrompt = 'x'.repeat(100001);
|
|
511
|
+
const warning = validatePrompt(veryLongPrompt);
|
|
512
|
+
|
|
513
|
+
expect(warning).toContain('Very long prompt (100001 characters)');
|
|
514
|
+
expect(warning).toContain('may cause performance issues or timeouts');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
it('should handle empty prompts', () => {
|
|
518
|
+
expect(validatePrompt('')).toBeUndefined();
|
|
519
|
+
});
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
describe('validateSessionId', () => {
|
|
523
|
+
it('should accept valid session IDs', () => {
|
|
524
|
+
const validIds = [
|
|
525
|
+
'abc-123-def',
|
|
526
|
+
'session_12345',
|
|
527
|
+
'UUID-4a5b6c7d-8e9f',
|
|
528
|
+
'123456789',
|
|
529
|
+
'test-session',
|
|
530
|
+
];
|
|
531
|
+
|
|
532
|
+
validIds.forEach((id) => {
|
|
533
|
+
expect(validateSessionId(id)).toBeUndefined();
|
|
534
|
+
});
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
it('should warn about unusual session ID formats', () => {
|
|
538
|
+
const unusualIds = [
|
|
539
|
+
'session with spaces',
|
|
540
|
+
'special@characters#',
|
|
541
|
+
'unicode-🔥-session',
|
|
542
|
+
'new\nline',
|
|
543
|
+
'tab\tcharacter',
|
|
544
|
+
];
|
|
545
|
+
|
|
546
|
+
unusualIds.forEach((id) => {
|
|
547
|
+
const warning = validateSessionId(id);
|
|
548
|
+
expect(warning).toContain('Unusual session ID format');
|
|
549
|
+
expect(warning).toContain('may cause issues with session resumption');
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
it('should handle empty session IDs', () => {
|
|
554
|
+
expect(validateSessionId('')).toBeUndefined();
|
|
555
|
+
expect(validateSessionId(null as any)).toBeUndefined();
|
|
556
|
+
expect(validateSessionId(undefined as any)).toBeUndefined();
|
|
557
|
+
});
|
|
558
|
+
});
|