@dexto/core 1.2.5 → 1.3.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/README.md +47 -13
- package/dist/agent/DextoAgent.cjs +92 -34
- package/dist/agent/DextoAgent.d.ts +3 -3
- package/dist/agent/DextoAgent.d.ts.map +1 -1
- package/dist/agent/DextoAgent.js +93 -36
- package/dist/agent/schemas.cjs +24 -20
- package/dist/agent/schemas.d.ts +111 -107
- package/dist/agent/schemas.d.ts.map +1 -1
- package/dist/agent/schemas.js +8 -4
- package/dist/agent/types.d.ts +0 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/approval/schemas.cjs +2 -1
- package/dist/approval/schemas.d.ts +21 -20
- package/dist/approval/schemas.d.ts.map +1 -1
- package/dist/approval/schemas.js +2 -1
- package/dist/context/manager.cjs +7 -0
- package/dist/context/manager.d.ts.map +1 -1
- package/dist/context/manager.js +7 -0
- package/dist/context/types.d.ts +6 -2
- package/dist/context/types.d.ts.map +1 -1
- package/dist/errors/types.cjs +1 -0
- package/dist/errors/types.d.ts +4 -2
- package/dist/errors/types.d.ts.map +1 -1
- package/dist/errors/types.js +1 -0
- package/dist/events/index.cjs +4 -1
- package/dist/events/index.d.ts +23 -52
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js +4 -1
- package/dist/llm/formatters/openai.cjs +8 -1
- package/dist/llm/formatters/openai.d.ts.map +1 -1
- package/dist/llm/formatters/openai.js +8 -1
- package/dist/llm/tokenizer/openai.d.ts +8 -0
- package/dist/llm/tokenizer/openai.d.ts.map +1 -1
- package/dist/logger/v2/schemas.cjs +1 -1
- package/dist/logger/v2/schemas.js +1 -1
- package/dist/memory/index.cjs +2 -0
- package/dist/memory/index.d.ts +1 -1
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +3 -1
- package/dist/memory/schemas.cjs +10 -0
- package/dist/memory/schemas.d.ts +33 -4
- package/dist/memory/schemas.d.ts.map +1 -1
- package/dist/memory/schemas.js +9 -0
- package/dist/plugins/error-codes.cjs +1 -0
- package/dist/plugins/error-codes.d.ts +3 -1
- package/dist/plugins/error-codes.d.ts.map +1 -1
- package/dist/plugins/error-codes.js +1 -0
- package/dist/plugins/loader.cjs +25 -5
- package/dist/plugins/loader.d.ts.map +1 -1
- package/dist/plugins/loader.js +25 -5
- package/dist/prompts/index.cjs +6 -8
- package/dist/prompts/index.d.ts +2 -4
- package/dist/prompts/index.d.ts.map +1 -1
- package/dist/prompts/index.js +4 -6
- package/dist/prompts/prompt-manager.cjs +2 -4
- package/dist/prompts/prompt-manager.d.ts.map +1 -1
- package/dist/prompts/prompt-manager.js +2 -4
- package/dist/prompts/providers/config-prompt-provider.cjs +331 -0
- package/dist/prompts/providers/config-prompt-provider.d.ts +34 -0
- package/dist/prompts/providers/config-prompt-provider.d.ts.map +1 -0
- package/dist/prompts/providers/config-prompt-provider.js +308 -0
- package/dist/prompts/providers/custom-prompt-provider.cjs +2 -2
- package/dist/prompts/providers/custom-prompt-provider.d.ts +1 -1
- package/dist/prompts/providers/custom-prompt-provider.d.ts.map +1 -1
- package/dist/prompts/providers/custom-prompt-provider.js +2 -2
- package/dist/prompts/schemas.cjs +42 -23
- package/dist/prompts/schemas.d.ts +121 -12
- package/dist/prompts/schemas.d.ts.map +1 -1
- package/dist/prompts/schemas.js +39 -22
- package/dist/prompts/types.d.ts +1 -1
- package/dist/prompts/types.d.ts.map +1 -1
- package/dist/storage/cache/factory.cjs +6 -2
- package/dist/storage/cache/factory.d.ts +2 -1
- package/dist/storage/cache/factory.d.ts.map +1 -1
- package/dist/storage/cache/factory.js +6 -2
- package/dist/storage/database/factory.cjs +11 -17
- package/dist/storage/database/factory.d.ts +2 -1
- package/dist/storage/database/factory.d.ts.map +1 -1
- package/dist/storage/database/factory.js +11 -17
- package/dist/storage/database/sqlite-store.cjs +8 -0
- package/dist/storage/database/sqlite-store.d.ts.map +1 -1
- package/dist/storage/database/sqlite-store.js +8 -0
- package/dist/storage/error-codes.cjs +1 -0
- package/dist/storage/error-codes.d.ts +1 -0
- package/dist/storage/error-codes.d.ts.map +1 -1
- package/dist/storage/error-codes.js +1 -0
- package/dist/storage/errors.cjs +17 -0
- package/dist/storage/errors.d.ts +9 -0
- package/dist/storage/errors.d.ts.map +1 -1
- package/dist/storage/errors.js +17 -0
- package/dist/systemPrompt/in-built-prompts.cjs +0 -5
- package/dist/systemPrompt/in-built-prompts.d.ts +1 -2
- package/dist/systemPrompt/in-built-prompts.d.ts.map +1 -1
- package/dist/systemPrompt/in-built-prompts.js +0 -4
- package/dist/systemPrompt/manager.cjs +24 -19
- package/dist/systemPrompt/manager.d.ts +2 -2
- package/dist/systemPrompt/manager.d.ts.map +1 -1
- package/dist/systemPrompt/manager.js +24 -19
- package/dist/systemPrompt/registry.cjs +1 -2
- package/dist/systemPrompt/registry.d.ts +1 -1
- package/dist/systemPrompt/registry.d.ts.map +1 -1
- package/dist/systemPrompt/registry.js +1 -2
- package/dist/systemPrompt/schemas.cjs +3 -17
- package/dist/systemPrompt/schemas.d.ts +13 -189
- package/dist/systemPrompt/schemas.d.ts.map +1 -1
- package/dist/systemPrompt/schemas.js +3 -17
- package/dist/telemetry/error-codes.cjs +36 -0
- package/dist/telemetry/error-codes.d.ts +13 -0
- package/dist/telemetry/error-codes.d.ts.map +1 -0
- package/dist/telemetry/error-codes.js +13 -0
- package/dist/telemetry/errors.cjs +105 -0
- package/dist/telemetry/errors.d.ts +28 -0
- package/dist/telemetry/errors.d.ts.map +1 -0
- package/dist/telemetry/errors.js +82 -0
- package/dist/telemetry/telemetry.cjs +92 -26
- package/dist/telemetry/telemetry.d.ts +1 -1
- package/dist/telemetry/telemetry.d.ts.map +1 -1
- package/dist/telemetry/telemetry.js +74 -18
- package/dist/tools/schemas.cjs +1 -1
- package/dist/tools/schemas.js +1 -1
- package/dist/tools/types.d.ts +0 -11
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/utils/schema.d.ts +6 -0
- package/dist/utils/schema.d.ts.map +1 -1
- package/dist/utils/service-initializer.cjs +1 -0
- package/dist/utils/service-initializer.d.ts.map +1 -1
- package/dist/utils/service-initializer.js +1 -0
- package/package.json +52 -14
- package/dist/prompts/providers/file-prompt-provider.cjs +0 -401
- package/dist/prompts/providers/file-prompt-provider.d.ts +0 -49
- package/dist/prompts/providers/file-prompt-provider.d.ts.map +0 -1
- package/dist/prompts/providers/file-prompt-provider.js +0 -378
- package/dist/prompts/providers/starter-prompt-provider.cjs +0 -172
- package/dist/prompts/providers/starter-prompt-provider.d.ts +0 -47
- package/dist/prompts/providers/starter-prompt-provider.d.ts.map +0 -1
- package/dist/prompts/providers/starter-prompt-provider.js +0 -149
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var config_prompt_provider_exports = {};
|
|
20
|
+
__export(config_prompt_provider_exports, {
|
|
21
|
+
ConfigPromptProvider: () => ConfigPromptProvider
|
|
22
|
+
});
|
|
23
|
+
module.exports = __toCommonJS(config_prompt_provider_exports);
|
|
24
|
+
var import_types = require("../../logger/v2/types.js");
|
|
25
|
+
var import_errors = require("../errors.js");
|
|
26
|
+
var import_utils = require("../utils.js");
|
|
27
|
+
var import_name_validation = require("../name-validation.js");
|
|
28
|
+
var import_promises = require("fs/promises");
|
|
29
|
+
var import_fs = require("fs");
|
|
30
|
+
var import_path = require("path");
|
|
31
|
+
class ConfigPromptProvider {
|
|
32
|
+
prompts = [];
|
|
33
|
+
promptsCache = [];
|
|
34
|
+
promptContent = /* @__PURE__ */ new Map();
|
|
35
|
+
cacheValid = false;
|
|
36
|
+
logger;
|
|
37
|
+
constructor(agentConfig, logger) {
|
|
38
|
+
this.logger = logger.createChild(import_types.DextoLogComponent.PROMPT);
|
|
39
|
+
this.prompts = agentConfig.prompts;
|
|
40
|
+
this.buildPromptsCache();
|
|
41
|
+
}
|
|
42
|
+
getSource() {
|
|
43
|
+
return "config";
|
|
44
|
+
}
|
|
45
|
+
invalidateCache() {
|
|
46
|
+
this.cacheValid = false;
|
|
47
|
+
this.promptsCache = [];
|
|
48
|
+
this.promptContent.clear();
|
|
49
|
+
this.logger.debug("ConfigPromptProvider cache invalidated");
|
|
50
|
+
}
|
|
51
|
+
updateConfig(agentConfig) {
|
|
52
|
+
this.prompts = agentConfig.prompts;
|
|
53
|
+
this.invalidateCache();
|
|
54
|
+
this.buildPromptsCache();
|
|
55
|
+
}
|
|
56
|
+
async listPrompts(_cursor) {
|
|
57
|
+
if (!this.cacheValid) {
|
|
58
|
+
await this.buildPromptsCache();
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
prompts: this.promptsCache
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
async getPrompt(name, args) {
|
|
65
|
+
if (!this.cacheValid) {
|
|
66
|
+
await this.buildPromptsCache();
|
|
67
|
+
}
|
|
68
|
+
const promptInfo = this.promptsCache.find((p) => p.name === name);
|
|
69
|
+
if (!promptInfo) {
|
|
70
|
+
throw import_errors.PromptError.notFound(name);
|
|
71
|
+
}
|
|
72
|
+
let content = this.promptContent.get(name);
|
|
73
|
+
if (!content) {
|
|
74
|
+
throw import_errors.PromptError.missingText();
|
|
75
|
+
}
|
|
76
|
+
content = this.applyArguments(content, args);
|
|
77
|
+
return {
|
|
78
|
+
description: promptInfo.description,
|
|
79
|
+
messages: [
|
|
80
|
+
{
|
|
81
|
+
role: "user",
|
|
82
|
+
content: {
|
|
83
|
+
type: "text",
|
|
84
|
+
text: content
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
]
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async getPromptDefinition(name) {
|
|
91
|
+
if (!this.cacheValid) {
|
|
92
|
+
await this.buildPromptsCache();
|
|
93
|
+
}
|
|
94
|
+
const promptInfo = this.promptsCache.find((p) => p.name === name);
|
|
95
|
+
if (!promptInfo) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
name: promptInfo.name,
|
|
100
|
+
...promptInfo.title && { title: promptInfo.title },
|
|
101
|
+
...promptInfo.description && { description: promptInfo.description },
|
|
102
|
+
...promptInfo.arguments && { arguments: promptInfo.arguments }
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
async buildPromptsCache() {
|
|
106
|
+
const cache = [];
|
|
107
|
+
const contentMap = /* @__PURE__ */ new Map();
|
|
108
|
+
for (const prompt of this.prompts) {
|
|
109
|
+
try {
|
|
110
|
+
if (prompt.type === "inline") {
|
|
111
|
+
const { info, content } = this.processInlinePrompt(prompt);
|
|
112
|
+
cache.push(info);
|
|
113
|
+
contentMap.set(info.name, content);
|
|
114
|
+
} else if (prompt.type === "file") {
|
|
115
|
+
const result = await this.processFilePrompt(prompt);
|
|
116
|
+
if (result) {
|
|
117
|
+
cache.push(result.info);
|
|
118
|
+
contentMap.set(result.info.name, result.content);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
this.logger.warn(
|
|
123
|
+
`Failed to process prompt: ${error instanceof Error ? error.message : String(error)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
cache.sort((a, b) => {
|
|
128
|
+
const priorityA = a.metadata?.priority ?? 0;
|
|
129
|
+
const priorityB = b.metadata?.priority ?? 0;
|
|
130
|
+
return priorityB - priorityA;
|
|
131
|
+
});
|
|
132
|
+
this.promptsCache = cache;
|
|
133
|
+
this.promptContent = contentMap;
|
|
134
|
+
this.cacheValid = true;
|
|
135
|
+
this.logger.debug(`Cached ${cache.length} config prompts`);
|
|
136
|
+
}
|
|
137
|
+
processInlinePrompt(prompt) {
|
|
138
|
+
const promptName = `config:${prompt.id}`;
|
|
139
|
+
const promptInfo = {
|
|
140
|
+
name: promptName,
|
|
141
|
+
title: prompt.title,
|
|
142
|
+
description: prompt.description,
|
|
143
|
+
source: "config",
|
|
144
|
+
metadata: {
|
|
145
|
+
type: "inline",
|
|
146
|
+
category: prompt.category,
|
|
147
|
+
priority: prompt.priority,
|
|
148
|
+
showInStarters: prompt.showInStarters,
|
|
149
|
+
originalId: prompt.id
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
return { info: promptInfo, content: prompt.prompt };
|
|
153
|
+
}
|
|
154
|
+
async processFilePrompt(prompt) {
|
|
155
|
+
const filePath = prompt.file;
|
|
156
|
+
if (!(0, import_fs.existsSync)(filePath)) {
|
|
157
|
+
this.logger.warn(`Prompt file not found: ${filePath}`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
const resolvedDir = await (0, import_promises.realpath)((0, import_path.dirname)(filePath));
|
|
162
|
+
const resolvedFile = await (0, import_promises.realpath)(filePath);
|
|
163
|
+
const rel = (0, import_path.relative)(resolvedDir, resolvedFile);
|
|
164
|
+
if (rel.startsWith(".." + import_path.sep) || rel === "..") {
|
|
165
|
+
this.logger.warn(
|
|
166
|
+
`Skipping prompt file '${filePath}': path traversal attempt detected (resolved outside directory)`
|
|
167
|
+
);
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
} catch (realpathError) {
|
|
171
|
+
this.logger.warn(
|
|
172
|
+
`Skipping prompt file '${filePath}': unable to resolve path (${realpathError instanceof Error ? realpathError.message : String(realpathError)})`
|
|
173
|
+
);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
try {
|
|
177
|
+
const rawContent = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
178
|
+
const parsed = this.parseMarkdownPrompt(rawContent, filePath);
|
|
179
|
+
try {
|
|
180
|
+
(0, import_name_validation.assertValidPromptName)(parsed.id, {
|
|
181
|
+
context: `file prompt '${filePath}'`,
|
|
182
|
+
hint: "Use kebab-case in the 'id:' frontmatter field or file name."
|
|
183
|
+
});
|
|
184
|
+
} catch (validationError) {
|
|
185
|
+
this.logger.warn(
|
|
186
|
+
`Invalid prompt name in '${filePath}': ${validationError instanceof Error ? validationError.message : String(validationError)}`
|
|
187
|
+
);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
const promptInfo = {
|
|
191
|
+
name: `config:${parsed.id}`,
|
|
192
|
+
title: parsed.title,
|
|
193
|
+
description: parsed.description,
|
|
194
|
+
source: "config",
|
|
195
|
+
...parsed.arguments && { arguments: parsed.arguments },
|
|
196
|
+
metadata: {
|
|
197
|
+
type: "file",
|
|
198
|
+
filePath,
|
|
199
|
+
category: parsed.category,
|
|
200
|
+
priority: parsed.priority,
|
|
201
|
+
showInStarters: prompt.showInStarters,
|
|
202
|
+
originalId: parsed.id
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
return { info: promptInfo, content: parsed.content };
|
|
206
|
+
} catch (error) {
|
|
207
|
+
this.logger.warn(
|
|
208
|
+
`Failed to read prompt file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
209
|
+
);
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
parseMarkdownPrompt(rawContent, filePath) {
|
|
214
|
+
const lines = rawContent.trim().split("\n");
|
|
215
|
+
const fileName = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "unknown";
|
|
216
|
+
let id = fileName;
|
|
217
|
+
let title = fileName;
|
|
218
|
+
let description = `File prompt: ${fileName}`;
|
|
219
|
+
let category;
|
|
220
|
+
let priority;
|
|
221
|
+
let argumentHint;
|
|
222
|
+
let contentBody;
|
|
223
|
+
if (lines[0]?.trim() === "---") {
|
|
224
|
+
let frontmatterEnd = 0;
|
|
225
|
+
for (let i = 1; i < lines.length; i++) {
|
|
226
|
+
if (lines[i]?.trim() === "---") {
|
|
227
|
+
frontmatterEnd = i;
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (frontmatterEnd > 0) {
|
|
232
|
+
const frontmatterLines = lines.slice(1, frontmatterEnd);
|
|
233
|
+
contentBody = lines.slice(frontmatterEnd + 1).join("\n");
|
|
234
|
+
for (const line of frontmatterLines) {
|
|
235
|
+
const match = (key) => {
|
|
236
|
+
const regex = new RegExp(`${key}:\\s*(?:['"](.+)['"]|(.+))`);
|
|
237
|
+
const m = line.match(regex);
|
|
238
|
+
return m ? (m[1] || m[2] || "").trim() : null;
|
|
239
|
+
};
|
|
240
|
+
if (line.includes("id:")) {
|
|
241
|
+
const val = match("id");
|
|
242
|
+
if (val) id = val;
|
|
243
|
+
} else if (line.includes("title:")) {
|
|
244
|
+
const val = match("title");
|
|
245
|
+
if (val) title = val;
|
|
246
|
+
} else if (line.includes("description:")) {
|
|
247
|
+
const val = match("description");
|
|
248
|
+
if (val) description = val;
|
|
249
|
+
} else if (line.includes("category:")) {
|
|
250
|
+
const val = match("category");
|
|
251
|
+
if (val) category = val;
|
|
252
|
+
} else if (line.includes("priority:")) {
|
|
253
|
+
const val = match("priority");
|
|
254
|
+
if (val) priority = parseInt(val, 10);
|
|
255
|
+
} else if (line.includes("argument-hint:")) {
|
|
256
|
+
const val = match("argument-hint");
|
|
257
|
+
if (val) argumentHint = val;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
contentBody = rawContent;
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
contentBody = rawContent;
|
|
265
|
+
}
|
|
266
|
+
if (title === fileName) {
|
|
267
|
+
for (const line of contentBody.trim().split("\n")) {
|
|
268
|
+
if (line.trim().startsWith("#")) {
|
|
269
|
+
title = line.trim().replace(/^#+\s*/, "");
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
const parsedArguments = argumentHint ? this.parseArgumentHint(argumentHint) : void 0;
|
|
275
|
+
return {
|
|
276
|
+
id,
|
|
277
|
+
title,
|
|
278
|
+
description,
|
|
279
|
+
content: contentBody.trim(),
|
|
280
|
+
...category !== void 0 && { category },
|
|
281
|
+
...priority !== void 0 && { priority },
|
|
282
|
+
...parsedArguments !== void 0 && { arguments: parsedArguments }
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
parseArgumentHint(hint) {
|
|
286
|
+
const args = [];
|
|
287
|
+
const argPattern = /\[([^\]]+)\]/g;
|
|
288
|
+
let match;
|
|
289
|
+
while ((match = argPattern.exec(hint)) !== null) {
|
|
290
|
+
const argText = match[1];
|
|
291
|
+
if (!argText) continue;
|
|
292
|
+
const isOptional = argText.endsWith("?");
|
|
293
|
+
const name = isOptional ? argText.slice(0, -1).trim() : argText.trim();
|
|
294
|
+
if (name) {
|
|
295
|
+
args.push({
|
|
296
|
+
name,
|
|
297
|
+
required: !isOptional
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return args;
|
|
302
|
+
}
|
|
303
|
+
applyArguments(content, args) {
|
|
304
|
+
const detectionTarget = content.replaceAll("$$", "");
|
|
305
|
+
const usesPositionalPlaceholders = /\$[1-9](?!\d)/.test(detectionTarget) || detectionTarget.includes("$ARGUMENTS");
|
|
306
|
+
const expanded = (0, import_utils.expandPlaceholders)(content, args).trim();
|
|
307
|
+
if (!args || typeof args !== "object" || Object.keys(args).length === 0) {
|
|
308
|
+
return expanded;
|
|
309
|
+
}
|
|
310
|
+
if (!usesPositionalPlaceholders) {
|
|
311
|
+
if (args._context) {
|
|
312
|
+
const contextString = String(args._context);
|
|
313
|
+
return `${expanded}
|
|
314
|
+
|
|
315
|
+
Context: ${contextString}`;
|
|
316
|
+
}
|
|
317
|
+
const argEntries = Object.entries(args).filter(([key]) => !key.startsWith("_"));
|
|
318
|
+
if (argEntries.length > 0) {
|
|
319
|
+
const formattedArgs = argEntries.map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
320
|
+
return `${expanded}
|
|
321
|
+
|
|
322
|
+
Arguments: ${formattedArgs}`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return expanded;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
329
|
+
0 && (module.exports = {
|
|
330
|
+
ConfigPromptProvider
|
|
331
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { PromptProvider, PromptDefinition, PromptListResult } from '../types.js';
|
|
2
|
+
import type { GetPromptResult } from '@modelcontextprotocol/sdk/types.js';
|
|
3
|
+
import type { ValidatedAgentConfig } from '../../agent/schemas.js';
|
|
4
|
+
import type { IDextoLogger } from '../../logger/v2/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Config Prompt Provider - Unified provider for prompts from agent configuration
|
|
7
|
+
*
|
|
8
|
+
* Handles both inline prompts (text defined directly in config) and file-based prompts
|
|
9
|
+
* (loaded from markdown files). This replaces the old StarterPromptProvider and
|
|
10
|
+
* FilePromptProvider with a single, unified approach.
|
|
11
|
+
*
|
|
12
|
+
* Prompts with showInStarters: true are displayed as clickable buttons in the WebUI.
|
|
13
|
+
*/
|
|
14
|
+
export declare class ConfigPromptProvider implements PromptProvider {
|
|
15
|
+
private prompts;
|
|
16
|
+
private promptsCache;
|
|
17
|
+
private promptContent;
|
|
18
|
+
private cacheValid;
|
|
19
|
+
private logger;
|
|
20
|
+
constructor(agentConfig: ValidatedAgentConfig, logger: IDextoLogger);
|
|
21
|
+
getSource(): string;
|
|
22
|
+
invalidateCache(): void;
|
|
23
|
+
updateConfig(agentConfig: ValidatedAgentConfig): void;
|
|
24
|
+
listPrompts(_cursor?: string): Promise<PromptListResult>;
|
|
25
|
+
getPrompt(name: string, args?: Record<string, unknown>): Promise<GetPromptResult>;
|
|
26
|
+
getPromptDefinition(name: string): Promise<PromptDefinition | null>;
|
|
27
|
+
private buildPromptsCache;
|
|
28
|
+
private processInlinePrompt;
|
|
29
|
+
private processFilePrompt;
|
|
30
|
+
private parseMarkdownPrompt;
|
|
31
|
+
private parseArgumentHint;
|
|
32
|
+
private applyArguments;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=config-prompt-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-prompt-provider.d.ts","sourceRoot":"","sources":["../../../src/prompts/providers/config-prompt-provider.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAc,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAClG,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAC;AAC1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAEnE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAS7D;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,YAAW,cAAc;IACvD,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,YAAY,CAAoB;IACxC,OAAO,CAAC,aAAa,CAAkC;IACvD,OAAO,CAAC,UAAU,CAAkB;IACpC,OAAO,CAAC,MAAM,CAAe;gBAEjB,WAAW,EAAE,oBAAoB,EAAE,MAAM,EAAE,YAAY;IAMnE,SAAS,IAAI,MAAM;IAInB,eAAe,IAAI,IAAI;IAOvB,YAAY,CAAC,WAAW,EAAE,oBAAoB,GAAG,IAAI;IAM/C,WAAW,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAUxD,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,eAAe,CAAC;IAgCjF,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;YAkB3D,iBAAiB;IAsC/B,OAAO,CAAC,mBAAmB;YAsBb,iBAAiB;IAwE/B,OAAO,CAAC,mBAAmB;IA+F3B,OAAO,CAAC,iBAAiB;IAyBzB,OAAO,CAAC,cAAc;CA+BzB"}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import "../../chunk-C6A6W6XS.js";
|
|
2
|
+
import { DextoLogComponent } from "../../logger/v2/types.js";
|
|
3
|
+
import { PromptError } from "../errors.js";
|
|
4
|
+
import { expandPlaceholders } from "../utils.js";
|
|
5
|
+
import { assertValidPromptName } from "../name-validation.js";
|
|
6
|
+
import { readFile, realpath } from "fs/promises";
|
|
7
|
+
import { existsSync } from "fs";
|
|
8
|
+
import { dirname, relative, sep } from "path";
|
|
9
|
+
class ConfigPromptProvider {
|
|
10
|
+
prompts = [];
|
|
11
|
+
promptsCache = [];
|
|
12
|
+
promptContent = /* @__PURE__ */ new Map();
|
|
13
|
+
cacheValid = false;
|
|
14
|
+
logger;
|
|
15
|
+
constructor(agentConfig, logger) {
|
|
16
|
+
this.logger = logger.createChild(DextoLogComponent.PROMPT);
|
|
17
|
+
this.prompts = agentConfig.prompts;
|
|
18
|
+
this.buildPromptsCache();
|
|
19
|
+
}
|
|
20
|
+
getSource() {
|
|
21
|
+
return "config";
|
|
22
|
+
}
|
|
23
|
+
invalidateCache() {
|
|
24
|
+
this.cacheValid = false;
|
|
25
|
+
this.promptsCache = [];
|
|
26
|
+
this.promptContent.clear();
|
|
27
|
+
this.logger.debug("ConfigPromptProvider cache invalidated");
|
|
28
|
+
}
|
|
29
|
+
updateConfig(agentConfig) {
|
|
30
|
+
this.prompts = agentConfig.prompts;
|
|
31
|
+
this.invalidateCache();
|
|
32
|
+
this.buildPromptsCache();
|
|
33
|
+
}
|
|
34
|
+
async listPrompts(_cursor) {
|
|
35
|
+
if (!this.cacheValid) {
|
|
36
|
+
await this.buildPromptsCache();
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
prompts: this.promptsCache
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async getPrompt(name, args) {
|
|
43
|
+
if (!this.cacheValid) {
|
|
44
|
+
await this.buildPromptsCache();
|
|
45
|
+
}
|
|
46
|
+
const promptInfo = this.promptsCache.find((p) => p.name === name);
|
|
47
|
+
if (!promptInfo) {
|
|
48
|
+
throw PromptError.notFound(name);
|
|
49
|
+
}
|
|
50
|
+
let content = this.promptContent.get(name);
|
|
51
|
+
if (!content) {
|
|
52
|
+
throw PromptError.missingText();
|
|
53
|
+
}
|
|
54
|
+
content = this.applyArguments(content, args);
|
|
55
|
+
return {
|
|
56
|
+
description: promptInfo.description,
|
|
57
|
+
messages: [
|
|
58
|
+
{
|
|
59
|
+
role: "user",
|
|
60
|
+
content: {
|
|
61
|
+
type: "text",
|
|
62
|
+
text: content
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async getPromptDefinition(name) {
|
|
69
|
+
if (!this.cacheValid) {
|
|
70
|
+
await this.buildPromptsCache();
|
|
71
|
+
}
|
|
72
|
+
const promptInfo = this.promptsCache.find((p) => p.name === name);
|
|
73
|
+
if (!promptInfo) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
name: promptInfo.name,
|
|
78
|
+
...promptInfo.title && { title: promptInfo.title },
|
|
79
|
+
...promptInfo.description && { description: promptInfo.description },
|
|
80
|
+
...promptInfo.arguments && { arguments: promptInfo.arguments }
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async buildPromptsCache() {
|
|
84
|
+
const cache = [];
|
|
85
|
+
const contentMap = /* @__PURE__ */ new Map();
|
|
86
|
+
for (const prompt of this.prompts) {
|
|
87
|
+
try {
|
|
88
|
+
if (prompt.type === "inline") {
|
|
89
|
+
const { info, content } = this.processInlinePrompt(prompt);
|
|
90
|
+
cache.push(info);
|
|
91
|
+
contentMap.set(info.name, content);
|
|
92
|
+
} else if (prompt.type === "file") {
|
|
93
|
+
const result = await this.processFilePrompt(prompt);
|
|
94
|
+
if (result) {
|
|
95
|
+
cache.push(result.info);
|
|
96
|
+
contentMap.set(result.info.name, result.content);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} catch (error) {
|
|
100
|
+
this.logger.warn(
|
|
101
|
+
`Failed to process prompt: ${error instanceof Error ? error.message : String(error)}`
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
cache.sort((a, b) => {
|
|
106
|
+
const priorityA = a.metadata?.priority ?? 0;
|
|
107
|
+
const priorityB = b.metadata?.priority ?? 0;
|
|
108
|
+
return priorityB - priorityA;
|
|
109
|
+
});
|
|
110
|
+
this.promptsCache = cache;
|
|
111
|
+
this.promptContent = contentMap;
|
|
112
|
+
this.cacheValid = true;
|
|
113
|
+
this.logger.debug(`Cached ${cache.length} config prompts`);
|
|
114
|
+
}
|
|
115
|
+
processInlinePrompt(prompt) {
|
|
116
|
+
const promptName = `config:${prompt.id}`;
|
|
117
|
+
const promptInfo = {
|
|
118
|
+
name: promptName,
|
|
119
|
+
title: prompt.title,
|
|
120
|
+
description: prompt.description,
|
|
121
|
+
source: "config",
|
|
122
|
+
metadata: {
|
|
123
|
+
type: "inline",
|
|
124
|
+
category: prompt.category,
|
|
125
|
+
priority: prompt.priority,
|
|
126
|
+
showInStarters: prompt.showInStarters,
|
|
127
|
+
originalId: prompt.id
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return { info: promptInfo, content: prompt.prompt };
|
|
131
|
+
}
|
|
132
|
+
async processFilePrompt(prompt) {
|
|
133
|
+
const filePath = prompt.file;
|
|
134
|
+
if (!existsSync(filePath)) {
|
|
135
|
+
this.logger.warn(`Prompt file not found: ${filePath}`);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
const resolvedDir = await realpath(dirname(filePath));
|
|
140
|
+
const resolvedFile = await realpath(filePath);
|
|
141
|
+
const rel = relative(resolvedDir, resolvedFile);
|
|
142
|
+
if (rel.startsWith(".." + sep) || rel === "..") {
|
|
143
|
+
this.logger.warn(
|
|
144
|
+
`Skipping prompt file '${filePath}': path traversal attempt detected (resolved outside directory)`
|
|
145
|
+
);
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
} catch (realpathError) {
|
|
149
|
+
this.logger.warn(
|
|
150
|
+
`Skipping prompt file '${filePath}': unable to resolve path (${realpathError instanceof Error ? realpathError.message : String(realpathError)})`
|
|
151
|
+
);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
try {
|
|
155
|
+
const rawContent = await readFile(filePath, "utf-8");
|
|
156
|
+
const parsed = this.parseMarkdownPrompt(rawContent, filePath);
|
|
157
|
+
try {
|
|
158
|
+
assertValidPromptName(parsed.id, {
|
|
159
|
+
context: `file prompt '${filePath}'`,
|
|
160
|
+
hint: "Use kebab-case in the 'id:' frontmatter field or file name."
|
|
161
|
+
});
|
|
162
|
+
} catch (validationError) {
|
|
163
|
+
this.logger.warn(
|
|
164
|
+
`Invalid prompt name in '${filePath}': ${validationError instanceof Error ? validationError.message : String(validationError)}`
|
|
165
|
+
);
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
const promptInfo = {
|
|
169
|
+
name: `config:${parsed.id}`,
|
|
170
|
+
title: parsed.title,
|
|
171
|
+
description: parsed.description,
|
|
172
|
+
source: "config",
|
|
173
|
+
...parsed.arguments && { arguments: parsed.arguments },
|
|
174
|
+
metadata: {
|
|
175
|
+
type: "file",
|
|
176
|
+
filePath,
|
|
177
|
+
category: parsed.category,
|
|
178
|
+
priority: parsed.priority,
|
|
179
|
+
showInStarters: prompt.showInStarters,
|
|
180
|
+
originalId: parsed.id
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
return { info: promptInfo, content: parsed.content };
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.logger.warn(
|
|
186
|
+
`Failed to read prompt file ${filePath}: ${error instanceof Error ? error.message : String(error)}`
|
|
187
|
+
);
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
parseMarkdownPrompt(rawContent, filePath) {
|
|
192
|
+
const lines = rawContent.trim().split("\n");
|
|
193
|
+
const fileName = filePath.split("/").pop()?.replace(/\.md$/, "") ?? "unknown";
|
|
194
|
+
let id = fileName;
|
|
195
|
+
let title = fileName;
|
|
196
|
+
let description = `File prompt: ${fileName}`;
|
|
197
|
+
let category;
|
|
198
|
+
let priority;
|
|
199
|
+
let argumentHint;
|
|
200
|
+
let contentBody;
|
|
201
|
+
if (lines[0]?.trim() === "---") {
|
|
202
|
+
let frontmatterEnd = 0;
|
|
203
|
+
for (let i = 1; i < lines.length; i++) {
|
|
204
|
+
if (lines[i]?.trim() === "---") {
|
|
205
|
+
frontmatterEnd = i;
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (frontmatterEnd > 0) {
|
|
210
|
+
const frontmatterLines = lines.slice(1, frontmatterEnd);
|
|
211
|
+
contentBody = lines.slice(frontmatterEnd + 1).join("\n");
|
|
212
|
+
for (const line of frontmatterLines) {
|
|
213
|
+
const match = (key) => {
|
|
214
|
+
const regex = new RegExp(`${key}:\\s*(?:['"](.+)['"]|(.+))`);
|
|
215
|
+
const m = line.match(regex);
|
|
216
|
+
return m ? (m[1] || m[2] || "").trim() : null;
|
|
217
|
+
};
|
|
218
|
+
if (line.includes("id:")) {
|
|
219
|
+
const val = match("id");
|
|
220
|
+
if (val) id = val;
|
|
221
|
+
} else if (line.includes("title:")) {
|
|
222
|
+
const val = match("title");
|
|
223
|
+
if (val) title = val;
|
|
224
|
+
} else if (line.includes("description:")) {
|
|
225
|
+
const val = match("description");
|
|
226
|
+
if (val) description = val;
|
|
227
|
+
} else if (line.includes("category:")) {
|
|
228
|
+
const val = match("category");
|
|
229
|
+
if (val) category = val;
|
|
230
|
+
} else if (line.includes("priority:")) {
|
|
231
|
+
const val = match("priority");
|
|
232
|
+
if (val) priority = parseInt(val, 10);
|
|
233
|
+
} else if (line.includes("argument-hint:")) {
|
|
234
|
+
const val = match("argument-hint");
|
|
235
|
+
if (val) argumentHint = val;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
contentBody = rawContent;
|
|
240
|
+
}
|
|
241
|
+
} else {
|
|
242
|
+
contentBody = rawContent;
|
|
243
|
+
}
|
|
244
|
+
if (title === fileName) {
|
|
245
|
+
for (const line of contentBody.trim().split("\n")) {
|
|
246
|
+
if (line.trim().startsWith("#")) {
|
|
247
|
+
title = line.trim().replace(/^#+\s*/, "");
|
|
248
|
+
break;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
const parsedArguments = argumentHint ? this.parseArgumentHint(argumentHint) : void 0;
|
|
253
|
+
return {
|
|
254
|
+
id,
|
|
255
|
+
title,
|
|
256
|
+
description,
|
|
257
|
+
content: contentBody.trim(),
|
|
258
|
+
...category !== void 0 && { category },
|
|
259
|
+
...priority !== void 0 && { priority },
|
|
260
|
+
...parsedArguments !== void 0 && { arguments: parsedArguments }
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
parseArgumentHint(hint) {
|
|
264
|
+
const args = [];
|
|
265
|
+
const argPattern = /\[([^\]]+)\]/g;
|
|
266
|
+
let match;
|
|
267
|
+
while ((match = argPattern.exec(hint)) !== null) {
|
|
268
|
+
const argText = match[1];
|
|
269
|
+
if (!argText) continue;
|
|
270
|
+
const isOptional = argText.endsWith("?");
|
|
271
|
+
const name = isOptional ? argText.slice(0, -1).trim() : argText.trim();
|
|
272
|
+
if (name) {
|
|
273
|
+
args.push({
|
|
274
|
+
name,
|
|
275
|
+
required: !isOptional
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return args;
|
|
280
|
+
}
|
|
281
|
+
applyArguments(content, args) {
|
|
282
|
+
const detectionTarget = content.replaceAll("$$", "");
|
|
283
|
+
const usesPositionalPlaceholders = /\$[1-9](?!\d)/.test(detectionTarget) || detectionTarget.includes("$ARGUMENTS");
|
|
284
|
+
const expanded = expandPlaceholders(content, args).trim();
|
|
285
|
+
if (!args || typeof args !== "object" || Object.keys(args).length === 0) {
|
|
286
|
+
return expanded;
|
|
287
|
+
}
|
|
288
|
+
if (!usesPositionalPlaceholders) {
|
|
289
|
+
if (args._context) {
|
|
290
|
+
const contextString = String(args._context);
|
|
291
|
+
return `${expanded}
|
|
292
|
+
|
|
293
|
+
Context: ${contextString}`;
|
|
294
|
+
}
|
|
295
|
+
const argEntries = Object.entries(args).filter(([key]) => !key.startsWith("_"));
|
|
296
|
+
if (argEntries.length > 0) {
|
|
297
|
+
const formattedArgs = argEntries.map(([key, value]) => `${key}: ${value}`).join(", ");
|
|
298
|
+
return `${expanded}
|
|
299
|
+
|
|
300
|
+
Arguments: ${formattedArgs}`;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return expanded;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
export {
|
|
307
|
+
ConfigPromptProvider
|
|
308
|
+
};
|
|
@@ -134,8 +134,8 @@ class CustomPromptProvider {
|
|
|
134
134
|
if (input.resource) {
|
|
135
135
|
try {
|
|
136
136
|
const blobService = this.resourceManager.getBlobStore();
|
|
137
|
-
const {
|
|
138
|
-
const blobRef = await blobService.store(
|
|
137
|
+
const { data, mimeType, filename } = input.resource;
|
|
138
|
+
const blobRef = await blobService.store(data, {
|
|
139
139
|
mimeType,
|
|
140
140
|
originalName: filename,
|
|
141
141
|
source: "system"
|