@promptbook/core 0.103.0-48 → 0.103.0-49

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 (37) hide show
  1. package/esm/index.es.js +527 -337
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/servers.d.ts +1 -0
  4. package/esm/typings/src/_packages/types.index.d.ts +2 -0
  5. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  6. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +12 -2
  7. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  8. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  9. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  10. package/esm/typings/src/commitments/index.d.ts +2 -1
  11. package/esm/typings/src/config.d.ts +1 -0
  12. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  13. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  14. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  15. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  16. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  17. package/esm/typings/src/llm-providers/agent/Agent.d.ts +11 -3
  18. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +6 -1
  19. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
  20. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +6 -1
  21. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  22. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  23. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  24. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  25. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  26. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  27. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  28. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  29. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  30. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  31. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  32. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  33. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  34. package/esm/typings/src/version.d.ts +1 -1
  35. package/package.json +1 -1
  36. package/umd/index.umd.js +527 -337
  37. package/umd/index.umd.js.map +1 -1
package/esm/index.es.js CHANGED
@@ -27,140 +27,12 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
27
27
  * @generated
28
28
  * @see https://github.com/webgptorg/promptbook
29
29
  */
30
- const PROMPTBOOK_ENGINE_VERSION = '0.103.0-48';
30
+ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-49';
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
- /**
37
- * Computes SHA-256 hash of the agent source
38
- *
39
- * @public exported from `@promptbook/core`
40
- */
41
- function computeAgentHash(agentSource) {
42
- return SHA256(hexEncoder.parse(agentSource /* <- TODO: !!!!! spaceTrim */)).toString( /* hex */);
43
- }
44
-
45
- var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModels",description:"List of available model names together with their descriptions as JSON",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelsRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n```json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n```\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n```json\n{availableModels}\n```\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelsRequirements",format:"JSON",dependentParameterNames:["availableModels","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModels}` List of available model names together with their descriptions as JSON\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelsRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n\\`\\`\\`json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n\\`\\`\\`json\n{availableModels}\n\\`\\`\\`\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelsRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
46
-
47
- /**
48
- * Checks if value is valid email
49
- *
50
- * @public exported from `@promptbook/utils`
51
- */
52
- function isValidEmail(email) {
53
- if (typeof email !== 'string') {
54
- return false;
55
- }
56
- if (email.split('\n').length > 1) {
57
- return false;
58
- }
59
- return /^.+@.+\..+$/.test(email);
60
- }
61
-
62
- /**
63
- * Tests if given string is valid file path.
64
- *
65
- * Note: This does not check if the file exists only if the path is valid
66
- * @public exported from `@promptbook/utils`
67
- */
68
- function isValidFilePath(filename) {
69
- if (typeof filename !== 'string') {
70
- return false;
71
- }
72
- if (filename.split('\n').length > 1) {
73
- return false;
74
- }
75
- // Normalize slashes early so heuristics can detect path-like inputs
76
- const filenameSlashes = filename.replace(/\\/g, '/');
77
- // Reject strings that look like sentences (informational text)
78
- // Heuristic: contains multiple spaces and ends with a period, or contains typical sentence punctuation
79
- // But skip this heuristic if the string looks like a path (contains '/' or starts with a drive letter)
80
- if (filename.trim().length > 60 && // long enough to be a sentence
81
- /[.!?]/.test(filename) && // contains sentence punctuation
82
- filename.split(' ').length > 8 && // has many words
83
- !/\/|^[A-Z]:/i.test(filenameSlashes) // do NOT treat as sentence if looks like a path
84
- ) {
85
- return false;
86
- }
87
- // Absolute Unix path: /hello.txt
88
- if (/^(\/)/i.test(filenameSlashes)) {
89
- // console.log(filename, 'Absolute Unix path: /hello.txt');
90
- return true;
91
- }
92
- // Absolute Windows path: C:/ or C:\ (allow spaces and multiple dots in filename)
93
- if (/^[A-Z]:\/.+$/i.test(filenameSlashes)) {
94
- // console.log(filename, 'Absolute Windows path: /hello.txt');
95
- return true;
96
- }
97
- // Relative path: ./hello.txt
98
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
99
- // console.log(filename, 'Relative path: ./hello.txt');
100
- return true;
101
- }
102
- // Allow paths like foo/hello
103
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
104
- // console.log(filename, 'Allow paths like foo/hello');
105
- return true;
106
- }
107
- // Allow paths like hello.book
108
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
109
- // console.log(filename, 'Allow paths like hello.book');
110
- return true;
111
- }
112
- return false;
113
- }
114
- /**
115
- * TODO: [🍏] Implement for MacOs
116
- */
117
-
118
- /**
119
- * Tests if given string is valid URL.
120
- *
121
- * Note: [🔂] This function is idempotent.
122
- * Note: Dataurl are considered perfectly valid.
123
- * Note: There are two similar functions:
124
- * - `isValidUrl` which tests any URL
125
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
126
- *
127
- * @public exported from `@promptbook/utils`
128
- */
129
- function isValidUrl(url) {
130
- if (typeof url !== 'string') {
131
- return false;
132
- }
133
- try {
134
- if (url.startsWith('blob:')) {
135
- url = url.replace(/^blob:/, '');
136
- }
137
- const urlObject = new URL(url /* because fail is handled */);
138
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
139
- return false;
140
- }
141
- return true;
142
- }
143
- catch (error) {
144
- return false;
145
- }
146
- }
147
-
148
- /**
149
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
150
- *
151
- * @public exported from `@promptbook/core`
152
- */
153
- class ParseError extends Error {
154
- constructor(message) {
155
- super(message);
156
- this.name = 'ParseError';
157
- Object.setPrototypeOf(this, ParseError.prototype);
158
- }
159
- }
160
- /**
161
- * TODO: Maybe split `ParseError` and `ApplyError`
162
- */
163
-
164
36
  /**
165
37
  * Available remote servers for the Promptbook
166
38
  *
@@ -194,6 +66,7 @@ const REMOTE_SERVER_URLS = [
194
66
  */
195
67
  ];
196
68
  /**
69
+ * TODO: [🐱‍🚀] Auto-federated server from url in here
197
70
  * Note: [💞] Ignore a discrepancy between file name and entity name
198
71
  */
199
72
 
@@ -527,6 +400,9 @@ class Color {
527
400
  if (hex.length === 3) {
528
401
  return Color.fromHex3(hex);
529
402
  }
403
+ if (hex.length === 4) {
404
+ return Color.fromHex4(hex);
405
+ }
530
406
  if (hex.length === 6) {
531
407
  return Color.fromHex6(hex);
532
408
  }
@@ -547,6 +423,19 @@ class Color {
547
423
  const b = parseInt(hex.substr(2, 1), 16) * 16;
548
424
  return take(new Color(r, g, b));
549
425
  }
426
+ /**
427
+ * Creates a new Color instance from color in hex format with 4 digits (with alpha channel)
428
+ *
429
+ * @param color in hex for example `09df`
430
+ * @returns Color object
431
+ */
432
+ static fromHex4(hex) {
433
+ const r = parseInt(hex.substr(0, 1), 16) * 16;
434
+ const g = parseInt(hex.substr(1, 1), 16) * 16;
435
+ const b = parseInt(hex.substr(2, 1), 16) * 16;
436
+ const a = parseInt(hex.substr(3, 1), 16) * 16;
437
+ return take(new Color(r, g, b, a));
438
+ }
550
439
  /**
551
440
  * Creates a new Color instance from color in hex format with 6 color digits (without alpha channel)
552
441
  *
@@ -737,7 +626,8 @@ class Color {
737
626
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
738
627
  */
739
628
  static isHexColorString(value) {
740
- return typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value);
629
+ return (typeof value === 'string' &&
630
+ /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
741
631
  }
742
632
  /**
743
633
  * Creates new Color object
@@ -1078,6 +968,7 @@ const PROMPTBOOK_COLOR = Color.fromHex('#79EAFD');
1078
968
  const PROMPTBOOK_SYNTAX_COLORS = {
1079
969
  TITLE: Color.fromHex('#244EA8'),
1080
970
  LINE: Color.fromHex('#eeeeee'),
971
+ SEPARATOR: Color.fromHex('#cccccc'),
1081
972
  COMMITMENT: Color.fromHex('#DA0F78'),
1082
973
  PARAMETER: Color.fromHex('#8e44ad'),
1083
974
  };
@@ -1456,76 +1347,297 @@ function getErrorReportUrl(error) {
1456
1347
  }
1457
1348
 
1458
1349
  /**
1459
- * This error type indicates that the error should not happen and its last check before crashing with some other error
1350
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
1351
+ *
1352
+ * @public exported from `@promptbook/core`
1353
+ */
1354
+ class UnexpectedError extends Error {
1355
+ constructor(message) {
1356
+ super(spaceTrim$2((block) => `
1357
+ ${block(message)}
1358
+
1359
+ Note: This error should not happen.
1360
+ It's probably a bug in the pipeline collection
1361
+
1362
+ Please report issue:
1363
+ ${block(getErrorReportUrl(new Error(message)).href)}
1364
+
1365
+ Or contact us on ${ADMIN_EMAIL}
1366
+
1367
+ `));
1368
+ this.name = 'UnexpectedError';
1369
+ Object.setPrototypeOf(this, UnexpectedError.prototype);
1370
+ }
1371
+ }
1372
+
1373
+ /**
1374
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
1375
+ *
1376
+ * @public exported from `@promptbook/core`
1377
+ */
1378
+ class WrappedError extends Error {
1379
+ constructor(whatWasThrown) {
1380
+ const tag = `[🤮]`;
1381
+ console.error(tag, whatWasThrown);
1382
+ super(spaceTrim$2(`
1383
+ Non-Error object was thrown
1384
+
1385
+ Note: Look for ${tag} in the console for more details
1386
+ Please report issue on ${ADMIN_EMAIL}
1387
+ `));
1388
+ this.name = 'WrappedError';
1389
+ Object.setPrototypeOf(this, WrappedError.prototype);
1390
+ }
1391
+ }
1392
+
1393
+ /**
1394
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
1395
+ *
1396
+ * @param whatWasThrown Any object that was thrown
1397
+ * @returns Nothing if the error is an instance of `Error`
1398
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
1399
+ *
1400
+ * @private within the repository
1401
+ */
1402
+ function assertsError(whatWasThrown) {
1403
+ // Case 1: Handle error which was rethrown as `WrappedError`
1404
+ if (whatWasThrown instanceof WrappedError) {
1405
+ const wrappedError = whatWasThrown;
1406
+ throw wrappedError;
1407
+ }
1408
+ // Case 2: Handle unexpected errors
1409
+ if (whatWasThrown instanceof UnexpectedError) {
1410
+ const unexpectedError = whatWasThrown;
1411
+ throw unexpectedError;
1412
+ }
1413
+ // Case 3: Handle standard errors - keep them up to consumer
1414
+ if (whatWasThrown instanceof Error) {
1415
+ return;
1416
+ }
1417
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
1418
+ throw new WrappedError(whatWasThrown);
1419
+ }
1420
+
1421
+ /**
1422
+ * Format either small or big number
1423
+ *
1424
+ * @public exported from `@promptbook/utils`
1425
+ */
1426
+ function numberToString(value) {
1427
+ if (value === 0) {
1428
+ return '0';
1429
+ }
1430
+ else if (Number.isNaN(value)) {
1431
+ return VALUE_STRINGS.nan;
1432
+ }
1433
+ else if (value === Infinity) {
1434
+ return VALUE_STRINGS.infinity;
1435
+ }
1436
+ else if (value === -Infinity) {
1437
+ return VALUE_STRINGS.negativeInfinity;
1438
+ }
1439
+ for (let exponent = 0; exponent < 15; exponent++) {
1440
+ const factor = 10 ** exponent;
1441
+ const valueRounded = Math.round(value * factor) / factor;
1442
+ if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
1443
+ return valueRounded.toFixed(exponent);
1444
+ }
1445
+ }
1446
+ return value.toString();
1447
+ }
1448
+
1449
+ /**
1450
+ * Function `valueToString` will convert the given value to string
1451
+ * This is useful and used in the `templateParameters` function
1452
+ *
1453
+ * Note: This function is not just calling `toString` method
1454
+ * It's more complex and can handle this conversion specifically for LLM models
1455
+ * See `VALUE_STRINGS`
1456
+ *
1457
+ * Note: There are 2 similar functions
1458
+ * - `valueToString` converts value to string for LLM models as human-readable string
1459
+ * - `asSerializable` converts value to string to preserve full information to be able to convert it back
1460
+ *
1461
+ * @public exported from `@promptbook/utils`
1462
+ */
1463
+ function valueToString(value) {
1464
+ try {
1465
+ if (value === '') {
1466
+ return VALUE_STRINGS.empty;
1467
+ }
1468
+ else if (value === null) {
1469
+ return VALUE_STRINGS.null;
1470
+ }
1471
+ else if (value === undefined) {
1472
+ return VALUE_STRINGS.undefined;
1473
+ }
1474
+ else if (typeof value === 'string') {
1475
+ return value;
1476
+ }
1477
+ else if (typeof value === 'number') {
1478
+ return numberToString(value);
1479
+ }
1480
+ else if (value instanceof Date) {
1481
+ return value.toISOString();
1482
+ }
1483
+ else {
1484
+ try {
1485
+ return JSON.stringify(value);
1486
+ }
1487
+ catch (error) {
1488
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
1489
+ return VALUE_STRINGS.circular;
1490
+ }
1491
+ throw error;
1492
+ }
1493
+ }
1494
+ }
1495
+ catch (error) {
1496
+ assertsError(error);
1497
+ console.error(error);
1498
+ return VALUE_STRINGS.unserializable;
1499
+ }
1500
+ }
1501
+
1502
+ /**
1503
+ * Computes SHA-256 hash of the given object
1504
+ *
1505
+ * @public exported from `@promptbook/utils`
1506
+ */
1507
+ function computeHash(value) {
1508
+ return SHA256(hexEncoder.parse(spaceTrim$1(valueToString(value)))).toString( /* hex */);
1509
+ }
1510
+ /**
1511
+ * TODO: [🥬][🥬] Use this ACRY
1512
+ */
1513
+
1514
+ /**
1515
+ * Computes SHA-256 hash of the agent source
1516
+ *
1517
+ * @public exported from `@promptbook/core`
1518
+ */
1519
+ function computeAgentHash(agentSource) {
1520
+ return computeHash(agentSource);
1521
+ }
1522
+
1523
+ var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModels",description:"List of available model names together with their descriptions as JSON",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelsRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n```json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n```\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n```json\n{availableModels}\n```\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelsRequirements",format:"JSON",dependentParameterNames:["availableModels","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModels}` List of available model names together with their descriptions as JSON\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelsRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n\\`\\`\\`json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpful assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n\\`\\`\\`json\n{availableModels}\n\\`\\`\\`\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelsRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
1524
+
1525
+ /**
1526
+ * Checks if value is valid email
1527
+ *
1528
+ * @public exported from `@promptbook/utils`
1529
+ */
1530
+ function isValidEmail(email) {
1531
+ if (typeof email !== 'string') {
1532
+ return false;
1533
+ }
1534
+ if (email.split('\n').length > 1) {
1535
+ return false;
1536
+ }
1537
+ return /^.+@.+\..+$/.test(email);
1538
+ }
1539
+
1540
+ /**
1541
+ * Tests if given string is valid file path.
1542
+ *
1543
+ * Note: This does not check if the file exists only if the path is valid
1544
+ * @public exported from `@promptbook/utils`
1545
+ */
1546
+ function isValidFilePath(filename) {
1547
+ if (typeof filename !== 'string') {
1548
+ return false;
1549
+ }
1550
+ if (filename.split('\n').length > 1) {
1551
+ return false;
1552
+ }
1553
+ // Normalize slashes early so heuristics can detect path-like inputs
1554
+ const filenameSlashes = filename.replace(/\\/g, '/');
1555
+ // Reject strings that look like sentences (informational text)
1556
+ // Heuristic: contains multiple spaces and ends with a period, or contains typical sentence punctuation
1557
+ // But skip this heuristic if the string looks like a path (contains '/' or starts with a drive letter)
1558
+ if (filename.trim().length > 60 && // long enough to be a sentence
1559
+ /[.!?]/.test(filename) && // contains sentence punctuation
1560
+ filename.split(' ').length > 8 && // has many words
1561
+ !/\/|^[A-Z]:/i.test(filenameSlashes) // do NOT treat as sentence if looks like a path
1562
+ ) {
1563
+ return false;
1564
+ }
1565
+ // Absolute Unix path: /hello.txt
1566
+ if (/^(\/)/i.test(filenameSlashes)) {
1567
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
1568
+ return true;
1569
+ }
1570
+ // Absolute Windows path: C:/ or C:\ (allow spaces and multiple dots in filename)
1571
+ if (/^[A-Z]:\/.+$/i.test(filenameSlashes)) {
1572
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
1573
+ return true;
1574
+ }
1575
+ // Relative path: ./hello.txt
1576
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
1577
+ // console.log(filename, 'Relative path: ./hello.txt');
1578
+ return true;
1579
+ }
1580
+ // Allow paths like foo/hello
1581
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
1582
+ // console.log(filename, 'Allow paths like foo/hello');
1583
+ return true;
1584
+ }
1585
+ // Allow paths like hello.book
1586
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
1587
+ // console.log(filename, 'Allow paths like hello.book');
1588
+ return true;
1589
+ }
1590
+ return false;
1591
+ }
1592
+ /**
1593
+ * TODO: [🍏] Implement for MacOs
1594
+ */
1595
+
1596
+ /**
1597
+ * Tests if given string is valid URL.
1460
1598
  *
1461
- * @public exported from `@promptbook/core`
1599
+ * Note: [🔂] This function is idempotent.
1600
+ * Note: Dataurl are considered perfectly valid.
1601
+ * Note: There are two similar functions:
1602
+ * - `isValidUrl` which tests any URL
1603
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
1604
+ *
1605
+ * @public exported from `@promptbook/utils`
1462
1606
  */
1463
- class UnexpectedError extends Error {
1464
- constructor(message) {
1465
- super(spaceTrim$2((block) => `
1466
- ${block(message)}
1467
-
1468
- Note: This error should not happen.
1469
- It's probably a bug in the pipeline collection
1470
-
1471
- Please report issue:
1472
- ${block(getErrorReportUrl(new Error(message)).href)}
1473
-
1474
- Or contact us on ${ADMIN_EMAIL}
1475
-
1476
- `));
1477
- this.name = 'UnexpectedError';
1478
- Object.setPrototypeOf(this, UnexpectedError.prototype);
1607
+ function isValidUrl(url) {
1608
+ if (typeof url !== 'string') {
1609
+ return false;
1610
+ }
1611
+ try {
1612
+ if (url.startsWith('blob:')) {
1613
+ url = url.replace(/^blob:/, '');
1614
+ }
1615
+ const urlObject = new URL(url /* because fail is handled */);
1616
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
1617
+ return false;
1618
+ }
1619
+ return true;
1620
+ }
1621
+ catch (error) {
1622
+ return false;
1479
1623
  }
1480
1624
  }
1481
1625
 
1482
1626
  /**
1483
- * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
1627
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
1484
1628
  *
1485
1629
  * @public exported from `@promptbook/core`
1486
1630
  */
1487
- class WrappedError extends Error {
1488
- constructor(whatWasThrown) {
1489
- const tag = `[🤮]`;
1490
- console.error(tag, whatWasThrown);
1491
- super(spaceTrim$2(`
1492
- Non-Error object was thrown
1493
-
1494
- Note: Look for ${tag} in the console for more details
1495
- Please report issue on ${ADMIN_EMAIL}
1496
- `));
1497
- this.name = 'WrappedError';
1498
- Object.setPrototypeOf(this, WrappedError.prototype);
1631
+ class ParseError extends Error {
1632
+ constructor(message) {
1633
+ super(message);
1634
+ this.name = 'ParseError';
1635
+ Object.setPrototypeOf(this, ParseError.prototype);
1499
1636
  }
1500
1637
  }
1501
-
1502
1638
  /**
1503
- * Helper used in catch blocks to assert that the error is an instance of `Error`
1504
- *
1505
- * @param whatWasThrown Any object that was thrown
1506
- * @returns Nothing if the error is an instance of `Error`
1507
- * @throws `WrappedError` or `UnexpectedError` if the error is not standard
1508
- *
1509
- * @private within the repository
1639
+ * TODO: Maybe split `ParseError` and `ApplyError`
1510
1640
  */
1511
- function assertsError(whatWasThrown) {
1512
- // Case 1: Handle error which was rethrown as `WrappedError`
1513
- if (whatWasThrown instanceof WrappedError) {
1514
- const wrappedError = whatWasThrown;
1515
- throw wrappedError;
1516
- }
1517
- // Case 2: Handle unexpected errors
1518
- if (whatWasThrown instanceof UnexpectedError) {
1519
- const unexpectedError = whatWasThrown;
1520
- throw unexpectedError;
1521
- }
1522
- // Case 3: Handle standard errors - keep them up to consumer
1523
- if (whatWasThrown instanceof Error) {
1524
- return;
1525
- }
1526
- // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
1527
- throw new WrappedError(whatWasThrown);
1528
- }
1529
1641
 
1530
1642
  /**
1531
1643
  * Function isValidJsonString will tell you if the string is valid JSON or not
@@ -1939,7 +2051,7 @@ function deepClone(objectValue) {
1939
2051
  TODO: [🧠] Is there a better implementation?
1940
2052
  > const propertyNames = Object.getOwnPropertyNames(objectValue);
1941
2053
  > for (const propertyName of propertyNames) {
1942
- > const value = (objectValue as really_any)[propertyName];
2054
+ > const value = (objectValue as chococake)[propertyName];
1943
2055
  > if (value && typeof value === 'object') {
1944
2056
  > deepClone(value);
1945
2057
  > }
@@ -2792,7 +2904,7 @@ class DatabaseError extends Error {
2792
2904
  }
2793
2905
  }
2794
2906
  /**
2795
- * TODO: !!!! Explain that NotFoundError (!!! and other specific errors) has priority over DatabaseError in some contexts
2907
+ * TODO: [🐱‍🚀] Explain that NotFoundError ([🐱‍🚀] and other specific errors) has priority over DatabaseError in some contexts
2796
2908
  */
2797
2909
 
2798
2910
  /**
@@ -5061,87 +5173,6 @@ async function preparePipeline(pipeline, tools, options) {
5061
5173
  * @see https://docs.anthropic.com/en/docs/test-and-evaluate/strengthen-guardrails/increase-consistency#specify-the-desired-output-format
5062
5174
  */
5063
5175
 
5064
- /**
5065
- * Format either small or big number
5066
- *
5067
- * @public exported from `@promptbook/utils`
5068
- */
5069
- function numberToString(value) {
5070
- if (value === 0) {
5071
- return '0';
5072
- }
5073
- else if (Number.isNaN(value)) {
5074
- return VALUE_STRINGS.nan;
5075
- }
5076
- else if (value === Infinity) {
5077
- return VALUE_STRINGS.infinity;
5078
- }
5079
- else if (value === -Infinity) {
5080
- return VALUE_STRINGS.negativeInfinity;
5081
- }
5082
- for (let exponent = 0; exponent < 15; exponent++) {
5083
- const factor = 10 ** exponent;
5084
- const valueRounded = Math.round(value * factor) / factor;
5085
- if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
5086
- return valueRounded.toFixed(exponent);
5087
- }
5088
- }
5089
- return value.toString();
5090
- }
5091
-
5092
- /**
5093
- * Function `valueToString` will convert the given value to string
5094
- * This is useful and used in the `templateParameters` function
5095
- *
5096
- * Note: This function is not just calling `toString` method
5097
- * It's more complex and can handle this conversion specifically for LLM models
5098
- * See `VALUE_STRINGS`
5099
- *
5100
- * Note: There are 2 similar functions
5101
- * - `valueToString` converts value to string for LLM models as human-readable string
5102
- * - `asSerializable` converts value to string to preserve full information to be able to convert it back
5103
- *
5104
- * @public exported from `@promptbook/utils`
5105
- */
5106
- function valueToString(value) {
5107
- try {
5108
- if (value === '') {
5109
- return VALUE_STRINGS.empty;
5110
- }
5111
- else if (value === null) {
5112
- return VALUE_STRINGS.null;
5113
- }
5114
- else if (value === undefined) {
5115
- return VALUE_STRINGS.undefined;
5116
- }
5117
- else if (typeof value === 'string') {
5118
- return value;
5119
- }
5120
- else if (typeof value === 'number') {
5121
- return numberToString(value);
5122
- }
5123
- else if (value instanceof Date) {
5124
- return value.toISOString();
5125
- }
5126
- else {
5127
- try {
5128
- return JSON.stringify(value);
5129
- }
5130
- catch (error) {
5131
- if (error instanceof TypeError && error.message.includes('circular structure')) {
5132
- return VALUE_STRINGS.circular;
5133
- }
5134
- throw error;
5135
- }
5136
- }
5137
- }
5138
- catch (error) {
5139
- assertsError(error);
5140
- console.error(error);
5141
- return VALUE_STRINGS.unserializable;
5142
- }
5143
- }
5144
-
5145
5176
  /**
5146
5177
  * Parses the given script and returns the list of all used variables that are not defined in the script
5147
5178
  *
@@ -8068,6 +8099,60 @@ class MemoryCommitmentDefinition extends BaseCommitmentDefinition {
8068
8099
  * Note: [💞] Ignore a discrepancy between file name and entity name
8069
8100
  */
8070
8101
 
8102
+ /**
8103
+ * INITIAL MESSAGE commitment definition
8104
+ *
8105
+ * The INITIAL MESSAGE commitment defines the first message that the user sees when opening the chat.
8106
+ * It is used to greet the user and set the tone of the conversation.
8107
+ *
8108
+ * Example usage in agent source:
8109
+ *
8110
+ * ```book
8111
+ * INITIAL MESSAGE Hello! I am ready to help you with your tasks.
8112
+ * ```
8113
+ *
8114
+ * @private [🪔] Maybe export the commitments through some package
8115
+ */
8116
+ class InitialMessageCommitmentDefinition extends BaseCommitmentDefinition {
8117
+ constructor() {
8118
+ super('INITIAL MESSAGE');
8119
+ }
8120
+ /**
8121
+ * Short one-line description of INITIAL MESSAGE.
8122
+ */
8123
+ get description() {
8124
+ return 'Defines the **initial message** shown to the user when the chat starts.';
8125
+ }
8126
+ /**
8127
+ * Markdown documentation for INITIAL MESSAGE commitment.
8128
+ */
8129
+ get documentation() {
8130
+ return spaceTrim$2(`
8131
+ # ${this.type}
8132
+
8133
+ Defines the first message that the user sees when opening the chat. This message is purely for display purposes in the UI and does not inherently become part of the LLM's system prompt context (unless also included via other means).
8134
+
8135
+ ## Key aspects
8136
+
8137
+ - Used to greet the user.
8138
+ - Sets the tone of the conversation.
8139
+ - Displayed immediately when the chat interface loads.
8140
+
8141
+ ## Examples
8142
+
8143
+ \`\`\`book
8144
+ Support Agent
8145
+
8146
+ PERSONA You are a helpful support agent.
8147
+ INITIAL MESSAGE Hi there! How can I assist you today?
8148
+ \`\`\`
8149
+ `);
8150
+ }
8151
+ applyToAgentModelRequirements(requirements, content) {
8152
+ return requirements;
8153
+ }
8154
+ }
8155
+
8071
8156
  /**
8072
8157
  * MESSAGE commitment definition
8073
8158
  *
@@ -9229,6 +9314,7 @@ const COMMITMENT_REGISTRY = [
9229
9314
  new NoteCommitmentDefinition('NONCE'),
9230
9315
  new GoalCommitmentDefinition('GOAL'),
9231
9316
  new GoalCommitmentDefinition('GOALS'),
9317
+ new InitialMessageCommitmentDefinition(),
9232
9318
  new MessageCommitmentDefinition('MESSAGE'),
9233
9319
  new MessageCommitmentDefinition('MESSAGES'),
9234
9320
  new ScenarioCommitmentDefinition('SCENARIO'),
@@ -10117,13 +10203,31 @@ function parseAgentSource(agentSource) {
10117
10203
  }
10118
10204
  personaDescription += commitment.content;
10119
10205
  }
10206
+ let initialMessage = null;
10207
+ for (const commitment of parseResult.commitments) {
10208
+ if (commitment.type !== 'INITIAL MESSAGE') {
10209
+ continue;
10210
+ }
10211
+ // Note: Initial message override logic - later overrides earlier
10212
+ // Or should it append? Usually initial message is just one block.
10213
+ // Let's stick to "later overrides earlier" for simplicity, or just take the last one.
10214
+ initialMessage = commitment.content;
10215
+ }
10120
10216
  const meta = {};
10217
+ const links = [];
10121
10218
  for (const commitment of parseResult.commitments) {
10219
+ if (commitment.type === 'META LINK') {
10220
+ links.push(spaceTrim$1(commitment.content));
10221
+ continue;
10222
+ }
10122
10223
  if (commitment.type !== 'META') {
10123
10224
  continue;
10124
10225
  }
10125
10226
  // Parse META commitments - format is "META TYPE content"
10126
10227
  const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
10228
+ if (metaTypeRaw === 'LINK') {
10229
+ links.push(spaceTrim$1(commitment.content.substring(metaTypeRaw.length)));
10230
+ }
10127
10231
  const metaType = normalizeTo_camelCase(metaTypeRaw);
10128
10232
  meta[metaType] = spaceTrim$1(commitment.content.substring(metaTypeRaw.length));
10129
10233
  }
@@ -10139,7 +10243,9 @@ function parseAgentSource(agentSource) {
10139
10243
  agentName: normalizeAgentName(parseResult.agentName || createDefaultAgentName(agentSource)),
10140
10244
  agentHash,
10141
10245
  personaDescription,
10246
+ initialMessage,
10142
10247
  meta,
10248
+ links,
10143
10249
  parameters,
10144
10250
  };
10145
10251
  }
@@ -10310,26 +10416,28 @@ const DEFAULT_BOOK = padBook(validateBook(spaceTrim$1(`
10310
10416
  PERSONA A friendly AI assistant that helps you with your tasks
10311
10417
  `)));
10312
10418
  // <- Note: Not using book`...` notation to avoid strange error in jest unit tests `TypeError: (0 , book_notation_1.book) is not a function`
10313
- // <- TODO: !!! `GENESIS_BOOK` / `ADAM_BOOK` in `/agents/adam.book`
10314
- // <- !!! Buttons into genesis book
10315
- // <- TODO: !!! generateBookBoilerplate and deprecate `DEFAULT_BOOK`
10419
+ // <- TODO: [🐱‍🚀] `GENESIS_BOOK` / `ADAM_BOOK` in `/agents/adam.book`
10420
+ // <- [🐱‍🚀] Buttons into genesis book
10421
+ // <- TODO: [🐱‍🚀] generateBookBoilerplate and deprecate `DEFAULT_BOOK`
10316
10422
 
10423
+ // import { getTableName } from '../../../../../apps/agents-server/src/database/getTableName';
10424
+ // <- TODO: [🐱‍🚀] Prevent imports from `/apps` -> `/src`
10317
10425
  /**
10318
10426
  * Agent collection stored in Supabase table
10319
10427
  *
10320
10428
  * Note: This object can work both from Node.js and browser environment depending on the Supabase client provided
10321
10429
  *
10322
10430
  * @public exported from `@promptbook/core`
10323
- * <- TODO: !!! Move to `@promptbook/supabase` package
10431
+ * <- TODO: [🐱‍🚀] Move to `@promptbook/supabase` package
10324
10432
  */
10325
- class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10433
+ class AgentCollectionInSupabase /* TODO: [🐱‍🚀] implements Agent */ {
10326
10434
  /**
10327
10435
  * @param rootPath - path to the directory with agents
10328
- * @param tools - Execution tools to be used in !!! `Agent` itself and listing the agents
10436
+ * @param tools - Execution tools to be used in [🐱‍🚀] `Agent` itself and listing the agents
10329
10437
  * @param options - Options for the collection creation
10330
10438
  */
10331
10439
  constructor(supabaseClient,
10332
- /// TODO: !!! Remove> private readonly tools?: Pick<ExecutionTools, 'llm' | 'fs' | 'scrapers'>,
10440
+ /// TODO: [🐱‍🚀] Remove> private readonly tools?: Pick<ExecutionTools, 'llm' | 'fs' | 'scrapers'>,
10333
10441
  options) {
10334
10442
  this.supabaseClient = supabaseClient;
10335
10443
  this.options = options;
@@ -10343,7 +10451,9 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10343
10451
  */
10344
10452
  async listAgents( /* TODO: [🧠] Allow to pass some condition here */) {
10345
10453
  const { isVerbose = DEFAULT_IS_VERBOSE } = this.options || {};
10346
- const selectResult = await this.supabaseClient.from('Agent').select('agentName,agentProfile');
10454
+ const selectResult = await this.supabaseClient
10455
+ .from(this.getTableName('Agent'))
10456
+ .select('agentName,agentProfile');
10347
10457
  if (selectResult.error) {
10348
10458
  throw new DatabaseError(spaceTrim((block) => `
10349
10459
 
@@ -10371,11 +10481,11 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10371
10481
  });
10372
10482
  }
10373
10483
  /**
10374
- * !!!@@@
10484
+ * [🐱‍🚀]@@@
10375
10485
  */
10376
10486
  async getAgentSource(agentName) {
10377
10487
  const selectResult = await this.supabaseClient
10378
- .from('Agent')
10488
+ .from(this.getTableName('Agent'))
10379
10489
  .select('agentSource')
10380
10490
  .eq('agentName', agentName)
10381
10491
  .single();
@@ -10391,7 +10501,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10391
10501
 
10392
10502
  ${block(selectResult.error.message)}
10393
10503
  `));
10394
- // <- TODO: !!! First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
10504
+ // <- TODO: [🐱‍🚀] First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
10395
10505
  }
10396
10506
  return selectResult.data.agentSource;
10397
10507
  }
@@ -10404,7 +10514,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10404
10514
  const agentProfile = parseAgentSource(agentSource);
10405
10515
  // <- TODO: [🕛]
10406
10516
  const { agentName, agentHash } = agentProfile;
10407
- const insertAgentResult = await this.supabaseClient.from('Agent').insert({
10517
+ const insertAgentResult = await this.supabaseClient.from(this.getTableName('Agent')).insert({
10408
10518
  agentName,
10409
10519
  agentHash,
10410
10520
  agentProfile,
@@ -10421,7 +10531,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10421
10531
  ${block(insertAgentResult.error.message)}
10422
10532
  `));
10423
10533
  }
10424
- await this.supabaseClient.from('AgentHistory').insert({
10534
+ await this.supabaseClient.from(this.getTableName('AgentHistory')).insert({
10425
10535
  createdAt: new Date().toISOString(),
10426
10536
  agentName,
10427
10537
  agentHash,
@@ -10437,7 +10547,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10437
10547
  */
10438
10548
  async updateAgentSource(agentName, agentSource) {
10439
10549
  const selectPreviousAgentResult = await this.supabaseClient
10440
- .from('Agent')
10550
+ .from(this.getTableName('Agent'))
10441
10551
  .select('agentHash,agentName')
10442
10552
  .eq('agentName', agentName)
10443
10553
  .single();
@@ -10448,7 +10558,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10448
10558
 
10449
10559
  ${block(selectPreviousAgentResult.error.message)}
10450
10560
  `));
10451
- // <- TODO: !!! First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
10561
+ // <- TODO: [🐱‍🚀] First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
10452
10562
  }
10453
10563
  selectPreviousAgentResult.data.agentName;
10454
10564
  const previousAgentHash = selectPreviousAgentResult.data.agentHash;
@@ -10456,9 +10566,9 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10456
10566
  // <- TODO: [🕛]
10457
10567
  const { agentHash } = agentProfile;
10458
10568
  const updateAgentResult = await this.supabaseClient
10459
- .from('Agent')
10569
+ .from(this.getTableName('Agent'))
10460
10570
  .update({
10461
- // TODO: !!!! Compare not update> agentName: agentProfile.agentName || '!!!!!' /* <- TODO: !!!! Remove */,
10571
+ // TODO: [🐱‍🚀] Compare not update> agentName: agentProfile.agentName || '[🐱‍🚀]' /* <- TODO: [🐱‍🚀] Remove */,
10462
10572
  agentProfile,
10463
10573
  updatedAt: new Date().toISOString(),
10464
10574
  agentHash: agentProfile.agentHash,
@@ -10466,9 +10576,9 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10466
10576
  promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
10467
10577
  })
10468
10578
  .eq('agentName', agentName);
10469
- // console.log('!!! updateAgent', updateResult);
10470
- // console.log('!!! old', oldAgentSource);
10471
- // console.log('!!! new', newAgentSource);
10579
+ // console.log('[🐱‍🚀] updateAgent', updateResult);
10580
+ // console.log('[🐱‍🚀] old', oldAgentSource);
10581
+ // console.log('[🐱‍🚀] new', newAgentSource);
10472
10582
  if (updateAgentResult.error) {
10473
10583
  throw new DatabaseError(spaceTrim((block) => `
10474
10584
  Error updating agent "${agentName}" in Supabase:
@@ -10476,7 +10586,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10476
10586
  ${block(updateAgentResult.error.message)}
10477
10587
  `));
10478
10588
  }
10479
- await this.supabaseClient.from('AgentHistory').insert({
10589
+ await this.supabaseClient.from(this.getTableName('AgentHistory')).insert({
10480
10590
  createdAt: new Date().toISOString(),
10481
10591
  agentName,
10482
10592
  agentHash,
@@ -10486,7 +10596,7 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10486
10596
  });
10487
10597
  // <- TODO: [🧠] What to do with `insertAgentHistoryResult.error`, ignore? wait?
10488
10598
  }
10489
- // TODO: !!!! public async getAgentSourceSubject(agentName: string_agent_name): Promise<BehaviorSubject<string_book>>
10599
+ // TODO: [🐱‍🚀] public async getAgentSourceSubject(agentName: string_agent_name): Promise<BehaviorSubject<string_book>>
10490
10600
  // Use Supabase realtime logic
10491
10601
  /**
10492
10602
  * Deletes an agent from the collection
@@ -10494,9 +10604,19 @@ class AgentCollectionInSupabase /* TODO: !!!!!! implements Agent */ {
10494
10604
  async deleteAgent(agentName) {
10495
10605
  throw new NotYetImplementedError('Method not implemented.');
10496
10606
  }
10607
+ /**
10608
+ * Get the Supabase table name with prefix
10609
+ *
10610
+ * @param tableName - The original table name
10611
+ * @returns The prefixed table name
10612
+ */
10613
+ getTableName(tableName) {
10614
+ const { tablePrefix = '' } = this.options || {};
10615
+ return `${tablePrefix}${tableName}`;
10616
+ }
10497
10617
  }
10498
10618
  /**
10499
- * TODO: !!!! Implement it here correctly and update JSDoc comments here, and on interface + other implementations
10619
+ * TODO: [🐱‍🚀] Implement it here correctly and update JSDoc comments here, and on interface + other implementations
10500
10620
  * TODO: Write unit test
10501
10621
  * TODO: [🧠][🚙] `AgentXxx` vs `AgentsXxx` naming convention
10502
10622
  */
@@ -16651,11 +16771,12 @@ class OpenAiExecutionTools extends OpenAiCompatibleExecutionTools {
16651
16771
  *
16652
16772
  * This is useful for calling OpenAI API with a single assistant, for more wide usage use `OpenAiExecutionTools`.
16653
16773
  *
16654
- * !!! Note: [🦖] There are several different things in Promptbook:
16774
+ * Note: [🦖] There are several different things in Promptbook:
16655
16775
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
16656
16776
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
16657
16777
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
16658
16778
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
16779
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
16659
16780
  *
16660
16781
  * @public exported from `@promptbook/openai`
16661
16782
  */
@@ -16690,6 +16811,12 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
16690
16811
  * Calls OpenAI API to use a chat model.
16691
16812
  */
16692
16813
  async callChatModel(prompt) {
16814
+ return this.callChatModelStream(prompt, () => { });
16815
+ }
16816
+ /**
16817
+ * Calls OpenAI API to use a chat model with streaming.
16818
+ */
16819
+ async callChatModelStream(prompt, onProgress) {
16693
16820
  var _a, _b, _c;
16694
16821
  if (this.options.isVerbose) {
16695
16822
  console.info('💬 OpenAI callChatModel call', { prompt });
@@ -16757,21 +16884,24 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
16757
16884
  console.info('connect', stream.currentEvent);
16758
16885
  }
16759
16886
  });
16760
- /*
16761
- stream.on('messageDelta', (messageDelta) => {
16762
- if (
16763
- this.options.isVerbose &&
16764
- messageDelta &&
16765
- messageDelta.content &&
16766
- messageDelta.content[0] &&
16767
- messageDelta.content[0].type === 'text'
16768
- ) {
16769
- console.info('messageDelta', messageDelta.content[0].text?.value);
16887
+ stream.on('textDelta', (textDelta, snapshot) => {
16888
+ if (this.options.isVerbose && textDelta.value) {
16889
+ console.info('textDelta', textDelta.value);
16770
16890
  }
16771
-
16772
- // <- TODO: [🐚] Make streaming and running tasks working
16891
+ const chunk = {
16892
+ content: textDelta.value || '',
16893
+ modelName: 'assistant',
16894
+ timing: {
16895
+ start,
16896
+ complete: $getCurrentDate(),
16897
+ },
16898
+ usage: UNCERTAIN_USAGE,
16899
+ rawPromptContent,
16900
+ rawRequest,
16901
+ rawResponse: snapshot,
16902
+ };
16903
+ onProgress(chunk);
16773
16904
  });
16774
- */
16775
16905
  stream.on('messageCreated', (message) => {
16776
16906
  if (this.options.isVerbose) {
16777
16907
  console.info('messageCreated', message);
@@ -16807,7 +16937,7 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
16807
16937
  }
16808
16938
  return exportJson({
16809
16939
  name: 'promptResult',
16810
- message: `Result of \`OpenAiAssistantExecutionTools.callChatModel\``,
16940
+ message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\``,
16811
16941
  order: [],
16812
16942
  value: {
16813
16943
  content: resultContent,
@@ -16946,9 +17076,9 @@ class OpenAiAssistantExecutionTools extends OpenAiExecutionTools {
16946
17076
  }
16947
17077
  const assistant = await client.beta.assistants.create(assistantConfig);
16948
17078
  console.log(`✅ Assistant created: ${assistant.id}`);
16949
- // TODO: !!!! Try listing existing assistants
16950
- // TODO: !!!! Try marking existing assistants by DISCRIMINANT
16951
- // TODO: !!!! Allow to update and reconnect to existing assistants
17079
+ // TODO: [🐱‍🚀] Try listing existing assistants
17080
+ // TODO: [🐱‍🚀] Try marking existing assistants by DISCRIMINANT
17081
+ // TODO: [🐱‍🚀] Allow to update and reconnect to existing assistants
16952
17082
  return new OpenAiAssistantExecutionTools({
16953
17083
  ...this.options,
16954
17084
  isCreatingNewAssistantsAllowed: false,
@@ -17076,11 +17206,12 @@ const DISCRIMINANT = 'OPEN_AI_ASSISTANT_V1';
17076
17206
  * Execution Tools for calling LLM models with a predefined agent "soul"
17077
17207
  * This wraps underlying LLM execution tools and applies agent-specific system prompts and requirements
17078
17208
  *
17079
- * !!! Note: [🦖] There are several different things in Promptbook:
17209
+ * Note: [🦖] There are several different things in Promptbook:
17080
17210
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
17081
17211
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
17082
17212
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
17083
17213
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
17214
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
17084
17215
  *
17085
17216
  * @public exported from `@promptbook/core`
17086
17217
  */
@@ -17174,9 +17305,12 @@ class AgentLlmExecutionTools {
17174
17305
  * Calls the chat model with agent-specific system prompt and requirements
17175
17306
  */
17176
17307
  async callChatModel(prompt) {
17177
- if (!this.options.llmTools.callChatModel) {
17178
- throw new Error('Underlying LLM execution tools do not support chat model calls');
17179
- }
17308
+ return this.callChatModelStream(prompt, () => { });
17309
+ }
17310
+ /**
17311
+ * Calls the chat model with agent-specific system prompt and requirements with streaming
17312
+ */
17313
+ async callChatModelStream(prompt, onProgress) {
17180
17314
  // Ensure we're working with a chat prompt
17181
17315
  if (prompt.modelRequirements.modelVariant !== 'CHAT') {
17182
17316
  throw new Error('AgentLlmExecutionTools only supports chat prompts');
@@ -17215,7 +17349,7 @@ class AgentLlmExecutionTools {
17215
17349
  if (this.options.isVerbose) {
17216
17350
  console.log(`1️⃣ Creating new OpenAI Assistant for agent ${this.title}...`);
17217
17351
  }
17218
- // <- TODO: !!! Check also `isCreatingNewAssistantsAllowed` and warn about it
17352
+ // <- TODO: [🐱‍🚀] Check also `isCreatingNewAssistantsAllowed` and warn about it
17219
17353
  assistant = await this.options.llmTools.createNewAssistant({
17220
17354
  name: this.title,
17221
17355
  instructions: modelRequirements.systemMessage,
@@ -17232,7 +17366,7 @@ class AgentLlmExecutionTools {
17232
17366
  requirementsHash,
17233
17367
  });
17234
17368
  }
17235
- underlyingLlmResult = await assistant.callChatModel(chatPrompt);
17369
+ underlyingLlmResult = await assistant.callChatModelStream(chatPrompt, onProgress);
17236
17370
  }
17237
17371
  else {
17238
17372
  if (this.options.isVerbose) {
@@ -17251,7 +17385,16 @@ class AgentLlmExecutionTools {
17251
17385
  : ''),
17252
17386
  },
17253
17387
  };
17254
- underlyingLlmResult = await this.options.llmTools.callChatModel(modifiedChatPrompt);
17388
+ if (this.options.llmTools.callChatModelStream) {
17389
+ underlyingLlmResult = await this.options.llmTools.callChatModelStream(modifiedChatPrompt, onProgress);
17390
+ }
17391
+ else if (this.options.llmTools.callChatModel) {
17392
+ underlyingLlmResult = await this.options.llmTools.callChatModel(modifiedChatPrompt);
17393
+ onProgress(underlyingLlmResult);
17394
+ }
17395
+ else {
17396
+ throw new Error('Underlying LLM execution tools do not support chat model calls');
17397
+ }
17255
17398
  }
17256
17399
  let content = underlyingLlmResult.content;
17257
17400
  // Note: Cleanup the AI artifacts from the content
@@ -17278,11 +17421,12 @@ AgentLlmExecutionTools.assistantCache = new Map();
17278
17421
  /**
17279
17422
  * Represents one AI Agent
17280
17423
  *
17281
- * !!! Note: [🦖] There are several different things in Promptbook:
17424
+ * Note: [🦖] There are several different things in Promptbook:
17282
17425
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
17283
17426
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
17284
17427
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
17285
17428
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
17429
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
17286
17430
  *
17287
17431
  * @public exported from `@promptbook/core`
17288
17432
  */
@@ -17312,31 +17456,40 @@ class Agent extends AgentLlmExecutionTools {
17312
17456
  super({
17313
17457
  isVerbose: options.isVerbose,
17314
17458
  llmTools: getSingleLlmExecutionTools(options.executionTools.llm),
17315
- agentSource: agentSource.value, // <- TODO: !!!! Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
17459
+ agentSource: agentSource.value, // <- TODO: [🐱‍🚀] Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
17316
17460
  });
17317
17461
  this._agentName = undefined;
17318
17462
  /**
17319
17463
  * Description of the agent
17320
17464
  */
17321
17465
  this.personaDescription = null;
17466
+ /**
17467
+ * The initial message shown to the user when the chat starts
17468
+ */
17469
+ this.initialMessage = null;
17470
+ /**
17471
+ * Links found in the agent source
17472
+ */
17473
+ this.links = [];
17322
17474
  /**
17323
17475
  * Metadata like image or color
17324
17476
  */
17325
17477
  this.meta = {};
17326
- // TODO: !!!!! Add `Agent` simple "mocked" learning by appending to agent source
17327
- // TODO: !!!!! Add `Agent` learning by promptbookAgent
17478
+ // TODO: [🐱‍🚀] Add `Agent` simple "mocked" learning by appending to agent source
17479
+ // TODO: [🐱‍🚀] Add `Agent` learning by promptbookAgent
17328
17480
  this.agentSource = agentSource;
17329
17481
  this.agentSource.subscribe((source) => {
17330
- const { agentName, personaDescription, meta } = parseAgentSource(source);
17482
+ const { agentName, personaDescription, initialMessage, links, meta } = parseAgentSource(source);
17331
17483
  this._agentName = agentName;
17332
17484
  this.personaDescription = personaDescription;
17485
+ this.initialMessage = initialMessage;
17486
+ this.links = links;
17333
17487
  this.meta = { ...this.meta, ...meta };
17334
17488
  });
17335
17489
  }
17336
17490
  }
17337
17491
  /**
17338
17492
  * TODO: [🧠][😰]Agent is not working with the parameters, should it be?
17339
- * TODO: !!! Agent on remote server
17340
17493
  */
17341
17494
 
17342
17495
  /**
@@ -17402,24 +17555,24 @@ const _AgentRegistration = $llmToolsRegister.register(createAgentLlmExecutionToo
17402
17555
  /**
17403
17556
  * Represents one AI Agent
17404
17557
  *
17405
- * !!!!!! Note: [🦖] There are several different things in Promptbook:
17558
+ * Note: [🦖] There are several different things in Promptbook:
17406
17559
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
17407
- * !!!!!! `RemoteAgent`
17408
17560
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
17409
17561
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
17410
17562
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
17563
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
17411
17564
  *
17412
17565
  * @public exported from `@promptbook/core`
17413
17566
  */
17414
17567
  class RemoteAgent extends Agent {
17415
17568
  static async connect(options) {
17416
- console.log('!!!!!', `${options.agentUrl}/api/book`);
17569
+ console.log('[🐱‍🚀]', `${options.agentUrl}/api/book`);
17417
17570
  const bookResponse = await fetch(`${options.agentUrl}/api/book`);
17418
- // <- TODO: !!!! What about closed-source agents?
17419
- // <- TODO: !!!! Maybe use promptbookFetch
17571
+ // <- TODO: [🐱‍🚀] What about closed-source agents?
17572
+ // <- TODO: [🐱‍🚀] Maybe use promptbookFetch
17420
17573
  const agentSourceValue = (await bookResponse.text());
17421
17574
  const agentSource = new BehaviorSubject(agentSourceValue);
17422
- // <- TODO: !!!! Support updating and self-updating
17575
+ // <- TODO: [🐱‍🚀] Support updating and self-updating
17423
17576
  return new RemoteAgent({
17424
17577
  ...options,
17425
17578
  executionTools: {
@@ -17446,13 +17599,29 @@ class RemoteAgent extends Agent {
17446
17599
  * Calls the agent on agents remote server
17447
17600
  */
17448
17601
  async callChatModel(prompt) {
17602
+ return this.callChatModelStream(prompt, () => { });
17603
+ }
17604
+ /**
17605
+ * Calls the agent on agents remote server with streaming
17606
+ */
17607
+ async callChatModelStream(prompt, onProgress) {
17449
17608
  // Ensure we're working with a chat prompt
17450
17609
  if (prompt.modelRequirements.modelVariant !== 'CHAT') {
17451
17610
  throw new Error('Agents only supports chat prompts');
17452
17611
  }
17453
- const bookResponse = await fetch(`${this.agentUrl}/api/chat?message=${encodeURIComponent(prompt.content)}`);
17454
- // <- TODO: !!!! What about closed-source agents?
17455
- // <- TODO: !!!! Maybe use promptbookFetch
17612
+ const chatPrompt = prompt;
17613
+ const bookResponse = await fetch(`${this.agentUrl}/api/chat`, {
17614
+ method: 'POST',
17615
+ headers: {
17616
+ 'Content-Type': 'application/json',
17617
+ },
17618
+ body: JSON.stringify({
17619
+ message: prompt.content,
17620
+ thread: chatPrompt.thread,
17621
+ }),
17622
+ });
17623
+ // <- TODO: [🐱‍🚀] What about closed-source agents?
17624
+ // <- TODO: [🐱‍🚀] Maybe use promptbookFetch
17456
17625
  let content = '';
17457
17626
  if (!bookResponse.body) {
17458
17627
  content = await bookResponse.text();
@@ -17471,16 +17640,37 @@ class RemoteAgent extends Agent {
17471
17640
  const textChunk = decoder.decode(value, { stream: true });
17472
17641
  // console.debug('RemoteAgent chunk:', textChunk);
17473
17642
  content += textChunk;
17643
+ onProgress({
17644
+ content,
17645
+ modelName: this.modelName,
17646
+ timing: {},
17647
+ usage: {},
17648
+ rawPromptContent: {},
17649
+ rawRequest: {},
17650
+ rawResponse: {},
17651
+ });
17474
17652
  }
17475
17653
  }
17476
17654
  // Flush any remaining decoder internal state
17477
- content += decoder.decode();
17655
+ const lastChunk = decoder.decode();
17656
+ if (lastChunk) {
17657
+ content += lastChunk;
17658
+ onProgress({
17659
+ content: lastChunk,
17660
+ modelName: this.modelName,
17661
+ timing: {},
17662
+ usage: {},
17663
+ rawPromptContent: {},
17664
+ rawRequest: {},
17665
+ rawResponse: {},
17666
+ });
17667
+ }
17478
17668
  }
17479
17669
  finally {
17480
17670
  reader.releaseLock();
17481
17671
  }
17482
17672
  }
17483
- // <- TODO: !!!! Transfer metadata
17673
+ // <- TODO: [🐱‍🚀] Transfer metadata
17484
17674
  const agentResult = {
17485
17675
  content,
17486
17676
  modelName: this.modelName,
@@ -17489,7 +17679,7 @@ class RemoteAgent extends Agent {
17489
17679
  rawPromptContent: {},
17490
17680
  rawRequest: {},
17491
17681
  rawResponse: {},
17492
- // <- TODO: !!!! Transfer and proxy the metadata
17682
+ // <- TODO: [🐱‍🚀] Transfer and proxy the metadata
17493
17683
  };
17494
17684
  return agentResult;
17495
17685
  }
@@ -18776,7 +18966,7 @@ function $generateBookBoilerplate(options) {
18776
18966
  const agentSource = validateBook(spaceTrim$1((block) => `
18777
18967
  ${agentName}
18778
18968
 
18779
- META COLOR ${color || '#3498db' /* <- TODO: [🧠] !!!! Best default color */}
18969
+ META COLOR ${color || '#3498db' /* <- TODO: [🧠] [🐱‍🚀] Best default color */}
18780
18970
  PERSONA ${block(personaDescription)}
18781
18971
  `));
18782
18972
  return agentSource;