@promptbook/core 0.100.0-14 → 0.100.0-16

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 (46) hide show
  1. package/esm/index.es.js +1649 -232
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/src/_packages/core.index.d.ts +14 -0
  4. package/esm/typings/src/_packages/types.index.d.ts +28 -0
  5. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.d.ts +30 -0
  6. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.test.d.ts +1 -0
  7. package/esm/typings/src/book-2.0/agent-source/string_agent_source.d.ts +42 -0
  8. package/esm/typings/src/book-2.0/commitments/ACTION/ACTION.d.ts +30 -0
  9. package/esm/typings/src/book-2.0/commitments/FORMAT/FORMAT.d.ts +31 -0
  10. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/FrontendRAGService.d.ts +48 -0
  11. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +43 -0
  12. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/RAGService.d.ts +54 -0
  13. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/BaseKnowledgeProcessor.d.ts +45 -0
  14. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/PdfProcessor.d.ts +31 -0
  15. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/ProcessorFactory.d.ts +23 -0
  16. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/TextProcessor.d.ts +18 -0
  17. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/types.d.ts +56 -0
  18. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/utils/ragHelper.d.ts +34 -0
  19. package/esm/typings/src/book-2.0/commitments/META_IMAGE/META_IMAGE.d.ts +36 -0
  20. package/esm/typings/src/book-2.0/commitments/META_LINK/META_LINK.d.ts +48 -0
  21. package/esm/typings/src/book-2.0/commitments/MODEL/MODEL.d.ts +31 -0
  22. package/esm/typings/src/book-2.0/commitments/NOTE/NOTE.d.ts +41 -0
  23. package/esm/typings/src/book-2.0/commitments/PERSONA/PERSONA.d.ts +38 -0
  24. package/esm/typings/src/book-2.0/commitments/RULE/RULE.d.ts +36 -0
  25. package/esm/typings/src/book-2.0/commitments/SAMPLE/SAMPLE.d.ts +36 -0
  26. package/esm/typings/src/book-2.0/commitments/STYLE/STYLE.d.ts +30 -0
  27. package/esm/typings/src/book-2.0/commitments/_base/BaseCommitmentDefinition.d.ts +43 -0
  28. package/esm/typings/src/book-2.0/commitments/_base/BookCommitment.d.ts +5 -0
  29. package/esm/typings/src/book-2.0/commitments/_base/CommitmentDefinition.d.ts +37 -0
  30. package/esm/typings/src/book-2.0/commitments/_base/NotYetImplementedCommitmentDefinition.d.ts +15 -0
  31. package/esm/typings/src/book-2.0/commitments/_base/createEmptyAgentModelRequirements.d.ts +19 -0
  32. package/esm/typings/src/book-2.0/commitments/_misc/AgentModelRequirements.d.ts +37 -0
  33. package/esm/typings/src/book-2.0/commitments/_misc/AgentSourceParseResult.d.ts +18 -0
  34. package/esm/typings/src/book-2.0/commitments/_misc/ParsedCommitment.d.ts +22 -0
  35. package/esm/typings/src/book-2.0/commitments/_misc/createAgentModelRequirements.d.ts +61 -0
  36. package/esm/typings/src/book-2.0/commitments/_misc/createAgentModelRequirementsWithCommitments.d.ts +35 -0
  37. package/esm/typings/src/book-2.0/commitments/_misc/createCommitmentRegex.d.ts +20 -0
  38. package/esm/typings/src/book-2.0/commitments/_misc/parseAgentSourceWithCommitments.d.ts +24 -0
  39. package/esm/typings/src/book-2.0/commitments/_misc/removeCommentsFromSystemMessage.d.ts +11 -0
  40. package/esm/typings/src/book-2.0/commitments/index.d.ts +54 -0
  41. package/esm/typings/src/book-2.0/utils/profileImageUtils.d.ts +39 -0
  42. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  43. package/esm/typings/src/version.d.ts +1 -1
  44. package/package.json +1 -1
  45. package/umd/index.umd.js +1655 -231
  46. package/umd/index.umd.js.map +1 -1
package/esm/index.es.js CHANGED
@@ -27,136 +27,231 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
27
27
  * @generated
28
28
  * @see https://github.com/webgptorg/promptbook
29
29
  */
30
- const PROMPTBOOK_ENGINE_VERSION = '0.100.0-14';
30
+ const PROMPTBOOK_ENGINE_VERSION = '0.100.0-16';
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
+ * Generates a regex pattern to match a specific commitment
54
69
  *
55
- * @public exported from `@promptbook/utils`
70
+ * Note: It always creates new Regex object
71
+ * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
72
+ *
73
+ * @private
56
74
  */
57
- function isValidEmail(email) {
58
- if (typeof email !== 'string') {
59
- return false;
60
- }
61
- if (email.split('\n').length > 1) {
62
- return false;
63
- }
64
- return /^.+@.+\..+$/.test(email);
75
+ function createCommitmentRegex(commitment) {
76
+ const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
77
+ const keywordPattern = escapedCommitment.split(/\s+/).join('\\s+');
78
+ const regex = new RegExp(`^\\s*(?<type>${keywordPattern})\\b\\s+(?<contents>.+)$`, 'gim');
79
+ return regex;
80
+ }
81
+ /**
82
+ * Generates a regex pattern to match a specific commitment type
83
+ *
84
+ * Note: It just matches the type part of the commitment
85
+ * Note: It always creates new Regex object
86
+ * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
87
+ *
88
+ * @private
89
+ */
90
+ function createCommitmentTypeRegex(commitment) {
91
+ const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
92
+ const keywordPattern = escapedCommitment.split(/\s+/).join('\\s+');
93
+ const regex = new RegExp(`^\\s*(?<type>${keywordPattern})\\b`, 'gim');
94
+ return regex;
65
95
  }
66
96
 
67
97
  /**
68
- * Tests if given string is valid URL.
98
+ * Base implementation of CommitmentDefinition that provides common functionality
99
+ * Most commitments can extend this class and only override the applyToAgentModelRequirements method
69
100
  *
70
- * Note: This does not check if the file exists only if the path is valid
71
- * @public exported from `@promptbook/utils`
101
+ * @private
72
102
  */
73
- function isValidFilePath(filename) {
74
- if (typeof filename !== 'string') {
75
- return false;
103
+ class BaseCommitmentDefinition {
104
+ constructor(type) {
105
+ this.type = type;
76
106
  }
77
- if (filename.split('\n').length > 1) {
78
- return false;
107
+ /**
108
+ * Creates a regex pattern to match this commitment in agent source
109
+ * Uses the existing createCommitmentRegex function as internal helper
110
+ */
111
+ createRegex() {
112
+ return createCommitmentRegex(this.type);
79
113
  }
80
- if (filename.split(' ').length >
81
- 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
82
- return false;
114
+ /**
115
+ * Creates a regex pattern to match just the commitment type
116
+ * Uses the existing createCommitmentTypeRegex function as internal helper
117
+ */
118
+ createTypeRegex() {
119
+ return createCommitmentTypeRegex(this.type);
83
120
  }
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;
121
+ /**
122
+ * Helper method to create a new requirements object with updated system message
123
+ * This is commonly used by many commitments
124
+ */
125
+ updateSystemMessage(requirements, messageUpdate) {
126
+ const newMessage = typeof messageUpdate === 'string' ? messageUpdate : messageUpdate(requirements.systemMessage);
127
+ return {
128
+ ...requirements,
129
+ systemMessage: newMessage,
130
+ };
89
131
  }
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;
132
+ /**
133
+ * Helper method to append content to the system message
134
+ */
135
+ appendToSystemMessage(requirements, content, separator = '\n\n') {
136
+ return this.updateSystemMessage(requirements, (currentMessage) => {
137
+ if (!currentMessage.trim()) {
138
+ return content;
139
+ }
140
+ return currentMessage + separator + content;
141
+ });
94
142
  }
95
- // Relative path: ./hello.txt
96
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
97
- // console.log(filename, 'Relative path: ./hello.txt');
98
- return true;
143
+ /**
144
+ * Helper method to add a comment section to the system message
145
+ * Comments are lines starting with # that will be removed from the final system message
146
+ * but can be useful for organizing and structuring the message during processing
147
+ */
148
+ addCommentSection(requirements, commentTitle, content, position = 'end') {
149
+ const commentSection = `# ${commentTitle.toUpperCase()}\n${content}`;
150
+ if (position === 'beginning') {
151
+ return this.updateSystemMessage(requirements, (currentMessage) => {
152
+ if (!currentMessage.trim()) {
153
+ return commentSection;
154
+ }
155
+ return commentSection + '\n\n' + currentMessage;
156
+ });
157
+ }
158
+ else {
159
+ return this.appendToSystemMessage(requirements, commentSection);
160
+ }
99
161
  }
100
- // Allow paths like foo/hello
101
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
102
- // console.log(filename, 'Allow paths like foo/hello');
103
- return true;
162
+ }
163
+
164
+ /**
165
+ * ACTION commitment definition
166
+ *
167
+ * The ACTION commitment defines specific actions or capabilities that the agent can perform.
168
+ * This helps define what the agent is capable of doing and how it should approach tasks.
169
+ *
170
+ * Example usage in agent source:
171
+ *
172
+ * ```book
173
+ * ACTION Can generate code snippets and explain programming concepts
174
+ * ACTION Able to analyze data and provide insights
175
+ * ```
176
+ *
177
+ * @private [🪔] Maybe export the commitments through some package
178
+ */
179
+ class ActionCommitmentDefinition extends BaseCommitmentDefinition {
180
+ constructor() {
181
+ super('ACTION');
104
182
  }
105
- // Allow paths like hello.book
106
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
107
- // console.log(filename, 'Allow paths like hello.book');
108
- return true;
183
+ applyToAgentModelRequirements(requirements, content) {
184
+ const trimmedContent = content.trim();
185
+ if (!trimmedContent) {
186
+ return requirements;
187
+ }
188
+ // Add action capability to the system message
189
+ const actionSection = `Capability: ${trimmedContent}`;
190
+ return this.appendToSystemMessage(requirements, actionSection, '\n\n');
109
191
  }
110
- return false;
111
192
  }
112
193
  /**
113
- * TODO: [🍏] Implement for MacOs
194
+ * Singleton instance of the ACTION commitment definition
195
+ *
196
+ * @private [🪔] Maybe export the commitments through some package
197
+ */
198
+ new ActionCommitmentDefinition();
199
+ /**
200
+ * Note: [💞] Ignore a discrepancy between file name and entity name
114
201
  */
115
202
 
116
203
  /**
117
- * Tests if given string is valid URL.
204
+ * FORMAT commitment definition
118
205
  *
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
206
+ * The FORMAT commitment defines the specific output structure and formatting
207
+ * that the agent should use in its responses. This includes data formats,
208
+ * response templates, and structural requirements.
123
209
  *
124
- * @public exported from `@promptbook/utils`
210
+ * Example usage in agent source:
211
+ *
212
+ * ```book
213
+ * FORMAT Always respond in JSON format with 'status' and 'data' fields
214
+ * FORMAT Use markdown formatting for all code blocks
215
+ * ```
216
+ *
217
+ * @private [🪔] Maybe export the commitments through some package
125
218
  */
126
- function isValidUrl(url) {
127
- if (typeof url !== 'string') {
128
- return false;
219
+ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
220
+ constructor() {
221
+ super('FORMAT');
129
222
  }
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;
223
+ applyToAgentModelRequirements(requirements, content) {
224
+ const trimmedContent = content.trim();
225
+ if (!trimmedContent) {
226
+ return requirements;
137
227
  }
138
- return true;
139
- }
140
- catch (error) {
141
- return false;
228
+ // Add format instructions to the system message
229
+ const formatSection = `Output Format: ${trimmedContent}`;
230
+ return this.appendToSystemMessage(requirements, formatSection, '\n\n');
142
231
  }
143
232
  }
233
+ /**
234
+ * Singleton instance of the FORMAT commitment definition
235
+ *
236
+ * @private [🪔] Maybe export the commitments through some package
237
+ */
238
+ new FormatCommitmentDefinition();
239
+ /**
240
+ * Note: [💞] Ignore a discrepancy between file name and entity name
241
+ */
144
242
 
145
243
  /**
146
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
244
+ * Error thrown when a fetch request fails
147
245
  *
148
246
  * @public exported from `@promptbook/core`
149
247
  */
150
- class ParseError extends Error {
248
+ class PromptbookFetchError extends Error {
151
249
  constructor(message) {
152
250
  super(message);
153
- this.name = 'ParseError';
154
- Object.setPrototypeOf(this, ParseError.prototype);
251
+ this.name = 'PromptbookFetchError';
252
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
155
253
  }
156
254
  }
157
- /**
158
- * TODO: Maybe split `ParseError` and `ApplyError`
159
- */
160
255
 
161
256
  /**
162
257
  * Available remote servers for the Promptbook
@@ -481,148 +576,1514 @@ function SET_IS_VERBOSE(isVerbose) {
481
576
  */
482
577
  const DEFAULT_IS_AUTO_INSTALLED = false;
483
578
  /**
484
- * Function name for generated function via `ptbk make` to get the pipeline collection
579
+ * Function name for generated function via `ptbk make` to get the pipeline collection
580
+ *
581
+ * @public exported from `@promptbook/core`
582
+ */
583
+ const DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME = `getPipelineCollection`;
584
+ /**
585
+ * Default rate limits (requests per minute)
586
+ *
587
+ * Note: Adjust based on the provider tier you are have
588
+ *
589
+ * @public exported from `@promptbook/core`
590
+ */
591
+ const DEFAULT_MAX_REQUESTS_PER_MINUTE = 60;
592
+ /**
593
+ * Indicates whether pipeline logic validation is enabled. When true, the pipeline logic is checked for consistency.
594
+ *
595
+ * @private within the repository
596
+ */
597
+ const IS_PIPELINE_LOGIC_VALIDATED = just(
598
+ /**/
599
+ // Note: In normal situations, we check the pipeline logic:
600
+ true);
601
+ /**
602
+ * Note: [💞] Ignore a discrepancy between file name and entity name
603
+ * TODO: [🧠][🧜‍♂️] Maybe join remoteServerUrl and path into single value
604
+ */
605
+
606
+ /**
607
+ * Make error report URL for the given error
608
+ *
609
+ * @private private within the repository
610
+ */
611
+ function getErrorReportUrl(error) {
612
+ const report = {
613
+ title: `🐜 Error report from ${NAME}`,
614
+ body: spaceTrim((block) => `
615
+
616
+
617
+ \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
618
+
619
+ \`\`\`
620
+ ${block(error.message || '(no error message)')}
621
+ \`\`\`
622
+
623
+
624
+ ## More info:
625
+
626
+ - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
627
+ - **Book language version:** ${BOOK_LANGUAGE_VERSION}
628
+ - **Time:** ${new Date().toISOString()}
629
+
630
+ <details>
631
+ <summary>Stack trace:</summary>
632
+
633
+ ## Stack trace:
634
+
635
+ \`\`\`stacktrace
636
+ ${block(error.stack || '(empty)')}
637
+ \`\`\`
638
+ </details>
639
+
640
+ `),
641
+ };
642
+ const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
643
+ reportUrl.searchParams.set('labels', 'bug');
644
+ reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
645
+ reportUrl.searchParams.set('title', report.title);
646
+ reportUrl.searchParams.set('body', report.body);
647
+ return reportUrl;
648
+ }
649
+
650
+ /**
651
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
652
+ *
653
+ * @public exported from `@promptbook/core`
654
+ */
655
+ class UnexpectedError extends Error {
656
+ constructor(message) {
657
+ super(spaceTrim$1((block) => `
658
+ ${block(message)}
659
+
660
+ Note: This error should not happen.
661
+ It's probably a bug in the pipeline collection
662
+
663
+ Please report issue:
664
+ ${block(getErrorReportUrl(new Error(message)).href)}
665
+
666
+ Or contact us on ${ADMIN_EMAIL}
667
+
668
+ `));
669
+ this.name = 'UnexpectedError';
670
+ Object.setPrototypeOf(this, UnexpectedError.prototype);
671
+ }
672
+ }
673
+
674
+ /**
675
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
676
+ *
677
+ * @public exported from `@promptbook/core`
678
+ */
679
+ class WrappedError extends Error {
680
+ constructor(whatWasThrown) {
681
+ const tag = `[🤮]`;
682
+ console.error(tag, whatWasThrown);
683
+ super(spaceTrim$1(`
684
+ Non-Error object was thrown
685
+
686
+ Note: Look for ${tag} in the console for more details
687
+ Please report issue on ${ADMIN_EMAIL}
688
+ `));
689
+ this.name = 'WrappedError';
690
+ Object.setPrototypeOf(this, WrappedError.prototype);
691
+ }
692
+ }
693
+
694
+ /**
695
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
696
+ *
697
+ * @param whatWasThrown Any object that was thrown
698
+ * @returns Nothing if the error is an instance of `Error`
699
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
700
+ *
701
+ * @private within the repository
702
+ */
703
+ function assertsError(whatWasThrown) {
704
+ // Case 1: Handle error which was rethrown as `WrappedError`
705
+ if (whatWasThrown instanceof WrappedError) {
706
+ const wrappedError = whatWasThrown;
707
+ throw wrappedError;
708
+ }
709
+ // Case 2: Handle unexpected errors
710
+ if (whatWasThrown instanceof UnexpectedError) {
711
+ const unexpectedError = whatWasThrown;
712
+ throw unexpectedError;
713
+ }
714
+ // Case 3: Handle standard errors - keep them up to consumer
715
+ if (whatWasThrown instanceof Error) {
716
+ return;
717
+ }
718
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
719
+ throw new WrappedError(whatWasThrown);
720
+ }
721
+
722
+ /**
723
+ * The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
724
+ *
725
+ * @public exported from `@promptbook/core`
726
+ */
727
+ const promptbookFetch = async (urlOrRequest, init) => {
728
+ try {
729
+ return await fetch(urlOrRequest, init);
730
+ }
731
+ catch (error) {
732
+ assertsError(error);
733
+ let url;
734
+ if (typeof urlOrRequest === 'string') {
735
+ url = urlOrRequest;
736
+ }
737
+ else if (urlOrRequest instanceof Request) {
738
+ url = urlOrRequest.url;
739
+ }
740
+ throw new PromptbookFetchError(spaceTrim((block) => `
741
+ Can not fetch "${url}"
742
+
743
+ Fetch error:
744
+ ${block(error.message)}
745
+
746
+ `));
747
+ }
748
+ };
749
+ /**
750
+ * TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
751
+ */
752
+
753
+ /**
754
+ * Frontend RAG Service that uses backend APIs for processing
755
+ * This avoids Node.js dependencies in the frontend
756
+ *
757
+ * @private - TODO: [🧠] Maybe should be public?
758
+ */
759
+ class FrontendRAGService {
760
+ constructor(config) {
761
+ this.chunks = [];
762
+ this.sources = [];
763
+ this.isInitialized = false;
764
+ this.config = {
765
+ maxChunkSize: 1000,
766
+ chunkOverlap: 200,
767
+ maxRetrievedChunks: 5,
768
+ minRelevanceScore: 0.1,
769
+ ...config,
770
+ };
771
+ }
772
+ /**
773
+ * Initialize knowledge sources by processing them on the backend
774
+ */
775
+ async initializeKnowledgeSources(sources) {
776
+ if (sources.length === 0) {
777
+ this.isInitialized = true;
778
+ return;
779
+ }
780
+ try {
781
+ const response = await promptbookFetch('/api/knowledge/process-sources', {
782
+ method: 'POST',
783
+ headers: {
784
+ 'Content-Type': 'application/json',
785
+ },
786
+ body: JSON.stringify({
787
+ sources,
788
+ config: {
789
+ maxChunkSize: this.config.maxChunkSize,
790
+ chunkOverlap: this.config.chunkOverlap,
791
+ },
792
+ }),
793
+ });
794
+ if (!response.ok) {
795
+ throw new Error(`Failed to process knowledge sources: ${response.status}`);
796
+ }
797
+ const result = (await response.json());
798
+ if (!result.success) {
799
+ throw new Error(result.message || 'Failed to process knowledge sources');
800
+ }
801
+ this.chunks = result.chunks;
802
+ this.sources = sources;
803
+ this.isInitialized = true;
804
+ console.log(`Initialized RAG service with ${this.chunks.length} chunks from ${sources.length} sources`);
805
+ }
806
+ catch (error) {
807
+ console.error('Failed to initialize knowledge sources:', error);
808
+ // Don't throw - allow the system to continue without RAG
809
+ this.isInitialized = true;
810
+ }
811
+ }
812
+ /**
813
+ * Get relevant context for a user query
814
+ */
815
+ async getContextForQuery(query) {
816
+ if (!this.isInitialized) {
817
+ console.warn('RAG service not initialized');
818
+ return '';
819
+ }
820
+ if (this.chunks.length === 0) {
821
+ return '';
822
+ }
823
+ try {
824
+ const response = await promptbookFetch('/api/knowledge/retrieve-context', {
825
+ method: 'POST',
826
+ headers: {
827
+ 'Content-Type': 'application/json',
828
+ },
829
+ body: JSON.stringify({
830
+ query,
831
+ chunks: this.chunks,
832
+ config: {
833
+ maxRetrievedChunks: this.config.maxRetrievedChunks,
834
+ minRelevanceScore: this.config.minRelevanceScore,
835
+ },
836
+ }),
837
+ });
838
+ if (!response.ok) {
839
+ console.error(`Failed to retrieve context: ${response.status}`);
840
+ return '';
841
+ }
842
+ const result = (await response.json());
843
+ if (!result.success) {
844
+ console.error('Context retrieval failed:', result.message);
845
+ return '';
846
+ }
847
+ return result.context;
848
+ }
849
+ catch (error) {
850
+ console.error('Error retrieving context:', error);
851
+ return '';
852
+ }
853
+ }
854
+ /**
855
+ * Get relevant chunks for a query (for debugging/inspection)
856
+ */
857
+ async getRelevantChunks(query) {
858
+ if (!this.isInitialized || this.chunks.length === 0) {
859
+ return [];
860
+ }
861
+ try {
862
+ const response = await promptbookFetch('/api/knowledge/retrieve-context', {
863
+ method: 'POST',
864
+ headers: {
865
+ 'Content-Type': 'application/json',
866
+ },
867
+ body: JSON.stringify({
868
+ query,
869
+ chunks: this.chunks,
870
+ config: {
871
+ maxRetrievedChunks: this.config.maxRetrievedChunks,
872
+ minRelevanceScore: this.config.minRelevanceScore,
873
+ },
874
+ }),
875
+ });
876
+ if (!response.ok) {
877
+ return [];
878
+ }
879
+ const result = (await response.json());
880
+ return result.success ? result.relevantChunks : [];
881
+ }
882
+ catch (error) {
883
+ console.error('Error retrieving relevant chunks:', error);
884
+ return [];
885
+ }
886
+ }
887
+ /**
888
+ * Get knowledge base statistics
889
+ */
890
+ getStats() {
891
+ return {
892
+ sources: this.sources.length,
893
+ chunks: this.chunks.length,
894
+ isInitialized: this.isInitialized,
895
+ };
896
+ }
897
+ /**
898
+ * Check if the service is ready to use
899
+ */
900
+ isReady() {
901
+ return this.isInitialized;
902
+ }
903
+ /**
904
+ * Clear all knowledge sources
905
+ */
906
+ clearKnowledgeBase() {
907
+ this.chunks = [];
908
+ this.sources = [];
909
+ this.isInitialized = false;
910
+ }
911
+ /**
912
+ * Add a single knowledge source (for incremental updates)
913
+ */
914
+ async addKnowledgeSource(url) {
915
+ if (this.sources.includes(url)) {
916
+ console.log(`Knowledge source already exists: ${url}`);
917
+ return;
918
+ }
919
+ try {
920
+ const response = await promptbookFetch('/api/knowledge/process-sources', {
921
+ method: 'POST',
922
+ headers: {
923
+ 'Content-Type': 'application/json',
924
+ },
925
+ body: JSON.stringify({
926
+ sources: [url],
927
+ config: {
928
+ maxChunkSize: this.config.maxChunkSize,
929
+ chunkOverlap: this.config.chunkOverlap,
930
+ },
931
+ }),
932
+ });
933
+ if (!response.ok) {
934
+ throw new Error(`Failed to process knowledge source: ${response.status}`);
935
+ }
936
+ const result = (await response.json());
937
+ if (!result.success) {
938
+ throw new Error(result.message || 'Failed to process knowledge source');
939
+ }
940
+ // Add new chunks to existing ones
941
+ this.chunks.push(...result.chunks);
942
+ this.sources.push(url);
943
+ console.log(`Added knowledge source: ${url} (${result.chunks.length} chunks)`);
944
+ }
945
+ catch (error) {
946
+ console.error(`Failed to add knowledge source ${url}:`, error);
947
+ throw error;
948
+ }
949
+ }
950
+ }
951
+
952
+ /**
953
+ * KNOWLEDGE commitment definition
954
+ *
955
+ * The KNOWLEDGE commitment adds specific knowledge, facts, or context to the agent
956
+ * using RAG (Retrieval-Augmented Generation) approach for external sources.
957
+ *
958
+ * Supports both direct text knowledge and external sources like PDFs.
959
+ *
960
+ * Example usage in agent source:
961
+ *
962
+ * ```book
963
+ * KNOWLEDGE The company was founded in 2020 and specializes in AI-powered solutions
964
+ * KNOWLEDGE https://example.com/company-handbook.pdf
965
+ * KNOWLEDGE https://example.com/product-documentation.pdf
966
+ * ```
967
+ *
968
+ * @private [🪔] Maybe export the commitments through some package
969
+ */
970
+ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
971
+ constructor() {
972
+ super('KNOWLEDGE');
973
+ this.ragService = new FrontendRAGService();
974
+ }
975
+ applyToAgentModelRequirements(requirements, content) {
976
+ var _a;
977
+ const trimmedContent = content.trim();
978
+ if (!trimmedContent) {
979
+ return requirements;
980
+ }
981
+ // Check if content is a URL (external knowledge source)
982
+ if (this.isUrl(trimmedContent)) {
983
+ // Store the URL for later async processing
984
+ const updatedRequirements = {
985
+ ...requirements,
986
+ metadata: {
987
+ ...requirements.metadata,
988
+ ragService: this.ragService,
989
+ knowledgeSources: [
990
+ ...(((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.knowledgeSources) || []),
991
+ trimmedContent,
992
+ ],
993
+ },
994
+ };
995
+ // Add placeholder information about knowledge sources to system message
996
+ const knowledgeInfo = `Knowledge Source URL: ${trimmedContent} (will be processed for retrieval during chat)`;
997
+ return this.appendToSystemMessage(updatedRequirements, knowledgeInfo, '\n\n');
998
+ }
999
+ else {
1000
+ // Direct text knowledge - add to system message
1001
+ const knowledgeSection = `Knowledge: ${trimmedContent}`;
1002
+ return this.appendToSystemMessage(requirements, knowledgeSection, '\n\n');
1003
+ }
1004
+ }
1005
+ /**
1006
+ * Check if content is a URL
1007
+ */
1008
+ isUrl(content) {
1009
+ try {
1010
+ new URL(content);
1011
+ return true;
1012
+ }
1013
+ catch (_a) {
1014
+ return false;
1015
+ }
1016
+ }
1017
+ /**
1018
+ * Get RAG service instance for retrieving context during chat
1019
+ */
1020
+ getRagService() {
1021
+ return this.ragService;
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Singleton instance of the KNOWLEDGE commitment definition
1026
+ *
1027
+ * @private [🪔] Maybe export the commitments through some package
1028
+ */
1029
+ new KnowledgeCommitmentDefinition();
1030
+ /**
1031
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1032
+ */
1033
+
1034
+ /**
1035
+ * META IMAGE commitment definition
1036
+ *
1037
+ * The META IMAGE commitment sets the agent's avatar/profile image URL.
1038
+ * This commitment is special because it doesn't affect the system message,
1039
+ * but is handled separately in the parsing logic.
1040
+ *
1041
+ * Example usage in agent source:
1042
+ *
1043
+ * ```book
1044
+ * META IMAGE https://example.com/avatar.jpg
1045
+ * META IMAGE /assets/agent-avatar.png
1046
+ * ```
1047
+ *
1048
+ * @private [🪔] Maybe export the commitments through some package
1049
+ */
1050
+ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
1051
+ constructor() {
1052
+ super('META IMAGE');
1053
+ }
1054
+ applyToAgentModelRequirements(requirements, content) {
1055
+ // META IMAGE doesn't modify the system message or model requirements
1056
+ // It's handled separately in the parsing logic for profile image extraction
1057
+ // This method exists for consistency with the CommitmentDefinition interface
1058
+ return requirements;
1059
+ }
1060
+ /**
1061
+ * Extracts the profile image URL from the content
1062
+ * This is used by the parsing logic
1063
+ */
1064
+ extractProfileImageUrl(content) {
1065
+ const trimmedContent = content.trim();
1066
+ return trimmedContent || null;
1067
+ }
1068
+ }
1069
+ /**
1070
+ * Singleton instance of the META IMAGE commitment definition
1071
+ *
1072
+ * @private [🪔] Maybe export the commitments through some package
1073
+ */
1074
+ new MetaImageCommitmentDefinition();
1075
+ /**
1076
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1077
+ */
1078
+
1079
+ /**
1080
+ * META LINK commitment definition
1081
+ *
1082
+ * The `META LINK` commitment represents the link to the person from whom the agent is created.
1083
+ * This commitment is special because it doesn't affect the system message,
1084
+ * but is handled separately in the parsing logic for profile display.
1085
+ *
1086
+ * Example usage in agent source:
1087
+ *
1088
+ * ```
1089
+ * META LINK https://twitter.com/username
1090
+ * META LINK https://linkedin.com/in/profile
1091
+ * META LINK https://github.com/username
1092
+ * ```
1093
+ *
1094
+ * Multiple `META LINK` commitments can be used when there are multiple sources:
1095
+ *
1096
+ * ```book
1097
+ * META LINK https://twitter.com/username
1098
+ * META LINK https://linkedin.com/in/profile
1099
+ * ```
1100
+ *
1101
+ * @private [🪔] Maybe export the commitments through some package
1102
+ */
1103
+ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
1104
+ constructor() {
1105
+ super('META LINK');
1106
+ }
1107
+ applyToAgentModelRequirements(requirements, content) {
1108
+ // META LINK doesn't modify the system message or model requirements
1109
+ // It's handled separately in the parsing logic for profile link extraction
1110
+ // This method exists for consistency with the CommitmentDefinition interface
1111
+ return requirements;
1112
+ }
1113
+ /**
1114
+ * Extracts the profile link URL from the content
1115
+ * This is used by the parsing logic
1116
+ */
1117
+ extractProfileLinkUrl(content) {
1118
+ const trimmedContent = content.trim();
1119
+ return trimmedContent || null;
1120
+ }
1121
+ /**
1122
+ * Validates if the provided content is a valid URL
1123
+ */
1124
+ isValidUrl(content) {
1125
+ try {
1126
+ new URL(content.trim());
1127
+ return true;
1128
+ }
1129
+ catch (_a) {
1130
+ return false;
1131
+ }
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Singleton instance of the META LINK commitment definition
1136
+ *
1137
+ * @private [🪔] Maybe export the commitments through some package
1138
+ */
1139
+ new MetaLinkCommitmentDefinition();
1140
+ /**
1141
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1142
+ */
1143
+
1144
+ /**
1145
+ * MODEL commitment definition
1146
+ *
1147
+ * The MODEL commitment specifies which AI model to use and can also set
1148
+ * model-specific parameters like temperature, topP, and topK.
1149
+ *
1150
+ * Example usage in agent source:
1151
+ *
1152
+ * ```book
1153
+ * MODEL gpt-4
1154
+ * MODEL claude-3-opus temperature=0.3
1155
+ * MODEL gpt-3.5-turbo temperature=0.8 topP=0.9
1156
+ * ```
1157
+ *
1158
+ * @private [🪔] Maybe export the commitments through some package
1159
+ */
1160
+ class ModelCommitmentDefinition extends BaseCommitmentDefinition {
1161
+ constructor() {
1162
+ super('MODEL');
1163
+ }
1164
+ applyToAgentModelRequirements(requirements, content) {
1165
+ const trimmedContent = content.trim();
1166
+ if (!trimmedContent) {
1167
+ return requirements;
1168
+ }
1169
+ // Parse the model specification
1170
+ const parts = trimmedContent.split(/\s+/);
1171
+ const modelName = parts[0];
1172
+ if (!modelName) {
1173
+ return requirements;
1174
+ }
1175
+ // Start with the model name
1176
+ const updatedRequirements = {
1177
+ ...requirements,
1178
+ modelName,
1179
+ };
1180
+ // Parse additional parameters
1181
+ const result = { ...updatedRequirements };
1182
+ for (let i = 1; i < parts.length; i++) {
1183
+ const param = parts[i];
1184
+ if (param && param.includes('=')) {
1185
+ const [key, value] = param.split('=');
1186
+ if (key && value) {
1187
+ const numValue = parseFloat(value);
1188
+ if (!isNaN(numValue)) {
1189
+ switch (key.toLowerCase()) {
1190
+ case 'temperature':
1191
+ result.temperature = numValue;
1192
+ break;
1193
+ case 'topp':
1194
+ case 'top_p':
1195
+ result.topP = numValue;
1196
+ break;
1197
+ case 'topk':
1198
+ case 'top_k':
1199
+ result.topK = Math.round(numValue);
1200
+ break;
1201
+ }
1202
+ }
1203
+ }
1204
+ }
1205
+ }
1206
+ return result;
1207
+ }
1208
+ }
1209
+ /**
1210
+ * Singleton instance of the MODEL commitment definition
1211
+ *
1212
+ * @private [🪔] Maybe export the commitments through some package
1213
+ */
1214
+ new ModelCommitmentDefinition();
1215
+ /**
1216
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1217
+ */
1218
+
1219
+ /**
1220
+ * NOTE commitment definition
1221
+ *
1222
+ * The NOTE commitment is used to add comments to the agent source without making any changes
1223
+ * to the system message or agent model requirements. It serves as a documentation mechanism
1224
+ * for developers to add explanatory comments, reminders, or annotations directly in the agent source.
1225
+ *
1226
+ * Key features:
1227
+ * - Makes no changes to the system message
1228
+ * - Makes no changes to agent model requirements
1229
+ * - Content is preserved in metadata.NOTE for debugging and inspection
1230
+ * - Multiple NOTE commitments are aggregated together
1231
+ * - Comments (# NOTE) are removed from the final system message
1232
+ *
1233
+ * Example usage in agent source:
1234
+ *
1235
+ * ```book
1236
+ * NOTE This agent was designed for customer support scenarios
1237
+ * NOTE Remember to update the knowledge base monthly
1238
+ * NOTE Performance optimized for quick response times
1239
+ * ```
1240
+ *
1241
+ * The above notes will be stored in metadata but won't affect the agent's behavior.
1242
+ *
1243
+ * @private [🪔] Maybe export the commitments through some package
1244
+ */
1245
+ class NoteCommitmentDefinition extends BaseCommitmentDefinition {
1246
+ constructor() {
1247
+ super('NOTE');
1248
+ }
1249
+ applyToAgentModelRequirements(requirements, content) {
1250
+ var _a;
1251
+ // The NOTE commitment makes no changes to the system message or model requirements
1252
+ // It only stores the note content in metadata for documentation purposes
1253
+ const trimmedContent = content.trim();
1254
+ if (!trimmedContent) {
1255
+ return requirements;
1256
+ }
1257
+ // Get existing note content from metadata
1258
+ const existingNoteContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.NOTE) || '';
1259
+ // Merge the new content with existing note content
1260
+ // When multiple NOTE commitments exist, they are aggregated together
1261
+ const mergedNoteContent = existingNoteContent ? `${existingNoteContent}\n${trimmedContent}` : trimmedContent;
1262
+ // Store the merged note content in metadata for debugging and inspection
1263
+ const updatedMetadata = {
1264
+ ...requirements.metadata,
1265
+ NOTE: mergedNoteContent,
1266
+ };
1267
+ // Return requirements with updated metadata but no changes to system message
1268
+ return {
1269
+ ...requirements,
1270
+ metadata: updatedMetadata,
1271
+ };
1272
+ }
1273
+ }
1274
+ /**
1275
+ * Singleton instance of the NOTE commitment definition
1276
+ *
1277
+ * @private [🪔] Maybe export the commitments through some package
1278
+ */
1279
+ new NoteCommitmentDefinition();
1280
+ /**
1281
+ * [💞] Ignore a discrepancy between file name and entity name
1282
+ */
1283
+
1284
+ /**
1285
+ * PERSONA commitment definition
1286
+ *
1287
+ * The PERSONA commitment modifies the agent's personality and character in the system message.
1288
+ * It defines who the agent is, their background, expertise, and personality traits.
1289
+ *
1290
+ * Key features:
1291
+ * - Multiple PERSONA commitments are automatically merged into one
1292
+ * - Content is placed at the beginning of the system message
1293
+ * - Original content with comments is preserved in metadata.PERSONA
1294
+ * - Comments (# PERSONA) are removed from the final system message
1295
+ *
1296
+ * Example usage in agent source:
1297
+ *
1298
+ * ```book
1299
+ * PERSONA You are a helpful programming assistant with expertise in TypeScript and React
1300
+ * PERSONA You have deep knowledge of modern web development practices
1301
+ * ```
1302
+ *
1303
+ * The above will be merged into a single persona section at the beginning of the system message.
1304
+ *
1305
+ * @private [🪔] Maybe export the commitments through some package
1306
+ */
1307
+ class PersonaCommitmentDefinition extends BaseCommitmentDefinition {
1308
+ constructor() {
1309
+ super('PERSONA');
1310
+ }
1311
+ applyToAgentModelRequirements(requirements, content) {
1312
+ var _a, _b;
1313
+ // The PERSONA commitment aggregates all persona content and places it at the beginning
1314
+ const trimmedContent = content.trim();
1315
+ if (!trimmedContent) {
1316
+ return requirements;
1317
+ }
1318
+ // Get existing persona content from metadata
1319
+ const existingPersonaContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.PERSONA) || '';
1320
+ // Merge the new content with existing persona content
1321
+ // When multiple PERSONA commitments exist, they are merged into one
1322
+ const mergedPersonaContent = existingPersonaContent
1323
+ ? `${existingPersonaContent}\n${trimmedContent}`
1324
+ : trimmedContent;
1325
+ // Store the merged persona content in metadata for debugging and inspection
1326
+ const updatedMetadata = {
1327
+ ...requirements.metadata,
1328
+ PERSONA: mergedPersonaContent,
1329
+ };
1330
+ // Get the agent name from metadata (which should contain the first line of agent source)
1331
+ // If not available, extract from current system message as fallback
1332
+ let agentName = (_b = requirements.metadata) === null || _b === void 0 ? void 0 : _b.agentName;
1333
+ if (!agentName) {
1334
+ // Fallback: extract from current system message
1335
+ const currentMessage = requirements.systemMessage.trim();
1336
+ const basicFormatMatch = currentMessage.match(/^You are (.+)$/);
1337
+ if (basicFormatMatch && basicFormatMatch[1]) {
1338
+ agentName = basicFormatMatch[1];
1339
+ }
1340
+ else {
1341
+ agentName = 'AI Agent'; // Final fallback
1342
+ }
1343
+ }
1344
+ // Remove any existing persona content from the system message
1345
+ // (this handles the case where we're processing multiple PERSONA commitments)
1346
+ const currentMessage = requirements.systemMessage.trim();
1347
+ let cleanedMessage = currentMessage;
1348
+ // Check if current message starts with persona content or is just the basic format
1349
+ const basicFormatRegex = /^You are .+$/;
1350
+ const isBasicFormat = basicFormatRegex.test(currentMessage) && !currentMessage.includes('\n');
1351
+ if (isBasicFormat) {
1352
+ // Replace the basic format entirely
1353
+ cleanedMessage = '';
1354
+ }
1355
+ else if (currentMessage.startsWith('# PERSONA')) {
1356
+ // Remove existing persona section by finding where it ends
1357
+ const lines = currentMessage.split('\n');
1358
+ let personaEndIndex = lines.length;
1359
+ // Find the end of the PERSONA section (next comment or end of message)
1360
+ for (let i = 1; i < lines.length; i++) {
1361
+ const line = lines[i].trim();
1362
+ if (line.startsWith('#') && !line.startsWith('# PERSONA')) {
1363
+ personaEndIndex = i;
1364
+ break;
1365
+ }
1366
+ }
1367
+ // Keep everything after the PERSONA section
1368
+ cleanedMessage = lines.slice(personaEndIndex).join('\n').trim();
1369
+ }
1370
+ // Create new system message with persona at the beginning
1371
+ // Format: "You are {agentName}\n{personaContent}"
1372
+ // The # PERSONA comment will be removed later by removeCommentsFromSystemMessage
1373
+ const personaSection = `# PERSONA\nYou are ${agentName}\n${mergedPersonaContent}`; // <- TODO: Use spaceTrim
1374
+ const newSystemMessage = cleanedMessage ? `${personaSection}\n\n${cleanedMessage}` : personaSection;
1375
+ return {
1376
+ ...requirements,
1377
+ systemMessage: newSystemMessage,
1378
+ metadata: updatedMetadata,
1379
+ };
1380
+ }
1381
+ }
1382
+ /**
1383
+ * Singleton instance of the PERSONA commitment definition
1384
+ *
1385
+ * @private [🪔] Maybe export the commitments through some package
1386
+ */
1387
+ new PersonaCommitmentDefinition();
1388
+ /**
1389
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1390
+ */
1391
+
1392
+ /**
1393
+ * RULE commitment definition
1394
+ *
1395
+ * The RULE/RULES commitment adds behavioral constraints and guidelines that the agent must follow.
1396
+ * These are specific instructions about what the agent should or shouldn't do.
1397
+ *
1398
+ * Example usage in agent source:
1399
+ *
1400
+ * ```book
1401
+ * RULE Always ask for clarification if the user's request is ambiguous
1402
+ * RULES Never provide medical advice, always refer to healthcare professionals
1403
+ * ```
1404
+ *
1405
+ * @private [🪔] Maybe export the commitments through some package
1406
+ */
1407
+ class RuleCommitmentDefinition extends BaseCommitmentDefinition {
1408
+ constructor(type = 'RULE') {
1409
+ super(type);
1410
+ }
1411
+ applyToAgentModelRequirements(requirements, content) {
1412
+ const trimmedContent = content.trim();
1413
+ if (!trimmedContent) {
1414
+ return requirements;
1415
+ }
1416
+ // Add rule to the system message
1417
+ const ruleSection = `Rule: ${trimmedContent}`;
1418
+ return this.appendToSystemMessage(requirements, ruleSection, '\n\n');
1419
+ }
1420
+ }
1421
+ /**
1422
+ * Singleton instances of the RULE commitment definitions
1423
+ *
1424
+ * @private [🪔] Maybe export the commitments through some package
1425
+ */
1426
+ new RuleCommitmentDefinition('RULE');
1427
+ /**
1428
+ * Singleton instances of the RULE commitment definitions
1429
+ *
1430
+ * @private [🪔] Maybe export the commitments through some package
1431
+ */
1432
+ new RuleCommitmentDefinition('RULES');
1433
+ /**
1434
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1435
+ */
1436
+
1437
+ /**
1438
+ * SAMPLE commitment definition
1439
+ *
1440
+ * The SAMPLE/EXAMPLE commitment provides examples of how the agent should respond
1441
+ * or behave in certain situations. These examples help guide the agent's responses.
1442
+ *
1443
+ * Example usage in agent source:
1444
+ *
1445
+ * ```book
1446
+ * SAMPLE When asked about pricing, respond: "Our basic plan starts at $10/month..."
1447
+ * EXAMPLE For code questions, always include working code snippets
1448
+ * ```
1449
+ *
1450
+ * @private [🪔] Maybe export the commitments through some package
1451
+ */
1452
+ class SampleCommitmentDefinition extends BaseCommitmentDefinition {
1453
+ constructor(type = 'SAMPLE') {
1454
+ super(type);
1455
+ }
1456
+ applyToAgentModelRequirements(requirements, content) {
1457
+ const trimmedContent = content.trim();
1458
+ if (!trimmedContent) {
1459
+ return requirements;
1460
+ }
1461
+ // Add example to the system message
1462
+ const exampleSection = `Example: ${trimmedContent}`;
1463
+ return this.appendToSystemMessage(requirements, exampleSection, '\n\n');
1464
+ }
1465
+ }
1466
+ /**
1467
+ * Singleton instances of the SAMPLE commitment definitions
1468
+ *
1469
+ * @private [🪔] Maybe export the commitments through some package
1470
+ */
1471
+ new SampleCommitmentDefinition('SAMPLE');
1472
+ /**
1473
+ * Singleton instances of the SAMPLE commitment definitions
1474
+ *
1475
+ * @private [🪔] Maybe export the commitments through some package
1476
+ */
1477
+ new SampleCommitmentDefinition('EXAMPLE');
1478
+ /**
1479
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1480
+ */
1481
+
1482
+ /**
1483
+ * STYLE commitment definition
1484
+ *
1485
+ * The STYLE commitment defines how the agent should format and present its responses.
1486
+ * This includes tone, writing style, formatting preferences, and communication patterns.
1487
+ *
1488
+ * Example usage in agent source:
1489
+ *
1490
+ * ```book
1491
+ * STYLE Write in a professional but friendly tone, use bullet points for lists
1492
+ * STYLE Always provide code examples when explaining programming concepts
1493
+ * ```
1494
+ *
1495
+ * @private [🪔] Maybe export the commitments through some package
1496
+ */
1497
+ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
1498
+ constructor() {
1499
+ super('STYLE');
1500
+ }
1501
+ applyToAgentModelRequirements(requirements, content) {
1502
+ const trimmedContent = content.trim();
1503
+ if (!trimmedContent) {
1504
+ return requirements;
1505
+ }
1506
+ // Add style instructions to the system message
1507
+ const styleSection = `Style: ${trimmedContent}`;
1508
+ return this.appendToSystemMessage(requirements, styleSection, '\n\n');
1509
+ }
1510
+ }
1511
+ /**
1512
+ * Singleton instance of the STYLE commitment definition
1513
+ *
1514
+ * @private [🪔] Maybe export the commitments through some package
1515
+ */
1516
+ new StyleCommitmentDefinition();
1517
+ /**
1518
+ * [💞] Ignore a discrepancy between file name and entity name
1519
+ */
1520
+
1521
+ /**
1522
+ * Placeholder commitment definition for commitments that are not yet implemented
1523
+ *
1524
+ * This commitment simply adds its content 1:1 into the system message,
1525
+ * preserving the original behavior until proper implementation is added.
1526
+ *
1527
+ * @public exported from `@promptbook/core`
1528
+ */
1529
+ class NotYetImplementedCommitmentDefinition extends BaseCommitmentDefinition {
1530
+ constructor(type) {
1531
+ super(type);
1532
+ }
1533
+ applyToAgentModelRequirements(requirements, content) {
1534
+ const trimmedContent = content.trim();
1535
+ if (!trimmedContent) {
1536
+ return requirements;
1537
+ }
1538
+ // Add the commitment content 1:1 to the system message
1539
+ const commitmentLine = `${this.type} ${trimmedContent}`;
1540
+ return this.appendToSystemMessage(requirements, commitmentLine, '\n\n');
1541
+ }
1542
+ }
1543
+
1544
+ // Import all commitment definition classes
1545
+ /**
1546
+ * Registry of all available commitment definitions
1547
+ * This array contains instances of all commitment definitions
1548
+ * This is the single source of truth for all commitments in the system
1549
+ *
1550
+ * @private TODO: Maybe should be public?
1551
+ */
1552
+ const COMMITMENT_REGISTRY = [
1553
+ // Fully implemented commitments
1554
+ new PersonaCommitmentDefinition(),
1555
+ new KnowledgeCommitmentDefinition(),
1556
+ new StyleCommitmentDefinition(),
1557
+ new RuleCommitmentDefinition('RULE'),
1558
+ new RuleCommitmentDefinition('RULES'),
1559
+ new SampleCommitmentDefinition('SAMPLE'),
1560
+ new SampleCommitmentDefinition('EXAMPLE'),
1561
+ new FormatCommitmentDefinition(),
1562
+ new ModelCommitmentDefinition(),
1563
+ new ActionCommitmentDefinition(),
1564
+ new MetaImageCommitmentDefinition(),
1565
+ new MetaLinkCommitmentDefinition(),
1566
+ new NoteCommitmentDefinition(),
1567
+ // Not yet implemented commitments (using placeholder)
1568
+ new NotYetImplementedCommitmentDefinition('EXPECT'),
1569
+ new NotYetImplementedCommitmentDefinition('SCENARIO'),
1570
+ new NotYetImplementedCommitmentDefinition('SCENARIOS'),
1571
+ new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
1572
+ new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
1573
+ new NotYetImplementedCommitmentDefinition('AVOID'),
1574
+ new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
1575
+ new NotYetImplementedCommitmentDefinition('GOAL'),
1576
+ new NotYetImplementedCommitmentDefinition('GOALS'),
1577
+ new NotYetImplementedCommitmentDefinition('CONTEXT'),
1578
+ ];
1579
+ /**
1580
+ * Gets a commitment definition by its type
1581
+ * @param type The commitment type to look up
1582
+ * @returns The commitment definition or undefined if not found
1583
+ *
1584
+ * @private TODO: Maybe should be public?
1585
+ */
1586
+ function getCommitmentDefinition(type) {
1587
+ return COMMITMENT_REGISTRY.find((def) => def.type === type);
1588
+ }
1589
+ /**
1590
+ * TODO: !!!! Maybe create through standardized $register
1591
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1592
+ */
1593
+
1594
+ /**
1595
+ * Parses agent source using the new commitment system with multiline support
1596
+ * This function replaces the hardcoded commitment parsing in the original parseAgentSource
1597
+ *
1598
+ * @private
1599
+ */
1600
+ function parseAgentSourceWithCommitments(agentSource) {
1601
+ var _a, _b, _c;
1602
+ if (!agentSource || !agentSource.trim()) {
1603
+ return {
1604
+ agentName: null,
1605
+ commitments: [],
1606
+ nonCommitmentLines: [],
1607
+ };
1608
+ }
1609
+ const lines = agentSource.split('\n');
1610
+ const agentName = (((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.trim()) || null);
1611
+ const commitments = [];
1612
+ const nonCommitmentLines = [];
1613
+ // Always add the first line (agent name) to non-commitment lines
1614
+ if (lines[0] !== undefined) {
1615
+ nonCommitmentLines.push(lines[0]);
1616
+ }
1617
+ // Parse commitments with multiline support
1618
+ let currentCommitment = null;
1619
+ // Process lines starting from the second line (skip agent name)
1620
+ for (let i = 1; i < lines.length; i++) {
1621
+ const line = lines[i];
1622
+ if (line === undefined) {
1623
+ continue;
1624
+ }
1625
+ // Check if this line starts a new commitment
1626
+ let foundNewCommitment = false;
1627
+ for (const definition of COMMITMENT_REGISTRY) {
1628
+ const typeRegex = definition.createTypeRegex();
1629
+ const match = typeRegex.exec(line.trim());
1630
+ if (match && ((_b = match.groups) === null || _b === void 0 ? void 0 : _b.type)) {
1631
+ // Save the previous commitment if it exists
1632
+ if (currentCommitment) {
1633
+ const fullContent = currentCommitment.contentLines.join('\n');
1634
+ commitments.push({
1635
+ type: currentCommitment.type,
1636
+ content: spaceTrim$1(fullContent),
1637
+ originalLine: currentCommitment.originalStartLine,
1638
+ lineNumber: currentCommitment.startLineNumber,
1639
+ });
1640
+ }
1641
+ // Extract the initial content from the commitment line
1642
+ const fullRegex = definition.createRegex();
1643
+ const fullMatch = fullRegex.exec(line.trim());
1644
+ const initialContent = ((_c = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _c === void 0 ? void 0 : _c.contents) || '';
1645
+ // Start a new commitment
1646
+ currentCommitment = {
1647
+ type: definition.type,
1648
+ startLineNumber: i + 1,
1649
+ originalStartLine: line,
1650
+ contentLines: initialContent ? [initialContent] : [],
1651
+ };
1652
+ foundNewCommitment = true;
1653
+ break;
1654
+ }
1655
+ }
1656
+ if (!foundNewCommitment) {
1657
+ if (currentCommitment) {
1658
+ // This line belongs to the current commitment
1659
+ currentCommitment.contentLines.push(line);
1660
+ }
1661
+ else {
1662
+ // This line is not part of any commitment
1663
+ nonCommitmentLines.push(line);
1664
+ }
1665
+ }
1666
+ }
1667
+ // Don't forget to save the last commitment if it exists
1668
+ if (currentCommitment) {
1669
+ const fullContent = currentCommitment.contentLines.join('\n');
1670
+ commitments.push({
1671
+ type: currentCommitment.type,
1672
+ content: spaceTrim$1(fullContent),
1673
+ originalLine: currentCommitment.originalStartLine,
1674
+ lineNumber: currentCommitment.startLineNumber,
1675
+ });
1676
+ }
1677
+ return {
1678
+ agentName,
1679
+ commitments,
1680
+ nonCommitmentLines,
1681
+ };
1682
+ }
1683
+ /**
1684
+ * Extracts basic information from agent source using the new commitment system
1685
+ * This maintains compatibility with the original parseAgentSource interface
1686
+ *
1687
+ * @private
1688
+ */
1689
+ function parseAgentSourceBasicInfo(agentSource) {
1690
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
1691
+ // Find PERSONA and META IMAGE commitments
1692
+ let personaDescription = null;
1693
+ let profileImageUrl;
1694
+ for (const commitment of parseResult.commitments) {
1695
+ if (commitment.type === 'PERSONA' && !personaDescription) {
1696
+ personaDescription = commitment.content;
1697
+ }
1698
+ else if (commitment.type === 'META IMAGE' && !profileImageUrl) {
1699
+ profileImageUrl = commitment.content;
1700
+ }
1701
+ }
1702
+ // Generate gravatar fallback if no profile image specified
1703
+ if (!profileImageUrl) {
1704
+ profileImageUrl = generateGravatarUrl(parseResult.agentName);
1705
+ }
1706
+ return {
1707
+ agentName: parseResult.agentName,
1708
+ personaDescription,
1709
+ profileImageUrl,
1710
+ };
1711
+ }
1712
+
1713
+ /**
1714
+ * Parses agent source string into its components
1715
+ */
1716
+ // Cache for parsed agent sources to prevent repeated parsing
1717
+ const parsedAgentSourceCache = new Map();
1718
+ /**
1719
+ * Parses basic information from agent source
1720
+ *
1721
+ * There are 2 similar functions:
1722
+ * - `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.
1723
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
1724
+ *
1725
+ * @public exported from `@promptbook/core`
1726
+ */
1727
+ function parseAgentSource(agentSource) {
1728
+ // Check if we already parsed this agent source
1729
+ if (parsedAgentSourceCache.has(agentSource)) {
1730
+ return parsedAgentSourceCache.get(agentSource);
1731
+ }
1732
+ // Use the new commitment-based parsing system
1733
+ const result = parseAgentSourceBasicInfo(agentSource);
1734
+ // Cache the result
1735
+ parsedAgentSourceCache.set(agentSource, result);
1736
+ return result;
1737
+ }
1738
+
1739
+ /**
1740
+ * Type guard to check if a string is a valid agent source
1741
+ *
1742
+ * @public exported from `@promptbook/core`
1743
+ */
1744
+ function isAgentSource(value) {
1745
+ // Basic validation - agent source should have at least a name (first line)
1746
+ return typeof value === 'string' /* && value.trim().length > 0 */;
1747
+ }
1748
+ /**
1749
+ * Validates and converts a string to agent source branded type
1750
+ * This function should be used when you have a string that you know represents agent source
1751
+ * but need to convert it to the branded type for type safety
1752
+ *
1753
+ * @public exported from `@promptbook/core`
1754
+ */
1755
+ function validateAgentSource(source) {
1756
+ if (!isAgentSource(source)) {
1757
+ throw new Error('Invalid agent source: must be a non-empty string');
1758
+ }
1759
+ return source;
1760
+ }
1761
+
1762
+ /**
1763
+ * Creates an empty/basic agent model requirements object
1764
+ * This serves as the starting point for the reduce-like pattern
1765
+ * where each commitment applies its changes to build the final requirements
1766
+ *
1767
+ * @public exported from `@promptbook/core`
1768
+ */
1769
+ function createEmptyAgentModelRequirements() {
1770
+ return {
1771
+ systemMessage: '',
1772
+ modelName: '!!!!DEFAULT_MODEL_ID',
1773
+ temperature: 0.7,
1774
+ topP: 0.9,
1775
+ topK: 50,
1776
+ };
1777
+ }
1778
+ /**
1779
+ * Creates a basic agent model requirements with just the agent name
1780
+ * This is used when we have an agent name but no commitments
1781
+ *
1782
+ * @public exported from `@promptbook/core`
1783
+ */
1784
+ function createBasicAgentModelRequirements(agentName) {
1785
+ const empty = createEmptyAgentModelRequirements();
1786
+ return {
1787
+ ...empty,
1788
+ systemMessage: `You are ${agentName || 'AI Agent'}`,
1789
+ };
1790
+ }
1791
+ /**
1792
+ * TODO: !!!! Deduplicate model requirements
1793
+ */
1794
+
1795
+ /**
1796
+ * Removes comment lines (lines starting with #) from a system message
1797
+ * This is used to clean up the final system message before sending it to the AI model
1798
+ * while preserving the original content with comments in metadata
1799
+ *
1800
+ * @param systemMessage The system message that may contain comment lines
1801
+ * @returns The system message with comment lines removed
1802
+ *
1803
+ * @private - TODO: [🧠] Maybe should be public?
1804
+ */
1805
+ function removeCommentsFromSystemMessage(systemMessage) {
1806
+ if (!systemMessage) {
1807
+ return systemMessage;
1808
+ }
1809
+ const lines = systemMessage.split('\n');
1810
+ const filteredLines = lines.filter((line) => {
1811
+ const trimmedLine = line.trim();
1812
+ // Remove lines that start with # (comments)
1813
+ return !trimmedLine.startsWith('#');
1814
+ });
1815
+ return filteredLines.join('\n').trim();
1816
+ }
1817
+
1818
+ /**
1819
+ * Creates agent model requirements using the new commitment system
1820
+ * This function uses a reduce-like pattern where each commitment applies its changes
1821
+ * to build the final requirements starting from a basic empty model
1822
+ *
1823
+ * @private
1824
+ */
1825
+ async function createAgentModelRequirementsWithCommitments(agentSource, modelName) {
1826
+ // Parse the agent source to extract commitments
1827
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
1828
+ // Start with basic agent model requirements
1829
+ let requirements = createBasicAgentModelRequirements(parseResult.agentName);
1830
+ // Store the agent name in metadata so commitments can access it
1831
+ requirements = {
1832
+ ...requirements,
1833
+ metadata: {
1834
+ ...requirements.metadata,
1835
+ agentName: parseResult.agentName,
1836
+ },
1837
+ };
1838
+ // Override model name if provided
1839
+ if (modelName) {
1840
+ requirements = {
1841
+ ...requirements,
1842
+ modelName,
1843
+ };
1844
+ }
1845
+ // Apply each commitment in order using reduce-like pattern
1846
+ for (const commitment of parseResult.commitments) {
1847
+ const definition = getCommitmentDefinition(commitment.type);
1848
+ if (definition) {
1849
+ try {
1850
+ requirements = definition.applyToAgentModelRequirements(requirements, commitment.content);
1851
+ }
1852
+ catch (error) {
1853
+ console.warn(`Failed to apply commitment ${commitment.type}:`, error);
1854
+ // Continue with other commitments even if one fails
1855
+ }
1856
+ }
1857
+ }
1858
+ // Handle MCP servers (extract from original agent source)
1859
+ const mcpServers = extractMcpServers(agentSource);
1860
+ if (mcpServers.length > 0) {
1861
+ requirements = {
1862
+ ...requirements,
1863
+ mcpServers,
1864
+ };
1865
+ }
1866
+ // Add non-commitment lines to system message if they exist
1867
+ const nonCommitmentContent = parseResult.nonCommitmentLines
1868
+ .filter((line, index) => index > 0 || !parseResult.agentName) // Skip first line if it's the agent name
1869
+ .filter((line) => line.trim()) // Remove empty lines
1870
+ .join('\n')
1871
+ .trim();
1872
+ if (nonCommitmentContent) {
1873
+ requirements = {
1874
+ ...requirements,
1875
+ systemMessage: requirements.systemMessage + '\n\n' + nonCommitmentContent,
1876
+ };
1877
+ }
1878
+ // Remove comment lines (lines starting with #) from the final system message
1879
+ // while preserving the original content with comments in metadata
1880
+ const cleanedSystemMessage = removeCommentsFromSystemMessage(requirements.systemMessage);
1881
+ return {
1882
+ ...requirements,
1883
+ systemMessage: cleanedSystemMessage,
1884
+ };
1885
+ }
1886
+ /**
1887
+ * Cache for expensive createAgentModelRequirementsWithCommitments calls
1888
+ * @private
1889
+ */
1890
+ const modelRequirementsCache = new Map();
1891
+ /**
1892
+ * @private - TODO: Maybe should be public
1893
+ */
1894
+ const CACHE_SIZE_LIMIT = 100;
1895
+ /**
1896
+ * Cached version of createAgentModelRequirementsWithCommitments
1897
+ * This maintains the same caching behavior as the original function
1898
+ *
1899
+ * @private
1900
+ */
1901
+ async function createAgentModelRequirementsWithCommitmentsCached(agentSource, modelName) {
1902
+ // Create cache key
1903
+ const cacheKey = `${agentSource}|${modelName || 'default'}`;
1904
+ // Check cache first
1905
+ if (modelRequirementsCache.has(cacheKey)) {
1906
+ return modelRequirementsCache.get(cacheKey);
1907
+ }
1908
+ // Limit cache size to prevent memory leaks
1909
+ if (modelRequirementsCache.size >= CACHE_SIZE_LIMIT) {
1910
+ const firstKey = modelRequirementsCache.keys().next().value;
1911
+ if (firstKey) {
1912
+ modelRequirementsCache.delete(firstKey);
1913
+ }
1914
+ }
1915
+ // Create requirements
1916
+ const requirements = await createAgentModelRequirementsWithCommitments(agentSource, modelName);
1917
+ // Cache the result
1918
+ modelRequirementsCache.set(cacheKey, requirements);
1919
+ return requirements;
1920
+ }
1921
+
1922
+ // TODO: Remove or use:
1923
+ //const CACHE_SIZE_LIMIT = 100; // Prevent memory leaks by limiting cache size
1924
+ /**
1925
+ * Creates model requirements for an agent based on its source
1926
+ * Results are cached to improve performance for repeated calls with the same agentSource and modelName
1927
+ *
1928
+ * There are 2 similar functions:
1929
+ * - `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.
1930
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
485
1931
  *
486
1932
  * @public exported from `@promptbook/core`
487
1933
  */
488
- const DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME = `getPipelineCollection`;
1934
+ async function createAgentModelRequirements(agentSource, modelName = '!!!!DEFAULT_MODEL_ID') {
1935
+ // Use the new commitment-based system
1936
+ return createAgentModelRequirementsWithCommitmentsCached(agentSource, modelName);
1937
+ }
489
1938
  /**
490
- * Default rate limits (requests per minute)
1939
+ * Extracts MCP servers from agent source
491
1940
  *
492
- * Note: Adjust based on the provider tier you are have
1941
+ * @param agentSource The agent source string that may contain MCP lines
1942
+ * @returns Array of MCP server identifiers
493
1943
  *
494
- * @public exported from `@promptbook/core`
1944
+ * @private TODO: [🧠] Maybe should be public
495
1945
  */
496
- const DEFAULT_MAX_REQUESTS_PER_MINUTE = 60;
1946
+ function extractMcpServers(agentSource) {
1947
+ if (!agentSource) {
1948
+ return [];
1949
+ }
1950
+ const lines = agentSource.split('\n');
1951
+ const mcpRegex = /^\s*MCP\s+(.+)$/i;
1952
+ const mcpServers = [];
1953
+ // Look for MCP lines
1954
+ for (const line of lines) {
1955
+ const match = line.match(mcpRegex);
1956
+ if (match && match[1]) {
1957
+ mcpServers.push(match[1].trim());
1958
+ }
1959
+ }
1960
+ return mcpServers;
1961
+ }
1962
+
497
1963
  /**
498
- * Indicates whether pipeline logic validation is enabled. When true, the pipeline logic is checked for consistency.
1964
+ * Converts PipelineCollection to serialized JSON
499
1965
  *
500
- * @private within the repository
1966
+ * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
1967
+ *
1968
+ * @public exported from `@promptbook/core`
501
1969
  */
502
- const IS_PIPELINE_LOGIC_VALIDATED = just(
503
- /**/
504
- // Note: In normal situations, we check the pipeline logic:
505
- true);
1970
+ async function collectionToJson(collection) {
1971
+ const pipelineUrls = await collection.listPipelines();
1972
+ const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
1973
+ return promptbooks;
1974
+ }
506
1975
  /**
507
- * Note: [💞] Ignore a discrepancy between file name and entity name
508
- * TODO: [🧠][🧜‍♂️] Maybe join remoteServerUrl and path into single value
1976
+ * TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
509
1977
  */
510
1978
 
511
1979
  /**
512
- * Make error report URL for the given error
1980
+ * Checks if value is valid email
513
1981
  *
514
- * @private private within the repository
1982
+ * @public exported from `@promptbook/utils`
515
1983
  */
516
- function getErrorReportUrl(error) {
517
- const report = {
518
- title: `🐜 Error report from ${NAME}`,
519
- body: spaceTrim((block) => `
520
-
521
-
522
- \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
523
-
524
- \`\`\`
525
- ${block(error.message || '(no error message)')}
526
- \`\`\`
527
-
528
-
529
- ## More info:
530
-
531
- - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
532
- - **Book language version:** ${BOOK_LANGUAGE_VERSION}
533
- - **Time:** ${new Date().toISOString()}
534
-
535
- <details>
536
- <summary>Stack trace:</summary>
537
-
538
- ## Stack trace:
539
-
540
- \`\`\`stacktrace
541
- ${block(error.stack || '(empty)')}
542
- \`\`\`
543
- </details>
544
-
545
- `),
546
- };
547
- const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
548
- reportUrl.searchParams.set('labels', 'bug');
549
- reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
550
- reportUrl.searchParams.set('title', report.title);
551
- reportUrl.searchParams.set('body', report.body);
552
- return reportUrl;
1984
+ function isValidEmail(email) {
1985
+ if (typeof email !== 'string') {
1986
+ return false;
1987
+ }
1988
+ if (email.split('\n').length > 1) {
1989
+ return false;
1990
+ }
1991
+ return /^.+@.+\..+$/.test(email);
553
1992
  }
554
1993
 
555
1994
  /**
556
- * This error type indicates that the error should not happen and its last check before crashing with some other error
1995
+ * Tests if given string is valid URL.
557
1996
  *
558
- * @public exported from `@promptbook/core`
1997
+ * Note: This does not check if the file exists only if the path is valid
1998
+ * @public exported from `@promptbook/utils`
559
1999
  */
560
- class UnexpectedError extends Error {
561
- constructor(message) {
562
- super(spaceTrim$1((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);
2000
+ function isValidFilePath(filename) {
2001
+ if (typeof filename !== 'string') {
2002
+ return false;
2003
+ }
2004
+ if (filename.split('\n').length > 1) {
2005
+ return false;
2006
+ }
2007
+ if (filename.split(' ').length >
2008
+ 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
2009
+ return false;
2010
+ }
2011
+ const filenameSlashes = filename.split('\\').join('/');
2012
+ // Absolute Unix path: /hello.txt
2013
+ if (/^(\/)/i.test(filenameSlashes)) {
2014
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
2015
+ return true;
2016
+ }
2017
+ // Absolute Windows path: /hello.txt
2018
+ if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
2019
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
2020
+ return true;
2021
+ }
2022
+ // Relative path: ./hello.txt
2023
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
2024
+ // console.log(filename, 'Relative path: ./hello.txt');
2025
+ return true;
2026
+ }
2027
+ // Allow paths like foo/hello
2028
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
2029
+ // console.log(filename, 'Allow paths like foo/hello');
2030
+ return true;
2031
+ }
2032
+ // Allow paths like hello.book
2033
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
2034
+ // console.log(filename, 'Allow paths like hello.book');
2035
+ return true;
576
2036
  }
2037
+ return false;
577
2038
  }
2039
+ /**
2040
+ * TODO: [🍏] Implement for MacOs
2041
+ */
578
2042
 
579
2043
  /**
580
- * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
2044
+ * Tests if given string is valid URL.
581
2045
  *
582
- * @public exported from `@promptbook/core`
2046
+ * Note: Dataurl are considered perfectly valid.
2047
+ * Note: There are two similar functions:
2048
+ * - `isValidUrl` which tests any URL
2049
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
2050
+ *
2051
+ * @public exported from `@promptbook/utils`
583
2052
  */
584
- class WrappedError extends Error {
585
- constructor(whatWasThrown) {
586
- const tag = `[🤮]`;
587
- console.error(tag, whatWasThrown);
588
- super(spaceTrim$1(`
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);
2053
+ function isValidUrl(url) {
2054
+ if (typeof url !== 'string') {
2055
+ return false;
2056
+ }
2057
+ try {
2058
+ if (url.startsWith('blob:')) {
2059
+ url = url.replace(/^blob:/, '');
2060
+ }
2061
+ const urlObject = new URL(url /* because fail is handled */);
2062
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
2063
+ return false;
2064
+ }
2065
+ return true;
2066
+ }
2067
+ catch (error) {
2068
+ return false;
596
2069
  }
597
2070
  }
598
2071
 
599
2072
  /**
600
- * Helper used in catch blocks to assert that the error is an instance of `Error`
601
- *
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
2073
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
605
2074
  *
606
- * @private within the repository
2075
+ * @public exported from `@promptbook/core`
607
2076
  */
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;
613
- }
614
- // Case 2: Handle unexpected errors
615
- if (whatWasThrown instanceof UnexpectedError) {
616
- const unexpectedError = whatWasThrown;
617
- throw unexpectedError;
618
- }
619
- // Case 3: Handle standard errors - keep them up to consumer
620
- if (whatWasThrown instanceof Error) {
621
- return;
2077
+ class ParseError extends Error {
2078
+ constructor(message) {
2079
+ super(message);
2080
+ this.name = 'ParseError';
2081
+ Object.setPrototypeOf(this, ParseError.prototype);
622
2082
  }
623
- // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
624
- throw new WrappedError(whatWasThrown);
625
2083
  }
2084
+ /**
2085
+ * TODO: Maybe split `ParseError` and `ApplyError`
2086
+ */
626
2087
 
627
2088
  /**
628
2089
  * Function isValidJsonString will tell you if the string is valid JSON or not
@@ -2121,19 +3582,6 @@ class PipelineExecutionError extends Error {
2121
3582
  * TODO: [🧠][🌂] Add id to all errors
2122
3583
  */
2123
3584
 
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
3585
  /**
2138
3586
  * Index of all custom errors
2139
3587
  *
@@ -5835,37 +7283,6 @@ function titleToName(value) {
5835
7283
  return value;
5836
7284
  }
5837
7285
 
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((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
7286
  /**
5870
7287
  * Factory function that creates a handler for processing knowledge sources.
5871
7288
  * Provides standardized processing of different types of knowledge sources
@@ -11990,5 +13407,5 @@ class PrefixStorage {
11990
13407
  }
11991
13408
  }
11992
13409
 
11993
- export { $llmToolsMetadataRegister, $llmToolsRegister, $scrapersMetadataRegister, $scrapersRegister, ADMIN_EMAIL, ADMIN_GITHUB_NAME, AbstractFormatError, AuthenticationError, BIG_DATASET_TRESHOLD, BOOK_LANGUAGE_VERSION, BlackholeStorage, BoilerplateError, BoilerplateFormfactorDefinition, CLAIM, CLI_APP_ID, CallbackInterfaceTools, ChatbotFormfactorDefinition, CollectionError, CompletionFormfactorDefinition, CsvFormatError, CsvFormatParser, DEFAULT_BOOKS_DIRNAME, DEFAULT_BOOK_OUTPUT_PARAMETER_NAME, DEFAULT_BOOK_TITLE, DEFAULT_CSV_SETTINGS, DEFAULT_DOWNLOAD_CACHE_DIRNAME, DEFAULT_EXECUTION_CACHE_DIRNAME, DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME, DEFAULT_INTERMEDIATE_FILES_STRATEGY, DEFAULT_IS_AUTO_INSTALLED, DEFAULT_IS_VERBOSE, DEFAULT_MAX_EXECUTION_ATTEMPTS, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_KNOWLEDGE_SOURCES_SCRAPING_DEPTH, DEFAULT_MAX_KNOWLEDGE_SOURCES_SCRAPING_TOTAL, DEFAULT_MAX_PARALLEL_COUNT, DEFAULT_MAX_REQUESTS_PER_MINUTE, DEFAULT_PIPELINE_COLLECTION_BASE_FILENAME, DEFAULT_PROMPT_TASK_TITLE, DEFAULT_REMOTE_SERVER_URL, DEFAULT_SCRAPE_CACHE_DIRNAME, DEFAULT_TASK_TITLE, EXPECTATION_UNITS, EnvironmentMismatchError, ExecutionReportStringOptionsDefaults, ExpectError, FAILED_VALUE_PLACEHOLDER, FORMFACTOR_DEFINITIONS, GENERIC_PIPELINE_INTERFACE, GeneratorFormfactorDefinition, GenericFormfactorDefinition, ImageGeneratorFormfactorDefinition, KnowledgeScrapeError, LimitReachedError, MANDATORY_CSV_SETTINGS, MAX_FILENAME_LENGTH, MODEL_ORDERS, MODEL_TRUST_LEVELS, MODEL_VARIANTS, MatcherFormfactorDefinition, MemoryStorage, MissingToolsError, MultipleLlmExecutionTools, NAME, NonTaskSectionTypes, NotFoundError, NotYetImplementedError, ORDER_OF_PIPELINE_JSON, PENDING_VALUE_PLACEHOLDER, PLAYGROUND_APP_ID, PROMPTBOOK_ENGINE_VERSION, PROMPTBOOK_ERRORS, ParseError, PipelineExecutionError, PipelineLogicError, PipelineUrlError, PrefixStorage, PromptbookFetchError, REMOTE_SERVER_URLS, RESERVED_PARAMETER_NAMES, SET_IS_VERBOSE, SectionTypes, SheetsFormfactorDefinition, TaskTypes, TextFormatParser, TranslatorFormfactorDefinition, UNCERTAIN_USAGE, UNCERTAIN_ZERO_VALUE, UnexpectedError, WrappedError, ZERO_USAGE, ZERO_VALUE, _AnthropicClaudeMetadataRegistration, _AzureOpenAiMetadataRegistration, _BoilerplateScraperMetadataRegistration, _DeepseekMetadataRegistration, _DocumentScraperMetadataRegistration, _GoogleMetadataRegistration, _LegacyDocumentScraperMetadataRegistration, _MarkdownScraperMetadataRegistration, _MarkitdownScraperMetadataRegistration, _OllamaMetadataRegistration, _OpenAiAssistantMetadataRegistration, _OpenAiCompatibleMetadataRegistration, _OpenAiMetadataRegistration, _PdfScraperMetadataRegistration, _WebsiteScraperMetadataRegistration, addUsage, book, cacheLlmTools, collectionToJson, compilePipeline, computeCosineSimilarity, countUsage, createCollectionFromJson, createCollectionFromPromise, createCollectionFromUrl, createLlmToolsFromConfiguration, createPipelineExecutor, createSubcollection, embeddingVectorToString, executionReportJsonToString, extractParameterNamesFromTask, filterModels, getPipelineInterface, identificationToPromptbookToken, isPassingExpectations, isPipelineImplementingInterface, isPipelineInterfacesEqual, isPipelinePrepared, isValidPipelineString, joinLlmExecutionTools, limitTotalUsage, makeKnowledgeSourceHandler, migratePipeline, parsePipeline, pipelineJsonToString, prepareKnowledgePieces, preparePersona, preparePipeline, prettifyPipelineString, promptbookFetch, promptbookTokenToIdentification, unpreparePipeline, usageToHuman, usageToWorktime, validatePipeline, validatePipelineString };
13410
+ export { $llmToolsMetadataRegister, $llmToolsRegister, $scrapersMetadataRegister, $scrapersRegister, ADMIN_EMAIL, ADMIN_GITHUB_NAME, AbstractFormatError, AuthenticationError, BIG_DATASET_TRESHOLD, BOOK_LANGUAGE_VERSION, BlackholeStorage, BoilerplateError, BoilerplateFormfactorDefinition, CLAIM, CLI_APP_ID, CallbackInterfaceTools, ChatbotFormfactorDefinition, CollectionError, CompletionFormfactorDefinition, CsvFormatError, CsvFormatParser, DEFAULT_BOOKS_DIRNAME, DEFAULT_BOOK_OUTPUT_PARAMETER_NAME, DEFAULT_BOOK_TITLE, DEFAULT_CSV_SETTINGS, DEFAULT_DOWNLOAD_CACHE_DIRNAME, DEFAULT_EXECUTION_CACHE_DIRNAME, DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME, DEFAULT_INTERMEDIATE_FILES_STRATEGY, DEFAULT_IS_AUTO_INSTALLED, DEFAULT_IS_VERBOSE, DEFAULT_MAX_EXECUTION_ATTEMPTS, DEFAULT_MAX_FILE_SIZE, DEFAULT_MAX_KNOWLEDGE_SOURCES_SCRAPING_DEPTH, DEFAULT_MAX_KNOWLEDGE_SOURCES_SCRAPING_TOTAL, DEFAULT_MAX_PARALLEL_COUNT, DEFAULT_MAX_REQUESTS_PER_MINUTE, DEFAULT_PIPELINE_COLLECTION_BASE_FILENAME, DEFAULT_PROMPT_TASK_TITLE, DEFAULT_REMOTE_SERVER_URL, DEFAULT_SCRAPE_CACHE_DIRNAME, DEFAULT_TASK_TITLE, EXPECTATION_UNITS, EnvironmentMismatchError, ExecutionReportStringOptionsDefaults, ExpectError, FAILED_VALUE_PLACEHOLDER, FORMFACTOR_DEFINITIONS, GENERIC_PIPELINE_INTERFACE, GeneratorFormfactorDefinition, GenericFormfactorDefinition, ImageGeneratorFormfactorDefinition, KnowledgeScrapeError, LimitReachedError, MANDATORY_CSV_SETTINGS, MAX_FILENAME_LENGTH, MODEL_ORDERS, MODEL_TRUST_LEVELS, MODEL_VARIANTS, MatcherFormfactorDefinition, MemoryStorage, MissingToolsError, MultipleLlmExecutionTools, NAME, NonTaskSectionTypes, NotFoundError, NotYetImplementedCommitmentDefinition, NotYetImplementedError, ORDER_OF_PIPELINE_JSON, PENDING_VALUE_PLACEHOLDER, PLAYGROUND_APP_ID, PROMPTBOOK_ENGINE_VERSION, PROMPTBOOK_ERRORS, ParseError, PipelineExecutionError, PipelineLogicError, PipelineUrlError, PrefixStorage, PromptbookFetchError, REMOTE_SERVER_URLS, RESERVED_PARAMETER_NAMES, SET_IS_VERBOSE, SectionTypes, SheetsFormfactorDefinition, TaskTypes, TextFormatParser, TranslatorFormfactorDefinition, UNCERTAIN_USAGE, UNCERTAIN_ZERO_VALUE, UnexpectedError, WrappedError, ZERO_USAGE, ZERO_VALUE, _AnthropicClaudeMetadataRegistration, _AzureOpenAiMetadataRegistration, _BoilerplateScraperMetadataRegistration, _DeepseekMetadataRegistration, _DocumentScraperMetadataRegistration, _GoogleMetadataRegistration, _LegacyDocumentScraperMetadataRegistration, _MarkdownScraperMetadataRegistration, _MarkitdownScraperMetadataRegistration, _OllamaMetadataRegistration, _OpenAiAssistantMetadataRegistration, _OpenAiCompatibleMetadataRegistration, _OpenAiMetadataRegistration, _PdfScraperMetadataRegistration, _WebsiteScraperMetadataRegistration, addUsage, book, cacheLlmTools, collectionToJson, compilePipeline, computeCosineSimilarity, countUsage, createAgentModelRequirements, createBasicAgentModelRequirements, createCollectionFromJson, createCollectionFromPromise, createCollectionFromUrl, createEmptyAgentModelRequirements, createLlmToolsFromConfiguration, createPipelineExecutor, createSubcollection, embeddingVectorToString, executionReportJsonToString, extractParameterNamesFromTask, filterModels, getPipelineInterface, identificationToPromptbookToken, isAgentSource, isPassingExpectations, isPipelineImplementingInterface, isPipelineInterfacesEqual, isPipelinePrepared, isValidPipelineString, joinLlmExecutionTools, limitTotalUsage, makeKnowledgeSourceHandler, migratePipeline, parseAgentSource, parsePipeline, pipelineJsonToString, prepareKnowledgePieces, preparePersona, preparePipeline, prettifyPipelineString, promptbookFetch, promptbookTokenToIdentification, unpreparePipeline, usageToHuman, usageToWorktime, validateAgentSource, validatePipeline, validatePipelineString };
11994
13411
  //# sourceMappingURL=index.es.js.map