@promptbook/core 0.100.0-4 → 0.100.0-41

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/README.md +1 -0
  2. package/esm/index.es.js +2444 -321
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/typings/src/_packages/components.index.d.ts +14 -0
  5. package/esm/typings/src/_packages/core.index.d.ts +26 -0
  6. package/esm/typings/src/_packages/types.index.d.ts +34 -0
  7. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.d.ts +30 -0
  8. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.test.d.ts +1 -0
  9. package/esm/typings/src/book-2.0/agent-source/string_book.d.ts +26 -0
  10. package/esm/typings/src/book-2.0/commitments/ACTION/ACTION.d.ts +38 -0
  11. package/esm/typings/src/book-2.0/commitments/FORMAT/FORMAT.d.ts +39 -0
  12. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/FrontendRAGService.d.ts +48 -0
  13. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +51 -0
  14. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/RAGService.d.ts +54 -0
  15. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/BaseKnowledgeProcessor.d.ts +45 -0
  16. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/PdfProcessor.d.ts +31 -0
  17. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/ProcessorFactory.d.ts +23 -0
  18. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/TextProcessor.d.ts +18 -0
  19. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/types.d.ts +56 -0
  20. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/utils/ragHelper.d.ts +34 -0
  21. package/esm/typings/src/book-2.0/commitments/META_IMAGE/META_IMAGE.d.ts +44 -0
  22. package/esm/typings/src/book-2.0/commitments/META_LINK/META_LINK.d.ts +56 -0
  23. package/esm/typings/src/book-2.0/commitments/MODEL/MODEL.d.ts +39 -0
  24. package/esm/typings/src/book-2.0/commitments/NOTE/NOTE.d.ts +49 -0
  25. package/esm/typings/src/book-2.0/commitments/PERSONA/PERSONA.d.ts +46 -0
  26. package/esm/typings/src/book-2.0/commitments/RULE/RULE.d.ts +44 -0
  27. package/esm/typings/src/book-2.0/commitments/SAMPLE/SAMPLE.d.ts +44 -0
  28. package/esm/typings/src/book-2.0/commitments/STYLE/STYLE.d.ts +38 -0
  29. package/esm/typings/src/book-2.0/commitments/_base/BaseCommitmentDefinition.d.ts +52 -0
  30. package/esm/typings/src/book-2.0/commitments/_base/BookCommitment.d.ts +5 -0
  31. package/esm/typings/src/book-2.0/commitments/_base/CommitmentDefinition.d.ts +48 -0
  32. package/esm/typings/src/book-2.0/commitments/_base/NotYetImplementedCommitmentDefinition.d.ts +22 -0
  33. package/esm/typings/src/book-2.0/commitments/_base/createEmptyAgentModelRequirements.d.ts +19 -0
  34. package/esm/typings/src/book-2.0/commitments/_misc/AgentModelRequirements.d.ts +37 -0
  35. package/esm/typings/src/book-2.0/commitments/_misc/AgentSourceParseResult.d.ts +18 -0
  36. package/esm/typings/src/book-2.0/commitments/_misc/ParsedCommitment.d.ts +22 -0
  37. package/esm/typings/src/book-2.0/commitments/_misc/createAgentModelRequirements.d.ts +61 -0
  38. package/esm/typings/src/book-2.0/commitments/_misc/createAgentModelRequirementsWithCommitments.d.ts +35 -0
  39. package/esm/typings/src/book-2.0/commitments/_misc/createCommitmentRegex.d.ts +20 -0
  40. package/esm/typings/src/book-2.0/commitments/_misc/parseAgentSourceWithCommitments.d.ts +24 -0
  41. package/esm/typings/src/book-2.0/commitments/_misc/removeCommentsFromSystemMessage.d.ts +11 -0
  42. package/esm/typings/src/book-2.0/commitments/index.d.ts +56 -0
  43. package/esm/typings/src/book-2.0/utils/profileImageUtils.d.ts +39 -0
  44. package/esm/typings/src/book-components/AvatarProfile/AvatarChip/AvatarChip.d.ts +35 -0
  45. package/esm/typings/src/book-components/AvatarProfile/AvatarChip/AvatarChipFromSource.d.ts +21 -0
  46. package/esm/typings/src/book-components/AvatarProfile/AvatarChip/index.d.ts +2 -0
  47. package/esm/typings/src/book-components/BookEditor/BookEditor.d.ts +35 -0
  48. package/esm/typings/src/book-components/BookEditor/config.d.ts +10 -0
  49. package/esm/typings/src/book-components/BookEditor/injectCssModuleIntoShadowRoot.d.ts +11 -0
  50. package/esm/typings/src/book-components/_common/react-utils/classNames.d.ts +7 -0
  51. package/esm/typings/src/book-components/_common/react-utils/collectCssTextsForClass.d.ts +7 -0
  52. package/esm/typings/src/book-components/_common/react-utils/escapeHtml.d.ts +6 -0
  53. package/esm/typings/src/book-components/_common/react-utils/escapeRegex.d.ts +6 -0
  54. package/esm/typings/src/config.d.ts +6 -0
  55. package/esm/typings/src/execution/AvailableModel.d.ts +4 -0
  56. package/esm/typings/src/execution/ExecutionTask.d.ts +27 -0
  57. package/esm/typings/src/execution/createPipelineExecutor/40-executeAttempts.d.ts +6 -1
  58. package/esm/typings/src/llm-providers/anthropic-claude/AnthropicClaudeExecutionTools.d.ts +0 -5
  59. package/esm/typings/src/llm-providers/anthropic-claude/anthropic-claude-models.d.ts +1 -1
  60. package/esm/typings/src/llm-providers/deepseek/deepseek-models.d.ts +1 -1
  61. package/esm/typings/src/llm-providers/google/google-models.d.ts +1 -1
  62. package/esm/typings/src/llm-providers/ollama/ollama-models.d.ts +1 -1
  63. package/esm/typings/src/llm-providers/openai/openai-models.d.ts +1 -1
  64. package/esm/typings/src/pipeline/book-notation.d.ts +2 -1
  65. package/esm/typings/src/types/ModelRequirements.d.ts +0 -2
  66. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  67. package/esm/typings/src/version.d.ts +1 -1
  68. package/package.json +1 -1
  69. package/umd/index.umd.js +2341 -205
  70. package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js CHANGED
@@ -27,136 +27,342 @@
27
27
  * @generated
28
28
  * @see https://github.com/webgptorg/promptbook
29
29
  */
30
- const PROMPTBOOK_ENGINE_VERSION = '0.100.0-4';
30
+ const PROMPTBOOK_ENGINE_VERSION = '0.100.0-41';
31
31
  /**
32
32
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
33
33
  * Note: [💞] Ignore a discrepancy between file name and entity name
34
34
  */
35
35
 
36
36
  /**
37
- * Converts PipelineCollection to serialized JSON
37
+ * Extracts profile image URL from agent definition text and returns cleaned system message
38
+ * @param systemMessage The original system message that may contain META IMAGE line
39
+ * @returns Object with profileImageUrl (if found) and cleanedSystemMessage (without META IMAGE line)
38
40
  *
39
- * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
41
+ * @private - TODO: [🧠] Maybe should be public?
42
+ */
43
+ /**
44
+ * Generates a gravatar URL based on agent name for fallback avatar
45
+ * @param name The agent name to generate avatar for
46
+ * @returns Gravatar URL
40
47
  *
41
- * @public exported from `@promptbook/core`
48
+ * @private - TODO: [🧠] Maybe should be public?
42
49
  */
43
- async function collectionToJson(collection) {
44
- const pipelineUrls = await collection.listPipelines();
45
- const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
46
- return promptbooks;
50
+ function generateGravatarUrl(name) {
51
+ // Use a default name if none provided
52
+ const safeName = name || 'Anonymous Agent';
53
+ // Create a simple hash from the name for consistent avatar
54
+ let hash = 0;
55
+ for (let i = 0; i < safeName.length; i++) {
56
+ const char = safeName.charCodeAt(i);
57
+ hash = (hash << 5) - hash + char;
58
+ hash = hash & hash; // Convert to 32bit integer
59
+ }
60
+ const avatarId = Math.abs(hash).toString();
61
+ return `https://www.gravatar.com/avatar/${avatarId}?default=robohash&size=200&rating=x`;
47
62
  }
48
63
  /**
49
- * TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
64
+ * Note: [💞] Ignore a discrepancy between file name and entity name
50
65
  */
51
66
 
52
67
  /**
53
- * Checks if value is valid email
68
+ * Freezes the given object and all its nested objects recursively
69
+ *
70
+ * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
71
+ * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
54
72
  *
73
+ * @returns The same object as the input, but deeply frozen
55
74
  * @public exported from `@promptbook/utils`
56
75
  */
57
- function isValidEmail(email) {
58
- if (typeof email !== 'string') {
59
- return false;
76
+ function $deepFreeze(objectValue) {
77
+ if (Array.isArray(objectValue)) {
78
+ return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
60
79
  }
61
- if (email.split('\n').length > 1) {
62
- return false;
80
+ const propertyNames = Object.getOwnPropertyNames(objectValue);
81
+ for (const propertyName of propertyNames) {
82
+ const value = objectValue[propertyName];
83
+ if (value && typeof value === 'object') {
84
+ $deepFreeze(value);
85
+ }
63
86
  }
64
- return /^.+@.+\..+$/.test(email);
87
+ Object.freeze(objectValue);
88
+ return objectValue;
65
89
  }
90
+ /**
91
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
92
+ */
66
93
 
67
94
  /**
68
- * Tests if given string is valid URL.
95
+ * Generates a regex pattern to match a specific commitment
69
96
  *
70
- * Note: This does not check if the file exists only if the path is valid
71
- * @public exported from `@promptbook/utils`
97
+ * Note: It always creates new Regex object
98
+ * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
99
+ *
100
+ * @private
72
101
  */
73
- function isValidFilePath(filename) {
74
- if (typeof filename !== 'string') {
75
- return false;
102
+ function createCommitmentRegex(commitment) {
103
+ const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
104
+ const keywordPattern = escapedCommitment.split(/\s+/).join('\\s+');
105
+ const regex = new RegExp(`^\\s*(?<type>${keywordPattern})\\b\\s+(?<contents>.+)$`, 'gim');
106
+ return regex;
107
+ }
108
+ /**
109
+ * Generates a regex pattern to match a specific commitment type
110
+ *
111
+ * Note: It just matches the type part of the commitment
112
+ * Note: It always creates new Regex object
113
+ * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
114
+ *
115
+ * @private
116
+ */
117
+ function createCommitmentTypeRegex(commitment) {
118
+ const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
119
+ const keywordPattern = escapedCommitment.split(/\s+/).join('\\s+');
120
+ const regex = new RegExp(`^\\s*(?<type>${keywordPattern})\\b`, 'gim');
121
+ return regex;
122
+ }
123
+
124
+ /**
125
+ * Base implementation of CommitmentDefinition that provides common functionality
126
+ * Most commitments can extend this class and only override the applyToAgentModelRequirements method
127
+ *
128
+ * @private
129
+ */
130
+ class BaseCommitmentDefinition {
131
+ constructor(type) {
132
+ this.type = type;
76
133
  }
77
- if (filename.split('\n').length > 1) {
78
- return false;
134
+ /**
135
+ * Creates a regex pattern to match this commitment in agent source
136
+ * Uses the existing createCommitmentRegex function as internal helper
137
+ */
138
+ createRegex() {
139
+ return createCommitmentRegex(this.type);
79
140
  }
80
- if (filename.split(' ').length >
81
- 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
82
- return false;
141
+ /**
142
+ * Creates a regex pattern to match just the commitment type
143
+ * Uses the existing createCommitmentTypeRegex function as internal helper
144
+ */
145
+ createTypeRegex() {
146
+ return createCommitmentTypeRegex(this.type);
83
147
  }
84
- const filenameSlashes = filename.split('\\').join('/');
85
- // Absolute Unix path: /hello.txt
86
- if (/^(\/)/i.test(filenameSlashes)) {
87
- // console.log(filename, 'Absolute Unix path: /hello.txt');
88
- return true;
148
+ /**
149
+ * Helper method to create a new requirements object with updated system message
150
+ * This is commonly used by many commitments
151
+ */
152
+ updateSystemMessage(requirements, messageUpdate) {
153
+ const newMessage = typeof messageUpdate === 'string' ? messageUpdate : messageUpdate(requirements.systemMessage);
154
+ return {
155
+ ...requirements,
156
+ systemMessage: newMessage,
157
+ };
89
158
  }
90
- // Absolute Windows path: /hello.txt
91
- if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
92
- // console.log(filename, 'Absolute Windows path: /hello.txt');
93
- return true;
159
+ /**
160
+ * Helper method to append content to the system message
161
+ */
162
+ appendToSystemMessage(requirements, content, separator = '\n\n') {
163
+ return this.updateSystemMessage(requirements, (currentMessage) => {
164
+ if (!currentMessage.trim()) {
165
+ return content;
166
+ }
167
+ return currentMessage + separator + content;
168
+ });
94
169
  }
95
- // Relative path: ./hello.txt
96
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
97
- // console.log(filename, 'Relative path: ./hello.txt');
98
- return true;
170
+ /**
171
+ * Helper method to add a comment section to the system message
172
+ * Comments are lines starting with # that will be removed from the final system message
173
+ * but can be useful for organizing and structuring the message during processing
174
+ */
175
+ addCommentSection(requirements, commentTitle, content, position = 'end') {
176
+ const commentSection = `# ${commentTitle.toUpperCase()}\n${content}`;
177
+ if (position === 'beginning') {
178
+ return this.updateSystemMessage(requirements, (currentMessage) => {
179
+ if (!currentMessage.trim()) {
180
+ return commentSection;
181
+ }
182
+ return commentSection + '\n\n' + currentMessage;
183
+ });
184
+ }
185
+ else {
186
+ return this.appendToSystemMessage(requirements, commentSection);
187
+ }
99
188
  }
100
- // Allow paths like foo/hello
101
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
102
- // console.log(filename, 'Allow paths like foo/hello');
103
- return true;
189
+ }
190
+
191
+ /**
192
+ * ACTION commitment definition
193
+ *
194
+ * The ACTION commitment defines specific actions or capabilities that the agent can perform.
195
+ * This helps define what the agent is capable of doing and how it should approach tasks.
196
+ *
197
+ * Example usage in agent source:
198
+ *
199
+ * ```book
200
+ * ACTION Can generate code snippets and explain programming concepts
201
+ * ACTION Able to analyze data and provide insights
202
+ * ```
203
+ *
204
+ * @private [🪔] Maybe export the commitments through some package
205
+ */
206
+ class ActionCommitmentDefinition extends BaseCommitmentDefinition {
207
+ constructor() {
208
+ super('ACTION');
104
209
  }
105
- // Allow paths like hello.book
106
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
107
- // console.log(filename, 'Allow paths like hello.book');
108
- return true;
210
+ /**
211
+ * Short one-line description of ACTION.
212
+ */
213
+ get description() {
214
+ return 'Define agent capabilities and actions it can perform.';
215
+ }
216
+ /**
217
+ * Markdown documentation for ACTION commitment.
218
+ */
219
+ get documentation() {
220
+ return spaceTrim.spaceTrim(`
221
+ # ACTION
222
+
223
+ Defines specific actions or capabilities that the agent can perform.
224
+
225
+ ## Key behaviors
226
+
227
+ - Multiple \`ACTION\` commitments are applied sequentially.
228
+ - Each action adds to the agent's capability list.
229
+ - Actions help users understand what the agent can do.
230
+
231
+ ## Examples
232
+
233
+ \`\`\`book
234
+ Code Assistant
235
+
236
+ PERSONA You are a programming assistant
237
+ ACTION Can generate code snippets and explain programming concepts
238
+ ACTION Able to debug existing code and suggest improvements
239
+ ACTION Can create unit tests for functions
240
+ \`\`\`
241
+
242
+ \`\`\`book
243
+ Data Scientist
244
+
245
+ PERSONA You are a data analysis expert
246
+ ACTION Able to analyze data and provide insights
247
+ ACTION Can create visualizations and charts
248
+ ACTION Capable of statistical analysis and modeling
249
+ KNOWLEDGE Data analysis best practices and statistical methods
250
+ \`\`\`
251
+ `);
252
+ }
253
+ applyToAgentModelRequirements(requirements, content) {
254
+ const trimmedContent = content.trim();
255
+ if (!trimmedContent) {
256
+ return requirements;
257
+ }
258
+ // Add action capability to the system message
259
+ const actionSection = `Capability: ${trimmedContent}`;
260
+ return this.appendToSystemMessage(requirements, actionSection, '\n\n');
109
261
  }
110
- return false;
111
262
  }
112
263
  /**
113
- * TODO: [🍏] Implement for MacOs
264
+ * Singleton instance of the ACTION commitment definition
265
+ *
266
+ * @private [🪔] Maybe export the commitments through some package
267
+ */
268
+ new ActionCommitmentDefinition();
269
+ /**
270
+ * Note: [💞] Ignore a discrepancy between file name and entity name
114
271
  */
115
272
 
116
273
  /**
117
- * Tests if given string is valid URL.
274
+ * FORMAT commitment definition
118
275
  *
119
- * Note: Dataurl are considered perfectly valid.
120
- * Note: There are two similar functions:
121
- * - `isValidUrl` which tests any URL
122
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
276
+ * The FORMAT commitment defines the specific output structure and formatting
277
+ * that the agent should use in its responses. This includes data formats,
278
+ * response templates, and structural requirements.
123
279
  *
124
- * @public exported from `@promptbook/utils`
280
+ * Example usage in agent source:
281
+ *
282
+ * ```book
283
+ * FORMAT Always respond in JSON format with 'status' and 'data' fields
284
+ * FORMAT Use markdown formatting for all code blocks
285
+ * ```
286
+ *
287
+ * @private [🪔] Maybe export the commitments through some package
125
288
  */
126
- function isValidUrl(url) {
127
- if (typeof url !== 'string') {
128
- return false;
289
+ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
290
+ constructor() {
291
+ super('FORMAT');
129
292
  }
130
- try {
131
- if (url.startsWith('blob:')) {
132
- url = url.replace(/^blob:/, '');
133
- }
134
- const urlObject = new URL(url /* because fail is handled */);
135
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
136
- return false;
137
- }
138
- return true;
293
+ /**
294
+ * Short one-line description of FORMAT.
295
+ */
296
+ get description() {
297
+ return 'Specify output structure or formatting requirements.';
139
298
  }
140
- catch (error) {
141
- return false;
299
+ /**
300
+ * Markdown documentation for FORMAT commitment.
301
+ */
302
+ get documentation() {
303
+ return spaceTrim.spaceTrim(`
304
+ # FORMAT
305
+
306
+ Defines the specific output structure and formatting for responses (data formats, templates, structure).
307
+
308
+ ## Key behaviors
309
+
310
+ - Multiple \`FORMAT\` commitments are applied sequentially.
311
+ - If they are in conflict, the last one takes precedence.
312
+ - You can specify both data formats and presentation styles.
313
+
314
+ ## Examples
315
+
316
+ \`\`\`book
317
+ Customer Support Bot
318
+
319
+ PERSONA You are a helpful customer support agent
320
+ FORMAT Always respond in JSON format with 'status' and 'data' fields
321
+ FORMAT Use markdown formatting for all code blocks
322
+ \`\`\`
323
+
324
+ \`\`\`book
325
+ Data Analyst
326
+
327
+ PERSONA You are a data analysis expert
328
+ FORMAT Present results in structured tables
329
+ FORMAT Include confidence scores for all predictions
330
+ STYLE Be concise and precise in explanations
331
+ \`\`\`
332
+ `);
333
+ }
334
+ applyToAgentModelRequirements(requirements, content) {
335
+ const trimmedContent = content.trim();
336
+ if (!trimmedContent) {
337
+ return requirements;
338
+ }
339
+ // Add format instructions to the system message
340
+ const formatSection = `Output Format: ${trimmedContent}`;
341
+ return this.appendToSystemMessage(requirements, formatSection, '\n\n');
142
342
  }
143
343
  }
344
+ /**
345
+ * Singleton instance of the FORMAT commitment definition
346
+ *
347
+ * @private [🪔] Maybe export the commitments through some package
348
+ */
349
+ new FormatCommitmentDefinition();
350
+ /**
351
+ * Note: [💞] Ignore a discrepancy between file name and entity name
352
+ */
144
353
 
145
354
  /**
146
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
355
+ * Error thrown when a fetch request fails
147
356
  *
148
357
  * @public exported from `@promptbook/core`
149
358
  */
150
- class ParseError extends Error {
359
+ class PromptbookFetchError extends Error {
151
360
  constructor(message) {
152
361
  super(message);
153
- this.name = 'ParseError';
154
- Object.setPrototypeOf(this, ParseError.prototype);
362
+ this.name = 'PromptbookFetchError';
363
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
155
364
  }
156
365
  }
157
- /**
158
- * TODO: Maybe split `ParseError` and `ApplyError`
159
- */
160
366
 
161
367
  /**
162
368
  * Available remote servers for the Promptbook
@@ -480,6 +686,12 @@
480
686
  * @public exported from `@promptbook/core`
481
687
  */
482
688
  const DEFAULT_IS_AUTO_INSTALLED = false;
689
+ /**
690
+ * Default simulated duration for a task in milliseconds (used for progress reporting)
691
+ *
692
+ * @public exported from `@promptbook/core`
693
+ */
694
+ const DEFAULT_TASK_SIMULATED_DURATION_MS = 5 * 60 * 1000; // 5 minutes
483
695
  /**
484
696
  * Function name for generated function via `ptbk make` to get the pipeline collection
485
697
  *
@@ -553,76 +765,1907 @@
553
765
  }
554
766
 
555
767
  /**
556
- * This error type indicates that the error should not happen and its last check before crashing with some other error
768
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
769
+ *
770
+ * @public exported from `@promptbook/core`
771
+ */
772
+ class UnexpectedError extends Error {
773
+ constructor(message) {
774
+ super(spaceTrim.spaceTrim((block) => `
775
+ ${block(message)}
776
+
777
+ Note: This error should not happen.
778
+ It's probably a bug in the pipeline collection
779
+
780
+ Please report issue:
781
+ ${block(getErrorReportUrl(new Error(message)).href)}
782
+
783
+ Or contact us on ${ADMIN_EMAIL}
784
+
785
+ `));
786
+ this.name = 'UnexpectedError';
787
+ Object.setPrototypeOf(this, UnexpectedError.prototype);
788
+ }
789
+ }
790
+
791
+ /**
792
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
793
+ *
794
+ * @public exported from `@promptbook/core`
795
+ */
796
+ class WrappedError extends Error {
797
+ constructor(whatWasThrown) {
798
+ const tag = `[🤮]`;
799
+ console.error(tag, whatWasThrown);
800
+ super(spaceTrim.spaceTrim(`
801
+ Non-Error object was thrown
802
+
803
+ Note: Look for ${tag} in the console for more details
804
+ Please report issue on ${ADMIN_EMAIL}
805
+ `));
806
+ this.name = 'WrappedError';
807
+ Object.setPrototypeOf(this, WrappedError.prototype);
808
+ }
809
+ }
810
+
811
+ /**
812
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
813
+ *
814
+ * @param whatWasThrown Any object that was thrown
815
+ * @returns Nothing if the error is an instance of `Error`
816
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
817
+ *
818
+ * @private within the repository
819
+ */
820
+ function assertsError(whatWasThrown) {
821
+ // Case 1: Handle error which was rethrown as `WrappedError`
822
+ if (whatWasThrown instanceof WrappedError) {
823
+ const wrappedError = whatWasThrown;
824
+ throw wrappedError;
825
+ }
826
+ // Case 2: Handle unexpected errors
827
+ if (whatWasThrown instanceof UnexpectedError) {
828
+ const unexpectedError = whatWasThrown;
829
+ throw unexpectedError;
830
+ }
831
+ // Case 3: Handle standard errors - keep them up to consumer
832
+ if (whatWasThrown instanceof Error) {
833
+ return;
834
+ }
835
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
836
+ throw new WrappedError(whatWasThrown);
837
+ }
838
+
839
+ /**
840
+ * The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
841
+ *
842
+ * @public exported from `@promptbook/core`
843
+ */
844
+ const promptbookFetch = async (urlOrRequest, init) => {
845
+ try {
846
+ return await fetch(urlOrRequest, init);
847
+ }
848
+ catch (error) {
849
+ assertsError(error);
850
+ let url;
851
+ if (typeof urlOrRequest === 'string') {
852
+ url = urlOrRequest;
853
+ }
854
+ else if (urlOrRequest instanceof Request) {
855
+ url = urlOrRequest.url;
856
+ }
857
+ throw new PromptbookFetchError(spaceTrim__default["default"]((block) => `
858
+ Can not fetch "${url}"
859
+
860
+ Fetch error:
861
+ ${block(error.message)}
862
+
863
+ `));
864
+ }
865
+ };
866
+ /**
867
+ * TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
868
+ */
869
+
870
+ /**
871
+ * Frontend RAG Service that uses backend APIs for processing
872
+ * This avoids Node.js dependencies in the frontend
873
+ *
874
+ * @private - TODO: [🧠] Maybe should be public?
875
+ */
876
+ class FrontendRAGService {
877
+ constructor(config) {
878
+ this.chunks = [];
879
+ this.sources = [];
880
+ this.isInitialized = false;
881
+ this.config = {
882
+ maxChunkSize: 1000,
883
+ chunkOverlap: 200,
884
+ maxRetrievedChunks: 5,
885
+ minRelevanceScore: 0.1,
886
+ ...config,
887
+ };
888
+ }
889
+ /**
890
+ * Initialize knowledge sources by processing them on the backend
891
+ */
892
+ async initializeKnowledgeSources(sources) {
893
+ if (sources.length === 0) {
894
+ this.isInitialized = true;
895
+ return;
896
+ }
897
+ try {
898
+ const response = await promptbookFetch('/api/knowledge/process-sources', {
899
+ method: 'POST',
900
+ headers: {
901
+ 'Content-Type': 'application/json',
902
+ },
903
+ body: JSON.stringify({
904
+ sources,
905
+ config: {
906
+ maxChunkSize: this.config.maxChunkSize,
907
+ chunkOverlap: this.config.chunkOverlap,
908
+ },
909
+ }),
910
+ });
911
+ if (!response.ok) {
912
+ throw new Error(`Failed to process knowledge sources: ${response.status}`);
913
+ }
914
+ const result = (await response.json());
915
+ if (!result.success) {
916
+ throw new Error(result.message || 'Failed to process knowledge sources');
917
+ }
918
+ this.chunks = result.chunks;
919
+ this.sources = sources;
920
+ this.isInitialized = true;
921
+ console.log(`Initialized RAG service with ${this.chunks.length} chunks from ${sources.length} sources`);
922
+ }
923
+ catch (error) {
924
+ console.error('Failed to initialize knowledge sources:', error);
925
+ // Don't throw - allow the system to continue without RAG
926
+ this.isInitialized = true;
927
+ }
928
+ }
929
+ /**
930
+ * Get relevant context for a user query
931
+ */
932
+ async getContextForQuery(query) {
933
+ if (!this.isInitialized) {
934
+ console.warn('RAG service not initialized');
935
+ return '';
936
+ }
937
+ if (this.chunks.length === 0) {
938
+ return '';
939
+ }
940
+ try {
941
+ const response = await promptbookFetch('/api/knowledge/retrieve-context', {
942
+ method: 'POST',
943
+ headers: {
944
+ 'Content-Type': 'application/json',
945
+ },
946
+ body: JSON.stringify({
947
+ query,
948
+ chunks: this.chunks,
949
+ config: {
950
+ maxRetrievedChunks: this.config.maxRetrievedChunks,
951
+ minRelevanceScore: this.config.minRelevanceScore,
952
+ },
953
+ }),
954
+ });
955
+ if (!response.ok) {
956
+ console.error(`Failed to retrieve context: ${response.status}`);
957
+ return '';
958
+ }
959
+ const result = (await response.json());
960
+ if (!result.success) {
961
+ console.error('Context retrieval failed:', result.message);
962
+ return '';
963
+ }
964
+ return result.context;
965
+ }
966
+ catch (error) {
967
+ console.error('Error retrieving context:', error);
968
+ return '';
969
+ }
970
+ }
971
+ /**
972
+ * Get relevant chunks for a query (for debugging/inspection)
973
+ */
974
+ async getRelevantChunks(query) {
975
+ if (!this.isInitialized || this.chunks.length === 0) {
976
+ return [];
977
+ }
978
+ try {
979
+ const response = await promptbookFetch('/api/knowledge/retrieve-context', {
980
+ method: 'POST',
981
+ headers: {
982
+ 'Content-Type': 'application/json',
983
+ },
984
+ body: JSON.stringify({
985
+ query,
986
+ chunks: this.chunks,
987
+ config: {
988
+ maxRetrievedChunks: this.config.maxRetrievedChunks,
989
+ minRelevanceScore: this.config.minRelevanceScore,
990
+ },
991
+ }),
992
+ });
993
+ if (!response.ok) {
994
+ return [];
995
+ }
996
+ const result = (await response.json());
997
+ return result.success ? result.relevantChunks : [];
998
+ }
999
+ catch (error) {
1000
+ console.error('Error retrieving relevant chunks:', error);
1001
+ return [];
1002
+ }
1003
+ }
1004
+ /**
1005
+ * Get knowledge base statistics
1006
+ */
1007
+ getStats() {
1008
+ return {
1009
+ sources: this.sources.length,
1010
+ chunks: this.chunks.length,
1011
+ isInitialized: this.isInitialized,
1012
+ };
1013
+ }
1014
+ /**
1015
+ * Check if the service is ready to use
1016
+ */
1017
+ isReady() {
1018
+ return this.isInitialized;
1019
+ }
1020
+ /**
1021
+ * Clear all knowledge sources
1022
+ */
1023
+ clearKnowledgeBase() {
1024
+ this.chunks = [];
1025
+ this.sources = [];
1026
+ this.isInitialized = false;
1027
+ }
1028
+ /**
1029
+ * Add a single knowledge source (for incremental updates)
1030
+ */
1031
+ async addKnowledgeSource(url) {
1032
+ if (this.sources.includes(url)) {
1033
+ console.log(`Knowledge source already exists: ${url}`);
1034
+ return;
1035
+ }
1036
+ try {
1037
+ const response = await promptbookFetch('/api/knowledge/process-sources', {
1038
+ method: 'POST',
1039
+ headers: {
1040
+ 'Content-Type': 'application/json',
1041
+ },
1042
+ body: JSON.stringify({
1043
+ sources: [url],
1044
+ config: {
1045
+ maxChunkSize: this.config.maxChunkSize,
1046
+ chunkOverlap: this.config.chunkOverlap,
1047
+ },
1048
+ }),
1049
+ });
1050
+ if (!response.ok) {
1051
+ throw new Error(`Failed to process knowledge source: ${response.status}`);
1052
+ }
1053
+ const result = (await response.json());
1054
+ if (!result.success) {
1055
+ throw new Error(result.message || 'Failed to process knowledge source');
1056
+ }
1057
+ // Add new chunks to existing ones
1058
+ this.chunks.push(...result.chunks);
1059
+ this.sources.push(url);
1060
+ console.log(`Added knowledge source: ${url} (${result.chunks.length} chunks)`);
1061
+ }
1062
+ catch (error) {
1063
+ console.error(`Failed to add knowledge source ${url}:`, error);
1064
+ throw error;
1065
+ }
1066
+ }
1067
+ }
1068
+
1069
+ /**
1070
+ * KNOWLEDGE commitment definition
1071
+ *
1072
+ * The KNOWLEDGE commitment adds specific knowledge, facts, or context to the agent
1073
+ * using RAG (Retrieval-Augmented Generation) approach for external sources.
1074
+ *
1075
+ * Supports both direct text knowledge and external sources like PDFs.
1076
+ *
1077
+ * Example usage in agent source:
1078
+ *
1079
+ * ```book
1080
+ * KNOWLEDGE The company was founded in 2020 and specializes in AI-powered solutions
1081
+ * KNOWLEDGE https://example.com/company-handbook.pdf
1082
+ * KNOWLEDGE https://example.com/product-documentation.pdf
1083
+ * ```
1084
+ *
1085
+ * @private [🪔] Maybe export the commitments through some package
1086
+ */
1087
+ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
1088
+ constructor() {
1089
+ super('KNOWLEDGE');
1090
+ this.ragService = new FrontendRAGService();
1091
+ }
1092
+ /**
1093
+ * Short one-line description of KNOWLEDGE.
1094
+ */
1095
+ get description() {
1096
+ return 'Add domain **knowledge** via direct text or external sources (RAG).';
1097
+ }
1098
+ /**
1099
+ * Markdown documentation for KNOWLEDGE commitment.
1100
+ */
1101
+ get documentation() {
1102
+ return spaceTrim.spaceTrim(`
1103
+ # KNOWLEDGE
1104
+
1105
+ Adds specific knowledge, facts, or context to the agent using a RAG (Retrieval-Augmented Generation) approach for external sources.
1106
+
1107
+ ## Key behaviors
1108
+
1109
+ - Multiple \`KNOWLEDGE\` commitments are applied sequentially.
1110
+ - Supports both direct text knowledge and external URLs.
1111
+ - External sources (PDFs, websites) are processed via RAG for context retrieval.
1112
+
1113
+ ## Supported formats
1114
+
1115
+ - Direct text: Immediate knowledge incorporated into agent
1116
+ - URLs: External documents processed for contextual retrieval
1117
+ - Supported file types: PDF, text, markdown, HTML
1118
+
1119
+ ## Examples
1120
+
1121
+ \`\`\`book
1122
+ Customer Support Bot
1123
+
1124
+ PERSONA You are a helpful customer support agent for TechCorp
1125
+ KNOWLEDGE TechCorp was founded in 2020 and specializes in AI-powered solutions
1126
+ KNOWLEDGE https://example.com/company-handbook.pdf
1127
+ KNOWLEDGE https://example.com/product-documentation.pdf
1128
+ RULE Always be polite and professional
1129
+ \`\`\`
1130
+
1131
+ \`\`\`book
1132
+ Research Assistant
1133
+
1134
+ PERSONA You are a knowledgeable research assistant
1135
+ KNOWLEDGE Academic research requires careful citation and verification
1136
+ KNOWLEDGE https://example.com/research-guidelines.pdf
1137
+ ACTION Can help with literature reviews and data analysis
1138
+ STYLE Present information in clear, academic format
1139
+ \`\`\`
1140
+ `);
1141
+ }
1142
+ applyToAgentModelRequirements(requirements, content) {
1143
+ var _a;
1144
+ const trimmedContent = content.trim();
1145
+ if (!trimmedContent) {
1146
+ return requirements;
1147
+ }
1148
+ // Check if content is a URL (external knowledge source)
1149
+ if (this.isUrl(trimmedContent)) {
1150
+ // Store the URL for later async processing
1151
+ const updatedRequirements = {
1152
+ ...requirements,
1153
+ metadata: {
1154
+ ...requirements.metadata,
1155
+ ragService: this.ragService,
1156
+ knowledgeSources: [
1157
+ ...(((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.knowledgeSources) || []),
1158
+ trimmedContent,
1159
+ ],
1160
+ },
1161
+ };
1162
+ // Add placeholder information about knowledge sources to system message
1163
+ const knowledgeInfo = `Knowledge Source URL: ${trimmedContent} (will be processed for retrieval during chat)`;
1164
+ return this.appendToSystemMessage(updatedRequirements, knowledgeInfo, '\n\n');
1165
+ }
1166
+ else {
1167
+ // Direct text knowledge - add to system message
1168
+ const knowledgeSection = `Knowledge: ${trimmedContent}`;
1169
+ return this.appendToSystemMessage(requirements, knowledgeSection, '\n\n');
1170
+ }
1171
+ }
1172
+ /**
1173
+ * Check if content is a URL
1174
+ */
1175
+ isUrl(content) {
1176
+ try {
1177
+ new URL(content);
1178
+ return true;
1179
+ }
1180
+ catch (_a) {
1181
+ return false;
1182
+ }
1183
+ }
1184
+ /**
1185
+ * Get RAG service instance for retrieving context during chat
1186
+ */
1187
+ getRagService() {
1188
+ return this.ragService;
1189
+ }
1190
+ }
1191
+ /**
1192
+ * Singleton instance of the KNOWLEDGE commitment definition
1193
+ *
1194
+ * @private [🪔] Maybe export the commitments through some package
1195
+ */
1196
+ new KnowledgeCommitmentDefinition();
1197
+ /**
1198
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1199
+ */
1200
+
1201
+ /**
1202
+ * META IMAGE commitment definition
1203
+ *
1204
+ * The META IMAGE commitment sets the agent's avatar/profile image URL.
1205
+ * This commitment is special because it doesn't affect the system message,
1206
+ * but is handled separately in the parsing logic.
1207
+ *
1208
+ * Example usage in agent source:
1209
+ *
1210
+ * ```book
1211
+ * META IMAGE https://example.com/avatar.jpg
1212
+ * META IMAGE /assets/agent-avatar.png
1213
+ * ```
1214
+ *
1215
+ * @private [🪔] Maybe export the commitments through some package
1216
+ */
1217
+ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
1218
+ constructor() {
1219
+ super('META IMAGE');
1220
+ }
1221
+ /**
1222
+ * Short one-line description of META IMAGE.
1223
+ */
1224
+ get description() {
1225
+ return "Set the agent's profile image URL.";
1226
+ }
1227
+ /**
1228
+ * Markdown documentation for META IMAGE commitment.
1229
+ */
1230
+ get documentation() {
1231
+ return spaceTrim.spaceTrim(`
1232
+ # META IMAGE
1233
+
1234
+ Sets the agent's avatar/profile image URL.
1235
+
1236
+ ## Key behaviors
1237
+
1238
+ - Does not modify the agent's behavior or responses.
1239
+ - Only one \`META IMAGE\` should be used per agent.
1240
+ - If multiple are specified, the last one takes precedence.
1241
+ - Used for visual representation in user interfaces.
1242
+
1243
+ ## Examples
1244
+
1245
+ \`\`\`book
1246
+ Professional Assistant
1247
+
1248
+ META IMAGE https://example.com/professional-avatar.jpg
1249
+ PERSONA You are a professional business assistant
1250
+ STYLE Maintain a formal and courteous tone
1251
+ \`\`\`
1252
+
1253
+ \`\`\`book
1254
+ Creative Helper
1255
+
1256
+ META IMAGE /assets/creative-bot-avatar.png
1257
+ PERSONA You are a creative and inspiring assistant
1258
+ STYLE Be enthusiastic and encouraging
1259
+ ACTION Can help with brainstorming and ideation
1260
+ \`\`\`
1261
+ `);
1262
+ }
1263
+ applyToAgentModelRequirements(requirements, content) {
1264
+ // META IMAGE doesn't modify the system message or model requirements
1265
+ // It's handled separately in the parsing logic for profile image extraction
1266
+ // This method exists for consistency with the CommitmentDefinition interface
1267
+ return requirements;
1268
+ }
1269
+ /**
1270
+ * Extracts the profile image URL from the content
1271
+ * This is used by the parsing logic
1272
+ */
1273
+ extractProfileImageUrl(content) {
1274
+ const trimmedContent = content.trim();
1275
+ return trimmedContent || null;
1276
+ }
1277
+ }
1278
+ /**
1279
+ * Singleton instance of the META IMAGE commitment definition
1280
+ *
1281
+ * @private [🪔] Maybe export the commitments through some package
1282
+ */
1283
+ new MetaImageCommitmentDefinition();
1284
+ /**
1285
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1286
+ */
1287
+
1288
+ /**
1289
+ * META LINK commitment definition
1290
+ *
1291
+ * The `META LINK` commitment represents the link to the person from whom the agent is created.
1292
+ * This commitment is special because it doesn't affect the system message,
1293
+ * but is handled separately in the parsing logic for profile display.
1294
+ *
1295
+ * Example usage in agent source:
1296
+ *
1297
+ * ```
1298
+ * META LINK https://twitter.com/username
1299
+ * META LINK https://linkedin.com/in/profile
1300
+ * META LINK https://github.com/username
1301
+ * ```
1302
+ *
1303
+ * Multiple `META LINK` commitments can be used when there are multiple sources:
1304
+ *
1305
+ * ```book
1306
+ * META LINK https://twitter.com/username
1307
+ * META LINK https://linkedin.com/in/profile
1308
+ * ```
1309
+ *
1310
+ * @private [🪔] Maybe export the commitments through some package
1311
+ */
1312
+ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
1313
+ constructor() {
1314
+ super('META LINK');
1315
+ }
1316
+ /**
1317
+ * Short one-line description of META LINK.
1318
+ */
1319
+ get description() {
1320
+ return 'Provide profile/source links for the person the agent models.';
1321
+ }
1322
+ /**
1323
+ * Markdown documentation for META LINK commitment.
1324
+ */
1325
+ get documentation() {
1326
+ return spaceTrim.spaceTrim(`
1327
+ # META LINK
1328
+
1329
+ Represents a profile or source link for the person the agent is modeled after.
1330
+
1331
+ ## Key behaviors
1332
+
1333
+ - Does not modify the agent's behavior or responses.
1334
+ - Multiple \`META LINK\` commitments can be used for different social profiles.
1335
+ - Used for attribution and crediting the original person.
1336
+ - Displayed in user interfaces for transparency.
1337
+
1338
+ ## Examples
1339
+
1340
+ \`\`\`book
1341
+ Expert Consultant
1342
+
1343
+ META LINK https://twitter.com/expertname
1344
+ META LINK https://linkedin.com/in/expertprofile
1345
+ PERSONA You are Dr. Smith, a renowned expert in artificial intelligence
1346
+ KNOWLEDGE Extensive background in machine learning and neural networks
1347
+ \`\`\`
1348
+
1349
+ \`\`\`book
1350
+ Open Source Developer
1351
+
1352
+ META LINK https://github.com/developer
1353
+ META LINK https://twitter.com/devhandle
1354
+ PERSONA You are an experienced open source developer
1355
+ ACTION Can help with code reviews and architecture decisions
1356
+ STYLE Be direct and technical in explanations
1357
+ \`\`\`
1358
+ `);
1359
+ }
1360
+ applyToAgentModelRequirements(requirements, content) {
1361
+ // META LINK doesn't modify the system message or model requirements
1362
+ // It's handled separately in the parsing logic for profile link extraction
1363
+ // This method exists for consistency with the CommitmentDefinition interface
1364
+ return requirements;
1365
+ }
1366
+ /**
1367
+ * Extracts the profile link URL from the content
1368
+ * This is used by the parsing logic
1369
+ */
1370
+ extractProfileLinkUrl(content) {
1371
+ const trimmedContent = content.trim();
1372
+ return trimmedContent || null;
1373
+ }
1374
+ /**
1375
+ * Validates if the provided content is a valid URL
1376
+ */
1377
+ isValidUrl(content) {
1378
+ try {
1379
+ new URL(content.trim());
1380
+ return true;
1381
+ }
1382
+ catch (_a) {
1383
+ return false;
1384
+ }
1385
+ }
1386
+ }
1387
+ /**
1388
+ * Singleton instance of the META LINK commitment definition
1389
+ *
1390
+ * @private [🪔] Maybe export the commitments through some package
1391
+ */
1392
+ new MetaLinkCommitmentDefinition();
1393
+ /**
1394
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1395
+ */
1396
+
1397
+ /**
1398
+ * MODEL commitment definition
1399
+ *
1400
+ * The MODEL commitment specifies which AI model to use and can also set
1401
+ * model-specific parameters like temperature, topP, and topK.
1402
+ *
1403
+ * Example usage in agent source:
1404
+ *
1405
+ * ```book
1406
+ * MODEL gpt-4
1407
+ * MODEL claude-3-opus temperature=0.3
1408
+ * MODEL gpt-3.5-turbo temperature=0.8 topP=0.9
1409
+ * ```
1410
+ *
1411
+ * @private [🪔] Maybe export the commitments through some package
1412
+ */
1413
+ class ModelCommitmentDefinition extends BaseCommitmentDefinition {
1414
+ constructor() {
1415
+ super('MODEL');
1416
+ }
1417
+ /**
1418
+ * Short one-line description of MODEL.
1419
+ */
1420
+ get description() {
1421
+ return 'Select the AI model and optional decoding parameters.';
1422
+ }
1423
+ /**
1424
+ * Markdown documentation for MODEL commitment.
1425
+ */
1426
+ get documentation() {
1427
+ return spaceTrim.spaceTrim(`
1428
+ # MODEL
1429
+
1430
+ Specifies which AI model to use and optional decoding parameters.
1431
+
1432
+ ## Key behaviors
1433
+
1434
+ - Only one \`MODEL\` commitment should be used per agent.
1435
+ - If multiple are specified, the last one takes precedence.
1436
+ - Parameters control the randomness and creativity of responses.
1437
+
1438
+ ## Supported parameters
1439
+
1440
+ - \`temperature\`: Controls randomness (0.0 = deterministic, 1.0+ = creative)
1441
+ - \`topP\` (aka \`top_p\`): Nucleus sampling parameter
1442
+ - \`topK\` (aka \`top_k\`): Top-k sampling parameter
1443
+
1444
+ ## Examples
1445
+
1446
+ \`\`\`book
1447
+ Precise Assistant
1448
+
1449
+ PERSONA You are a precise and accurate assistant
1450
+ MODEL gpt-4 temperature=0.1
1451
+ RULE Always provide factual information
1452
+ \`\`\`
1453
+
1454
+ \`\`\`book
1455
+ Creative Writer
1456
+
1457
+ PERSONA You are a creative writing assistant
1458
+ MODEL claude-3-opus temperature=0.8 topP=0.9
1459
+ STYLE Be imaginative and expressive
1460
+ ACTION Can help with storytelling and character development
1461
+ \`\`\`
1462
+ `);
1463
+ }
1464
+ applyToAgentModelRequirements(requirements, content) {
1465
+ const trimmedContent = content.trim();
1466
+ if (!trimmedContent) {
1467
+ return requirements;
1468
+ }
1469
+ // Parse the model specification
1470
+ const parts = trimmedContent.split(/\s+/);
1471
+ const modelName = parts[0];
1472
+ if (!modelName) {
1473
+ return requirements;
1474
+ }
1475
+ // Start with the model name
1476
+ const updatedRequirements = {
1477
+ ...requirements,
1478
+ modelName,
1479
+ };
1480
+ // Parse additional parameters
1481
+ const result = { ...updatedRequirements };
1482
+ for (let i = 1; i < parts.length; i++) {
1483
+ const param = parts[i];
1484
+ if (param && param.includes('=')) {
1485
+ const [key, value] = param.split('=');
1486
+ if (key && value) {
1487
+ const numValue = parseFloat(value);
1488
+ if (!isNaN(numValue)) {
1489
+ switch (key.toLowerCase()) {
1490
+ case 'temperature':
1491
+ result.temperature = numValue;
1492
+ break;
1493
+ case 'topp':
1494
+ case 'top_p':
1495
+ result.topP = numValue;
1496
+ break;
1497
+ case 'topk':
1498
+ case 'top_k':
1499
+ result.topK = Math.round(numValue);
1500
+ break;
1501
+ }
1502
+ }
1503
+ }
1504
+ }
1505
+ }
1506
+ return result;
1507
+ }
1508
+ }
1509
+ /**
1510
+ * Singleton instance of the MODEL commitment definition
1511
+ *
1512
+ * @private [🪔] Maybe export the commitments through some package
1513
+ */
1514
+ new ModelCommitmentDefinition();
1515
+ /**
1516
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1517
+ */
1518
+
1519
+ /**
1520
+ * NOTE commitment definition
1521
+ *
1522
+ * The NOTE commitment is used to add comments to the agent source without making any changes
1523
+ * to the system message or agent model requirements. It serves as a documentation mechanism
1524
+ * for developers to add explanatory comments, reminders, or annotations directly in the agent source.
1525
+ *
1526
+ * Key features:
1527
+ * - Makes no changes to the system message
1528
+ * - Makes no changes to agent model requirements
1529
+ * - Content is preserved in metadata.NOTE for debugging and inspection
1530
+ * - Multiple NOTE commitments are aggregated together
1531
+ * - Comments (# NOTE) are removed from the final system message
1532
+ *
1533
+ * Example usage in agent source:
1534
+ *
1535
+ * ```book
1536
+ * NOTE This agent was designed for customer support scenarios
1537
+ * NOTE Remember to update the knowledge base monthly
1538
+ * NOTE Performance optimized for quick response times
1539
+ * ```
1540
+ *
1541
+ * The above notes will be stored in metadata but won't affect the agent's behavior.
1542
+ *
1543
+ * @private [🪔] Maybe export the commitments through some package
1544
+ */
1545
+ class NoteCommitmentDefinition extends BaseCommitmentDefinition {
1546
+ constructor() {
1547
+ super('NOTE');
1548
+ }
1549
+ /**
1550
+ * Short one-line description of NOTE.
1551
+ */
1552
+ get description() {
1553
+ return 'Add developer-facing notes without changing behavior or output.';
1554
+ }
1555
+ /**
1556
+ * Markdown documentation for NOTE commitment.
1557
+ */
1558
+ get documentation() {
1559
+ return spaceTrim.spaceTrim(`
1560
+ # NOTE
1561
+
1562
+ Adds comments for documentation without changing agent behavior.
1563
+
1564
+ ## Key behaviors
1565
+
1566
+ - Does not modify the agent's behavior or responses.
1567
+ - Multiple \`NOTE\` commitments are aggregated for debugging.
1568
+ - Useful for documenting design decisions and reminders.
1569
+ - Content is preserved in metadata for inspection.
1570
+
1571
+ ## Examples
1572
+
1573
+ \`\`\`book
1574
+ Customer Support Bot
1575
+
1576
+ NOTE This agent was designed for customer support scenarios
1577
+ NOTE Remember to update the knowledge base monthly
1578
+ PERSONA You are a helpful customer support representative
1579
+ KNOWLEDGE Company policies and procedures
1580
+ RULE Always be polite and professional
1581
+ \`\`\`
1582
+
1583
+ \`\`\`book
1584
+ Research Assistant
1585
+
1586
+ NOTE Performance optimized for quick response times
1587
+ NOTE Uses RAG for accessing latest research papers
1588
+ PERSONA You are a knowledgeable research assistant
1589
+ ACTION Can help with literature reviews and citations
1590
+ STYLE Present information in academic format
1591
+ \`\`\`
1592
+ `);
1593
+ }
1594
+ applyToAgentModelRequirements(requirements, content) {
1595
+ var _a;
1596
+ // The NOTE commitment makes no changes to the system message or model requirements
1597
+ // It only stores the note content in metadata for documentation purposes
1598
+ const trimmedContent = content.trim();
1599
+ if (!trimmedContent) {
1600
+ return requirements;
1601
+ }
1602
+ // Get existing note content from metadata
1603
+ const existingNoteContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.NOTE) || '';
1604
+ // Merge the new content with existing note content
1605
+ // When multiple NOTE commitments exist, they are aggregated together
1606
+ const mergedNoteContent = existingNoteContent ? `${existingNoteContent}\n${trimmedContent}` : trimmedContent;
1607
+ // Store the merged note content in metadata for debugging and inspection
1608
+ const updatedMetadata = {
1609
+ ...requirements.metadata,
1610
+ NOTE: mergedNoteContent,
1611
+ };
1612
+ // Return requirements with updated metadata but no changes to system message
1613
+ return {
1614
+ ...requirements,
1615
+ metadata: updatedMetadata,
1616
+ };
1617
+ }
1618
+ }
1619
+ /**
1620
+ * Singleton instance of the NOTE commitment definition
1621
+ *
1622
+ * @private [🪔] Maybe export the commitments through some package
1623
+ */
1624
+ new NoteCommitmentDefinition();
1625
+ /**
1626
+ * [💞] Ignore a discrepancy between file name and entity name
1627
+ */
1628
+
1629
+ /**
1630
+ * PERSONA commitment definition
1631
+ *
1632
+ * The PERSONA commitment modifies the agent's personality and character in the system message.
1633
+ * It defines who the agent is, their background, expertise, and personality traits.
1634
+ *
1635
+ * Key features:
1636
+ * - Multiple PERSONA commitments are automatically merged into one
1637
+ * - Content is placed at the beginning of the system message
1638
+ * - Original content with comments is preserved in metadata.PERSONA
1639
+ * - Comments (# PERSONA) are removed from the final system message
1640
+ *
1641
+ * Example usage in agent source:
1642
+ *
1643
+ * ```book
1644
+ * PERSONA You are a helpful programming assistant with expertise in TypeScript and React
1645
+ * PERSONA You have deep knowledge of modern web development practices
1646
+ * ```
1647
+ *
1648
+ * The above will be merged into a single persona section at the beginning of the system message.
1649
+ *
1650
+ * @private [🪔] Maybe export the commitments through some package
1651
+ */
1652
+ class PersonaCommitmentDefinition extends BaseCommitmentDefinition {
1653
+ constructor() {
1654
+ super('PERSONA');
1655
+ }
1656
+ /**
1657
+ * Short one-line description of PERSONA.
1658
+ */
1659
+ get description() {
1660
+ return 'Define who the agent is: background, expertise, and personality.';
1661
+ }
1662
+ /**
1663
+ * Markdown documentation for PERSONA commitment.
1664
+ */
1665
+ get documentation() {
1666
+ return spaceTrim.spaceTrim(`
1667
+ # PERSONA
1668
+
1669
+ Defines who the agent is, their background, expertise, and personality traits.
1670
+
1671
+ ## Key behaviors
1672
+
1673
+ - Multiple \`PERSONA\` commitments are merged together.
1674
+ - If they are in conflict, the last one takes precedence.
1675
+ - You can write persona content in multiple lines.
1676
+
1677
+ ## Examples
1678
+
1679
+ \`\`\`book
1680
+ Programming Assistant
1681
+
1682
+ PERSONA You are a helpful programming assistant with expertise in TypeScript and React
1683
+ PERSONA You have deep knowledge of modern web development practices
1684
+ \`\`\`
1685
+ `);
1686
+ }
1687
+ applyToAgentModelRequirements(requirements, content) {
1688
+ var _a, _b;
1689
+ // The PERSONA commitment aggregates all persona content and places it at the beginning
1690
+ const trimmedContent = content.trim();
1691
+ if (!trimmedContent) {
1692
+ return requirements;
1693
+ }
1694
+ // Get existing persona content from metadata
1695
+ const existingPersonaContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.PERSONA) || '';
1696
+ // Merge the new content with existing persona content
1697
+ // When multiple PERSONA commitments exist, they are merged into one
1698
+ const mergedPersonaContent = existingPersonaContent
1699
+ ? `${existingPersonaContent}\n${trimmedContent}`
1700
+ : trimmedContent;
1701
+ // Store the merged persona content in metadata for debugging and inspection
1702
+ const updatedMetadata = {
1703
+ ...requirements.metadata,
1704
+ PERSONA: mergedPersonaContent,
1705
+ };
1706
+ // Get the agent name from metadata (which should contain the first line of agent source)
1707
+ // If not available, extract from current system message as fallback
1708
+ let agentName = (_b = requirements.metadata) === null || _b === void 0 ? void 0 : _b.agentName;
1709
+ if (!agentName) {
1710
+ // Fallback: extract from current system message
1711
+ const currentMessage = requirements.systemMessage.trim();
1712
+ const basicFormatMatch = currentMessage.match(/^You are (.+)$/);
1713
+ if (basicFormatMatch && basicFormatMatch[1]) {
1714
+ agentName = basicFormatMatch[1];
1715
+ }
1716
+ else {
1717
+ agentName = 'AI Agent'; // Final fallback
1718
+ }
1719
+ }
1720
+ // Remove any existing persona content from the system message
1721
+ // (this handles the case where we're processing multiple PERSONA commitments)
1722
+ const currentMessage = requirements.systemMessage.trim();
1723
+ let cleanedMessage = currentMessage;
1724
+ // Check if current message starts with persona content or is just the basic format
1725
+ const basicFormatRegex = /^You are .+$/;
1726
+ const isBasicFormat = basicFormatRegex.test(currentMessage) && !currentMessage.includes('\n');
1727
+ if (isBasicFormat) {
1728
+ // Replace the basic format entirely
1729
+ cleanedMessage = '';
1730
+ }
1731
+ else if (currentMessage.startsWith('# PERSONA')) {
1732
+ // Remove existing persona section by finding where it ends
1733
+ const lines = currentMessage.split('\n');
1734
+ let personaEndIndex = lines.length;
1735
+ // Find the end of the PERSONA section (next comment or end of message)
1736
+ for (let i = 1; i < lines.length; i++) {
1737
+ const line = lines[i].trim();
1738
+ if (line.startsWith('#') && !line.startsWith('# PERSONA')) {
1739
+ personaEndIndex = i;
1740
+ break;
1741
+ }
1742
+ }
1743
+ // Keep everything after the PERSONA section
1744
+ cleanedMessage = lines.slice(personaEndIndex).join('\n').trim();
1745
+ }
1746
+ // Create new system message with persona at the beginning
1747
+ // Format: "You are {agentName}\n{personaContent}"
1748
+ // The # PERSONA comment will be removed later by removeCommentsFromSystemMessage
1749
+ const personaSection = `# PERSONA\nYou are ${agentName}\n${mergedPersonaContent}`; // <- TODO: Use spaceTrim
1750
+ const newSystemMessage = cleanedMessage ? `${personaSection}\n\n${cleanedMessage}` : personaSection;
1751
+ return {
1752
+ ...requirements,
1753
+ systemMessage: newSystemMessage,
1754
+ metadata: updatedMetadata,
1755
+ };
1756
+ }
1757
+ }
1758
+ /**
1759
+ * Singleton instance of the PERSONA commitment definition
1760
+ *
1761
+ * @private [🪔] Maybe export the commitments through some package
1762
+ */
1763
+ new PersonaCommitmentDefinition();
1764
+ /**
1765
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1766
+ */
1767
+
1768
+ /**
1769
+ * RULE commitment definition
1770
+ *
1771
+ * The RULE/RULES commitment adds behavioral constraints and guidelines that the agent must follow.
1772
+ * These are specific instructions about what the agent should or shouldn't do.
1773
+ *
1774
+ * Example usage in agent source:
1775
+ *
1776
+ * ```book
1777
+ * RULE Always ask for clarification if the user's request is ambiguous
1778
+ * RULES Never provide medical advice, always refer to healthcare professionals
1779
+ * ```
1780
+ *
1781
+ * @private [🪔] Maybe export the commitments through some package
1782
+ */
1783
+ class RuleCommitmentDefinition extends BaseCommitmentDefinition {
1784
+ constructor(type = 'RULE') {
1785
+ super(type);
1786
+ }
1787
+ /**
1788
+ * Short one-line description of RULE/RULES.
1789
+ */
1790
+ get description() {
1791
+ return 'Add behavioral rules the agent must follow.';
1792
+ }
1793
+ /**
1794
+ * Markdown documentation for RULE/RULES commitment.
1795
+ */
1796
+ get documentation() {
1797
+ return spaceTrim.spaceTrim(`
1798
+ # ${this.type}
1799
+
1800
+ Adds behavioral constraints and guidelines that the agent must follow.
1801
+
1802
+ ## Key behaviors
1803
+
1804
+ - Multiple \`RULE\` and \`RULES\` commitments are applied sequentially.
1805
+ - All rules are treated equally regardless of singular/plural form.
1806
+ - Rules define what the agent must or must not do.
1807
+
1808
+ ## Examples
1809
+
1810
+ \`\`\`book
1811
+ Customer Support Agent
1812
+
1813
+ PERSONA You are a helpful customer support representative
1814
+ RULE Always ask for clarification if the user's request is ambiguous
1815
+ RULE Be polite and professional in all interactions
1816
+ RULES Never provide medical or legal advice
1817
+ STYLE Maintain a friendly and helpful tone
1818
+ \`\`\`
1819
+
1820
+ \`\`\`book
1821
+ Educational Tutor
1822
+
1823
+ PERSONA You are a patient and knowledgeable tutor
1824
+ RULE Break down complex concepts into simple steps
1825
+ RULE Always encourage students and celebrate their progress
1826
+ RULE If you don't know something, admit it and suggest resources
1827
+ SAMPLE When explaining math: "Let's work through this step by step..."
1828
+ \`\`\`
1829
+ `);
1830
+ }
1831
+ applyToAgentModelRequirements(requirements, content) {
1832
+ const trimmedContent = content.trim();
1833
+ if (!trimmedContent) {
1834
+ return requirements;
1835
+ }
1836
+ // Add rule to the system message
1837
+ const ruleSection = `Rule: ${trimmedContent}`;
1838
+ return this.appendToSystemMessage(requirements, ruleSection, '\n\n');
1839
+ }
1840
+ }
1841
+ /**
1842
+ * Singleton instances of the RULE commitment definitions
1843
+ *
1844
+ * @private [🪔] Maybe export the commitments through some package
1845
+ */
1846
+ new RuleCommitmentDefinition('RULE');
1847
+ /**
1848
+ * Singleton instances of the RULE commitment definitions
1849
+ *
1850
+ * @private [🪔] Maybe export the commitments through some package
1851
+ */
1852
+ new RuleCommitmentDefinition('RULES');
1853
+ /**
1854
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1855
+ */
1856
+
1857
+ /**
1858
+ * SAMPLE commitment definition
1859
+ *
1860
+ * The SAMPLE/EXAMPLE commitment provides examples of how the agent should respond
1861
+ * or behave in certain situations. These examples help guide the agent's responses.
1862
+ *
1863
+ * Example usage in agent source:
1864
+ *
1865
+ * ```book
1866
+ * SAMPLE When asked about pricing, respond: "Our basic plan starts at $10/month..."
1867
+ * EXAMPLE For code questions, always include working code snippets
1868
+ * ```
1869
+ *
1870
+ * @private [🪔] Maybe export the commitments through some package
1871
+ */
1872
+ class SampleCommitmentDefinition extends BaseCommitmentDefinition {
1873
+ constructor(type = 'SAMPLE') {
1874
+ super(type);
1875
+ }
1876
+ /**
1877
+ * Short one-line description of SAMPLE/EXAMPLE.
1878
+ */
1879
+ get description() {
1880
+ return 'Provide example responses to guide behavior.';
1881
+ }
1882
+ /**
1883
+ * Markdown documentation for SAMPLE/EXAMPLE commitment.
1884
+ */
1885
+ get documentation() {
1886
+ return spaceTrim.spaceTrim(`
1887
+ # ${this.type}
1888
+
1889
+ Provides examples of how the agent should respond or behave in certain situations.
1890
+
1891
+ ## Key behaviors
1892
+
1893
+ - Multiple \`SAMPLE\` and \`EXAMPLE\` commitments are applied sequentially.
1894
+ - Both terms work identically and can be used interchangeably.
1895
+ - Examples help guide the agent's response patterns and style.
1896
+
1897
+ ## Examples
1898
+
1899
+ \`\`\`book
1900
+ Sales Assistant
1901
+
1902
+ PERSONA You are a knowledgeable sales representative
1903
+ SAMPLE When asked about pricing, respond: "Our basic plan starts at $10/month..."
1904
+ SAMPLE For feature comparisons, create a clear comparison table
1905
+ RULE Always be honest about limitations
1906
+ \`\`\`
1907
+
1908
+ \`\`\`book
1909
+ Code Reviewer
1910
+
1911
+ PERSONA You are an experienced software engineer
1912
+ EXAMPLE For code questions, always include working code snippets
1913
+ EXAMPLE When suggesting improvements: "Here's a more efficient approach..."
1914
+ RULE Explain the reasoning behind your suggestions
1915
+ STYLE Be constructive and encouraging in feedback
1916
+ \`\`\`
1917
+ `);
1918
+ }
1919
+ applyToAgentModelRequirements(requirements, content) {
1920
+ const trimmedContent = content.trim();
1921
+ if (!trimmedContent) {
1922
+ return requirements;
1923
+ }
1924
+ // Add example to the system message
1925
+ const exampleSection = `Example: ${trimmedContent}`;
1926
+ return this.appendToSystemMessage(requirements, exampleSection, '\n\n');
1927
+ }
1928
+ }
1929
+ /**
1930
+ * Singleton instances of the SAMPLE commitment definitions
1931
+ *
1932
+ * @private [🪔] Maybe export the commitments through some package
1933
+ */
1934
+ new SampleCommitmentDefinition('SAMPLE');
1935
+ /**
1936
+ * Singleton instances of the SAMPLE commitment definitions
1937
+ *
1938
+ * @private [🪔] Maybe export the commitments through some package
1939
+ */
1940
+ new SampleCommitmentDefinition('EXAMPLE');
1941
+ /**
1942
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1943
+ */
1944
+
1945
+ /**
1946
+ * STYLE commitment definition
1947
+ *
1948
+ * The STYLE commitment defines how the agent should format and present its responses.
1949
+ * This includes tone, writing style, formatting preferences, and communication patterns.
1950
+ *
1951
+ * Example usage in agent source:
1952
+ *
1953
+ * ```book
1954
+ * STYLE Write in a professional but friendly tone, use bullet points for lists
1955
+ * STYLE Always provide code examples when explaining programming concepts
1956
+ * ```
1957
+ *
1958
+ * @private [🪔] Maybe export the commitments through some package
1959
+ */
1960
+ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
1961
+ constructor() {
1962
+ super('STYLE');
1963
+ }
1964
+ /**
1965
+ * Short one-line description of STYLE.
1966
+ */
1967
+ get description() {
1968
+ return 'Control the tone and writing style of responses.';
1969
+ }
1970
+ /**
1971
+ * Markdown documentation for STYLE commitment.
1972
+ */
1973
+ get documentation() {
1974
+ return spaceTrim.spaceTrim(`
1975
+ # STYLE
1976
+
1977
+ Defines how the agent should format and present its responses (tone, writing style, formatting).
1978
+
1979
+ ## Key behaviors
1980
+
1981
+ - Multiple \`STYLE\` commitments are applied sequentially.
1982
+ - Later style instructions can override earlier ones.
1983
+ - Style affects both tone and presentation format.
1984
+
1985
+ ## Examples
1986
+
1987
+ \`\`\`book
1988
+ Technical Writer
1989
+
1990
+ PERSONA You are a technical documentation expert
1991
+ STYLE Write in a professional but friendly tone, use bullet points for lists
1992
+ STYLE Always provide code examples when explaining programming concepts
1993
+ FORMAT Use markdown formatting with clear headings
1994
+ \`\`\`
1995
+
1996
+ \`\`\`book
1997
+ Creative Assistant
1998
+
1999
+ PERSONA You are a creative writing helper
2000
+ STYLE Be enthusiastic and encouraging in your responses
2001
+ STYLE Use vivid metaphors and analogies to explain concepts
2002
+ STYLE Keep responses conversational and engaging
2003
+ RULE Always maintain a positive and supportive tone
2004
+ \`\`\`
2005
+ `);
2006
+ }
2007
+ applyToAgentModelRequirements(requirements, content) {
2008
+ const trimmedContent = content.trim();
2009
+ if (!trimmedContent) {
2010
+ return requirements;
2011
+ }
2012
+ // Add style instructions to the system message
2013
+ const styleSection = `Style: ${trimmedContent}`;
2014
+ return this.appendToSystemMessage(requirements, styleSection, '\n\n');
2015
+ }
2016
+ }
2017
+ /**
2018
+ * Singleton instance of the STYLE commitment definition
2019
+ *
2020
+ * @private [🪔] Maybe export the commitments through some package
2021
+ */
2022
+ new StyleCommitmentDefinition();
2023
+ /**
2024
+ * [💞] Ignore a discrepancy between file name and entity name
2025
+ */
2026
+
2027
+ /**
2028
+ * Placeholder commitment definition for commitments that are not yet implemented
2029
+ *
2030
+ * This commitment simply adds its content 1:1 into the system message,
2031
+ * preserving the original behavior until proper implementation is added.
2032
+ *
2033
+ * @public exported from `@promptbook/core`
2034
+ */
2035
+ class NotYetImplementedCommitmentDefinition extends BaseCommitmentDefinition {
2036
+ constructor(type) {
2037
+ super(type);
2038
+ }
2039
+ /**
2040
+ * Short one-line description of a placeholder commitment.
2041
+ */
2042
+ get description() {
2043
+ return 'Placeholder commitment that appends content verbatim to the system message.';
2044
+ }
2045
+ /**
2046
+ * Markdown documentation available at runtime.
2047
+ */
2048
+ get documentation() {
2049
+ return spaceTrim.spaceTrim(`
2050
+ # ${this.type}
2051
+
2052
+ This commitment is not yet fully implemented.
2053
+
2054
+ ## Key behaviors
2055
+
2056
+ - Content is appended directly to the system message.
2057
+ - No special processing or validation is performed.
2058
+ - Behavior preserved until proper implementation is added.
2059
+
2060
+ ## Status
2061
+
2062
+ - **Status:** Placeholder implementation
2063
+ - **Effect:** Appends content prefixed by commitment type
2064
+ - **Future:** Will be replaced with specialized logic
2065
+
2066
+ ## Examples
2067
+
2068
+ \`\`\`book
2069
+ Example Agent
2070
+
2071
+ PERSONA You are a helpful assistant
2072
+ ${this.type} Your content here
2073
+ RULE Always be helpful
2074
+ \`\`\`
2075
+ `);
2076
+ }
2077
+ applyToAgentModelRequirements(requirements, content) {
2078
+ const trimmedContent = content.trim();
2079
+ if (!trimmedContent) {
2080
+ return requirements;
2081
+ }
2082
+ // Add the commitment content 1:1 to the system message
2083
+ const commitmentLine = `${this.type} ${trimmedContent}`;
2084
+ return this.appendToSystemMessage(requirements, commitmentLine, '\n\n');
2085
+ }
2086
+ }
2087
+
2088
+ // Import all commitment definition classes
2089
+ /**
2090
+ * Registry of all available commitment definitions
2091
+ * This array contains instances of all commitment definitions
2092
+ * This is the single source of truth for all commitments in the system
2093
+ *
2094
+ * @private Use functions to access commitments instead of this array directly
2095
+ */
2096
+ const COMMITMENT_REGISTRY = [
2097
+ // Fully implemented commitments
2098
+ new PersonaCommitmentDefinition(),
2099
+ new KnowledgeCommitmentDefinition(),
2100
+ new StyleCommitmentDefinition(),
2101
+ new RuleCommitmentDefinition('RULE'),
2102
+ new RuleCommitmentDefinition('RULES'),
2103
+ new SampleCommitmentDefinition('SAMPLE'),
2104
+ new SampleCommitmentDefinition('EXAMPLE'),
2105
+ new FormatCommitmentDefinition(),
2106
+ new ModelCommitmentDefinition(),
2107
+ new ActionCommitmentDefinition(),
2108
+ new MetaImageCommitmentDefinition(),
2109
+ new MetaLinkCommitmentDefinition(),
2110
+ new NoteCommitmentDefinition(),
2111
+ // Not yet implemented commitments (using placeholder)
2112
+ new NotYetImplementedCommitmentDefinition('EXPECT'),
2113
+ new NotYetImplementedCommitmentDefinition('SCENARIO'),
2114
+ new NotYetImplementedCommitmentDefinition('SCENARIOS'),
2115
+ new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
2116
+ new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
2117
+ new NotYetImplementedCommitmentDefinition('AVOID'),
2118
+ new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
2119
+ new NotYetImplementedCommitmentDefinition('GOAL'),
2120
+ new NotYetImplementedCommitmentDefinition('GOALS'),
2121
+ new NotYetImplementedCommitmentDefinition('CONTEXT'),
2122
+ ];
2123
+ /**
2124
+ * Gets a commitment definition by its type
2125
+ * @param type The commitment type to look up
2126
+ * @returns The commitment definition or null if not found
2127
+ *
2128
+ * @public exported from `@promptbook/core`
2129
+ */
2130
+ function getCommitmentDefinition(type) {
2131
+ return COMMITMENT_REGISTRY.find((commitmentDefinition) => commitmentDefinition.type === type) || null;
2132
+ }
2133
+ /**
2134
+ * Gets all available commitment definitions
2135
+ * @returns Array of all commitment definitions
2136
+ *
2137
+ * @public exported from `@promptbook/core`
2138
+ */
2139
+ function getAllCommitmentDefinitions() {
2140
+ return $deepFreeze([...COMMITMENT_REGISTRY]);
2141
+ }
2142
+ /**
2143
+ * Gets all available commitment types
2144
+ * @returns Array of all commitment types
2145
+ *
2146
+ * @public exported from `@promptbook/core`
2147
+ */
2148
+ function getAllCommitmentTypes() {
2149
+ return $deepFreeze(COMMITMENT_REGISTRY.map((commitmentDefinition) => commitmentDefinition.type));
2150
+ }
2151
+ /**
2152
+ * Checks if a commitment type is supported
2153
+ * @param type The commitment type to check
2154
+ * @returns True if the commitment type is supported
2155
+ *
2156
+ * @public exported from `@promptbook/core`
2157
+ */
2158
+ function isCommitmentSupported(type) {
2159
+ return COMMITMENT_REGISTRY.some((commitmentDefinition) => commitmentDefinition.type === type);
2160
+ }
2161
+ /**
2162
+ * TODO: !!!! Maybe create through standardized $register
2163
+ * Note: [💞] Ignore a discrepancy between file name and entity name
2164
+ */
2165
+
2166
+ /**
2167
+ * Parses agent source using the new commitment system with multiline support
2168
+ * This function replaces the hardcoded commitment parsing in the original parseAgentSource
2169
+ *
2170
+ * @private
2171
+ */
2172
+ function parseAgentSourceWithCommitments(agentSource) {
2173
+ var _a, _b, _c;
2174
+ if (!agentSource || !agentSource.trim()) {
2175
+ return {
2176
+ agentName: null,
2177
+ commitments: [],
2178
+ nonCommitmentLines: [],
2179
+ };
2180
+ }
2181
+ const lines = agentSource.split('\n');
2182
+ const agentName = (((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.trim()) || null);
2183
+ const commitments = [];
2184
+ const nonCommitmentLines = [];
2185
+ // Always add the first line (agent name) to non-commitment lines
2186
+ if (lines[0] !== undefined) {
2187
+ nonCommitmentLines.push(lines[0]);
2188
+ }
2189
+ // Parse commitments with multiline support
2190
+ let currentCommitment = null;
2191
+ // Process lines starting from the second line (skip agent name)
2192
+ for (let i = 1; i < lines.length; i++) {
2193
+ const line = lines[i];
2194
+ if (line === undefined) {
2195
+ continue;
2196
+ }
2197
+ // Check if this line starts a new commitment
2198
+ let foundNewCommitment = false;
2199
+ for (const definition of COMMITMENT_REGISTRY) {
2200
+ const typeRegex = definition.createTypeRegex();
2201
+ const match = typeRegex.exec(line.trim());
2202
+ if (match && ((_b = match.groups) === null || _b === void 0 ? void 0 : _b.type)) {
2203
+ // Save the previous commitment if it exists
2204
+ if (currentCommitment) {
2205
+ const fullContent = currentCommitment.contentLines.join('\n');
2206
+ commitments.push({
2207
+ type: currentCommitment.type,
2208
+ content: spaceTrim.spaceTrim(fullContent),
2209
+ originalLine: currentCommitment.originalStartLine,
2210
+ lineNumber: currentCommitment.startLineNumber,
2211
+ });
2212
+ }
2213
+ // Extract the initial content from the commitment line
2214
+ const fullRegex = definition.createRegex();
2215
+ const fullMatch = fullRegex.exec(line.trim());
2216
+ const initialContent = ((_c = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _c === void 0 ? void 0 : _c.contents) || '';
2217
+ // Start a new commitment
2218
+ currentCommitment = {
2219
+ type: definition.type,
2220
+ startLineNumber: i + 1,
2221
+ originalStartLine: line,
2222
+ contentLines: initialContent ? [initialContent] : [],
2223
+ };
2224
+ foundNewCommitment = true;
2225
+ break;
2226
+ }
2227
+ }
2228
+ if (!foundNewCommitment) {
2229
+ if (currentCommitment) {
2230
+ // This line belongs to the current commitment
2231
+ currentCommitment.contentLines.push(line);
2232
+ }
2233
+ else {
2234
+ // This line is not part of any commitment
2235
+ nonCommitmentLines.push(line);
2236
+ }
2237
+ }
2238
+ }
2239
+ // Don't forget to save the last commitment if it exists
2240
+ if (currentCommitment) {
2241
+ const fullContent = currentCommitment.contentLines.join('\n');
2242
+ commitments.push({
2243
+ type: currentCommitment.type,
2244
+ content: spaceTrim.spaceTrim(fullContent),
2245
+ originalLine: currentCommitment.originalStartLine,
2246
+ lineNumber: currentCommitment.startLineNumber,
2247
+ });
2248
+ }
2249
+ return {
2250
+ agentName,
2251
+ commitments,
2252
+ nonCommitmentLines,
2253
+ };
2254
+ }
2255
+ /**
2256
+ * Extracts basic information from agent source using the new commitment system
2257
+ * This maintains compatibility with the original parseAgentSource interface
2258
+ *
2259
+ * @private
2260
+ */
2261
+ function parseAgentSourceBasicInfo(agentSource) {
2262
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
2263
+ // Find PERSONA and META IMAGE commitments
2264
+ let personaDescription = null;
2265
+ let profileImageUrl;
2266
+ for (const commitment of parseResult.commitments) {
2267
+ if (commitment.type === 'PERSONA' && !personaDescription) {
2268
+ personaDescription = commitment.content;
2269
+ }
2270
+ else if (commitment.type === 'META IMAGE' && !profileImageUrl) {
2271
+ profileImageUrl = commitment.content;
2272
+ }
2273
+ }
2274
+ // Generate gravatar fallback if no profile image specified
2275
+ if (!profileImageUrl) {
2276
+ profileImageUrl = generateGravatarUrl(parseResult.agentName);
2277
+ }
2278
+ return {
2279
+ agentName: parseResult.agentName,
2280
+ personaDescription,
2281
+ profileImageUrl,
2282
+ };
2283
+ }
2284
+
2285
+ /**
2286
+ * Parses agent source string into its components
2287
+ */
2288
+ // Cache for parsed agent sources to prevent repeated parsing
2289
+ const parsedAgentSourceCache = new Map();
2290
+ /**
2291
+ * Parses basic information from agent source
2292
+ *
2293
+ * There are 2 similar functions:
2294
+ * - `parseAgentSource` which is a lightweight parser for agent source, it parses basic information and its purpose is to be quick and synchronous. The commitments there are hardcoded.
2295
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
2296
+ *
2297
+ * @public exported from `@promptbook/core`
2298
+ */
2299
+ function parseAgentSource(agentSource) {
2300
+ // Check if we already parsed this agent source
2301
+ if (parsedAgentSourceCache.has(agentSource)) {
2302
+ return parsedAgentSourceCache.get(agentSource);
2303
+ }
2304
+ // Use the new commitment-based parsing system
2305
+ const result = parseAgentSourceBasicInfo(agentSource);
2306
+ // Cache the result
2307
+ parsedAgentSourceCache.set(agentSource, result);
2308
+ return result;
2309
+ }
2310
+
2311
+ /**
2312
+ * Type guard to check if a string is a valid agent source
2313
+ *
2314
+ * @public exported from `@promptbook/core`
2315
+ */
2316
+ function isValidBook(value) {
2317
+ // Basic validation - agent source should have at least a name (first line)
2318
+ return typeof value === 'string' /* && value.trim().length > 0 */;
2319
+ }
2320
+ /**
2321
+ * Validates and converts a string to agent source branded type
2322
+ * This function should be used when you have a string that you know represents agent source
2323
+ * but need to convert it to the branded type for type safety
2324
+ *
2325
+ * @public exported from `@promptbook/core`
2326
+ */
2327
+ function validateBook(source) {
2328
+ if (!isValidBook(source)) {
2329
+ throw new Error('Invalid agent source: must be a string');
2330
+ }
2331
+ return source;
2332
+ }
2333
+ /**
2334
+ * Default book
2335
+ *
2336
+ * @public exported from `@promptbook/core`
2337
+ */
2338
+ const DEFAULT_BOOK = validateBook(spaceTrim__default["default"](`
2339
+ AI Avatar
2340
+
2341
+ PERSONA A friendly AI assistant that helps you with your tasks
2342
+ `));
2343
+
2344
+ /**
2345
+ * Creates an empty/basic agent model requirements object
2346
+ * This serves as the starting point for the reduce-like pattern
2347
+ * where each commitment applies its changes to build the final requirements
2348
+ *
2349
+ * @public exported from `@promptbook/core`
2350
+ */
2351
+ function createEmptyAgentModelRequirements() {
2352
+ return {
2353
+ systemMessage: '',
2354
+ modelName: '!!!!DEFAULT_MODEL_ID',
2355
+ temperature: 0.7,
2356
+ topP: 0.9,
2357
+ topK: 50,
2358
+ };
2359
+ }
2360
+ /**
2361
+ * Creates a basic agent model requirements with just the agent name
2362
+ * This is used when we have an agent name but no commitments
2363
+ *
2364
+ * @public exported from `@promptbook/core`
2365
+ */
2366
+ function createBasicAgentModelRequirements(agentName) {
2367
+ const empty = createEmptyAgentModelRequirements();
2368
+ return {
2369
+ ...empty,
2370
+ systemMessage: `You are ${agentName || 'AI Agent'}`,
2371
+ };
2372
+ }
2373
+ /**
2374
+ * TODO: !!!! Deduplicate model requirements
2375
+ */
2376
+
2377
+ /**
2378
+ * Removes comment lines (lines starting with #) from a system message
2379
+ * This is used to clean up the final system message before sending it to the AI model
2380
+ * while preserving the original content with comments in metadata
2381
+ *
2382
+ * @param systemMessage The system message that may contain comment lines
2383
+ * @returns The system message with comment lines removed
2384
+ *
2385
+ * @private - TODO: [🧠] Maybe should be public?
2386
+ */
2387
+ function removeCommentsFromSystemMessage(systemMessage) {
2388
+ if (!systemMessage) {
2389
+ return systemMessage;
2390
+ }
2391
+ const lines = systemMessage.split('\n');
2392
+ const filteredLines = lines.filter((line) => {
2393
+ const trimmedLine = line.trim();
2394
+ // Remove lines that start with # (comments)
2395
+ return !trimmedLine.startsWith('#');
2396
+ });
2397
+ return filteredLines.join('\n').trim();
2398
+ }
2399
+
2400
+ /**
2401
+ * Creates agent model requirements using the new commitment system
2402
+ * This function uses a reduce-like pattern where each commitment applies its changes
2403
+ * to build the final requirements starting from a basic empty model
2404
+ *
2405
+ * @private
2406
+ */
2407
+ async function createAgentModelRequirementsWithCommitments(agentSource, modelName) {
2408
+ // Parse the agent source to extract commitments
2409
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
2410
+ // Start with basic agent model requirements
2411
+ let requirements = createBasicAgentModelRequirements(parseResult.agentName);
2412
+ // Store the agent name in metadata so commitments can access it
2413
+ requirements = {
2414
+ ...requirements,
2415
+ metadata: {
2416
+ ...requirements.metadata,
2417
+ agentName: parseResult.agentName,
2418
+ },
2419
+ };
2420
+ // Override model name if provided
2421
+ if (modelName) {
2422
+ requirements = {
2423
+ ...requirements,
2424
+ modelName,
2425
+ };
2426
+ }
2427
+ // Apply each commitment in order using reduce-like pattern
2428
+ for (const commitment of parseResult.commitments) {
2429
+ const definition = getCommitmentDefinition(commitment.type);
2430
+ if (definition) {
2431
+ try {
2432
+ requirements = definition.applyToAgentModelRequirements(requirements, commitment.content);
2433
+ }
2434
+ catch (error) {
2435
+ console.warn(`Failed to apply commitment ${commitment.type}:`, error);
2436
+ // Continue with other commitments even if one fails
2437
+ }
2438
+ }
2439
+ }
2440
+ // Handle MCP servers (extract from original agent source)
2441
+ const mcpServers = extractMcpServers(agentSource);
2442
+ if (mcpServers.length > 0) {
2443
+ requirements = {
2444
+ ...requirements,
2445
+ mcpServers,
2446
+ };
2447
+ }
2448
+ // Add non-commitment lines to system message if they exist
2449
+ const nonCommitmentContent = parseResult.nonCommitmentLines
2450
+ .filter((line, index) => index > 0 || !parseResult.agentName) // Skip first line if it's the agent name
2451
+ .filter((line) => line.trim()) // Remove empty lines
2452
+ .join('\n')
2453
+ .trim();
2454
+ if (nonCommitmentContent) {
2455
+ requirements = {
2456
+ ...requirements,
2457
+ systemMessage: requirements.systemMessage + '\n\n' + nonCommitmentContent,
2458
+ };
2459
+ }
2460
+ // Remove comment lines (lines starting with #) from the final system message
2461
+ // while preserving the original content with comments in metadata
2462
+ const cleanedSystemMessage = removeCommentsFromSystemMessage(requirements.systemMessage);
2463
+ return {
2464
+ ...requirements,
2465
+ systemMessage: cleanedSystemMessage,
2466
+ };
2467
+ }
2468
+ /**
2469
+ * Cache for expensive createAgentModelRequirementsWithCommitments calls
2470
+ * @private
2471
+ */
2472
+ const modelRequirementsCache = new Map();
2473
+ /**
2474
+ * @private - TODO: Maybe should be public
2475
+ */
2476
+ const CACHE_SIZE_LIMIT = 100;
2477
+ /**
2478
+ * Cached version of createAgentModelRequirementsWithCommitments
2479
+ * This maintains the same caching behavior as the original function
2480
+ *
2481
+ * @private
2482
+ */
2483
+ async function createAgentModelRequirementsWithCommitmentsCached(agentSource, modelName) {
2484
+ // Create cache key
2485
+ const cacheKey = `${agentSource}|${modelName || 'default'}`;
2486
+ // Check cache first
2487
+ if (modelRequirementsCache.has(cacheKey)) {
2488
+ return modelRequirementsCache.get(cacheKey);
2489
+ }
2490
+ // Limit cache size to prevent memory leaks
2491
+ if (modelRequirementsCache.size >= CACHE_SIZE_LIMIT) {
2492
+ const firstKey = modelRequirementsCache.keys().next().value;
2493
+ if (firstKey) {
2494
+ modelRequirementsCache.delete(firstKey);
2495
+ }
2496
+ }
2497
+ // Create requirements
2498
+ const requirements = await createAgentModelRequirementsWithCommitments(agentSource, modelName);
2499
+ // Cache the result
2500
+ modelRequirementsCache.set(cacheKey, requirements);
2501
+ return requirements;
2502
+ }
2503
+
2504
+ // TODO: Remove or use:
2505
+ //const CACHE_SIZE_LIMIT = 100; // Prevent memory leaks by limiting cache size
2506
+ /**
2507
+ * Creates model requirements for an agent based on its source
2508
+ * Results are cached to improve performance for repeated calls with the same agentSource and modelName
2509
+ *
2510
+ * There are 2 similar functions:
2511
+ * - `parseAgentSource` which is a lightweight parser for agent source, it parses basic information and its purpose is to be quick and synchronous. The commitments there are hardcoded.
2512
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
2513
+ *
2514
+ * @public exported from `@promptbook/core`
2515
+ */
2516
+ async function createAgentModelRequirements(agentSource, modelName = '!!!!DEFAULT_MODEL_ID') {
2517
+ // Use the new commitment-based system
2518
+ return createAgentModelRequirementsWithCommitmentsCached(agentSource, modelName);
2519
+ }
2520
+ /**
2521
+ * Extracts MCP servers from agent source
2522
+ *
2523
+ * @param agentSource The agent source string that may contain MCP lines
2524
+ * @returns Array of MCP server identifiers
2525
+ *
2526
+ * @private TODO: [🧠] Maybe should be public
2527
+ */
2528
+ function extractMcpServers(agentSource) {
2529
+ if (!agentSource) {
2530
+ return [];
2531
+ }
2532
+ const lines = agentSource.split('\n');
2533
+ const mcpRegex = /^\s*MCP\s+(.+)$/i;
2534
+ const mcpServers = [];
2535
+ // Look for MCP lines
2536
+ for (const line of lines) {
2537
+ const match = line.match(mcpRegex);
2538
+ if (match && match[1]) {
2539
+ mcpServers.push(match[1].trim());
2540
+ }
2541
+ }
2542
+ return mcpServers;
2543
+ }
2544
+
2545
+ /**
2546
+ * Converts PipelineCollection to serialized JSON
2547
+ *
2548
+ * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
2549
+ *
2550
+ * @public exported from `@promptbook/core`
2551
+ */
2552
+ async function collectionToJson(collection) {
2553
+ const pipelineUrls = await collection.listPipelines();
2554
+ const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
2555
+ return promptbooks;
2556
+ }
2557
+ /**
2558
+ * TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
2559
+ */
2560
+
2561
+ /**
2562
+ * Checks if value is valid email
557
2563
  *
558
- * @public exported from `@promptbook/core`
2564
+ * @public exported from `@promptbook/utils`
559
2565
  */
560
- class UnexpectedError extends Error {
561
- constructor(message) {
562
- super(spaceTrim.spaceTrim((block) => `
563
- ${block(message)}
564
-
565
- Note: This error should not happen.
566
- It's probably a bug in the pipeline collection
567
-
568
- Please report issue:
569
- ${block(getErrorReportUrl(new Error(message)).href)}
570
-
571
- Or contact us on ${ADMIN_EMAIL}
572
-
573
- `));
574
- this.name = 'UnexpectedError';
575
- Object.setPrototypeOf(this, UnexpectedError.prototype);
2566
+ function isValidEmail(email) {
2567
+ if (typeof email !== 'string') {
2568
+ return false;
2569
+ }
2570
+ if (email.split('\n').length > 1) {
2571
+ return false;
576
2572
  }
2573
+ return /^.+@.+\..+$/.test(email);
577
2574
  }
578
2575
 
579
2576
  /**
580
- * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
2577
+ * Tests if given string is valid URL.
581
2578
  *
582
- * @public exported from `@promptbook/core`
2579
+ * Note: This does not check if the file exists only if the path is valid
2580
+ * @public exported from `@promptbook/utils`
583
2581
  */
584
- class WrappedError extends Error {
585
- constructor(whatWasThrown) {
586
- const tag = `[🤮]`;
587
- console.error(tag, whatWasThrown);
588
- super(spaceTrim.spaceTrim(`
589
- Non-Error object was thrown
590
-
591
- Note: Look for ${tag} in the console for more details
592
- Please report issue on ${ADMIN_EMAIL}
593
- `));
594
- this.name = 'WrappedError';
595
- Object.setPrototypeOf(this, WrappedError.prototype);
2582
+ function isValidFilePath(filename) {
2583
+ if (typeof filename !== 'string') {
2584
+ return false;
2585
+ }
2586
+ if (filename.split('\n').length > 1) {
2587
+ return false;
2588
+ }
2589
+ if (filename.split(' ').length >
2590
+ 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
2591
+ return false;
2592
+ }
2593
+ const filenameSlashes = filename.split('\\').join('/');
2594
+ // Absolute Unix path: /hello.txt
2595
+ if (/^(\/)/i.test(filenameSlashes)) {
2596
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
2597
+ return true;
2598
+ }
2599
+ // Absolute Windows path: /hello.txt
2600
+ if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
2601
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
2602
+ return true;
2603
+ }
2604
+ // Relative path: ./hello.txt
2605
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
2606
+ // console.log(filename, 'Relative path: ./hello.txt');
2607
+ return true;
2608
+ }
2609
+ // Allow paths like foo/hello
2610
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
2611
+ // console.log(filename, 'Allow paths like foo/hello');
2612
+ return true;
2613
+ }
2614
+ // Allow paths like hello.book
2615
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
2616
+ // console.log(filename, 'Allow paths like hello.book');
2617
+ return true;
596
2618
  }
2619
+ return false;
597
2620
  }
2621
+ /**
2622
+ * TODO: [🍏] Implement for MacOs
2623
+ */
598
2624
 
599
2625
  /**
600
- * Helper used in catch blocks to assert that the error is an instance of `Error`
2626
+ * Tests if given string is valid URL.
601
2627
  *
602
- * @param whatWasThrown Any object that was thrown
603
- * @returns Nothing if the error is an instance of `Error`
604
- * @throws `WrappedError` or `UnexpectedError` if the error is not standard
2628
+ * Note: Dataurl are considered perfectly valid.
2629
+ * Note: There are two similar functions:
2630
+ * - `isValidUrl` which tests any URL
2631
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
605
2632
  *
606
- * @private within the repository
2633
+ * @public exported from `@promptbook/utils`
607
2634
  */
608
- function assertsError(whatWasThrown) {
609
- // Case 1: Handle error which was rethrown as `WrappedError`
610
- if (whatWasThrown instanceof WrappedError) {
611
- const wrappedError = whatWasThrown;
612
- throw wrappedError;
2635
+ function isValidUrl(url) {
2636
+ if (typeof url !== 'string') {
2637
+ return false;
613
2638
  }
614
- // Case 2: Handle unexpected errors
615
- if (whatWasThrown instanceof UnexpectedError) {
616
- const unexpectedError = whatWasThrown;
617
- throw unexpectedError;
2639
+ try {
2640
+ if (url.startsWith('blob:')) {
2641
+ url = url.replace(/^blob:/, '');
2642
+ }
2643
+ const urlObject = new URL(url /* because fail is handled */);
2644
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
2645
+ return false;
2646
+ }
2647
+ return true;
618
2648
  }
619
- // Case 3: Handle standard errors - keep them up to consumer
620
- if (whatWasThrown instanceof Error) {
621
- return;
2649
+ catch (error) {
2650
+ return false;
2651
+ }
2652
+ }
2653
+
2654
+ /**
2655
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
2656
+ *
2657
+ * @public exported from `@promptbook/core`
2658
+ */
2659
+ class ParseError extends Error {
2660
+ constructor(message) {
2661
+ super(message);
2662
+ this.name = 'ParseError';
2663
+ Object.setPrototypeOf(this, ParseError.prototype);
622
2664
  }
623
- // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
624
- throw new WrappedError(whatWasThrown);
625
2665
  }
2666
+ /**
2667
+ * TODO: Maybe split `ParseError` and `ApplyError`
2668
+ */
626
2669
 
627
2670
  /**
628
2671
  * Function isValidJsonString will tell you if the string is valid JSON or not
@@ -873,33 +2916,6 @@
873
2916
  return orderedValue;
874
2917
  }
875
2918
 
876
- /**
877
- * Freezes the given object and all its nested objects recursively
878
- *
879
- * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
880
- * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
881
- *
882
- * @returns The same object as the input, but deeply frozen
883
- * @public exported from `@promptbook/utils`
884
- */
885
- function $deepFreeze(objectValue) {
886
- if (Array.isArray(objectValue)) {
887
- return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
888
- }
889
- const propertyNames = Object.getOwnPropertyNames(objectValue);
890
- for (const propertyName of propertyNames) {
891
- const value = objectValue[propertyName];
892
- if (value && typeof value === 'object') {
893
- $deepFreeze(value);
894
- }
895
- }
896
- Object.freeze(objectValue);
897
- return objectValue;
898
- }
899
- /**
900
- * TODO: [🧠] Is there a way how to meaningfully test this utility
901
- */
902
-
903
2919
  /**
904
2920
  * Checks if the value is [🚉] serializable as JSON
905
2921
  * If not, throws an UnexpectedError with a rich error message and tracking
@@ -2121,19 +4137,6 @@
2121
4137
  * TODO: [🧠][🌂] Add id to all errors
2122
4138
  */
2123
4139
 
2124
- /**
2125
- * Error thrown when a fetch request fails
2126
- *
2127
- * @public exported from `@promptbook/core`
2128
- */
2129
- class PromptbookFetchError extends Error {
2130
- constructor(message) {
2131
- super(message);
2132
- this.name = 'PromptbookFetchError';
2133
- Object.setPrototypeOf(this, PromptbookFetchError.prototype);
2134
- }
2135
- }
2136
-
2137
4140
  /**
2138
4141
  * Index of all custom errors
2139
4142
  *
@@ -2364,7 +4367,7 @@
2364
4367
  * @private internal helper function
2365
4368
  */
2366
4369
  function createTask(options) {
2367
- const { taskType, taskProcessCallback } = options;
4370
+ const { taskType, taskProcessCallback, tldrProvider } = options;
2368
4371
  let { title } = options;
2369
4372
  // TODO: [🐙] DRY
2370
4373
  const taskId = `${taskType.toLowerCase().substring(0, 4)}-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
@@ -2437,6 +4440,78 @@
2437
4440
  return status;
2438
4441
  // <- Note: [1] --||--
2439
4442
  },
4443
+ get tldr() {
4444
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
4445
+ // Use custom tldr provider if available
4446
+ if (tldrProvider) {
4447
+ return tldrProvider(createdAt, status, currentValue, errors, warnings);
4448
+ }
4449
+ // Fallback to default implementation
4450
+ const cv = currentValue;
4451
+ // If explicit percent is provided, use it
4452
+ let percentRaw = (_f = (_d = (_b = (_a = cv === null || cv === void 0 ? void 0 : cv.tldr) === null || _a === void 0 ? void 0 : _a.percent) !== null && _b !== void 0 ? _b : (_c = cv === null || cv === void 0 ? void 0 : cv.usage) === null || _c === void 0 ? void 0 : _c.percent) !== null && _d !== void 0 ? _d : (_e = cv === null || cv === void 0 ? void 0 : cv.progress) === null || _e === void 0 ? void 0 : _e.percent) !== null && _f !== void 0 ? _f : cv === null || cv === void 0 ? void 0 : cv.percent;
4453
+ // Simulate progress if not provided
4454
+ if (typeof percentRaw !== 'number') {
4455
+ // Simulate progress: evenly split across subtasks, based on elapsed time
4456
+ const now = new Date();
4457
+ const elapsedMs = now.getTime() - createdAt.getTime();
4458
+ const totalMs = DEFAULT_TASK_SIMULATED_DURATION_MS;
4459
+ // If subtasks are defined, split progress evenly
4460
+ const subtaskCount = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) ? cv.subtasks.length : 1;
4461
+ const completedSubtasks = Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks)
4462
+ ? cv.subtasks.filter((s) => s.done || s.completed).length
4463
+ : 0;
4464
+ // Progress from completed subtasks
4465
+ const subtaskProgress = subtaskCount > 0 ? completedSubtasks / subtaskCount : 0;
4466
+ // Progress from elapsed time for current subtask
4467
+ const timeProgress = Math.min(elapsedMs / totalMs, 1);
4468
+ // Combine: completed subtasks + time progress for current subtask
4469
+ percentRaw = Math.min(subtaskProgress + (1 / subtaskCount) * timeProgress, 1);
4470
+ if (status === 'FINISHED')
4471
+ percentRaw = 1;
4472
+ if (status === 'ERROR')
4473
+ percentRaw = 0;
4474
+ }
4475
+ // Clamp to [0,1]
4476
+ let percent = Number(percentRaw) || 0;
4477
+ if (percent < 0)
4478
+ percent = 0;
4479
+ if (percent > 1)
4480
+ percent = 1;
4481
+ // Build a short message: prefer explicit tldr.message, then common summary/message fields, then errors/warnings, then status
4482
+ const messageFromResult = (_k = (_j = (_h = (_g = cv === null || cv === void 0 ? void 0 : cv.tldr) === null || _g === void 0 ? void 0 : _g.message) !== null && _h !== void 0 ? _h : cv === null || cv === void 0 ? void 0 : cv.message) !== null && _j !== void 0 ? _j : cv === null || cv === void 0 ? void 0 : cv.summary) !== null && _k !== void 0 ? _k : cv === null || cv === void 0 ? void 0 : cv.statusMessage;
4483
+ let message = messageFromResult;
4484
+ if (!message) {
4485
+ // If subtasks, show current subtask
4486
+ if (Array.isArray(cv === null || cv === void 0 ? void 0 : cv.subtasks) && cv.subtasks.length > 0) {
4487
+ const current = cv.subtasks.find((s) => !s.done && !s.completed);
4488
+ if (current && current.title) {
4489
+ message = `Working on ${current.title}`;
4490
+ }
4491
+ }
4492
+ if (!message) {
4493
+ if (errors.length) {
4494
+ message = errors[errors.length - 1].message || 'Error';
4495
+ }
4496
+ else if (warnings.length) {
4497
+ message = warnings[warnings.length - 1].message || 'Warning';
4498
+ }
4499
+ else if (status === 'FINISHED') {
4500
+ message = 'Finished';
4501
+ }
4502
+ else if (status === 'ERROR') {
4503
+ message = 'Error';
4504
+ }
4505
+ else {
4506
+ message = 'Running';
4507
+ }
4508
+ }
4509
+ }
4510
+ return {
4511
+ percent: percent,
4512
+ message,
4513
+ };
4514
+ },
2440
4515
  get createdAt() {
2441
4516
  return createdAt;
2442
4517
  // <- Note: [1] --||--
@@ -4112,7 +6187,7 @@
4112
6187
  */
4113
6188
  async function executeAttempts(options) {
4114
6189
  const { jokerParameterNames, priority, maxAttempts, // <- Note: [💂]
4115
- preparedContent, parameters, task, preparedPipeline, tools, $executionReport, pipelineIdentification, maxExecutionAttempts, } = options;
6190
+ preparedContent, parameters, task, preparedPipeline, tools, $executionReport, pipelineIdentification, maxExecutionAttempts, onProgress, } = options;
4116
6191
  const $ongoingTaskResult = {
4117
6192
  $result: null,
4118
6193
  $resultString: null,
@@ -4356,6 +6431,10 @@
4356
6431
  result: $ongoingTaskResult.$resultString,
4357
6432
  error: error,
4358
6433
  });
6434
+ // Report failed attempt
6435
+ onProgress({
6436
+ errors: [error],
6437
+ });
4359
6438
  }
4360
6439
  finally {
4361
6440
  if (!isJokerAttempt &&
@@ -5237,6 +7316,71 @@
5237
7316
  updateOngoingResult(newOngoingResult);
5238
7317
  });
5239
7318
  },
7319
+ tldrProvider(createdAt, status, currentValue, errors) {
7320
+ var _a;
7321
+ // Better progress estimation based on pipeline structure
7322
+ const cv = currentValue;
7323
+ // Handle finished/error states
7324
+ if (status === 'FINISHED') {
7325
+ return {
7326
+ percent: 1,
7327
+ message: 'Finished',
7328
+ };
7329
+ }
7330
+ if (status === 'ERROR') {
7331
+ const errorMessage = errors.length > 0 ? errors[errors.length - 1].message : 'Error';
7332
+ return {
7333
+ percent: 0,
7334
+ message: errorMessage,
7335
+ };
7336
+ }
7337
+ // Calculate progress based on pipeline tasks
7338
+ const totalTasks = pipeline.tasks.length;
7339
+ let completedTasks = 0;
7340
+ let currentTaskName = '';
7341
+ // Check execution report for completed tasks
7342
+ if ((_a = cv === null || cv === void 0 ? void 0 : cv.executionReport) === null || _a === void 0 ? void 0 : _a.promptExecutions) {
7343
+ const executedTaskTitles = new Set(cv.executionReport.promptExecutions.map((execution) => execution.prompt.title));
7344
+ // Count completed tasks by matching titles
7345
+ const completedTasksByTitle = pipeline.tasks.filter(task => executedTaskTitles.has(task.title));
7346
+ completedTasks = completedTasksByTitle.length;
7347
+ // Find current task being executed (first task not yet completed)
7348
+ const remainingTasks = pipeline.tasks.filter(task => !executedTaskTitles.has(task.title));
7349
+ if (remainingTasks.length > 0) {
7350
+ currentTaskName = remainingTasks[0].name;
7351
+ }
7352
+ }
7353
+ // Calculate progress percentage
7354
+ let percent = totalTasks > 0 ? completedTasks / totalTasks : 0;
7355
+ // Add time-based progress for current task (assuming 5 minutes total)
7356
+ if (completedTasks < totalTasks) {
7357
+ const elapsedMs = new Date().getTime() - createdAt.getTime();
7358
+ const totalMs = 5 * 60 * 1000; // 5 minutes
7359
+ const timeProgress = Math.min(elapsedMs / totalMs, 1);
7360
+ // Add partial progress for current task
7361
+ percent += (1 / totalTasks) * timeProgress;
7362
+ }
7363
+ // Clamp to [0,1]
7364
+ percent = Math.min(Math.max(percent, 0), 1);
7365
+ // Generate message
7366
+ let message = '';
7367
+ if (currentTaskName) {
7368
+ // Find the task to get its title
7369
+ const currentTask = pipeline.tasks.find(task => task.name === currentTaskName);
7370
+ const taskTitle = (currentTask === null || currentTask === void 0 ? void 0 : currentTask.title) || currentTaskName;
7371
+ message = `Working on task ${taskTitle}`;
7372
+ }
7373
+ else if (completedTasks === 0) {
7374
+ message = 'Starting pipeline execution';
7375
+ }
7376
+ else {
7377
+ message = `Processing pipeline (${completedTasks}/${totalTasks} tasks completed)`;
7378
+ }
7379
+ return {
7380
+ percent,
7381
+ message,
7382
+ };
7383
+ },
5240
7384
  });
5241
7385
  // <- TODO: Make types such as there is no need to do `as` for `createTask`
5242
7386
  return pipelineExecutor;
@@ -5835,37 +7979,6 @@
5835
7979
  return value;
5836
7980
  }
5837
7981
 
5838
- /**
5839
- * The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
5840
- *
5841
- * @public exported from `@promptbook/core`
5842
- */
5843
- const promptbookFetch = async (urlOrRequest, init) => {
5844
- try {
5845
- return await fetch(urlOrRequest, init);
5846
- }
5847
- catch (error) {
5848
- assertsError(error);
5849
- let url;
5850
- if (typeof urlOrRequest === 'string') {
5851
- url = urlOrRequest;
5852
- }
5853
- else if (urlOrRequest instanceof Request) {
5854
- url = urlOrRequest.url;
5855
- }
5856
- throw new PromptbookFetchError(spaceTrim__default["default"]((block) => `
5857
- Can not fetch "${url}"
5858
-
5859
- Fetch error:
5860
- ${block(error.message)}
5861
-
5862
- `));
5863
- }
5864
- };
5865
- /**
5866
- * TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
5867
- */
5868
-
5869
7982
  /**
5870
7983
  * Factory function that creates a handler for processing knowledge sources.
5871
7984
  * Provides standardized processing of different types of knowledge sources
@@ -11649,18 +13762,28 @@
11649
13762
  * @public exported from `@promptbook/core`
11650
13763
  */
11651
13764
  function book(strings, ...values) {
11652
- const pipelineString = prompt(strings, ...values);
11653
- if (!isValidPipelineString(pipelineString)) {
13765
+ const bookString = prompt(strings, ...values);
13766
+ if (!isValidPipelineString(bookString)) {
11654
13767
  // TODO: Make the CustomError for this
11655
13768
  throw new Error(spaceTrim__default["default"](`
11656
13769
  The string is not a valid pipeline string
11657
13770
 
11658
13771
  book\`
11659
- ${pipelineString}
13772
+ ${bookString}
11660
13773
  \`
11661
13774
  `));
11662
13775
  }
11663
- return pipelineString;
13776
+ if (!isValidBook(bookString)) {
13777
+ // TODO: Make the CustomError for this
13778
+ throw new Error(spaceTrim__default["default"](`
13779
+ The string is not a valid book
13780
+
13781
+ book\`
13782
+ ${bookString}
13783
+ \`
13784
+ `));
13785
+ }
13786
+ return bookString;
11664
13787
  }
11665
13788
  /**
11666
13789
  * TODO: [🧠][🈴] Where is the best location for this file
@@ -12011,6 +14134,7 @@
12011
14134
  exports.CompletionFormfactorDefinition = CompletionFormfactorDefinition;
12012
14135
  exports.CsvFormatError = CsvFormatError;
12013
14136
  exports.CsvFormatParser = CsvFormatParser;
14137
+ exports.DEFAULT_BOOK = DEFAULT_BOOK;
12014
14138
  exports.DEFAULT_BOOKS_DIRNAME = DEFAULT_BOOKS_DIRNAME;
12015
14139
  exports.DEFAULT_BOOK_OUTPUT_PARAMETER_NAME = DEFAULT_BOOK_OUTPUT_PARAMETER_NAME;
12016
14140
  exports.DEFAULT_BOOK_TITLE = DEFAULT_BOOK_TITLE;
@@ -12030,6 +14154,7 @@
12030
14154
  exports.DEFAULT_PROMPT_TASK_TITLE = DEFAULT_PROMPT_TASK_TITLE;
12031
14155
  exports.DEFAULT_REMOTE_SERVER_URL = DEFAULT_REMOTE_SERVER_URL;
12032
14156
  exports.DEFAULT_SCRAPE_CACHE_DIRNAME = DEFAULT_SCRAPE_CACHE_DIRNAME;
14157
+ exports.DEFAULT_TASK_SIMULATED_DURATION_MS = DEFAULT_TASK_SIMULATED_DURATION_MS;
12033
14158
  exports.DEFAULT_TASK_TITLE = DEFAULT_TASK_TITLE;
12034
14159
  exports.EXPECTATION_UNITS = EXPECTATION_UNITS;
12035
14160
  exports.EnvironmentMismatchError = EnvironmentMismatchError;
@@ -12055,6 +14180,7 @@
12055
14180
  exports.NAME = NAME;
12056
14181
  exports.NonTaskSectionTypes = NonTaskSectionTypes;
12057
14182
  exports.NotFoundError = NotFoundError;
14183
+ exports.NotYetImplementedCommitmentDefinition = NotYetImplementedCommitmentDefinition;
12058
14184
  exports.NotYetImplementedError = NotYetImplementedError;
12059
14185
  exports.ORDER_OF_PIPELINE_JSON = ORDER_OF_PIPELINE_JSON;
12060
14186
  exports.PENDING_VALUE_PLACEHOLDER = PENDING_VALUE_PLACEHOLDER;
@@ -12103,9 +14229,12 @@
12103
14229
  exports.compilePipeline = compilePipeline;
12104
14230
  exports.computeCosineSimilarity = computeCosineSimilarity;
12105
14231
  exports.countUsage = countUsage;
14232
+ exports.createAgentModelRequirements = createAgentModelRequirements;
14233
+ exports.createBasicAgentModelRequirements = createBasicAgentModelRequirements;
12106
14234
  exports.createCollectionFromJson = createCollectionFromJson;
12107
14235
  exports.createCollectionFromPromise = createCollectionFromPromise;
12108
14236
  exports.createCollectionFromUrl = createCollectionFromUrl;
14237
+ exports.createEmptyAgentModelRequirements = createEmptyAgentModelRequirements;
12109
14238
  exports.createLlmToolsFromConfiguration = createLlmToolsFromConfiguration;
12110
14239
  exports.createPipelineExecutor = createPipelineExecutor;
12111
14240
  exports.createSubcollection = createSubcollection;
@@ -12113,17 +14242,23 @@
12113
14242
  exports.executionReportJsonToString = executionReportJsonToString;
12114
14243
  exports.extractParameterNamesFromTask = extractParameterNamesFromTask;
12115
14244
  exports.filterModels = filterModels;
14245
+ exports.getAllCommitmentDefinitions = getAllCommitmentDefinitions;
14246
+ exports.getAllCommitmentTypes = getAllCommitmentTypes;
14247
+ exports.getCommitmentDefinition = getCommitmentDefinition;
12116
14248
  exports.getPipelineInterface = getPipelineInterface;
12117
14249
  exports.identificationToPromptbookToken = identificationToPromptbookToken;
14250
+ exports.isCommitmentSupported = isCommitmentSupported;
12118
14251
  exports.isPassingExpectations = isPassingExpectations;
12119
14252
  exports.isPipelineImplementingInterface = isPipelineImplementingInterface;
12120
14253
  exports.isPipelineInterfacesEqual = isPipelineInterfacesEqual;
12121
14254
  exports.isPipelinePrepared = isPipelinePrepared;
14255
+ exports.isValidBook = isValidBook;
12122
14256
  exports.isValidPipelineString = isValidPipelineString;
12123
14257
  exports.joinLlmExecutionTools = joinLlmExecutionTools;
12124
14258
  exports.limitTotalUsage = limitTotalUsage;
12125
14259
  exports.makeKnowledgeSourceHandler = makeKnowledgeSourceHandler;
12126
14260
  exports.migratePipeline = migratePipeline;
14261
+ exports.parseAgentSource = parseAgentSource;
12127
14262
  exports.parsePipeline = parsePipeline;
12128
14263
  exports.pipelineJsonToString = pipelineJsonToString;
12129
14264
  exports.prepareKnowledgePieces = prepareKnowledgePieces;
@@ -12135,6 +14270,7 @@
12135
14270
  exports.unpreparePipeline = unpreparePipeline;
12136
14271
  exports.usageToHuman = usageToHuman;
12137
14272
  exports.usageToWorktime = usageToWorktime;
14273
+ exports.validateBook = validateBook;
12138
14274
  exports.validatePipeline = validatePipeline;
12139
14275
  exports.validatePipelineString = validatePipelineString;
12140
14276