@mhalder/qdrant-mcp-server 1.3.1 → 1.4.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.
- package/CHANGELOG.md +7 -0
- package/README.md +68 -0
- package/build/index.js +71 -6
- package/build/index.js.map +1 -1
- package/build/prompts/index.d.ts +7 -0
- package/build/prompts/index.d.ts.map +1 -0
- package/build/prompts/index.js +7 -0
- package/build/prompts/index.js.map +1 -0
- package/build/prompts/index.test.d.ts +2 -0
- package/build/prompts/index.test.d.ts.map +1 -0
- package/build/prompts/index.test.js +25 -0
- package/build/prompts/index.test.js.map +1 -0
- package/build/prompts/loader.d.ts +25 -0
- package/build/prompts/loader.d.ts.map +1 -0
- package/build/prompts/loader.js +81 -0
- package/build/prompts/loader.js.map +1 -0
- package/build/prompts/loader.test.d.ts +2 -0
- package/build/prompts/loader.test.d.ts.map +1 -0
- package/build/prompts/loader.test.js +417 -0
- package/build/prompts/loader.test.js.map +1 -0
- package/build/prompts/template.d.ts +20 -0
- package/build/prompts/template.d.ts.map +1 -0
- package/build/prompts/template.js +52 -0
- package/build/prompts/template.js.map +1 -0
- package/build/prompts/template.test.d.ts +2 -0
- package/build/prompts/template.test.d.ts.map +1 -0
- package/build/prompts/template.test.js +163 -0
- package/build/prompts/template.test.js.map +1 -0
- package/build/prompts/types.d.ts +34 -0
- package/build/prompts/types.d.ts.map +1 -0
- package/build/prompts/types.js +5 -0
- package/build/prompts/types.js.map +1 -0
- package/package.json +1 -1
- package/prompts.example.json +96 -0
- package/src/index.ts +86 -5
- package/src/prompts/index.test.ts +29 -0
- package/src/prompts/index.ts +7 -0
- package/src/prompts/loader.test.ts +494 -0
- package/src/prompts/loader.ts +90 -0
- package/src/prompts/template.test.ts +212 -0
- package/src/prompts/template.ts +69 -0
- package/src/prompts/types.ts +37 -0
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import { getPrompt, listPrompts, loadPromptsConfig } from "./loader.js";
|
|
6
|
+
import type { PromptsConfig } from "./types.js";
|
|
7
|
+
|
|
8
|
+
describe("loadPromptsConfig", () => {
|
|
9
|
+
let testDir: string;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Create a temporary directory for test files
|
|
13
|
+
testDir = join(tmpdir(), `prompts-test-${Date.now()}`);
|
|
14
|
+
mkdirSync(testDir, { recursive: true });
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
// Clean up test directory
|
|
19
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("should load valid prompts configuration", () => {
|
|
23
|
+
const config = {
|
|
24
|
+
prompts: [
|
|
25
|
+
{
|
|
26
|
+
name: "test_prompt",
|
|
27
|
+
description: "A test prompt",
|
|
28
|
+
arguments: [
|
|
29
|
+
{
|
|
30
|
+
name: "arg1",
|
|
31
|
+
description: "First argument",
|
|
32
|
+
required: true,
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
template: "Test {{arg1}}",
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const filePath = join(testDir, "prompts.json");
|
|
41
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
42
|
+
|
|
43
|
+
const result = loadPromptsConfig(filePath);
|
|
44
|
+
|
|
45
|
+
expect(result).toEqual(config);
|
|
46
|
+
expect(result.prompts).toHaveLength(1);
|
|
47
|
+
expect(result.prompts[0].name).toBe("test_prompt");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("should load configuration with multiple prompts", () => {
|
|
51
|
+
const config = {
|
|
52
|
+
prompts: [
|
|
53
|
+
{
|
|
54
|
+
name: "prompt1",
|
|
55
|
+
description: "First prompt",
|
|
56
|
+
arguments: [],
|
|
57
|
+
template: "Template 1",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
name: "prompt2",
|
|
61
|
+
description: "Second prompt",
|
|
62
|
+
arguments: [],
|
|
63
|
+
template: "Template 2",
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const filePath = join(testDir, "prompts.json");
|
|
69
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
70
|
+
|
|
71
|
+
const result = loadPromptsConfig(filePath);
|
|
72
|
+
|
|
73
|
+
expect(result.prompts).toHaveLength(2);
|
|
74
|
+
expect(result.prompts[0].name).toBe("prompt1");
|
|
75
|
+
expect(result.prompts[1].name).toBe("prompt2");
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("should load configuration with optional arguments", () => {
|
|
79
|
+
const config = {
|
|
80
|
+
prompts: [
|
|
81
|
+
{
|
|
82
|
+
name: "test_prompt",
|
|
83
|
+
description: "A test prompt",
|
|
84
|
+
arguments: [
|
|
85
|
+
{
|
|
86
|
+
name: "required_arg",
|
|
87
|
+
description: "Required argument",
|
|
88
|
+
required: true,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: "optional_arg",
|
|
92
|
+
description: "Optional argument",
|
|
93
|
+
required: false,
|
|
94
|
+
default: "default_value",
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
template: "Test {{required_arg}} {{optional_arg}}",
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const filePath = join(testDir, "prompts.json");
|
|
103
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
104
|
+
|
|
105
|
+
const result = loadPromptsConfig(filePath);
|
|
106
|
+
|
|
107
|
+
expect(result.prompts[0].arguments).toHaveLength(2);
|
|
108
|
+
expect(result.prompts[0].arguments[0].required).toBe(true);
|
|
109
|
+
expect(result.prompts[0].arguments[1].required).toBe(false);
|
|
110
|
+
expect(result.prompts[0].arguments[1].default).toBe("default_value");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("should throw error for non-existent file", () => {
|
|
114
|
+
const filePath = join(testDir, "nonexistent.json");
|
|
115
|
+
|
|
116
|
+
expect(() => loadPromptsConfig(filePath)).toThrow();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it("should throw error for invalid JSON", () => {
|
|
120
|
+
const filePath = join(testDir, "invalid.json");
|
|
121
|
+
writeFileSync(filePath, "{ invalid json }");
|
|
122
|
+
|
|
123
|
+
expect(() => loadPromptsConfig(filePath)).toThrow();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should throw error for missing prompts array", () => {
|
|
127
|
+
const config = {};
|
|
128
|
+
const filePath = join(testDir, "missing-prompts.json");
|
|
129
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
130
|
+
|
|
131
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Invalid prompts configuration");
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it("should throw error for empty prompt name", () => {
|
|
135
|
+
const config = {
|
|
136
|
+
prompts: [
|
|
137
|
+
{
|
|
138
|
+
name: "",
|
|
139
|
+
description: "Test",
|
|
140
|
+
arguments: [],
|
|
141
|
+
template: "Test",
|
|
142
|
+
},
|
|
143
|
+
],
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const filePath = join(testDir, "empty-name.json");
|
|
147
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
148
|
+
|
|
149
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Invalid prompts configuration");
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("should throw error for invalid prompt name format", () => {
|
|
153
|
+
const config = {
|
|
154
|
+
prompts: [
|
|
155
|
+
{
|
|
156
|
+
name: "Invalid-Name",
|
|
157
|
+
description: "Test",
|
|
158
|
+
arguments: [],
|
|
159
|
+
template: "Test",
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const filePath = join(testDir, "invalid-name.json");
|
|
165
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
166
|
+
|
|
167
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Invalid prompts configuration");
|
|
168
|
+
expect(() => loadPromptsConfig(filePath)).toThrow(
|
|
169
|
+
"lowercase letters, numbers, and underscores"
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should accept valid prompt name formats", () => {
|
|
174
|
+
const config = {
|
|
175
|
+
prompts: [
|
|
176
|
+
{
|
|
177
|
+
name: "valid_name_123",
|
|
178
|
+
description: "Test",
|
|
179
|
+
arguments: [],
|
|
180
|
+
template: "Test",
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "another_valid",
|
|
184
|
+
description: "Test",
|
|
185
|
+
arguments: [],
|
|
186
|
+
template: "Test",
|
|
187
|
+
},
|
|
188
|
+
],
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
const filePath = join(testDir, "valid-names.json");
|
|
192
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
193
|
+
|
|
194
|
+
expect(() => loadPromptsConfig(filePath)).not.toThrow();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("should throw error for duplicate prompt names", () => {
|
|
198
|
+
const config = {
|
|
199
|
+
prompts: [
|
|
200
|
+
{
|
|
201
|
+
name: "test_prompt",
|
|
202
|
+
description: "First",
|
|
203
|
+
arguments: [],
|
|
204
|
+
template: "Test 1",
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "test_prompt",
|
|
208
|
+
description: "Second",
|
|
209
|
+
arguments: [],
|
|
210
|
+
template: "Test 2",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const filePath = join(testDir, "duplicate-names.json");
|
|
216
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
217
|
+
|
|
218
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Duplicate prompt name: test_prompt");
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("should throw error for duplicate argument names within a prompt", () => {
|
|
222
|
+
const config = {
|
|
223
|
+
prompts: [
|
|
224
|
+
{
|
|
225
|
+
name: "test_prompt",
|
|
226
|
+
description: "Test",
|
|
227
|
+
arguments: [
|
|
228
|
+
{
|
|
229
|
+
name: "arg1",
|
|
230
|
+
description: "First",
|
|
231
|
+
required: true,
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
name: "arg1",
|
|
235
|
+
description: "Second",
|
|
236
|
+
required: true,
|
|
237
|
+
},
|
|
238
|
+
],
|
|
239
|
+
template: "Test",
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const filePath = join(testDir, "duplicate-args.json");
|
|
245
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
246
|
+
|
|
247
|
+
expect(() => loadPromptsConfig(filePath)).toThrow(
|
|
248
|
+
'Duplicate argument name in prompt "test_prompt": arg1'
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should allow same argument name in different prompts", () => {
|
|
253
|
+
const config = {
|
|
254
|
+
prompts: [
|
|
255
|
+
{
|
|
256
|
+
name: "prompt1",
|
|
257
|
+
description: "First",
|
|
258
|
+
arguments: [
|
|
259
|
+
{
|
|
260
|
+
name: "collection",
|
|
261
|
+
description: "Collection name",
|
|
262
|
+
required: true,
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
template: "Test 1",
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
name: "prompt2",
|
|
269
|
+
description: "Second",
|
|
270
|
+
arguments: [
|
|
271
|
+
{
|
|
272
|
+
name: "collection",
|
|
273
|
+
description: "Collection name",
|
|
274
|
+
required: true,
|
|
275
|
+
},
|
|
276
|
+
],
|
|
277
|
+
template: "Test 2",
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const filePath = join(testDir, "same-args.json");
|
|
283
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
284
|
+
|
|
285
|
+
expect(() => loadPromptsConfig(filePath)).not.toThrow();
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("should throw error for missing argument name", () => {
|
|
289
|
+
const config = {
|
|
290
|
+
prompts: [
|
|
291
|
+
{
|
|
292
|
+
name: "test_prompt",
|
|
293
|
+
description: "Test",
|
|
294
|
+
arguments: [
|
|
295
|
+
{
|
|
296
|
+
name: "",
|
|
297
|
+
description: "Test",
|
|
298
|
+
required: true,
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
template: "Test",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
const filePath = join(testDir, "empty-arg-name.json");
|
|
307
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
308
|
+
|
|
309
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Invalid prompts configuration");
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("should throw error for missing argument description", () => {
|
|
313
|
+
const config = {
|
|
314
|
+
prompts: [
|
|
315
|
+
{
|
|
316
|
+
name: "test_prompt",
|
|
317
|
+
description: "Test",
|
|
318
|
+
arguments: [
|
|
319
|
+
{
|
|
320
|
+
name: "arg1",
|
|
321
|
+
description: "",
|
|
322
|
+
required: true,
|
|
323
|
+
},
|
|
324
|
+
],
|
|
325
|
+
template: "Test",
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
const filePath = join(testDir, "empty-arg-description.json");
|
|
331
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
332
|
+
|
|
333
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Invalid prompts configuration");
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it("should throw error for missing template", () => {
|
|
337
|
+
const config = {
|
|
338
|
+
prompts: [
|
|
339
|
+
{
|
|
340
|
+
name: "test_prompt",
|
|
341
|
+
description: "Test",
|
|
342
|
+
arguments: [],
|
|
343
|
+
template: "",
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
const filePath = join(testDir, "empty-template.json");
|
|
349
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
350
|
+
|
|
351
|
+
expect(() => loadPromptsConfig(filePath)).toThrow("Invalid prompts configuration");
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it("should load empty prompts array", () => {
|
|
355
|
+
const config = {
|
|
356
|
+
prompts: [],
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const filePath = join(testDir, "empty-prompts.json");
|
|
360
|
+
writeFileSync(filePath, JSON.stringify(config, null, 2));
|
|
361
|
+
|
|
362
|
+
const result = loadPromptsConfig(filePath);
|
|
363
|
+
|
|
364
|
+
expect(result.prompts).toHaveLength(0);
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
describe("getPrompt", () => {
|
|
369
|
+
it("should return prompt by name", () => {
|
|
370
|
+
const config: PromptsConfig = {
|
|
371
|
+
prompts: [
|
|
372
|
+
{
|
|
373
|
+
name: "test_prompt",
|
|
374
|
+
description: "A test prompt",
|
|
375
|
+
arguments: [],
|
|
376
|
+
template: "Test template",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
name: "another_prompt",
|
|
380
|
+
description: "Another prompt",
|
|
381
|
+
arguments: [],
|
|
382
|
+
template: "Another template",
|
|
383
|
+
},
|
|
384
|
+
],
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
const result = getPrompt(config, "test_prompt");
|
|
388
|
+
|
|
389
|
+
expect(result).toBeDefined();
|
|
390
|
+
expect(result?.name).toBe("test_prompt");
|
|
391
|
+
expect(result?.template).toBe("Test template");
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it("should return undefined for non-existent prompt", () => {
|
|
395
|
+
const config: PromptsConfig = {
|
|
396
|
+
prompts: [
|
|
397
|
+
{
|
|
398
|
+
name: "test_prompt",
|
|
399
|
+
description: "A test prompt",
|
|
400
|
+
arguments: [],
|
|
401
|
+
template: "Test template",
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
const result = getPrompt(config, "nonexistent");
|
|
407
|
+
|
|
408
|
+
expect(result).toBeUndefined();
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("should return undefined for empty config", () => {
|
|
412
|
+
const config: PromptsConfig = {
|
|
413
|
+
prompts: [],
|
|
414
|
+
};
|
|
415
|
+
|
|
416
|
+
const result = getPrompt(config, "test_prompt");
|
|
417
|
+
|
|
418
|
+
expect(result).toBeUndefined();
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
it("should find prompt case-sensitively", () => {
|
|
422
|
+
const config: PromptsConfig = {
|
|
423
|
+
prompts: [
|
|
424
|
+
{
|
|
425
|
+
name: "test_prompt",
|
|
426
|
+
description: "A test prompt",
|
|
427
|
+
arguments: [],
|
|
428
|
+
template: "Test template",
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
const result1 = getPrompt(config, "test_prompt");
|
|
434
|
+
const result2 = getPrompt(config, "TEST_PROMPT");
|
|
435
|
+
|
|
436
|
+
expect(result1).toBeDefined();
|
|
437
|
+
expect(result2).toBeUndefined();
|
|
438
|
+
});
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
describe("listPrompts", () => {
|
|
442
|
+
it("should return all prompts", () => {
|
|
443
|
+
const config: PromptsConfig = {
|
|
444
|
+
prompts: [
|
|
445
|
+
{
|
|
446
|
+
name: "prompt1",
|
|
447
|
+
description: "First prompt",
|
|
448
|
+
arguments: [],
|
|
449
|
+
template: "Template 1",
|
|
450
|
+
},
|
|
451
|
+
{
|
|
452
|
+
name: "prompt2",
|
|
453
|
+
description: "Second prompt",
|
|
454
|
+
arguments: [],
|
|
455
|
+
template: "Template 2",
|
|
456
|
+
},
|
|
457
|
+
],
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const result = listPrompts(config);
|
|
461
|
+
|
|
462
|
+
expect(result).toHaveLength(2);
|
|
463
|
+
expect(result[0].name).toBe("prompt1");
|
|
464
|
+
expect(result[1].name).toBe("prompt2");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("should return empty array for empty config", () => {
|
|
468
|
+
const config: PromptsConfig = {
|
|
469
|
+
prompts: [],
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
const result = listPrompts(config);
|
|
473
|
+
|
|
474
|
+
expect(result).toHaveLength(0);
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
it("should return copy of prompts array", () => {
|
|
478
|
+
const config: PromptsConfig = {
|
|
479
|
+
prompts: [
|
|
480
|
+
{
|
|
481
|
+
name: "test_prompt",
|
|
482
|
+
description: "A test prompt",
|
|
483
|
+
arguments: [],
|
|
484
|
+
template: "Test template",
|
|
485
|
+
},
|
|
486
|
+
],
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
const result = listPrompts(config);
|
|
490
|
+
|
|
491
|
+
// Should return the same array reference (direct access)
|
|
492
|
+
expect(result).toBe(config.prompts);
|
|
493
|
+
});
|
|
494
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt configuration loader
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import type { PromptDefinition, PromptsConfig } from "./types.js";
|
|
8
|
+
|
|
9
|
+
// Zod schema for validation
|
|
10
|
+
const PromptArgumentSchema = z.object({
|
|
11
|
+
name: z.string().min(1, "Argument name is required"),
|
|
12
|
+
description: z.string().min(1, "Argument description is required"),
|
|
13
|
+
required: z.boolean(),
|
|
14
|
+
default: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
const PromptDefinitionSchema = z.object({
|
|
18
|
+
name: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1, "Prompt name is required")
|
|
21
|
+
.regex(/^[a-z_][a-z0-9_]*$/, {
|
|
22
|
+
message: "Prompt name must be lowercase letters, numbers, and underscores only",
|
|
23
|
+
}),
|
|
24
|
+
description: z.string().min(1, "Prompt description is required"),
|
|
25
|
+
arguments: z.array(PromptArgumentSchema),
|
|
26
|
+
template: z.string().min(1, "Prompt template is required"),
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const PromptsConfigSchema = z.object({
|
|
30
|
+
prompts: z.array(PromptDefinitionSchema),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Load and parse prompts configuration from a JSON file
|
|
35
|
+
* @param filePath Path to the prompts configuration file
|
|
36
|
+
* @returns Parsed prompts configuration
|
|
37
|
+
* @throws Error if file cannot be read or parsed
|
|
38
|
+
*/
|
|
39
|
+
export function loadPromptsConfig(filePath: string): PromptsConfig {
|
|
40
|
+
try {
|
|
41
|
+
const content = readFileSync(filePath, "utf-8");
|
|
42
|
+
const parsed = JSON.parse(content);
|
|
43
|
+
const validated = PromptsConfigSchema.parse(parsed);
|
|
44
|
+
|
|
45
|
+
// Validate unique prompt names
|
|
46
|
+
const names = new Set<string>();
|
|
47
|
+
for (const prompt of validated.prompts) {
|
|
48
|
+
if (names.has(prompt.name)) {
|
|
49
|
+
throw new Error(`Duplicate prompt name: ${prompt.name}`);
|
|
50
|
+
}
|
|
51
|
+
names.add(prompt.name);
|
|
52
|
+
|
|
53
|
+
// Validate unique argument names within each prompt
|
|
54
|
+
const argNames = new Set<string>();
|
|
55
|
+
for (const arg of prompt.arguments) {
|
|
56
|
+
if (argNames.has(arg.name)) {
|
|
57
|
+
throw new Error(`Duplicate argument name in prompt "${prompt.name}": ${arg.name}`);
|
|
58
|
+
}
|
|
59
|
+
argNames.add(arg.name);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return validated;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof z.ZodError) {
|
|
66
|
+
const issues = error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
67
|
+
throw new Error(`Invalid prompts configuration:\n${issues.join("\n")}`);
|
|
68
|
+
}
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get a specific prompt by name
|
|
75
|
+
* @param config Prompts configuration
|
|
76
|
+
* @param name Prompt name
|
|
77
|
+
* @returns Prompt definition or undefined if not found
|
|
78
|
+
*/
|
|
79
|
+
export function getPrompt(config: PromptsConfig, name: string): PromptDefinition | undefined {
|
|
80
|
+
return config.prompts.find((p) => p.name === name);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* List all available prompts
|
|
85
|
+
* @param config Prompts configuration
|
|
86
|
+
* @returns Array of prompt definitions
|
|
87
|
+
*/
|
|
88
|
+
export function listPrompts(config: PromptsConfig): PromptDefinition[] {
|
|
89
|
+
return config.prompts;
|
|
90
|
+
}
|