@promptbook/core 0.100.0-2 → 0.100.0-21

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 (49) hide show
  1. package/README.md +1 -0
  2. package/esm/index.es.js +1685 -203
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/typings/src/_packages/core.index.d.ts +24 -0
  5. package/esm/typings/src/_packages/types.index.d.ts +28 -0
  6. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.d.ts +30 -0
  7. package/esm/typings/src/book-2.0/agent-source/parseAgentSource.test.d.ts +1 -0
  8. package/esm/typings/src/book-2.0/agent-source/string_book.d.ts +26 -0
  9. package/esm/typings/src/book-2.0/commitments/ACTION/ACTION.d.ts +30 -0
  10. package/esm/typings/src/book-2.0/commitments/FORMAT/FORMAT.d.ts +31 -0
  11. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/FrontendRAGService.d.ts +48 -0
  12. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/KNOWLEDGE.d.ts +43 -0
  13. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/RAGService.d.ts +54 -0
  14. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/BaseKnowledgeProcessor.d.ts +45 -0
  15. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/PdfProcessor.d.ts +31 -0
  16. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/ProcessorFactory.d.ts +23 -0
  17. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/processors/TextProcessor.d.ts +18 -0
  18. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/types.d.ts +56 -0
  19. package/esm/typings/src/book-2.0/commitments/KNOWLEDGE/utils/ragHelper.d.ts +34 -0
  20. package/esm/typings/src/book-2.0/commitments/META_IMAGE/META_IMAGE.d.ts +36 -0
  21. package/esm/typings/src/book-2.0/commitments/META_LINK/META_LINK.d.ts +48 -0
  22. package/esm/typings/src/book-2.0/commitments/MODEL/MODEL.d.ts +31 -0
  23. package/esm/typings/src/book-2.0/commitments/NOTE/NOTE.d.ts +41 -0
  24. package/esm/typings/src/book-2.0/commitments/PERSONA/PERSONA.d.ts +38 -0
  25. package/esm/typings/src/book-2.0/commitments/RULE/RULE.d.ts +36 -0
  26. package/esm/typings/src/book-2.0/commitments/SAMPLE/SAMPLE.d.ts +36 -0
  27. package/esm/typings/src/book-2.0/commitments/STYLE/STYLE.d.ts +30 -0
  28. package/esm/typings/src/book-2.0/commitments/_base/BaseCommitmentDefinition.d.ts +42 -0
  29. package/esm/typings/src/book-2.0/commitments/_base/BookCommitment.d.ts +5 -0
  30. package/esm/typings/src/book-2.0/commitments/_base/CommitmentDefinition.d.ts +37 -0
  31. package/esm/typings/src/book-2.0/commitments/_base/NotYetImplementedCommitmentDefinition.d.ts +14 -0
  32. package/esm/typings/src/book-2.0/commitments/_base/createEmptyAgentModelRequirements.d.ts +19 -0
  33. package/esm/typings/src/book-2.0/commitments/_misc/AgentModelRequirements.d.ts +37 -0
  34. package/esm/typings/src/book-2.0/commitments/_misc/AgentSourceParseResult.d.ts +18 -0
  35. package/esm/typings/src/book-2.0/commitments/_misc/ParsedCommitment.d.ts +22 -0
  36. package/esm/typings/src/book-2.0/commitments/_misc/createAgentModelRequirements.d.ts +61 -0
  37. package/esm/typings/src/book-2.0/commitments/_misc/createAgentModelRequirementsWithCommitments.d.ts +35 -0
  38. package/esm/typings/src/book-2.0/commitments/_misc/createCommitmentRegex.d.ts +20 -0
  39. package/esm/typings/src/book-2.0/commitments/_misc/parseAgentSourceWithCommitments.d.ts +24 -0
  40. package/esm/typings/src/book-2.0/commitments/_misc/removeCommentsFromSystemMessage.d.ts +11 -0
  41. package/esm/typings/src/book-2.0/commitments/index.d.ts +56 -0
  42. package/esm/typings/src/book-2.0/utils/profileImageUtils.d.ts +39 -0
  43. package/esm/typings/src/pipeline/book-notation.d.ts +2 -1
  44. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  45. package/esm/typings/src/version.d.ts +1 -1
  46. package/esm/typings/src/wizard/wizard.d.ts +14 -4
  47. package/package.json +1 -1
  48. package/umd/index.umd.js +1696 -202
  49. package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js CHANGED
@@ -27,136 +27,258 @@
27
27
  * @generated
28
28
  * @see https://github.com/webgptorg/promptbook
29
29
  */
30
- const PROMPTBOOK_ENGINE_VERSION = '0.100.0-2';
30
+ const PROMPTBOOK_ENGINE_VERSION = '0.100.0-21';
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
+ applyToAgentModelRequirements(requirements, content) {
211
+ const trimmedContent = content.trim();
212
+ if (!trimmedContent) {
213
+ return requirements;
214
+ }
215
+ // Add action capability to the system message
216
+ const actionSection = `Capability: ${trimmedContent}`;
217
+ return this.appendToSystemMessage(requirements, actionSection, '\n\n');
109
218
  }
110
- return false;
111
219
  }
112
220
  /**
113
- * TODO: [🍏] Implement for MacOs
221
+ * Singleton instance of the ACTION commitment definition
222
+ *
223
+ * @private [🪔] Maybe export the commitments through some package
224
+ */
225
+ new ActionCommitmentDefinition();
226
+ /**
227
+ * Note: [💞] Ignore a discrepancy between file name and entity name
114
228
  */
115
229
 
116
230
  /**
117
- * Tests if given string is valid URL.
231
+ * FORMAT commitment definition
118
232
  *
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
233
+ * The FORMAT commitment defines the specific output structure and formatting
234
+ * that the agent should use in its responses. This includes data formats,
235
+ * response templates, and structural requirements.
123
236
  *
124
- * @public exported from `@promptbook/utils`
237
+ * Example usage in agent source:
238
+ *
239
+ * ```book
240
+ * FORMAT Always respond in JSON format with 'status' and 'data' fields
241
+ * FORMAT Use markdown formatting for all code blocks
242
+ * ```
243
+ *
244
+ * @private [🪔] Maybe export the commitments through some package
125
245
  */
126
- function isValidUrl(url) {
127
- if (typeof url !== 'string') {
128
- return false;
246
+ class FormatCommitmentDefinition extends BaseCommitmentDefinition {
247
+ constructor() {
248
+ super('FORMAT');
129
249
  }
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;
250
+ applyToAgentModelRequirements(requirements, content) {
251
+ const trimmedContent = content.trim();
252
+ if (!trimmedContent) {
253
+ return requirements;
137
254
  }
138
- return true;
139
- }
140
- catch (error) {
141
- return false;
255
+ // Add format instructions to the system message
256
+ const formatSection = `Output Format: ${trimmedContent}`;
257
+ return this.appendToSystemMessage(requirements, formatSection, '\n\n');
142
258
  }
143
259
  }
260
+ /**
261
+ * Singleton instance of the FORMAT commitment definition
262
+ *
263
+ * @private [🪔] Maybe export the commitments through some package
264
+ */
265
+ new FormatCommitmentDefinition();
266
+ /**
267
+ * Note: [💞] Ignore a discrepancy between file name and entity name
268
+ */
144
269
 
145
270
  /**
146
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
271
+ * Error thrown when a fetch request fails
147
272
  *
148
273
  * @public exported from `@promptbook/core`
149
274
  */
150
- class ParseError extends Error {
275
+ class PromptbookFetchError extends Error {
151
276
  constructor(message) {
152
277
  super(message);
153
- this.name = 'ParseError';
154
- Object.setPrototypeOf(this, ParseError.prototype);
278
+ this.name = 'PromptbookFetchError';
279
+ Object.setPrototypeOf(this, PromptbookFetchError.prototype);
155
280
  }
156
281
  }
157
- /**
158
- * TODO: Maybe split `ParseError` and `ApplyError`
159
- */
160
282
 
161
283
  /**
162
284
  * Available remote servers for the Promptbook
@@ -553,76 +675,1480 @@
553
675
  }
554
676
 
555
677
  /**
556
- * This error type indicates that the error should not happen and its last check before crashing with some other error
678
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
679
+ *
680
+ * @public exported from `@promptbook/core`
681
+ */
682
+ class UnexpectedError extends Error {
683
+ constructor(message) {
684
+ super(spaceTrim.spaceTrim((block) => `
685
+ ${block(message)}
686
+
687
+ Note: This error should not happen.
688
+ It's probably a bug in the pipeline collection
689
+
690
+ Please report issue:
691
+ ${block(getErrorReportUrl(new Error(message)).href)}
692
+
693
+ Or contact us on ${ADMIN_EMAIL}
694
+
695
+ `));
696
+ this.name = 'UnexpectedError';
697
+ Object.setPrototypeOf(this, UnexpectedError.prototype);
698
+ }
699
+ }
700
+
701
+ /**
702
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
703
+ *
704
+ * @public exported from `@promptbook/core`
705
+ */
706
+ class WrappedError extends Error {
707
+ constructor(whatWasThrown) {
708
+ const tag = `[🤮]`;
709
+ console.error(tag, whatWasThrown);
710
+ super(spaceTrim.spaceTrim(`
711
+ Non-Error object was thrown
712
+
713
+ Note: Look for ${tag} in the console for more details
714
+ Please report issue on ${ADMIN_EMAIL}
715
+ `));
716
+ this.name = 'WrappedError';
717
+ Object.setPrototypeOf(this, WrappedError.prototype);
718
+ }
719
+ }
720
+
721
+ /**
722
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
723
+ *
724
+ * @param whatWasThrown Any object that was thrown
725
+ * @returns Nothing if the error is an instance of `Error`
726
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
727
+ *
728
+ * @private within the repository
729
+ */
730
+ function assertsError(whatWasThrown) {
731
+ // Case 1: Handle error which was rethrown as `WrappedError`
732
+ if (whatWasThrown instanceof WrappedError) {
733
+ const wrappedError = whatWasThrown;
734
+ throw wrappedError;
735
+ }
736
+ // Case 2: Handle unexpected errors
737
+ if (whatWasThrown instanceof UnexpectedError) {
738
+ const unexpectedError = whatWasThrown;
739
+ throw unexpectedError;
740
+ }
741
+ // Case 3: Handle standard errors - keep them up to consumer
742
+ if (whatWasThrown instanceof Error) {
743
+ return;
744
+ }
745
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
746
+ throw new WrappedError(whatWasThrown);
747
+ }
748
+
749
+ /**
750
+ * The built-in `fetch' function with a lightweight error handling wrapper as default fetch function used in Promptbook scrapers
751
+ *
752
+ * @public exported from `@promptbook/core`
753
+ */
754
+ const promptbookFetch = async (urlOrRequest, init) => {
755
+ try {
756
+ return await fetch(urlOrRequest, init);
757
+ }
758
+ catch (error) {
759
+ assertsError(error);
760
+ let url;
761
+ if (typeof urlOrRequest === 'string') {
762
+ url = urlOrRequest;
763
+ }
764
+ else if (urlOrRequest instanceof Request) {
765
+ url = urlOrRequest.url;
766
+ }
767
+ throw new PromptbookFetchError(spaceTrim__default["default"]((block) => `
768
+ Can not fetch "${url}"
769
+
770
+ Fetch error:
771
+ ${block(error.message)}
772
+
773
+ `));
774
+ }
775
+ };
776
+ /**
777
+ * TODO: [🧠] Maybe rename because it is not used only for scrapers but also in `$getCompiledBook`
778
+ */
779
+
780
+ /**
781
+ * Frontend RAG Service that uses backend APIs for processing
782
+ * This avoids Node.js dependencies in the frontend
783
+ *
784
+ * @private - TODO: [🧠] Maybe should be public?
785
+ */
786
+ class FrontendRAGService {
787
+ constructor(config) {
788
+ this.chunks = [];
789
+ this.sources = [];
790
+ this.isInitialized = false;
791
+ this.config = {
792
+ maxChunkSize: 1000,
793
+ chunkOverlap: 200,
794
+ maxRetrievedChunks: 5,
795
+ minRelevanceScore: 0.1,
796
+ ...config,
797
+ };
798
+ }
799
+ /**
800
+ * Initialize knowledge sources by processing them on the backend
801
+ */
802
+ async initializeKnowledgeSources(sources) {
803
+ if (sources.length === 0) {
804
+ this.isInitialized = true;
805
+ return;
806
+ }
807
+ try {
808
+ const response = await promptbookFetch('/api/knowledge/process-sources', {
809
+ method: 'POST',
810
+ headers: {
811
+ 'Content-Type': 'application/json',
812
+ },
813
+ body: JSON.stringify({
814
+ sources,
815
+ config: {
816
+ maxChunkSize: this.config.maxChunkSize,
817
+ chunkOverlap: this.config.chunkOverlap,
818
+ },
819
+ }),
820
+ });
821
+ if (!response.ok) {
822
+ throw new Error(`Failed to process knowledge sources: ${response.status}`);
823
+ }
824
+ const result = (await response.json());
825
+ if (!result.success) {
826
+ throw new Error(result.message || 'Failed to process knowledge sources');
827
+ }
828
+ this.chunks = result.chunks;
829
+ this.sources = sources;
830
+ this.isInitialized = true;
831
+ console.log(`Initialized RAG service with ${this.chunks.length} chunks from ${sources.length} sources`);
832
+ }
833
+ catch (error) {
834
+ console.error('Failed to initialize knowledge sources:', error);
835
+ // Don't throw - allow the system to continue without RAG
836
+ this.isInitialized = true;
837
+ }
838
+ }
839
+ /**
840
+ * Get relevant context for a user query
841
+ */
842
+ async getContextForQuery(query) {
843
+ if (!this.isInitialized) {
844
+ console.warn('RAG service not initialized');
845
+ return '';
846
+ }
847
+ if (this.chunks.length === 0) {
848
+ return '';
849
+ }
850
+ try {
851
+ const response = await promptbookFetch('/api/knowledge/retrieve-context', {
852
+ method: 'POST',
853
+ headers: {
854
+ 'Content-Type': 'application/json',
855
+ },
856
+ body: JSON.stringify({
857
+ query,
858
+ chunks: this.chunks,
859
+ config: {
860
+ maxRetrievedChunks: this.config.maxRetrievedChunks,
861
+ minRelevanceScore: this.config.minRelevanceScore,
862
+ },
863
+ }),
864
+ });
865
+ if (!response.ok) {
866
+ console.error(`Failed to retrieve context: ${response.status}`);
867
+ return '';
868
+ }
869
+ const result = (await response.json());
870
+ if (!result.success) {
871
+ console.error('Context retrieval failed:', result.message);
872
+ return '';
873
+ }
874
+ return result.context;
875
+ }
876
+ catch (error) {
877
+ console.error('Error retrieving context:', error);
878
+ return '';
879
+ }
880
+ }
881
+ /**
882
+ * Get relevant chunks for a query (for debugging/inspection)
883
+ */
884
+ async getRelevantChunks(query) {
885
+ if (!this.isInitialized || this.chunks.length === 0) {
886
+ return [];
887
+ }
888
+ try {
889
+ const response = await promptbookFetch('/api/knowledge/retrieve-context', {
890
+ method: 'POST',
891
+ headers: {
892
+ 'Content-Type': 'application/json',
893
+ },
894
+ body: JSON.stringify({
895
+ query,
896
+ chunks: this.chunks,
897
+ config: {
898
+ maxRetrievedChunks: this.config.maxRetrievedChunks,
899
+ minRelevanceScore: this.config.minRelevanceScore,
900
+ },
901
+ }),
902
+ });
903
+ if (!response.ok) {
904
+ return [];
905
+ }
906
+ const result = (await response.json());
907
+ return result.success ? result.relevantChunks : [];
908
+ }
909
+ catch (error) {
910
+ console.error('Error retrieving relevant chunks:', error);
911
+ return [];
912
+ }
913
+ }
914
+ /**
915
+ * Get knowledge base statistics
916
+ */
917
+ getStats() {
918
+ return {
919
+ sources: this.sources.length,
920
+ chunks: this.chunks.length,
921
+ isInitialized: this.isInitialized,
922
+ };
923
+ }
924
+ /**
925
+ * Check if the service is ready to use
926
+ */
927
+ isReady() {
928
+ return this.isInitialized;
929
+ }
930
+ /**
931
+ * Clear all knowledge sources
932
+ */
933
+ clearKnowledgeBase() {
934
+ this.chunks = [];
935
+ this.sources = [];
936
+ this.isInitialized = false;
937
+ }
938
+ /**
939
+ * Add a single knowledge source (for incremental updates)
940
+ */
941
+ async addKnowledgeSource(url) {
942
+ if (this.sources.includes(url)) {
943
+ console.log(`Knowledge source already exists: ${url}`);
944
+ return;
945
+ }
946
+ try {
947
+ const response = await promptbookFetch('/api/knowledge/process-sources', {
948
+ method: 'POST',
949
+ headers: {
950
+ 'Content-Type': 'application/json',
951
+ },
952
+ body: JSON.stringify({
953
+ sources: [url],
954
+ config: {
955
+ maxChunkSize: this.config.maxChunkSize,
956
+ chunkOverlap: this.config.chunkOverlap,
957
+ },
958
+ }),
959
+ });
960
+ if (!response.ok) {
961
+ throw new Error(`Failed to process knowledge source: ${response.status}`);
962
+ }
963
+ const result = (await response.json());
964
+ if (!result.success) {
965
+ throw new Error(result.message || 'Failed to process knowledge source');
966
+ }
967
+ // Add new chunks to existing ones
968
+ this.chunks.push(...result.chunks);
969
+ this.sources.push(url);
970
+ console.log(`Added knowledge source: ${url} (${result.chunks.length} chunks)`);
971
+ }
972
+ catch (error) {
973
+ console.error(`Failed to add knowledge source ${url}:`, error);
974
+ throw error;
975
+ }
976
+ }
977
+ }
978
+
979
+ /**
980
+ * KNOWLEDGE commitment definition
981
+ *
982
+ * The KNOWLEDGE commitment adds specific knowledge, facts, or context to the agent
983
+ * using RAG (Retrieval-Augmented Generation) approach for external sources.
984
+ *
985
+ * Supports both direct text knowledge and external sources like PDFs.
986
+ *
987
+ * Example usage in agent source:
988
+ *
989
+ * ```book
990
+ * KNOWLEDGE The company was founded in 2020 and specializes in AI-powered solutions
991
+ * KNOWLEDGE https://example.com/company-handbook.pdf
992
+ * KNOWLEDGE https://example.com/product-documentation.pdf
993
+ * ```
994
+ *
995
+ * @private [🪔] Maybe export the commitments through some package
996
+ */
997
+ class KnowledgeCommitmentDefinition extends BaseCommitmentDefinition {
998
+ constructor() {
999
+ super('KNOWLEDGE');
1000
+ this.ragService = new FrontendRAGService();
1001
+ }
1002
+ applyToAgentModelRequirements(requirements, content) {
1003
+ var _a;
1004
+ const trimmedContent = content.trim();
1005
+ if (!trimmedContent) {
1006
+ return requirements;
1007
+ }
1008
+ // Check if content is a URL (external knowledge source)
1009
+ if (this.isUrl(trimmedContent)) {
1010
+ // Store the URL for later async processing
1011
+ const updatedRequirements = {
1012
+ ...requirements,
1013
+ metadata: {
1014
+ ...requirements.metadata,
1015
+ ragService: this.ragService,
1016
+ knowledgeSources: [
1017
+ ...(((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.knowledgeSources) || []),
1018
+ trimmedContent,
1019
+ ],
1020
+ },
1021
+ };
1022
+ // Add placeholder information about knowledge sources to system message
1023
+ const knowledgeInfo = `Knowledge Source URL: ${trimmedContent} (will be processed for retrieval during chat)`;
1024
+ return this.appendToSystemMessage(updatedRequirements, knowledgeInfo, '\n\n');
1025
+ }
1026
+ else {
1027
+ // Direct text knowledge - add to system message
1028
+ const knowledgeSection = `Knowledge: ${trimmedContent}`;
1029
+ return this.appendToSystemMessage(requirements, knowledgeSection, '\n\n');
1030
+ }
1031
+ }
1032
+ /**
1033
+ * Check if content is a URL
1034
+ */
1035
+ isUrl(content) {
1036
+ try {
1037
+ new URL(content);
1038
+ return true;
1039
+ }
1040
+ catch (_a) {
1041
+ return false;
1042
+ }
1043
+ }
1044
+ /**
1045
+ * Get RAG service instance for retrieving context during chat
1046
+ */
1047
+ getRagService() {
1048
+ return this.ragService;
1049
+ }
1050
+ }
1051
+ /**
1052
+ * Singleton instance of the KNOWLEDGE commitment definition
1053
+ *
1054
+ * @private [🪔] Maybe export the commitments through some package
1055
+ */
1056
+ new KnowledgeCommitmentDefinition();
1057
+ /**
1058
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1059
+ */
1060
+
1061
+ /**
1062
+ * META IMAGE commitment definition
1063
+ *
1064
+ * The META IMAGE commitment sets the agent's avatar/profile image URL.
1065
+ * This commitment is special because it doesn't affect the system message,
1066
+ * but is handled separately in the parsing logic.
1067
+ *
1068
+ * Example usage in agent source:
1069
+ *
1070
+ * ```book
1071
+ * META IMAGE https://example.com/avatar.jpg
1072
+ * META IMAGE /assets/agent-avatar.png
1073
+ * ```
1074
+ *
1075
+ * @private [🪔] Maybe export the commitments through some package
1076
+ */
1077
+ class MetaImageCommitmentDefinition extends BaseCommitmentDefinition {
1078
+ constructor() {
1079
+ super('META IMAGE');
1080
+ }
1081
+ applyToAgentModelRequirements(requirements, content) {
1082
+ // META IMAGE doesn't modify the system message or model requirements
1083
+ // It's handled separately in the parsing logic for profile image extraction
1084
+ // This method exists for consistency with the CommitmentDefinition interface
1085
+ return requirements;
1086
+ }
1087
+ /**
1088
+ * Extracts the profile image URL from the content
1089
+ * This is used by the parsing logic
1090
+ */
1091
+ extractProfileImageUrl(content) {
1092
+ const trimmedContent = content.trim();
1093
+ return trimmedContent || null;
1094
+ }
1095
+ }
1096
+ /**
1097
+ * Singleton instance of the META IMAGE commitment definition
1098
+ *
1099
+ * @private [🪔] Maybe export the commitments through some package
1100
+ */
1101
+ new MetaImageCommitmentDefinition();
1102
+ /**
1103
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1104
+ */
1105
+
1106
+ /**
1107
+ * META LINK commitment definition
1108
+ *
1109
+ * The `META LINK` commitment represents the link to the person from whom the agent is created.
1110
+ * This commitment is special because it doesn't affect the system message,
1111
+ * but is handled separately in the parsing logic for profile display.
1112
+ *
1113
+ * Example usage in agent source:
1114
+ *
1115
+ * ```
1116
+ * META LINK https://twitter.com/username
1117
+ * META LINK https://linkedin.com/in/profile
1118
+ * META LINK https://github.com/username
1119
+ * ```
1120
+ *
1121
+ * Multiple `META LINK` commitments can be used when there are multiple sources:
1122
+ *
1123
+ * ```book
1124
+ * META LINK https://twitter.com/username
1125
+ * META LINK https://linkedin.com/in/profile
1126
+ * ```
1127
+ *
1128
+ * @private [🪔] Maybe export the commitments through some package
1129
+ */
1130
+ class MetaLinkCommitmentDefinition extends BaseCommitmentDefinition {
1131
+ constructor() {
1132
+ super('META LINK');
1133
+ }
1134
+ applyToAgentModelRequirements(requirements, content) {
1135
+ // META LINK doesn't modify the system message or model requirements
1136
+ // It's handled separately in the parsing logic for profile link extraction
1137
+ // This method exists for consistency with the CommitmentDefinition interface
1138
+ return requirements;
1139
+ }
1140
+ /**
1141
+ * Extracts the profile link URL from the content
1142
+ * This is used by the parsing logic
1143
+ */
1144
+ extractProfileLinkUrl(content) {
1145
+ const trimmedContent = content.trim();
1146
+ return trimmedContent || null;
1147
+ }
1148
+ /**
1149
+ * Validates if the provided content is a valid URL
1150
+ */
1151
+ isValidUrl(content) {
1152
+ try {
1153
+ new URL(content.trim());
1154
+ return true;
1155
+ }
1156
+ catch (_a) {
1157
+ return false;
1158
+ }
1159
+ }
1160
+ }
1161
+ /**
1162
+ * Singleton instance of the META LINK commitment definition
1163
+ *
1164
+ * @private [🪔] Maybe export the commitments through some package
1165
+ */
1166
+ new MetaLinkCommitmentDefinition();
1167
+ /**
1168
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1169
+ */
1170
+
1171
+ /**
1172
+ * MODEL commitment definition
1173
+ *
1174
+ * The MODEL commitment specifies which AI model to use and can also set
1175
+ * model-specific parameters like temperature, topP, and topK.
1176
+ *
1177
+ * Example usage in agent source:
1178
+ *
1179
+ * ```book
1180
+ * MODEL gpt-4
1181
+ * MODEL claude-3-opus temperature=0.3
1182
+ * MODEL gpt-3.5-turbo temperature=0.8 topP=0.9
1183
+ * ```
1184
+ *
1185
+ * @private [🪔] Maybe export the commitments through some package
1186
+ */
1187
+ class ModelCommitmentDefinition extends BaseCommitmentDefinition {
1188
+ constructor() {
1189
+ super('MODEL');
1190
+ }
1191
+ applyToAgentModelRequirements(requirements, content) {
1192
+ const trimmedContent = content.trim();
1193
+ if (!trimmedContent) {
1194
+ return requirements;
1195
+ }
1196
+ // Parse the model specification
1197
+ const parts = trimmedContent.split(/\s+/);
1198
+ const modelName = parts[0];
1199
+ if (!modelName) {
1200
+ return requirements;
1201
+ }
1202
+ // Start with the model name
1203
+ const updatedRequirements = {
1204
+ ...requirements,
1205
+ modelName,
1206
+ };
1207
+ // Parse additional parameters
1208
+ const result = { ...updatedRequirements };
1209
+ for (let i = 1; i < parts.length; i++) {
1210
+ const param = parts[i];
1211
+ if (param && param.includes('=')) {
1212
+ const [key, value] = param.split('=');
1213
+ if (key && value) {
1214
+ const numValue = parseFloat(value);
1215
+ if (!isNaN(numValue)) {
1216
+ switch (key.toLowerCase()) {
1217
+ case 'temperature':
1218
+ result.temperature = numValue;
1219
+ break;
1220
+ case 'topp':
1221
+ case 'top_p':
1222
+ result.topP = numValue;
1223
+ break;
1224
+ case 'topk':
1225
+ case 'top_k':
1226
+ result.topK = Math.round(numValue);
1227
+ break;
1228
+ }
1229
+ }
1230
+ }
1231
+ }
1232
+ }
1233
+ return result;
1234
+ }
1235
+ }
1236
+ /**
1237
+ * Singleton instance of the MODEL commitment definition
1238
+ *
1239
+ * @private [🪔] Maybe export the commitments through some package
1240
+ */
1241
+ new ModelCommitmentDefinition();
1242
+ /**
1243
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1244
+ */
1245
+
1246
+ /**
1247
+ * NOTE commitment definition
1248
+ *
1249
+ * The NOTE commitment is used to add comments to the agent source without making any changes
1250
+ * to the system message or agent model requirements. It serves as a documentation mechanism
1251
+ * for developers to add explanatory comments, reminders, or annotations directly in the agent source.
1252
+ *
1253
+ * Key features:
1254
+ * - Makes no changes to the system message
1255
+ * - Makes no changes to agent model requirements
1256
+ * - Content is preserved in metadata.NOTE for debugging and inspection
1257
+ * - Multiple NOTE commitments are aggregated together
1258
+ * - Comments (# NOTE) are removed from the final system message
1259
+ *
1260
+ * Example usage in agent source:
1261
+ *
1262
+ * ```book
1263
+ * NOTE This agent was designed for customer support scenarios
1264
+ * NOTE Remember to update the knowledge base monthly
1265
+ * NOTE Performance optimized for quick response times
1266
+ * ```
1267
+ *
1268
+ * The above notes will be stored in metadata but won't affect the agent's behavior.
1269
+ *
1270
+ * @private [🪔] Maybe export the commitments through some package
1271
+ */
1272
+ class NoteCommitmentDefinition extends BaseCommitmentDefinition {
1273
+ constructor() {
1274
+ super('NOTE');
1275
+ }
1276
+ applyToAgentModelRequirements(requirements, content) {
1277
+ var _a;
1278
+ // The NOTE commitment makes no changes to the system message or model requirements
1279
+ // It only stores the note content in metadata for documentation purposes
1280
+ const trimmedContent = content.trim();
1281
+ if (!trimmedContent) {
1282
+ return requirements;
1283
+ }
1284
+ // Get existing note content from metadata
1285
+ const existingNoteContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.NOTE) || '';
1286
+ // Merge the new content with existing note content
1287
+ // When multiple NOTE commitments exist, they are aggregated together
1288
+ const mergedNoteContent = existingNoteContent ? `${existingNoteContent}\n${trimmedContent}` : trimmedContent;
1289
+ // Store the merged note content in metadata for debugging and inspection
1290
+ const updatedMetadata = {
1291
+ ...requirements.metadata,
1292
+ NOTE: mergedNoteContent,
1293
+ };
1294
+ // Return requirements with updated metadata but no changes to system message
1295
+ return {
1296
+ ...requirements,
1297
+ metadata: updatedMetadata,
1298
+ };
1299
+ }
1300
+ }
1301
+ /**
1302
+ * Singleton instance of the NOTE commitment definition
1303
+ *
1304
+ * @private [🪔] Maybe export the commitments through some package
1305
+ */
1306
+ new NoteCommitmentDefinition();
1307
+ /**
1308
+ * [💞] Ignore a discrepancy between file name and entity name
1309
+ */
1310
+
1311
+ /**
1312
+ * PERSONA commitment definition
1313
+ *
1314
+ * The PERSONA commitment modifies the agent's personality and character in the system message.
1315
+ * It defines who the agent is, their background, expertise, and personality traits.
1316
+ *
1317
+ * Key features:
1318
+ * - Multiple PERSONA commitments are automatically merged into one
1319
+ * - Content is placed at the beginning of the system message
1320
+ * - Original content with comments is preserved in metadata.PERSONA
1321
+ * - Comments (# PERSONA) are removed from the final system message
1322
+ *
1323
+ * Example usage in agent source:
1324
+ *
1325
+ * ```book
1326
+ * PERSONA You are a helpful programming assistant with expertise in TypeScript and React
1327
+ * PERSONA You have deep knowledge of modern web development practices
1328
+ * ```
1329
+ *
1330
+ * The above will be merged into a single persona section at the beginning of the system message.
1331
+ *
1332
+ * @private [🪔] Maybe export the commitments through some package
1333
+ */
1334
+ class PersonaCommitmentDefinition extends BaseCommitmentDefinition {
1335
+ constructor() {
1336
+ super('PERSONA');
1337
+ }
1338
+ applyToAgentModelRequirements(requirements, content) {
1339
+ var _a, _b;
1340
+ // The PERSONA commitment aggregates all persona content and places it at the beginning
1341
+ const trimmedContent = content.trim();
1342
+ if (!trimmedContent) {
1343
+ return requirements;
1344
+ }
1345
+ // Get existing persona content from metadata
1346
+ const existingPersonaContent = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.PERSONA) || '';
1347
+ // Merge the new content with existing persona content
1348
+ // When multiple PERSONA commitments exist, they are merged into one
1349
+ const mergedPersonaContent = existingPersonaContent
1350
+ ? `${existingPersonaContent}\n${trimmedContent}`
1351
+ : trimmedContent;
1352
+ // Store the merged persona content in metadata for debugging and inspection
1353
+ const updatedMetadata = {
1354
+ ...requirements.metadata,
1355
+ PERSONA: mergedPersonaContent,
1356
+ };
1357
+ // Get the agent name from metadata (which should contain the first line of agent source)
1358
+ // If not available, extract from current system message as fallback
1359
+ let agentName = (_b = requirements.metadata) === null || _b === void 0 ? void 0 : _b.agentName;
1360
+ if (!agentName) {
1361
+ // Fallback: extract from current system message
1362
+ const currentMessage = requirements.systemMessage.trim();
1363
+ const basicFormatMatch = currentMessage.match(/^You are (.+)$/);
1364
+ if (basicFormatMatch && basicFormatMatch[1]) {
1365
+ agentName = basicFormatMatch[1];
1366
+ }
1367
+ else {
1368
+ agentName = 'AI Agent'; // Final fallback
1369
+ }
1370
+ }
1371
+ // Remove any existing persona content from the system message
1372
+ // (this handles the case where we're processing multiple PERSONA commitments)
1373
+ const currentMessage = requirements.systemMessage.trim();
1374
+ let cleanedMessage = currentMessage;
1375
+ // Check if current message starts with persona content or is just the basic format
1376
+ const basicFormatRegex = /^You are .+$/;
1377
+ const isBasicFormat = basicFormatRegex.test(currentMessage) && !currentMessage.includes('\n');
1378
+ if (isBasicFormat) {
1379
+ // Replace the basic format entirely
1380
+ cleanedMessage = '';
1381
+ }
1382
+ else if (currentMessage.startsWith('# PERSONA')) {
1383
+ // Remove existing persona section by finding where it ends
1384
+ const lines = currentMessage.split('\n');
1385
+ let personaEndIndex = lines.length;
1386
+ // Find the end of the PERSONA section (next comment or end of message)
1387
+ for (let i = 1; i < lines.length; i++) {
1388
+ const line = lines[i].trim();
1389
+ if (line.startsWith('#') && !line.startsWith('# PERSONA')) {
1390
+ personaEndIndex = i;
1391
+ break;
1392
+ }
1393
+ }
1394
+ // Keep everything after the PERSONA section
1395
+ cleanedMessage = lines.slice(personaEndIndex).join('\n').trim();
1396
+ }
1397
+ // Create new system message with persona at the beginning
1398
+ // Format: "You are {agentName}\n{personaContent}"
1399
+ // The # PERSONA comment will be removed later by removeCommentsFromSystemMessage
1400
+ const personaSection = `# PERSONA\nYou are ${agentName}\n${mergedPersonaContent}`; // <- TODO: Use spaceTrim
1401
+ const newSystemMessage = cleanedMessage ? `${personaSection}\n\n${cleanedMessage}` : personaSection;
1402
+ return {
1403
+ ...requirements,
1404
+ systemMessage: newSystemMessage,
1405
+ metadata: updatedMetadata,
1406
+ };
1407
+ }
1408
+ }
1409
+ /**
1410
+ * Singleton instance of the PERSONA commitment definition
1411
+ *
1412
+ * @private [🪔] Maybe export the commitments through some package
1413
+ */
1414
+ new PersonaCommitmentDefinition();
1415
+ /**
1416
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1417
+ */
1418
+
1419
+ /**
1420
+ * RULE commitment definition
1421
+ *
1422
+ * The RULE/RULES commitment adds behavioral constraints and guidelines that the agent must follow.
1423
+ * These are specific instructions about what the agent should or shouldn't do.
1424
+ *
1425
+ * Example usage in agent source:
1426
+ *
1427
+ * ```book
1428
+ * RULE Always ask for clarification if the user's request is ambiguous
1429
+ * RULES Never provide medical advice, always refer to healthcare professionals
1430
+ * ```
1431
+ *
1432
+ * @private [🪔] Maybe export the commitments through some package
1433
+ */
1434
+ class RuleCommitmentDefinition extends BaseCommitmentDefinition {
1435
+ constructor(type = 'RULE') {
1436
+ super(type);
1437
+ }
1438
+ applyToAgentModelRequirements(requirements, content) {
1439
+ const trimmedContent = content.trim();
1440
+ if (!trimmedContent) {
1441
+ return requirements;
1442
+ }
1443
+ // Add rule to the system message
1444
+ const ruleSection = `Rule: ${trimmedContent}`;
1445
+ return this.appendToSystemMessage(requirements, ruleSection, '\n\n');
1446
+ }
1447
+ }
1448
+ /**
1449
+ * Singleton instances of the RULE commitment definitions
1450
+ *
1451
+ * @private [🪔] Maybe export the commitments through some package
1452
+ */
1453
+ new RuleCommitmentDefinition('RULE');
1454
+ /**
1455
+ * Singleton instances of the RULE commitment definitions
1456
+ *
1457
+ * @private [🪔] Maybe export the commitments through some package
1458
+ */
1459
+ new RuleCommitmentDefinition('RULES');
1460
+ /**
1461
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1462
+ */
1463
+
1464
+ /**
1465
+ * SAMPLE commitment definition
1466
+ *
1467
+ * The SAMPLE/EXAMPLE commitment provides examples of how the agent should respond
1468
+ * or behave in certain situations. These examples help guide the agent's responses.
1469
+ *
1470
+ * Example usage in agent source:
1471
+ *
1472
+ * ```book
1473
+ * SAMPLE When asked about pricing, respond: "Our basic plan starts at $10/month..."
1474
+ * EXAMPLE For code questions, always include working code snippets
1475
+ * ```
1476
+ *
1477
+ * @private [🪔] Maybe export the commitments through some package
1478
+ */
1479
+ class SampleCommitmentDefinition extends BaseCommitmentDefinition {
1480
+ constructor(type = 'SAMPLE') {
1481
+ super(type);
1482
+ }
1483
+ applyToAgentModelRequirements(requirements, content) {
1484
+ const trimmedContent = content.trim();
1485
+ if (!trimmedContent) {
1486
+ return requirements;
1487
+ }
1488
+ // Add example to the system message
1489
+ const exampleSection = `Example: ${trimmedContent}`;
1490
+ return this.appendToSystemMessage(requirements, exampleSection, '\n\n');
1491
+ }
1492
+ }
1493
+ /**
1494
+ * Singleton instances of the SAMPLE commitment definitions
1495
+ *
1496
+ * @private [🪔] Maybe export the commitments through some package
1497
+ */
1498
+ new SampleCommitmentDefinition('SAMPLE');
1499
+ /**
1500
+ * Singleton instances of the SAMPLE commitment definitions
1501
+ *
1502
+ * @private [🪔] Maybe export the commitments through some package
1503
+ */
1504
+ new SampleCommitmentDefinition('EXAMPLE');
1505
+ /**
1506
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1507
+ */
1508
+
1509
+ /**
1510
+ * STYLE commitment definition
1511
+ *
1512
+ * The STYLE commitment defines how the agent should format and present its responses.
1513
+ * This includes tone, writing style, formatting preferences, and communication patterns.
1514
+ *
1515
+ * Example usage in agent source:
1516
+ *
1517
+ * ```book
1518
+ * STYLE Write in a professional but friendly tone, use bullet points for lists
1519
+ * STYLE Always provide code examples when explaining programming concepts
1520
+ * ```
1521
+ *
1522
+ * @private [🪔] Maybe export the commitments through some package
1523
+ */
1524
+ class StyleCommitmentDefinition extends BaseCommitmentDefinition {
1525
+ constructor() {
1526
+ super('STYLE');
1527
+ }
1528
+ applyToAgentModelRequirements(requirements, content) {
1529
+ const trimmedContent = content.trim();
1530
+ if (!trimmedContent) {
1531
+ return requirements;
1532
+ }
1533
+ // Add style instructions to the system message
1534
+ const styleSection = `Style: ${trimmedContent}`;
1535
+ return this.appendToSystemMessage(requirements, styleSection, '\n\n');
1536
+ }
1537
+ }
1538
+ /**
1539
+ * Singleton instance of the STYLE commitment definition
1540
+ *
1541
+ * @private [🪔] Maybe export the commitments through some package
1542
+ */
1543
+ new StyleCommitmentDefinition();
1544
+ /**
1545
+ * [💞] Ignore a discrepancy between file name and entity name
1546
+ */
1547
+
1548
+ /**
1549
+ * Placeholder commitment definition for commitments that are not yet implemented
1550
+ *
1551
+ * This commitment simply adds its content 1:1 into the system message,
1552
+ * preserving the original behavior until proper implementation is added.
1553
+ *
1554
+ * @public exported from `@promptbook/core`
1555
+ */
1556
+ class NotYetImplementedCommitmentDefinition extends BaseCommitmentDefinition {
1557
+ constructor(type) {
1558
+ super(type);
1559
+ }
1560
+ applyToAgentModelRequirements(requirements, content) {
1561
+ const trimmedContent = content.trim();
1562
+ if (!trimmedContent) {
1563
+ return requirements;
1564
+ }
1565
+ // Add the commitment content 1:1 to the system message
1566
+ const commitmentLine = `${this.type} ${trimmedContent}`;
1567
+ return this.appendToSystemMessage(requirements, commitmentLine, '\n\n');
1568
+ }
1569
+ }
1570
+
1571
+ // Import all commitment definition classes
1572
+ /**
1573
+ * Registry of all available commitment definitions
1574
+ * This array contains instances of all commitment definitions
1575
+ * This is the single source of truth for all commitments in the system
1576
+ *
1577
+ * @private Use functions to access commitments instead of this array directly
1578
+ */
1579
+ const COMMITMENT_REGISTRY = [
1580
+ // Fully implemented commitments
1581
+ new PersonaCommitmentDefinition(),
1582
+ new KnowledgeCommitmentDefinition(),
1583
+ new StyleCommitmentDefinition(),
1584
+ new RuleCommitmentDefinition('RULE'),
1585
+ new RuleCommitmentDefinition('RULES'),
1586
+ new SampleCommitmentDefinition('SAMPLE'),
1587
+ new SampleCommitmentDefinition('EXAMPLE'),
1588
+ new FormatCommitmentDefinition(),
1589
+ new ModelCommitmentDefinition(),
1590
+ new ActionCommitmentDefinition(),
1591
+ new MetaImageCommitmentDefinition(),
1592
+ new MetaLinkCommitmentDefinition(),
1593
+ new NoteCommitmentDefinition(),
1594
+ // Not yet implemented commitments (using placeholder)
1595
+ new NotYetImplementedCommitmentDefinition('EXPECT'),
1596
+ new NotYetImplementedCommitmentDefinition('SCENARIO'),
1597
+ new NotYetImplementedCommitmentDefinition('SCENARIOS'),
1598
+ new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
1599
+ new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
1600
+ new NotYetImplementedCommitmentDefinition('AVOID'),
1601
+ new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
1602
+ new NotYetImplementedCommitmentDefinition('GOAL'),
1603
+ new NotYetImplementedCommitmentDefinition('GOALS'),
1604
+ new NotYetImplementedCommitmentDefinition('CONTEXT'),
1605
+ ];
1606
+ /**
1607
+ * Gets a commitment definition by its type
1608
+ * @param type The commitment type to look up
1609
+ * @returns The commitment definition or null if not found
1610
+ *
1611
+ * @public exported from `@promptbook/core`
1612
+ */
1613
+ function getCommitmentDefinition(type) {
1614
+ return COMMITMENT_REGISTRY.find((commitmentDefinition) => commitmentDefinition.type === type) || null;
1615
+ }
1616
+ /**
1617
+ * Gets all available commitment definitions
1618
+ * @returns Array of all commitment definitions
1619
+ *
1620
+ * @public exported from `@promptbook/core`
1621
+ */
1622
+ function getAllCommitmentDefinitions() {
1623
+ return $deepFreeze([...COMMITMENT_REGISTRY]);
1624
+ }
1625
+ /**
1626
+ * Gets all available commitment types
1627
+ * @returns Array of all commitment types
1628
+ *
1629
+ * @public exported from `@promptbook/core`
1630
+ */
1631
+ function getAllCommitmentTypes() {
1632
+ return $deepFreeze(COMMITMENT_REGISTRY.map((commitmentDefinition) => commitmentDefinition.type));
1633
+ }
1634
+ /**
1635
+ * Checks if a commitment type is supported
1636
+ * @param type The commitment type to check
1637
+ * @returns True if the commitment type is supported
1638
+ *
1639
+ * @public exported from `@promptbook/core`
1640
+ */
1641
+ function isCommitmentSupported(type) {
1642
+ return COMMITMENT_REGISTRY.some((commitmentDefinition) => commitmentDefinition.type === type);
1643
+ }
1644
+ /**
1645
+ * TODO: !!!! Maybe create through standardized $register
1646
+ * Note: [💞] Ignore a discrepancy between file name and entity name
1647
+ */
1648
+
1649
+ /**
1650
+ * Parses agent source using the new commitment system with multiline support
1651
+ * This function replaces the hardcoded commitment parsing in the original parseAgentSource
1652
+ *
1653
+ * @private
1654
+ */
1655
+ function parseAgentSourceWithCommitments(agentSource) {
1656
+ var _a, _b, _c;
1657
+ if (!agentSource || !agentSource.trim()) {
1658
+ return {
1659
+ agentName: null,
1660
+ commitments: [],
1661
+ nonCommitmentLines: [],
1662
+ };
1663
+ }
1664
+ const lines = agentSource.split('\n');
1665
+ const agentName = (((_a = lines[0]) === null || _a === void 0 ? void 0 : _a.trim()) || null);
1666
+ const commitments = [];
1667
+ const nonCommitmentLines = [];
1668
+ // Always add the first line (agent name) to non-commitment lines
1669
+ if (lines[0] !== undefined) {
1670
+ nonCommitmentLines.push(lines[0]);
1671
+ }
1672
+ // Parse commitments with multiline support
1673
+ let currentCommitment = null;
1674
+ // Process lines starting from the second line (skip agent name)
1675
+ for (let i = 1; i < lines.length; i++) {
1676
+ const line = lines[i];
1677
+ if (line === undefined) {
1678
+ continue;
1679
+ }
1680
+ // Check if this line starts a new commitment
1681
+ let foundNewCommitment = false;
1682
+ for (const definition of COMMITMENT_REGISTRY) {
1683
+ const typeRegex = definition.createTypeRegex();
1684
+ const match = typeRegex.exec(line.trim());
1685
+ if (match && ((_b = match.groups) === null || _b === void 0 ? void 0 : _b.type)) {
1686
+ // Save the previous commitment if it exists
1687
+ if (currentCommitment) {
1688
+ const fullContent = currentCommitment.contentLines.join('\n');
1689
+ commitments.push({
1690
+ type: currentCommitment.type,
1691
+ content: spaceTrim.spaceTrim(fullContent),
1692
+ originalLine: currentCommitment.originalStartLine,
1693
+ lineNumber: currentCommitment.startLineNumber,
1694
+ });
1695
+ }
1696
+ // Extract the initial content from the commitment line
1697
+ const fullRegex = definition.createRegex();
1698
+ const fullMatch = fullRegex.exec(line.trim());
1699
+ const initialContent = ((_c = fullMatch === null || fullMatch === void 0 ? void 0 : fullMatch.groups) === null || _c === void 0 ? void 0 : _c.contents) || '';
1700
+ // Start a new commitment
1701
+ currentCommitment = {
1702
+ type: definition.type,
1703
+ startLineNumber: i + 1,
1704
+ originalStartLine: line,
1705
+ contentLines: initialContent ? [initialContent] : [],
1706
+ };
1707
+ foundNewCommitment = true;
1708
+ break;
1709
+ }
1710
+ }
1711
+ if (!foundNewCommitment) {
1712
+ if (currentCommitment) {
1713
+ // This line belongs to the current commitment
1714
+ currentCommitment.contentLines.push(line);
1715
+ }
1716
+ else {
1717
+ // This line is not part of any commitment
1718
+ nonCommitmentLines.push(line);
1719
+ }
1720
+ }
1721
+ }
1722
+ // Don't forget to save the last commitment if it exists
1723
+ if (currentCommitment) {
1724
+ const fullContent = currentCommitment.contentLines.join('\n');
1725
+ commitments.push({
1726
+ type: currentCommitment.type,
1727
+ content: spaceTrim.spaceTrim(fullContent),
1728
+ originalLine: currentCommitment.originalStartLine,
1729
+ lineNumber: currentCommitment.startLineNumber,
1730
+ });
1731
+ }
1732
+ return {
1733
+ agentName,
1734
+ commitments,
1735
+ nonCommitmentLines,
1736
+ };
1737
+ }
1738
+ /**
1739
+ * Extracts basic information from agent source using the new commitment system
1740
+ * This maintains compatibility with the original parseAgentSource interface
1741
+ *
1742
+ * @private
1743
+ */
1744
+ function parseAgentSourceBasicInfo(agentSource) {
1745
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
1746
+ // Find PERSONA and META IMAGE commitments
1747
+ let personaDescription = null;
1748
+ let profileImageUrl;
1749
+ for (const commitment of parseResult.commitments) {
1750
+ if (commitment.type === 'PERSONA' && !personaDescription) {
1751
+ personaDescription = commitment.content;
1752
+ }
1753
+ else if (commitment.type === 'META IMAGE' && !profileImageUrl) {
1754
+ profileImageUrl = commitment.content;
1755
+ }
1756
+ }
1757
+ // Generate gravatar fallback if no profile image specified
1758
+ if (!profileImageUrl) {
1759
+ profileImageUrl = generateGravatarUrl(parseResult.agentName);
1760
+ }
1761
+ return {
1762
+ agentName: parseResult.agentName,
1763
+ personaDescription,
1764
+ profileImageUrl,
1765
+ };
1766
+ }
1767
+
1768
+ /**
1769
+ * Parses agent source string into its components
1770
+ */
1771
+ // Cache for parsed agent sources to prevent repeated parsing
1772
+ const parsedAgentSourceCache = new Map();
1773
+ /**
1774
+ * Parses basic information from agent source
1775
+ *
1776
+ * There are 2 similar functions:
1777
+ * - `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.
1778
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
1779
+ *
1780
+ * @public exported from `@promptbook/core`
1781
+ */
1782
+ function parseAgentSource(agentSource) {
1783
+ // Check if we already parsed this agent source
1784
+ if (parsedAgentSourceCache.has(agentSource)) {
1785
+ return parsedAgentSourceCache.get(agentSource);
1786
+ }
1787
+ // Use the new commitment-based parsing system
1788
+ const result = parseAgentSourceBasicInfo(agentSource);
1789
+ // Cache the result
1790
+ parsedAgentSourceCache.set(agentSource, result);
1791
+ return result;
1792
+ }
1793
+
1794
+ /**
1795
+ * Type guard to check if a string is a valid agent source
1796
+ *
1797
+ * @public exported from `@promptbook/core`
1798
+ */
1799
+ function isValidBook(value) {
1800
+ // Basic validation - agent source should have at least a name (first line)
1801
+ return typeof value === 'string' /* && value.trim().length > 0 */;
1802
+ }
1803
+ /**
1804
+ * Validates and converts a string to agent source branded type
1805
+ * This function should be used when you have a string that you know represents agent source
1806
+ * but need to convert it to the branded type for type safety
1807
+ *
1808
+ * @public exported from `@promptbook/core`
1809
+ */
1810
+ function validateBook(source) {
1811
+ if (!isValidBook(source)) {
1812
+ throw new Error('Invalid agent source: must be a string');
1813
+ }
1814
+ return source;
1815
+ }
1816
+ /**
1817
+ * Default book
1818
+ *
1819
+ * @public exported from `@promptbook/core`
1820
+ */
1821
+ const DEFAULT_BOOK = validateBook(spaceTrim__default["default"](`
1822
+ AI Avatar
1823
+
1824
+ PERSONA A friendly AI assistant that helps you with your tasks
1825
+ `));
1826
+
1827
+ /**
1828
+ * Creates an empty/basic agent model requirements object
1829
+ * This serves as the starting point for the reduce-like pattern
1830
+ * where each commitment applies its changes to build the final requirements
1831
+ *
1832
+ * @public exported from `@promptbook/core`
1833
+ */
1834
+ function createEmptyAgentModelRequirements() {
1835
+ return {
1836
+ systemMessage: '',
1837
+ modelName: '!!!!DEFAULT_MODEL_ID',
1838
+ temperature: 0.7,
1839
+ topP: 0.9,
1840
+ topK: 50,
1841
+ };
1842
+ }
1843
+ /**
1844
+ * Creates a basic agent model requirements with just the agent name
1845
+ * This is used when we have an agent name but no commitments
1846
+ *
1847
+ * @public exported from `@promptbook/core`
1848
+ */
1849
+ function createBasicAgentModelRequirements(agentName) {
1850
+ const empty = createEmptyAgentModelRequirements();
1851
+ return {
1852
+ ...empty,
1853
+ systemMessage: `You are ${agentName || 'AI Agent'}`,
1854
+ };
1855
+ }
1856
+ /**
1857
+ * TODO: !!!! Deduplicate model requirements
1858
+ */
1859
+
1860
+ /**
1861
+ * Removes comment lines (lines starting with #) from a system message
1862
+ * This is used to clean up the final system message before sending it to the AI model
1863
+ * while preserving the original content with comments in metadata
1864
+ *
1865
+ * @param systemMessage The system message that may contain comment lines
1866
+ * @returns The system message with comment lines removed
1867
+ *
1868
+ * @private - TODO: [🧠] Maybe should be public?
1869
+ */
1870
+ function removeCommentsFromSystemMessage(systemMessage) {
1871
+ if (!systemMessage) {
1872
+ return systemMessage;
1873
+ }
1874
+ const lines = systemMessage.split('\n');
1875
+ const filteredLines = lines.filter((line) => {
1876
+ const trimmedLine = line.trim();
1877
+ // Remove lines that start with # (comments)
1878
+ return !trimmedLine.startsWith('#');
1879
+ });
1880
+ return filteredLines.join('\n').trim();
1881
+ }
1882
+
1883
+ /**
1884
+ * Creates agent model requirements using the new commitment system
1885
+ * This function uses a reduce-like pattern where each commitment applies its changes
1886
+ * to build the final requirements starting from a basic empty model
1887
+ *
1888
+ * @private
1889
+ */
1890
+ async function createAgentModelRequirementsWithCommitments(agentSource, modelName) {
1891
+ // Parse the agent source to extract commitments
1892
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
1893
+ // Start with basic agent model requirements
1894
+ let requirements = createBasicAgentModelRequirements(parseResult.agentName);
1895
+ // Store the agent name in metadata so commitments can access it
1896
+ requirements = {
1897
+ ...requirements,
1898
+ metadata: {
1899
+ ...requirements.metadata,
1900
+ agentName: parseResult.agentName,
1901
+ },
1902
+ };
1903
+ // Override model name if provided
1904
+ if (modelName) {
1905
+ requirements = {
1906
+ ...requirements,
1907
+ modelName,
1908
+ };
1909
+ }
1910
+ // Apply each commitment in order using reduce-like pattern
1911
+ for (const commitment of parseResult.commitments) {
1912
+ const definition = getCommitmentDefinition(commitment.type);
1913
+ if (definition) {
1914
+ try {
1915
+ requirements = definition.applyToAgentModelRequirements(requirements, commitment.content);
1916
+ }
1917
+ catch (error) {
1918
+ console.warn(`Failed to apply commitment ${commitment.type}:`, error);
1919
+ // Continue with other commitments even if one fails
1920
+ }
1921
+ }
1922
+ }
1923
+ // Handle MCP servers (extract from original agent source)
1924
+ const mcpServers = extractMcpServers(agentSource);
1925
+ if (mcpServers.length > 0) {
1926
+ requirements = {
1927
+ ...requirements,
1928
+ mcpServers,
1929
+ };
1930
+ }
1931
+ // Add non-commitment lines to system message if they exist
1932
+ const nonCommitmentContent = parseResult.nonCommitmentLines
1933
+ .filter((line, index) => index > 0 || !parseResult.agentName) // Skip first line if it's the agent name
1934
+ .filter((line) => line.trim()) // Remove empty lines
1935
+ .join('\n')
1936
+ .trim();
1937
+ if (nonCommitmentContent) {
1938
+ requirements = {
1939
+ ...requirements,
1940
+ systemMessage: requirements.systemMessage + '\n\n' + nonCommitmentContent,
1941
+ };
1942
+ }
1943
+ // Remove comment lines (lines starting with #) from the final system message
1944
+ // while preserving the original content with comments in metadata
1945
+ const cleanedSystemMessage = removeCommentsFromSystemMessage(requirements.systemMessage);
1946
+ return {
1947
+ ...requirements,
1948
+ systemMessage: cleanedSystemMessage,
1949
+ };
1950
+ }
1951
+ /**
1952
+ * Cache for expensive createAgentModelRequirementsWithCommitments calls
1953
+ * @private
1954
+ */
1955
+ const modelRequirementsCache = new Map();
1956
+ /**
1957
+ * @private - TODO: Maybe should be public
1958
+ */
1959
+ const CACHE_SIZE_LIMIT = 100;
1960
+ /**
1961
+ * Cached version of createAgentModelRequirementsWithCommitments
1962
+ * This maintains the same caching behavior as the original function
1963
+ *
1964
+ * @private
1965
+ */
1966
+ async function createAgentModelRequirementsWithCommitmentsCached(agentSource, modelName) {
1967
+ // Create cache key
1968
+ const cacheKey = `${agentSource}|${modelName || 'default'}`;
1969
+ // Check cache first
1970
+ if (modelRequirementsCache.has(cacheKey)) {
1971
+ return modelRequirementsCache.get(cacheKey);
1972
+ }
1973
+ // Limit cache size to prevent memory leaks
1974
+ if (modelRequirementsCache.size >= CACHE_SIZE_LIMIT) {
1975
+ const firstKey = modelRequirementsCache.keys().next().value;
1976
+ if (firstKey) {
1977
+ modelRequirementsCache.delete(firstKey);
1978
+ }
1979
+ }
1980
+ // Create requirements
1981
+ const requirements = await createAgentModelRequirementsWithCommitments(agentSource, modelName);
1982
+ // Cache the result
1983
+ modelRequirementsCache.set(cacheKey, requirements);
1984
+ return requirements;
1985
+ }
1986
+
1987
+ // TODO: Remove or use:
1988
+ //const CACHE_SIZE_LIMIT = 100; // Prevent memory leaks by limiting cache size
1989
+ /**
1990
+ * Creates model requirements for an agent based on its source
1991
+ * Results are cached to improve performance for repeated calls with the same agentSource and modelName
1992
+ *
1993
+ * There are 2 similar functions:
1994
+ * - `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.
1995
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
557
1996
  *
558
1997
  * @public exported from `@promptbook/core`
559
1998
  */
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);
1999
+ async function createAgentModelRequirements(agentSource, modelName = '!!!!DEFAULT_MODEL_ID') {
2000
+ // Use the new commitment-based system
2001
+ return createAgentModelRequirementsWithCommitmentsCached(agentSource, modelName);
2002
+ }
2003
+ /**
2004
+ * Extracts MCP servers from agent source
2005
+ *
2006
+ * @param agentSource The agent source string that may contain MCP lines
2007
+ * @returns Array of MCP server identifiers
2008
+ *
2009
+ * @private TODO: [🧠] Maybe should be public
2010
+ */
2011
+ function extractMcpServers(agentSource) {
2012
+ if (!agentSource) {
2013
+ return [];
576
2014
  }
2015
+ const lines = agentSource.split('\n');
2016
+ const mcpRegex = /^\s*MCP\s+(.+)$/i;
2017
+ const mcpServers = [];
2018
+ // Look for MCP lines
2019
+ for (const line of lines) {
2020
+ const match = line.match(mcpRegex);
2021
+ if (match && match[1]) {
2022
+ mcpServers.push(match[1].trim());
2023
+ }
2024
+ }
2025
+ return mcpServers;
577
2026
  }
578
2027
 
579
2028
  /**
580
- * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
2029
+ * Converts PipelineCollection to serialized JSON
2030
+ *
2031
+ * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
581
2032
  *
582
2033
  * @public exported from `@promptbook/core`
583
2034
  */
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
2035
+ async function collectionToJson(collection) {
2036
+ const pipelineUrls = await collection.listPipelines();
2037
+ const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
2038
+ return promptbooks;
2039
+ }
2040
+ /**
2041
+ * TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
2042
+ */
590
2043
 
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);
2044
+ /**
2045
+ * Checks if value is valid email
2046
+ *
2047
+ * @public exported from `@promptbook/utils`
2048
+ */
2049
+ function isValidEmail(email) {
2050
+ if (typeof email !== 'string') {
2051
+ return false;
2052
+ }
2053
+ if (email.split('\n').length > 1) {
2054
+ return false;
596
2055
  }
2056
+ return /^.+@.+\..+$/.test(email);
597
2057
  }
598
2058
 
599
2059
  /**
600
- * Helper used in catch blocks to assert that the error is an instance of `Error`
2060
+ * Tests if given string is valid URL.
601
2061
  *
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
2062
+ * Note: This does not check if the file exists only if the path is valid
2063
+ * @public exported from `@promptbook/utils`
2064
+ */
2065
+ function isValidFilePath(filename) {
2066
+ if (typeof filename !== 'string') {
2067
+ return false;
2068
+ }
2069
+ if (filename.split('\n').length > 1) {
2070
+ return false;
2071
+ }
2072
+ if (filename.split(' ').length >
2073
+ 5 /* <- TODO: [🧠][🈷] Make some better non-arbitrary way how to distinct filenames from informational texts */) {
2074
+ return false;
2075
+ }
2076
+ const filenameSlashes = filename.split('\\').join('/');
2077
+ // Absolute Unix path: /hello.txt
2078
+ if (/^(\/)/i.test(filenameSlashes)) {
2079
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
2080
+ return true;
2081
+ }
2082
+ // Absolute Windows path: /hello.txt
2083
+ if (/^([A-Z]{1,2}:\/?)\//i.test(filenameSlashes)) {
2084
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
2085
+ return true;
2086
+ }
2087
+ // Relative path: ./hello.txt
2088
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
2089
+ // console.log(filename, 'Relative path: ./hello.txt');
2090
+ return true;
2091
+ }
2092
+ // Allow paths like foo/hello
2093
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
2094
+ // console.log(filename, 'Allow paths like foo/hello');
2095
+ return true;
2096
+ }
2097
+ // Allow paths like hello.book
2098
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
2099
+ // console.log(filename, 'Allow paths like hello.book');
2100
+ return true;
2101
+ }
2102
+ return false;
2103
+ }
2104
+ /**
2105
+ * TODO: [🍏] Implement for MacOs
2106
+ */
2107
+
2108
+ /**
2109
+ * Tests if given string is valid URL.
605
2110
  *
606
- * @private within the repository
2111
+ * Note: Dataurl are considered perfectly valid.
2112
+ * Note: There are two similar functions:
2113
+ * - `isValidUrl` which tests any URL
2114
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
2115
+ *
2116
+ * @public exported from `@promptbook/utils`
607
2117
  */
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;
2118
+ function isValidUrl(url) {
2119
+ if (typeof url !== 'string') {
2120
+ return false;
613
2121
  }
614
- // Case 2: Handle unexpected errors
615
- if (whatWasThrown instanceof UnexpectedError) {
616
- const unexpectedError = whatWasThrown;
617
- throw unexpectedError;
2122
+ try {
2123
+ if (url.startsWith('blob:')) {
2124
+ url = url.replace(/^blob:/, '');
2125
+ }
2126
+ const urlObject = new URL(url /* because fail is handled */);
2127
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
2128
+ return false;
2129
+ }
2130
+ return true;
618
2131
  }
619
- // Case 3: Handle standard errors - keep them up to consumer
620
- if (whatWasThrown instanceof Error) {
621
- return;
2132
+ catch (error) {
2133
+ return false;
2134
+ }
2135
+ }
2136
+
2137
+ /**
2138
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
2139
+ *
2140
+ * @public exported from `@promptbook/core`
2141
+ */
2142
+ class ParseError extends Error {
2143
+ constructor(message) {
2144
+ super(message);
2145
+ this.name = 'ParseError';
2146
+ Object.setPrototypeOf(this, ParseError.prototype);
622
2147
  }
623
- // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
624
- throw new WrappedError(whatWasThrown);
625
2148
  }
2149
+ /**
2150
+ * TODO: Maybe split `ParseError` and `ApplyError`
2151
+ */
626
2152
 
627
2153
  /**
628
2154
  * Function isValidJsonString will tell you if the string is valid JSON or not
@@ -873,33 +2399,6 @@
873
2399
  return orderedValue;
874
2400
  }
875
2401
 
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
2402
  /**
904
2403
  * Checks if the value is [🚉] serializable as JSON
905
2404
  * If not, throws an UnexpectedError with a rich error message and tracking
@@ -2121,19 +3620,6 @@
2121
3620
  * TODO: [🧠][🌂] Add id to all errors
2122
3621
  */
2123
3622
 
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
3623
  /**
2138
3624
  * Index of all custom errors
2139
3625
  *
@@ -5835,37 +7321,6 @@
5835
7321
  return value;
5836
7322
  }
5837
7323
 
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
7324
  /**
5870
7325
  * Factory function that creates a handler for processing knowledge sources.
5871
7326
  * Provides standardized processing of different types of knowledge sources
@@ -5920,7 +7375,23 @@
5920
7375
  // <- TODO: [🥬] Encapsulate sha256 to some private utility function
5921
7376
  const rootDirname = path.join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
5922
7377
  const filepath = path.join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
5923
- await tools.fs.mkdir(path.dirname(path.join(rootDirname, filepath)), { recursive: true });
7378
+ // Note: Try to create cache directory, but don't fail if filesystem has issues
7379
+ try {
7380
+ await tools.fs.mkdir(path.dirname(path.join(rootDirname, filepath)), { recursive: true });
7381
+ }
7382
+ catch (error) {
7383
+ // Note: If we can't create cache directory, we'll handle it when trying to write the file
7384
+ // This handles read-only filesystems, permission issues, and missing parent directories
7385
+ if (error instanceof Error && (error.message.includes('EROFS') ||
7386
+ error.message.includes('read-only') ||
7387
+ error.message.includes('EACCES') ||
7388
+ error.message.includes('EPERM') ||
7389
+ error.message.includes('ENOENT'))) ;
7390
+ else {
7391
+ // Re-throw other unexpected errors
7392
+ throw error;
7393
+ }
7394
+ }
5924
7395
  const fileContent = Buffer.from(await response.arrayBuffer());
5925
7396
  if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
5926
7397
  throw new LimitReachedError(`File is too large (${Math.round(fileContent.length / 1024 / 1024)}MB). Maximum allowed size is ${Math.round(DEFAULT_MAX_FILE_SIZE / 1024 / 1024)}MB.`);
@@ -5935,7 +7406,8 @@
5935
7406
  if (error instanceof Error && (error.message.includes('EROFS') ||
5936
7407
  error.message.includes('read-only') ||
5937
7408
  error.message.includes('EACCES') ||
5938
- error.message.includes('EPERM'))) {
7409
+ error.message.includes('EPERM') ||
7410
+ error.message.includes('ENOENT'))) {
5939
7411
  // Return a handler that works directly with the downloaded content
5940
7412
  return {
5941
7413
  source: name,
@@ -11632,18 +13104,28 @@
11632
13104
  * @public exported from `@promptbook/core`
11633
13105
  */
11634
13106
  function book(strings, ...values) {
11635
- const pipelineString = prompt(strings, ...values);
11636
- if (!isValidPipelineString(pipelineString)) {
13107
+ const bookString = prompt(strings, ...values);
13108
+ if (!isValidPipelineString(bookString)) {
11637
13109
  // TODO: Make the CustomError for this
11638
13110
  throw new Error(spaceTrim__default["default"](`
11639
13111
  The string is not a valid pipeline string
11640
13112
 
11641
13113
  book\`
11642
- ${pipelineString}
13114
+ ${bookString}
11643
13115
  \`
11644
13116
  `));
11645
13117
  }
11646
- return pipelineString;
13118
+ if (!isValidBook(bookString)) {
13119
+ // TODO: Make the CustomError for this
13120
+ throw new Error(spaceTrim__default["default"](`
13121
+ The string is not a valid book
13122
+
13123
+ book\`
13124
+ ${bookString}
13125
+ \`
13126
+ `));
13127
+ }
13128
+ return bookString;
11647
13129
  }
11648
13130
  /**
11649
13131
  * TODO: [🧠][🈴] Where is the best location for this file
@@ -11994,6 +13476,7 @@
11994
13476
  exports.CompletionFormfactorDefinition = CompletionFormfactorDefinition;
11995
13477
  exports.CsvFormatError = CsvFormatError;
11996
13478
  exports.CsvFormatParser = CsvFormatParser;
13479
+ exports.DEFAULT_BOOK = DEFAULT_BOOK;
11997
13480
  exports.DEFAULT_BOOKS_DIRNAME = DEFAULT_BOOKS_DIRNAME;
11998
13481
  exports.DEFAULT_BOOK_OUTPUT_PARAMETER_NAME = DEFAULT_BOOK_OUTPUT_PARAMETER_NAME;
11999
13482
  exports.DEFAULT_BOOK_TITLE = DEFAULT_BOOK_TITLE;
@@ -12038,6 +13521,7 @@
12038
13521
  exports.NAME = NAME;
12039
13522
  exports.NonTaskSectionTypes = NonTaskSectionTypes;
12040
13523
  exports.NotFoundError = NotFoundError;
13524
+ exports.NotYetImplementedCommitmentDefinition = NotYetImplementedCommitmentDefinition;
12041
13525
  exports.NotYetImplementedError = NotYetImplementedError;
12042
13526
  exports.ORDER_OF_PIPELINE_JSON = ORDER_OF_PIPELINE_JSON;
12043
13527
  exports.PENDING_VALUE_PLACEHOLDER = PENDING_VALUE_PLACEHOLDER;
@@ -12086,9 +13570,12 @@
12086
13570
  exports.compilePipeline = compilePipeline;
12087
13571
  exports.computeCosineSimilarity = computeCosineSimilarity;
12088
13572
  exports.countUsage = countUsage;
13573
+ exports.createAgentModelRequirements = createAgentModelRequirements;
13574
+ exports.createBasicAgentModelRequirements = createBasicAgentModelRequirements;
12089
13575
  exports.createCollectionFromJson = createCollectionFromJson;
12090
13576
  exports.createCollectionFromPromise = createCollectionFromPromise;
12091
13577
  exports.createCollectionFromUrl = createCollectionFromUrl;
13578
+ exports.createEmptyAgentModelRequirements = createEmptyAgentModelRequirements;
12092
13579
  exports.createLlmToolsFromConfiguration = createLlmToolsFromConfiguration;
12093
13580
  exports.createPipelineExecutor = createPipelineExecutor;
12094
13581
  exports.createSubcollection = createSubcollection;
@@ -12096,17 +13583,23 @@
12096
13583
  exports.executionReportJsonToString = executionReportJsonToString;
12097
13584
  exports.extractParameterNamesFromTask = extractParameterNamesFromTask;
12098
13585
  exports.filterModels = filterModels;
13586
+ exports.getAllCommitmentDefinitions = getAllCommitmentDefinitions;
13587
+ exports.getAllCommitmentTypes = getAllCommitmentTypes;
13588
+ exports.getCommitmentDefinition = getCommitmentDefinition;
12099
13589
  exports.getPipelineInterface = getPipelineInterface;
12100
13590
  exports.identificationToPromptbookToken = identificationToPromptbookToken;
13591
+ exports.isCommitmentSupported = isCommitmentSupported;
12101
13592
  exports.isPassingExpectations = isPassingExpectations;
12102
13593
  exports.isPipelineImplementingInterface = isPipelineImplementingInterface;
12103
13594
  exports.isPipelineInterfacesEqual = isPipelineInterfacesEqual;
12104
13595
  exports.isPipelinePrepared = isPipelinePrepared;
13596
+ exports.isValidBook = isValidBook;
12105
13597
  exports.isValidPipelineString = isValidPipelineString;
12106
13598
  exports.joinLlmExecutionTools = joinLlmExecutionTools;
12107
13599
  exports.limitTotalUsage = limitTotalUsage;
12108
13600
  exports.makeKnowledgeSourceHandler = makeKnowledgeSourceHandler;
12109
13601
  exports.migratePipeline = migratePipeline;
13602
+ exports.parseAgentSource = parseAgentSource;
12110
13603
  exports.parsePipeline = parsePipeline;
12111
13604
  exports.pipelineJsonToString = pipelineJsonToString;
12112
13605
  exports.prepareKnowledgePieces = prepareKnowledgePieces;
@@ -12118,6 +13611,7 @@
12118
13611
  exports.unpreparePipeline = unpreparePipeline;
12119
13612
  exports.usageToHuman = usageToHuman;
12120
13613
  exports.usageToWorktime = usageToWorktime;
13614
+ exports.validateBook = validateBook;
12121
13615
  exports.validatePipeline = validatePipeline;
12122
13616
  exports.validatePipelineString = validatePipelineString;
12123
13617