@promptbook/core 0.103.0-47 → 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 (55) hide show
  1. package/esm/index.es.js +1287 -876
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/servers.d.ts +1 -0
  4. package/esm/typings/src/_packages/core.index.d.ts +6 -0
  5. package/esm/typings/src/_packages/types.index.d.ts +4 -0
  6. package/esm/typings/src/_packages/utils.index.d.ts +2 -0
  7. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +17 -3
  8. package/esm/typings/src/book-2.0/agent-source/AgentSourceParseResult.d.ts +2 -1
  9. package/esm/typings/src/book-2.0/agent-source/computeAgentHash.d.ts +8 -0
  10. package/esm/typings/src/book-2.0/agent-source/computeAgentHash.test.d.ts +1 -0
  11. package/esm/typings/src/book-2.0/agent-source/createDefaultAgentName.d.ts +8 -0
  12. package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.d.ts +9 -0
  13. package/esm/typings/src/book-2.0/agent-source/normalizeAgentName.test.d.ts +1 -0
  14. package/esm/typings/src/book-2.0/agent-source/parseAgentSourceWithCommitments.d.ts +1 -1
  15. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabase.d.ts +14 -8
  16. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentCollectionInSupabaseOptions.d.ts +10 -0
  17. package/esm/typings/src/collection/agent-collection/constructors/agent-collection-in-supabase/AgentsDatabaseSchema.d.ts +57 -32
  18. package/esm/typings/src/commitments/MESSAGE/InitialMessageCommitmentDefinition.d.ts +28 -0
  19. package/esm/typings/src/commitments/index.d.ts +2 -1
  20. package/esm/typings/src/config.d.ts +1 -0
  21. package/esm/typings/src/errors/DatabaseError.d.ts +2 -2
  22. package/esm/typings/src/errors/WrappedError.d.ts +2 -2
  23. package/esm/typings/src/execution/ExecutionTask.d.ts +2 -2
  24. package/esm/typings/src/execution/LlmExecutionTools.d.ts +6 -1
  25. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizardOrCli.d.ts +2 -2
  26. package/esm/typings/src/llm-providers/_common/utils/assertUniqueModels.d.ts +12 -0
  27. package/esm/typings/src/llm-providers/agent/Agent.d.ts +17 -4
  28. package/esm/typings/src/llm-providers/agent/AgentLlmExecutionTools.d.ts +10 -1
  29. package/esm/typings/src/llm-providers/agent/RemoteAgent.d.ts +6 -2
  30. package/esm/typings/src/llm-providers/openai/OpenAiAssistantExecutionTools.d.ts +30 -4
  31. package/esm/typings/src/llm-providers/openai/openai-models.test.d.ts +4 -0
  32. package/esm/typings/src/remote-server/startAgentServer.d.ts +2 -2
  33. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
  34. package/esm/typings/src/transpilers/openai-sdk/register.d.ts +1 -1
  35. package/esm/typings/src/types/typeAliases.d.ts +6 -0
  36. package/esm/typings/src/utils/color/Color.d.ts +7 -0
  37. package/esm/typings/src/utils/color/Color.test.d.ts +1 -0
  38. package/esm/typings/src/utils/environment/$getGlobalScope.d.ts +2 -2
  39. package/esm/typings/src/utils/misc/computeHash.d.ts +11 -0
  40. package/esm/typings/src/utils/misc/computeHash.test.d.ts +1 -0
  41. package/esm/typings/src/utils/normalization/normalize-to-kebab-case.d.ts +2 -0
  42. package/esm/typings/src/utils/normalization/normalizeTo_PascalCase.d.ts +3 -0
  43. package/esm/typings/src/utils/normalization/normalizeTo_camelCase.d.ts +2 -0
  44. package/esm/typings/src/utils/normalization/titleToName.d.ts +2 -0
  45. package/esm/typings/src/utils/organization/$sideEffect.d.ts +2 -2
  46. package/esm/typings/src/utils/organization/$side_effect.d.ts +2 -2
  47. package/esm/typings/src/utils/organization/TODO_USE.d.ts +2 -2
  48. package/esm/typings/src/utils/organization/keepUnused.d.ts +2 -2
  49. package/esm/typings/src/utils/organization/preserve.d.ts +3 -3
  50. package/esm/typings/src/utils/organization/really_any.d.ts +7 -0
  51. package/esm/typings/src/utils/serialization/asSerializable.d.ts +2 -2
  52. package/esm/typings/src/version.d.ts +1 -1
  53. package/package.json +1 -1
  54. package/umd/index.umd.js +1291 -877
  55. package/umd/index.umd.js.map +1 -1
package/umd/index.umd.js CHANGED
@@ -1,13 +1,13 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('spacetrim'), require('crypto'), require('rxjs'), require('waitasecond'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('path'), require('crypto-js'), require('mime-types'), require('papaparse'), require('moment'), require('colors'), require('bottleneck'), require('openai')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'spacetrim', 'crypto', 'rxjs', 'waitasecond', 'crypto-js/enc-hex', 'crypto-js/sha256', 'path', 'crypto-js', 'mime-types', 'papaparse', 'moment', 'colors', 'bottleneck', 'openai'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-core"] = {}, global.spaceTrim$1, global.crypto, global.rxjs, global.waitasecond, global.hexEncoder, global.sha256, global.path, global.cryptoJs, global.mimeTypes, global.papaparse, global.moment, global.colors, global.Bottleneck, global.OpenAI));
5
- })(this, (function (exports, spaceTrim$1, crypto, rxjs, waitasecond, hexEncoder, sha256, path, cryptoJs, mimeTypes, papaparse, moment, colors, Bottleneck, OpenAI) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('crypto-js'), require('crypto-js/enc-hex'), require('spacetrim'), require('crypto'), require('rxjs'), require('waitasecond'), require('crypto-js/sha256'), require('path'), require('mime-types'), require('papaparse'), require('moment'), require('colors'), require('bottleneck'), require('openai')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'crypto-js', 'crypto-js/enc-hex', 'spacetrim', 'crypto', 'rxjs', 'waitasecond', 'crypto-js/sha256', 'path', 'mime-types', 'papaparse', 'moment', 'colors', 'bottleneck', 'openai'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-core"] = {}, global.cryptoJs, global.hexEncoder, global.spaceTrim$1, global.crypto, global.rxjs, global.waitasecond, global.sha256, global.path, global.mimeTypes, global.papaparse, global.moment, global.colors, global.Bottleneck, global.OpenAI));
5
+ })(this, (function (exports, cryptoJs, hexEncoder, spaceTrim$1, crypto, rxjs, waitasecond, sha256, path, mimeTypes, papaparse, moment, colors, Bottleneck, OpenAI) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
9
- var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim$1);
10
9
  var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
10
+ var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim$1);
11
11
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
12
12
  var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
13
13
  var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
@@ -28,131 +28,12 @@
28
28
  * @generated
29
29
  * @see https://github.com/webgptorg/promptbook
30
30
  */
31
- const PROMPTBOOK_ENGINE_VERSION = '0.103.0-47';
31
+ const PROMPTBOOK_ENGINE_VERSION = '0.103.0-49';
32
32
  /**
33
33
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
34
34
  * Note: [šŸ’ž] Ignore a discrepancy between file name and entity name
35
35
  */
36
36
 
37
- 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"}];
38
-
39
- /**
40
- * Checks if value is valid email
41
- *
42
- * @public exported from `@promptbook/utils`
43
- */
44
- function isValidEmail(email) {
45
- if (typeof email !== 'string') {
46
- return false;
47
- }
48
- if (email.split('\n').length > 1) {
49
- return false;
50
- }
51
- return /^.+@.+\..+$/.test(email);
52
- }
53
-
54
- /**
55
- * Tests if given string is valid file path.
56
- *
57
- * Note: This does not check if the file exists only if the path is valid
58
- * @public exported from `@promptbook/utils`
59
- */
60
- function isValidFilePath(filename) {
61
- if (typeof filename !== 'string') {
62
- return false;
63
- }
64
- if (filename.split('\n').length > 1) {
65
- return false;
66
- }
67
- // Normalize slashes early so heuristics can detect path-like inputs
68
- const filenameSlashes = filename.replace(/\\/g, '/');
69
- // Reject strings that look like sentences (informational text)
70
- // Heuristic: contains multiple spaces and ends with a period, or contains typical sentence punctuation
71
- // But skip this heuristic if the string looks like a path (contains '/' or starts with a drive letter)
72
- if (filename.trim().length > 60 && // long enough to be a sentence
73
- /[.!?]/.test(filename) && // contains sentence punctuation
74
- filename.split(' ').length > 8 && // has many words
75
- !/\/|^[A-Z]:/i.test(filenameSlashes) // do NOT treat as sentence if looks like a path
76
- ) {
77
- return false;
78
- }
79
- // Absolute Unix path: /hello.txt
80
- if (/^(\/)/i.test(filenameSlashes)) {
81
- // console.log(filename, 'Absolute Unix path: /hello.txt');
82
- return true;
83
- }
84
- // Absolute Windows path: C:/ or C:\ (allow spaces and multiple dots in filename)
85
- if (/^[A-Z]:\/.+$/i.test(filenameSlashes)) {
86
- // console.log(filename, 'Absolute Windows path: /hello.txt');
87
- return true;
88
- }
89
- // Relative path: ./hello.txt
90
- if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
91
- // console.log(filename, 'Relative path: ./hello.txt');
92
- return true;
93
- }
94
- // Allow paths like foo/hello
95
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
96
- // console.log(filename, 'Allow paths like foo/hello');
97
- return true;
98
- }
99
- // Allow paths like hello.book
100
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
101
- // console.log(filename, 'Allow paths like hello.book');
102
- return true;
103
- }
104
- return false;
105
- }
106
- /**
107
- * TODO: [šŸ] Implement for MacOs
108
- */
109
-
110
- /**
111
- * Tests if given string is valid URL.
112
- *
113
- * Note: [šŸ”‚] This function is idempotent.
114
- * Note: Dataurl are considered perfectly valid.
115
- * Note: There are two similar functions:
116
- * - `isValidUrl` which tests any URL
117
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
118
- *
119
- * @public exported from `@promptbook/utils`
120
- */
121
- function isValidUrl(url) {
122
- if (typeof url !== 'string') {
123
- return false;
124
- }
125
- try {
126
- if (url.startsWith('blob:')) {
127
- url = url.replace(/^blob:/, '');
128
- }
129
- const urlObject = new URL(url /* because fail is handled */);
130
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
131
- return false;
132
- }
133
- return true;
134
- }
135
- catch (error) {
136
- return false;
137
- }
138
- }
139
-
140
- /**
141
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
142
- *
143
- * @public exported from `@promptbook/core`
144
- */
145
- class ParseError extends Error {
146
- constructor(message) {
147
- super(message);
148
- this.name = 'ParseError';
149
- Object.setPrototypeOf(this, ParseError.prototype);
150
- }
151
- }
152
- /**
153
- * TODO: Maybe split `ParseError` and `ApplyError`
154
- */
155
-
156
37
  /**
157
38
  * Available remote servers for the Promptbook
158
39
  *
@@ -186,6 +67,7 @@
186
67
  */
187
68
  ];
188
69
  /**
70
+ * TODO: [šŸ±ā€šŸš€] Auto-federated server from url in here
189
71
  * Note: [šŸ’ž] Ignore a discrepancy between file name and entity name
190
72
  */
191
73
 
@@ -519,6 +401,9 @@
519
401
  if (hex.length === 3) {
520
402
  return Color.fromHex3(hex);
521
403
  }
404
+ if (hex.length === 4) {
405
+ return Color.fromHex4(hex);
406
+ }
522
407
  if (hex.length === 6) {
523
408
  return Color.fromHex6(hex);
524
409
  }
@@ -539,6 +424,19 @@
539
424
  const b = parseInt(hex.substr(2, 1), 16) * 16;
540
425
  return take(new Color(r, g, b));
541
426
  }
427
+ /**
428
+ * Creates a new Color instance from color in hex format with 4 digits (with alpha channel)
429
+ *
430
+ * @param color in hex for example `09df`
431
+ * @returns Color object
432
+ */
433
+ static fromHex4(hex) {
434
+ const r = parseInt(hex.substr(0, 1), 16) * 16;
435
+ const g = parseInt(hex.substr(1, 1), 16) * 16;
436
+ const b = parseInt(hex.substr(2, 1), 16) * 16;
437
+ const a = parseInt(hex.substr(3, 1), 16) * 16;
438
+ return take(new Color(r, g, b, a));
439
+ }
542
440
  /**
543
441
  * Creates a new Color instance from color in hex format with 6 color digits (without alpha channel)
544
442
  *
@@ -729,7 +627,8 @@
729
627
  * @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
730
628
  */
731
629
  static isHexColorString(value) {
732
- return typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}){1,2}$/.test(value);
630
+ return (typeof value === 'string' &&
631
+ /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
733
632
  }
734
633
  /**
735
634
  * Creates new Color object
@@ -1070,6 +969,7 @@
1070
969
  const PROMPTBOOK_SYNTAX_COLORS = {
1071
970
  TITLE: Color.fromHex('#244EA8'),
1072
971
  LINE: Color.fromHex('#eeeeee'),
972
+ SEPARATOR: Color.fromHex('#cccccc'),
1073
973
  COMMITMENT: Color.fromHex('#DA0F78'),
1074
974
  PARAMETER: Color.fromHex('#8e44ad'),
1075
975
  };
@@ -1520,88 +1420,309 @@
1520
1420
  }
1521
1421
 
1522
1422
  /**
1523
- * Function isValidJsonString will tell you if the string is valid JSON or not
1524
- *
1525
- * @param value The string to check
1526
- * @returns `true` if the string is a valid JSON string, false otherwise
1423
+ * Format either small or big number
1527
1424
  *
1528
1425
  * @public exported from `@promptbook/utils`
1529
1426
  */
1530
- function isValidJsonString(value /* <- [šŸ‘Øā€āš–ļø] */) {
1531
- try {
1532
- JSON.parse(value);
1533
- return true;
1427
+ function numberToString(value) {
1428
+ if (value === 0) {
1429
+ return '0';
1534
1430
  }
1535
- catch (error) {
1536
- assertsError(error);
1537
- if (error.message.includes('Unexpected token')) {
1538
- return false;
1431
+ else if (Number.isNaN(value)) {
1432
+ return VALUE_STRINGS.nan;
1433
+ }
1434
+ else if (value === Infinity) {
1435
+ return VALUE_STRINGS.infinity;
1436
+ }
1437
+ else if (value === -Infinity) {
1438
+ return VALUE_STRINGS.negativeInfinity;
1439
+ }
1440
+ for (let exponent = 0; exponent < 15; exponent++) {
1441
+ const factor = 10 ** exponent;
1442
+ const valueRounded = Math.round(value * factor) / factor;
1443
+ if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
1444
+ return valueRounded.toFixed(exponent);
1539
1445
  }
1540
- return false;
1541
1446
  }
1447
+ return value.toString();
1542
1448
  }
1543
1449
 
1544
1450
  /**
1545
- * Function `validatePipelineString` will validate the if the string is a valid pipeline string
1546
- * It does not check if the string is fully logically correct, but if it is a string that can be a pipeline string or the string looks completely different.
1451
+ * Function `valueToString` will convert the given value to string
1452
+ * This is useful and used in the `templateParameters` function
1547
1453
  *
1548
- * Note: [šŸ”‚] This function is idempotent.
1454
+ * Note: This function is not just calling `toString` method
1455
+ * It's more complex and can handle this conversion specifically for LLM models
1456
+ * See `VALUE_STRINGS`
1549
1457
  *
1550
- * @param {string} pipelineString the candidate for a pipeline string
1551
- * @returns {PipelineString} the same string as input, but validated as valid
1552
- * @throws {ParseError} if the string is not a valid pipeline string
1553
- * @public exported from `@promptbook/core`
1458
+ * Note: There are 2 similar functions
1459
+ * - `valueToString` converts value to string for LLM models as human-readable string
1460
+ * - `asSerializable` converts value to string to preserve full information to be able to convert it back
1461
+ *
1462
+ * @public exported from `@promptbook/utils`
1554
1463
  */
1555
- function validatePipelineString(pipelineString) {
1556
- if (isValidJsonString(pipelineString)) {
1557
- throw new ParseError('Expected a book, but got a JSON string');
1558
- }
1559
- else if (isValidUrl(pipelineString)) {
1560
- throw new ParseError(`Expected a book, but got just the URL "${pipelineString}"`);
1561
- }
1562
- else if (isValidFilePath(pipelineString)) {
1563
- throw new ParseError(`Expected a book, but got just the file path "${pipelineString}"`);
1464
+ function valueToString(value) {
1465
+ try {
1466
+ if (value === '') {
1467
+ return VALUE_STRINGS.empty;
1468
+ }
1469
+ else if (value === null) {
1470
+ return VALUE_STRINGS.null;
1471
+ }
1472
+ else if (value === undefined) {
1473
+ return VALUE_STRINGS.undefined;
1474
+ }
1475
+ else if (typeof value === 'string') {
1476
+ return value;
1477
+ }
1478
+ else if (typeof value === 'number') {
1479
+ return numberToString(value);
1480
+ }
1481
+ else if (value instanceof Date) {
1482
+ return value.toISOString();
1483
+ }
1484
+ else {
1485
+ try {
1486
+ return JSON.stringify(value);
1487
+ }
1488
+ catch (error) {
1489
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
1490
+ return VALUE_STRINGS.circular;
1491
+ }
1492
+ throw error;
1493
+ }
1494
+ }
1564
1495
  }
1565
- else if (isValidEmail(pipelineString)) {
1566
- throw new ParseError(`Expected a book, but got just the email "${pipelineString}"`);
1496
+ catch (error) {
1497
+ assertsError(error);
1498
+ console.error(error);
1499
+ return VALUE_STRINGS.unserializable;
1567
1500
  }
1568
- // <- TODO: Implement the validation + add tests when the pipeline logic considered as invalid
1569
- return pipelineString;
1570
1501
  }
1571
- /**
1572
- * TODO: [🧠][🈓] Where is the best location for this file
1573
- */
1574
1502
 
1575
1503
  /**
1576
- * Prettify the html code
1504
+ * Computes SHA-256 hash of the given object
1577
1505
  *
1578
- * @param content raw html code
1579
- * @returns formatted html code
1580
- * @private withing the package because of HUGE size of prettier dependency
1581
- * @deprecated Prettier removed from Promptbook due to package size
1506
+ * @public exported from `@promptbook/utils`
1582
1507
  */
1583
- function prettifyMarkdown(content) {
1584
- return (content + `\n\n<!-- Note: Prettier removed from Promptbook -->`);
1508
+ function computeHash(value) {
1509
+ return cryptoJs.SHA256(hexEncoder__default["default"].parse(spaceTrim__default["default"](valueToString(value)))).toString( /* hex */);
1585
1510
  }
1511
+ /**
1512
+ * TODO: [🄬][🄬] Use this ACRY
1513
+ */
1586
1514
 
1587
1515
  /**
1588
- * Makes first letter of a string uppercase
1589
- *
1590
- * Note: [šŸ”‚] This function is idempotent.
1516
+ * Computes SHA-256 hash of the agent source
1591
1517
  *
1592
- * @public exported from `@promptbook/utils`
1518
+ * @public exported from `@promptbook/core`
1593
1519
  */
1594
- function capitalize(word) {
1595
- return word.substring(0, 1).toUpperCase() + word.substring(1);
1520
+ function computeAgentHash(agentSource) {
1521
+ return computeHash(agentSource);
1596
1522
  }
1597
1523
 
1524
+ 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"}];
1525
+
1598
1526
  /**
1599
- * Converts promptbook in JSON format to string format
1527
+ * Checks if value is valid email
1600
1528
  *
1601
- * @deprecated TODO: [šŸ„][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1602
- * @param pipelineJson Promptbook in JSON format (.bookc)
1603
- * @returns Promptbook in string format (.book.md)
1604
- * @public exported from `@promptbook/core`
1529
+ * @public exported from `@promptbook/utils`
1530
+ */
1531
+ function isValidEmail(email) {
1532
+ if (typeof email !== 'string') {
1533
+ return false;
1534
+ }
1535
+ if (email.split('\n').length > 1) {
1536
+ return false;
1537
+ }
1538
+ return /^.+@.+\..+$/.test(email);
1539
+ }
1540
+
1541
+ /**
1542
+ * Tests if given string is valid file path.
1543
+ *
1544
+ * Note: This does not check if the file exists only if the path is valid
1545
+ * @public exported from `@promptbook/utils`
1546
+ */
1547
+ function isValidFilePath(filename) {
1548
+ if (typeof filename !== 'string') {
1549
+ return false;
1550
+ }
1551
+ if (filename.split('\n').length > 1) {
1552
+ return false;
1553
+ }
1554
+ // Normalize slashes early so heuristics can detect path-like inputs
1555
+ const filenameSlashes = filename.replace(/\\/g, '/');
1556
+ // Reject strings that look like sentences (informational text)
1557
+ // Heuristic: contains multiple spaces and ends with a period, or contains typical sentence punctuation
1558
+ // But skip this heuristic if the string looks like a path (contains '/' or starts with a drive letter)
1559
+ if (filename.trim().length > 60 && // long enough to be a sentence
1560
+ /[.!?]/.test(filename) && // contains sentence punctuation
1561
+ filename.split(' ').length > 8 && // has many words
1562
+ !/\/|^[A-Z]:/i.test(filenameSlashes) // do NOT treat as sentence if looks like a path
1563
+ ) {
1564
+ return false;
1565
+ }
1566
+ // Absolute Unix path: /hello.txt
1567
+ if (/^(\/)/i.test(filenameSlashes)) {
1568
+ // console.log(filename, 'Absolute Unix path: /hello.txt');
1569
+ return true;
1570
+ }
1571
+ // Absolute Windows path: C:/ or C:\ (allow spaces and multiple dots in filename)
1572
+ if (/^[A-Z]:\/.+$/i.test(filenameSlashes)) {
1573
+ // console.log(filename, 'Absolute Windows path: /hello.txt');
1574
+ return true;
1575
+ }
1576
+ // Relative path: ./hello.txt
1577
+ if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
1578
+ // console.log(filename, 'Relative path: ./hello.txt');
1579
+ return true;
1580
+ }
1581
+ // Allow paths like foo/hello
1582
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
1583
+ // console.log(filename, 'Allow paths like foo/hello');
1584
+ return true;
1585
+ }
1586
+ // Allow paths like hello.book
1587
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
1588
+ // console.log(filename, 'Allow paths like hello.book');
1589
+ return true;
1590
+ }
1591
+ return false;
1592
+ }
1593
+ /**
1594
+ * TODO: [šŸ] Implement for MacOs
1595
+ */
1596
+
1597
+ /**
1598
+ * Tests if given string is valid URL.
1599
+ *
1600
+ * Note: [šŸ”‚] This function is idempotent.
1601
+ * Note: Dataurl are considered perfectly valid.
1602
+ * Note: There are two similar functions:
1603
+ * - `isValidUrl` which tests any URL
1604
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
1605
+ *
1606
+ * @public exported from `@promptbook/utils`
1607
+ */
1608
+ function isValidUrl(url) {
1609
+ if (typeof url !== 'string') {
1610
+ return false;
1611
+ }
1612
+ try {
1613
+ if (url.startsWith('blob:')) {
1614
+ url = url.replace(/^blob:/, '');
1615
+ }
1616
+ const urlObject = new URL(url /* because fail is handled */);
1617
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
1618
+ return false;
1619
+ }
1620
+ return true;
1621
+ }
1622
+ catch (error) {
1623
+ return false;
1624
+ }
1625
+ }
1626
+
1627
+ /**
1628
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
1629
+ *
1630
+ * @public exported from `@promptbook/core`
1631
+ */
1632
+ class ParseError extends Error {
1633
+ constructor(message) {
1634
+ super(message);
1635
+ this.name = 'ParseError';
1636
+ Object.setPrototypeOf(this, ParseError.prototype);
1637
+ }
1638
+ }
1639
+ /**
1640
+ * TODO: Maybe split `ParseError` and `ApplyError`
1641
+ */
1642
+
1643
+ /**
1644
+ * Function isValidJsonString will tell you if the string is valid JSON or not
1645
+ *
1646
+ * @param value The string to check
1647
+ * @returns `true` if the string is a valid JSON string, false otherwise
1648
+ *
1649
+ * @public exported from `@promptbook/utils`
1650
+ */
1651
+ function isValidJsonString(value /* <- [šŸ‘Øā€āš–ļø] */) {
1652
+ try {
1653
+ JSON.parse(value);
1654
+ return true;
1655
+ }
1656
+ catch (error) {
1657
+ assertsError(error);
1658
+ if (error.message.includes('Unexpected token')) {
1659
+ return false;
1660
+ }
1661
+ return false;
1662
+ }
1663
+ }
1664
+
1665
+ /**
1666
+ * Function `validatePipelineString` will validate the if the string is a valid pipeline string
1667
+ * It does not check if the string is fully logically correct, but if it is a string that can be a pipeline string or the string looks completely different.
1668
+ *
1669
+ * Note: [šŸ”‚] This function is idempotent.
1670
+ *
1671
+ * @param {string} pipelineString the candidate for a pipeline string
1672
+ * @returns {PipelineString} the same string as input, but validated as valid
1673
+ * @throws {ParseError} if the string is not a valid pipeline string
1674
+ * @public exported from `@promptbook/core`
1675
+ */
1676
+ function validatePipelineString(pipelineString) {
1677
+ if (isValidJsonString(pipelineString)) {
1678
+ throw new ParseError('Expected a book, but got a JSON string');
1679
+ }
1680
+ else if (isValidUrl(pipelineString)) {
1681
+ throw new ParseError(`Expected a book, but got just the URL "${pipelineString}"`);
1682
+ }
1683
+ else if (isValidFilePath(pipelineString)) {
1684
+ throw new ParseError(`Expected a book, but got just the file path "${pipelineString}"`);
1685
+ }
1686
+ else if (isValidEmail(pipelineString)) {
1687
+ throw new ParseError(`Expected a book, but got just the email "${pipelineString}"`);
1688
+ }
1689
+ // <- TODO: Implement the validation + add tests when the pipeline logic considered as invalid
1690
+ return pipelineString;
1691
+ }
1692
+ /**
1693
+ * TODO: [🧠][🈓] Where is the best location for this file
1694
+ */
1695
+
1696
+ /**
1697
+ * Prettify the html code
1698
+ *
1699
+ * @param content raw html code
1700
+ * @returns formatted html code
1701
+ * @private withing the package because of HUGE size of prettier dependency
1702
+ * @deprecated Prettier removed from Promptbook due to package size
1703
+ */
1704
+ function prettifyMarkdown(content) {
1705
+ return (content + `\n\n<!-- Note: Prettier removed from Promptbook -->`);
1706
+ }
1707
+
1708
+ /**
1709
+ * Makes first letter of a string uppercase
1710
+ *
1711
+ * Note: [šŸ”‚] This function is idempotent.
1712
+ *
1713
+ * @public exported from `@promptbook/utils`
1714
+ */
1715
+ function capitalize(word) {
1716
+ return word.substring(0, 1).toUpperCase() + word.substring(1);
1717
+ }
1718
+
1719
+ /**
1720
+ * Converts promptbook in JSON format to string format
1721
+ *
1722
+ * @deprecated TODO: [šŸ„][🧠] Backup original files in `PipelineJson` same as in Promptbook.studio
1723
+ * @param pipelineJson Promptbook in JSON format (.bookc)
1724
+ * @returns Promptbook in string format (.book.md)
1725
+ * @public exported from `@promptbook/core`
1605
1726
  */
1606
1727
  function pipelineJsonToString(pipelineJson) {
1607
1728
  const { title, pipelineUrl, bookVersion, description, parameters, tasks } = pipelineJson;
@@ -1931,7 +2052,7 @@
1931
2052
  TODO: [🧠] Is there a better implementation?
1932
2053
  > const propertyNames = Object.getOwnPropertyNames(objectValue);
1933
2054
  > for (const propertyName of propertyNames) {
1934
- > const value = (objectValue as really_any)[propertyName];
2055
+ > const value = (objectValue as chococake)[propertyName];
1935
2056
  > if (value && typeof value === 'object') {
1936
2057
  > deepClone(value);
1937
2058
  > }
@@ -2784,7 +2905,7 @@
2784
2905
  }
2785
2906
  }
2786
2907
  /**
2787
- * TODO: !!!! Explain that NotFoundError (!!! and other specific errors) has priority over DatabaseError in some contexts
2908
+ * TODO: [šŸ±ā€šŸš€] Explain that NotFoundError ([šŸ±ā€šŸš€] and other specific errors) has priority over DatabaseError in some contexts
2788
2909
  */
2789
2910
 
2790
2911
  /**
@@ -4326,6 +4447,8 @@
4326
4447
  /**
4327
4448
  * Converts a given text to kebab-case format.
4328
4449
  *
4450
+ * Note: [šŸ”‚] This function is idempotent.
4451
+ *
4329
4452
  * @param text The text to be converted.
4330
4453
  * @returns The kebab-case formatted string.
4331
4454
  * @example 'hello-world'
@@ -4481,6 +4604,8 @@
4481
4604
  /**
4482
4605
  * Converts a title string into a normalized name.
4483
4606
  *
4607
+ * Note: [šŸ”‚] This function is idempotent.
4608
+ *
4484
4609
  * @param value The title string to be converted to a name.
4485
4610
  * @returns A normalized name derived from the input title.
4486
4611
  * @example 'Hello World!' -> 'hello-world'
@@ -5050,98 +5175,17 @@
5050
5175
  */
5051
5176
 
5052
5177
  /**
5053
- * Format either small or big number
5054
- *
5055
- * @public exported from `@promptbook/utils`
5056
- */
5057
- function numberToString(value) {
5058
- if (value === 0) {
5059
- return '0';
5060
- }
5061
- else if (Number.isNaN(value)) {
5062
- return VALUE_STRINGS.nan;
5063
- }
5064
- else if (value === Infinity) {
5065
- return VALUE_STRINGS.infinity;
5066
- }
5067
- else if (value === -Infinity) {
5068
- return VALUE_STRINGS.negativeInfinity;
5069
- }
5070
- for (let exponent = 0; exponent < 15; exponent++) {
5071
- const factor = 10 ** exponent;
5072
- const valueRounded = Math.round(value * factor) / factor;
5073
- if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
5074
- return valueRounded.toFixed(exponent);
5075
- }
5076
- }
5077
- return value.toString();
5078
- }
5079
-
5080
- /**
5081
- * Function `valueToString` will convert the given value to string
5082
- * This is useful and used in the `templateParameters` function
5083
- *
5084
- * Note: This function is not just calling `toString` method
5085
- * It's more complex and can handle this conversion specifically for LLM models
5086
- * See `VALUE_STRINGS`
5087
- *
5088
- * Note: There are 2 similar functions
5089
- * - `valueToString` converts value to string for LLM models as human-readable string
5090
- * - `asSerializable` converts value to string to preserve full information to be able to convert it back
5178
+ * Parses the given script and returns the list of all used variables that are not defined in the script
5091
5179
  *
5092
- * @public exported from `@promptbook/utils`
5180
+ * @param script from which to extract the variables
5181
+ * @returns the list of variable names
5182
+ * @throws {ParseError} if the script is invalid
5183
+ * @public exported from `@promptbook/javascript`
5093
5184
  */
5094
- function valueToString(value) {
5095
- try {
5096
- if (value === '') {
5097
- return VALUE_STRINGS.empty;
5098
- }
5099
- else if (value === null) {
5100
- return VALUE_STRINGS.null;
5101
- }
5102
- else if (value === undefined) {
5103
- return VALUE_STRINGS.undefined;
5104
- }
5105
- else if (typeof value === 'string') {
5106
- return value;
5107
- }
5108
- else if (typeof value === 'number') {
5109
- return numberToString(value);
5110
- }
5111
- else if (value instanceof Date) {
5112
- return value.toISOString();
5113
- }
5114
- else {
5115
- try {
5116
- return JSON.stringify(value);
5117
- }
5118
- catch (error) {
5119
- if (error instanceof TypeError && error.message.includes('circular structure')) {
5120
- return VALUE_STRINGS.circular;
5121
- }
5122
- throw error;
5123
- }
5124
- }
5125
- }
5126
- catch (error) {
5127
- assertsError(error);
5128
- console.error(error);
5129
- return VALUE_STRINGS.unserializable;
5130
- }
5131
- }
5132
-
5133
- /**
5134
- * Parses the given script and returns the list of all used variables that are not defined in the script
5135
- *
5136
- * @param script from which to extract the variables
5137
- * @returns the list of variable names
5138
- * @throws {ParseError} if the script is invalid
5139
- * @public exported from `@promptbook/javascript`
5140
- */
5141
- function extractVariablesFromJavascript(script) {
5142
- const variables = new Set();
5143
- const originalScript = script;
5144
- script = `(()=>{${script}})()`;
5185
+ function extractVariablesFromJavascript(script) {
5186
+ const variables = new Set();
5187
+ const originalScript = script;
5188
+ script = `(()=>{${script}})()`;
5145
5189
  try {
5146
5190
  for (let i = 0; i < LOOP_LIMIT; i++)
5147
5191
  try {
@@ -8056,6 +8100,60 @@
8056
8100
  * Note: [šŸ’ž] Ignore a discrepancy between file name and entity name
8057
8101
  */
8058
8102
 
8103
+ /**
8104
+ * INITIAL MESSAGE commitment definition
8105
+ *
8106
+ * The INITIAL MESSAGE commitment defines the first message that the user sees when opening the chat.
8107
+ * It is used to greet the user and set the tone of the conversation.
8108
+ *
8109
+ * Example usage in agent source:
8110
+ *
8111
+ * ```book
8112
+ * INITIAL MESSAGE Hello! I am ready to help you with your tasks.
8113
+ * ```
8114
+ *
8115
+ * @private [šŸŖ”] Maybe export the commitments through some package
8116
+ */
8117
+ class InitialMessageCommitmentDefinition extends BaseCommitmentDefinition {
8118
+ constructor() {
8119
+ super('INITIAL MESSAGE');
8120
+ }
8121
+ /**
8122
+ * Short one-line description of INITIAL MESSAGE.
8123
+ */
8124
+ get description() {
8125
+ return 'Defines the **initial message** shown to the user when the chat starts.';
8126
+ }
8127
+ /**
8128
+ * Markdown documentation for INITIAL MESSAGE commitment.
8129
+ */
8130
+ get documentation() {
8131
+ return spaceTrim$1.spaceTrim(`
8132
+ # ${this.type}
8133
+
8134
+ 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).
8135
+
8136
+ ## Key aspects
8137
+
8138
+ - Used to greet the user.
8139
+ - Sets the tone of the conversation.
8140
+ - Displayed immediately when the chat interface loads.
8141
+
8142
+ ## Examples
8143
+
8144
+ \`\`\`book
8145
+ Support Agent
8146
+
8147
+ PERSONA You are a helpful support agent.
8148
+ INITIAL MESSAGE Hi there! How can I assist you today?
8149
+ \`\`\`
8150
+ `);
8151
+ }
8152
+ applyToAgentModelRequirements(requirements, content) {
8153
+ return requirements;
8154
+ }
8155
+ }
8156
+
8059
8157
  /**
8060
8158
  * MESSAGE commitment definition
8061
8159
  *
@@ -8760,6 +8858,7 @@
8760
8858
  // Keep everything after the PERSONA section
8761
8859
  cleanedMessage = lines.slice(personaEndIndex).join('\n').trim();
8762
8860
  }
8861
+ // TODO: [šŸ•›] There should be `agentFullname` not `agentName`
8763
8862
  // Create new system message with persona at the beginning
8764
8863
  // Format: "You are {agentName}\n{personaContent}"
8765
8864
  // The # PERSONA comment will be removed later by removeCommentsFromSystemMessage
@@ -9216,6 +9315,7 @@
9216
9315
  new NoteCommitmentDefinition('NONCE'),
9217
9316
  new GoalCommitmentDefinition('GOAL'),
9218
9317
  new GoalCommitmentDefinition('GOALS'),
9318
+ new InitialMessageCommitmentDefinition(),
9219
9319
  new MessageCommitmentDefinition('MESSAGE'),
9220
9320
  new MessageCommitmentDefinition('MESSAGES'),
9221
9321
  new ScenarioCommitmentDefinition('SCENARIO'),
@@ -9581,6 +9681,8 @@
9581
9681
  /**
9582
9682
  * Normalizes a given text to camelCase format.
9583
9683
  *
9684
+ * Note: [šŸ”‚] This function is idempotent.
9685
+ *
9584
9686
  * @param text The text to be normalized.
9585
9687
  * @param _isFirstLetterCapital Whether the first letter should be capitalized.
9586
9688
  * @returns The camelCase formatted string.
@@ -9669,89 +9771,520 @@
9669
9771
  */
9670
9772
 
9671
9773
  /**
9672
- * Parses basic information from agent source
9774
+ * Creates a Mermaid graph based on the promptbook
9673
9775
  *
9674
- * There are 2 similar functions:
9675
- * - `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.
9676
- * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
9776
+ * Note: The result is not wrapped in a Markdown code block
9677
9777
  *
9678
- * @public exported from `@promptbook/core`
9778
+ * @public exported from `@promptbook/utils`
9679
9779
  */
9680
- function parseAgentSource(agentSource) {
9681
- const parseResult = parseAgentSourceWithCommitments(agentSource);
9682
- // Find PERSONA and META commitments
9683
- let personaDescription = null;
9684
- for (const commitment of parseResult.commitments) {
9685
- if (commitment.type !== 'PERSONA') {
9686
- continue;
9780
+ function renderPromptbookMermaid(pipelineJson, options) {
9781
+ const { linkTask = () => null } = options || {};
9782
+ const MERMAID_PREFIX = 'pipeline_';
9783
+ const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
9784
+ const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
9785
+ const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
9786
+ const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
9787
+ const parameterNameToTaskName = (parameterName) => {
9788
+ if (parameterName === 'knowledge') {
9789
+ return MERMAID_KNOWLEDGE_NAME;
9687
9790
  }
9688
- if (personaDescription === null) {
9689
- personaDescription = '';
9791
+ else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
9792
+ return MERMAID_RESERVED_NAME;
9690
9793
  }
9691
- else {
9692
- personaDescription += `\n\n${personaDescription}`;
9794
+ const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
9795
+ if (!parameter) {
9796
+ throw new UnexpectedError(`Could not find {${parameterName}}`);
9797
+ // <- TODO: This causes problems when {knowledge} and other reserved parameters are used
9693
9798
  }
9694
- personaDescription += commitment.content;
9695
- }
9696
- const meta = {};
9697
- for (const commitment of parseResult.commitments) {
9698
- if (commitment.type !== 'META') {
9699
- continue;
9799
+ if (parameter.isInput) {
9800
+ return MERMAID_INPUT_NAME;
9700
9801
  }
9701
- // Parse META commitments - format is "META TYPE content"
9702
- const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
9703
- const metaType = normalizeTo_camelCase(metaTypeRaw);
9704
- meta[metaType] = spaceTrim__default["default"](commitment.content.substring(metaTypeRaw.length));
9705
- }
9706
- // Generate gravatar fallback if no meta image specified
9707
- if (!meta.image) {
9708
- meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
9709
- }
9710
- // Parse parameters using unified approach - both @Parameter and {parameter} notations
9711
- // are treated as the same syntax feature with unified representation
9712
- const parameters = parseParameters(agentSource);
9713
- return {
9714
- agentName: parseResult.agentName,
9715
- personaDescription,
9716
- meta,
9717
- parameters,
9802
+ const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
9803
+ if (!task) {
9804
+ throw new Error(`Could not find task for {${parameterName}}`);
9805
+ }
9806
+ return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
9718
9807
  };
9808
+ const inputAndIntermediateParametersMermaid = pipelineJson.tasks
9809
+ .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
9810
+ `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
9811
+ ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
9812
+ ])
9813
+ .join('\n');
9814
+ const outputParametersMermaid = pipelineJson.parameters
9815
+ .filter(({ isOutput }) => isOutput)
9816
+ .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
9817
+ .join('\n');
9818
+ const linksMermaid = pipelineJson.tasks
9819
+ .map((task) => {
9820
+ const link = linkTask(task);
9821
+ if (link === null) {
9822
+ return '';
9823
+ }
9824
+ const { href, title } = link;
9825
+ const taskName = parameterNameToTaskName(task.resultingParameterName);
9826
+ return `click ${taskName} href "${href}" "${title}";`;
9827
+ })
9828
+ .filter((line) => line !== '')
9829
+ .join('\n');
9830
+ const interactionPointsMermaid = Object.entries({
9831
+ [MERMAID_INPUT_NAME]: 'Input',
9832
+ [MERMAID_OUTPUT_NAME]: 'Output',
9833
+ [MERMAID_RESERVED_NAME]: 'Other',
9834
+ [MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
9835
+ })
9836
+ .filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
9837
+ .map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
9838
+ .join('\n');
9839
+ const promptbookMermaid = spaceTrim$1.spaceTrim((block) => `
9840
+
9841
+ %% šŸ”® Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
9842
+
9843
+ flowchart LR
9844
+ subgraph "${pipelineJson.title}"
9845
+
9846
+ %% Basic configuration
9847
+ direction TB
9848
+
9849
+ %% Interaction points from pipeline to outside
9850
+ ${block(interactionPointsMermaid)}
9851
+
9852
+ %% Input and intermediate parameters
9853
+ ${block(inputAndIntermediateParametersMermaid)}
9854
+
9855
+
9856
+ %% Output parameters
9857
+ ${block(outputParametersMermaid)}
9858
+
9859
+ %% Links
9860
+ ${block(linksMermaid)}
9861
+
9862
+ %% Styles
9863
+ classDef ${MERMAID_INPUT_NAME} color: grey;
9864
+ classDef ${MERMAID_OUTPUT_NAME} color: grey;
9865
+ classDef ${MERMAID_RESERVED_NAME} color: grey;
9866
+ classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
9867
+
9868
+ end;
9869
+
9870
+ `);
9871
+ return promptbookMermaid;
9719
9872
  }
9720
9873
  /**
9721
- * TODO: [šŸ•›] Unite `AgentBasicInformation`, `ChatParticipant`, `LlmExecutionTools` + `LlmToolsMetadata`
9874
+ * TODO: [🧠] FOREACH in mermaid graph
9875
+ * TODO: [🧠] Knowledge in mermaid graph
9876
+ * TODO: [🧠] Personas in mermaid graph
9877
+ * TODO: Maybe use some Mermaid package instead of string templating
9878
+ * TODO: [šŸ•Œ] When more than 2 functionalities, split into separate functions
9722
9879
  */
9723
9880
 
9724
9881
  /**
9725
- * Creates model requirements for an agent based on its source
9882
+ * Tag function for notating a prompt as template literal
9726
9883
  *
9727
- * There are 2 similar functions:
9728
- * - `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.
9729
- * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronous.
9884
+ * Note: There are 3 similar functions:
9885
+ * 1) `prompt` for notating single prompt exported from `@promptbook/utils`
9886
+ * 2) `promptTemplate` alias for `prompt`
9887
+ * 3) `book` for notating and validating entire books exported from `@promptbook/utils`
9730
9888
  *
9731
- * @public exported from `@promptbook/core`
9889
+ * @param strings
9890
+ * @param values
9891
+ * @returns the prompt string
9892
+ * @public exported from `@promptbook/utils`
9732
9893
  */
9733
- async function createAgentModelRequirements(agentSource, modelName, availableModels, llmTools) {
9734
- // If availableModels are provided and no specific modelName is given,
9735
- // use preparePersona to select the best model
9736
- if (availableModels && !modelName && llmTools) {
9737
- const selectedModelName = await selectBestModelUsingPersona(agentSource, llmTools);
9738
- return createAgentModelRequirementsWithCommitments(agentSource, selectedModelName);
9894
+ function prompt(strings, ...values) {
9895
+ if (values.length === 0) {
9896
+ return spaceTrim__default["default"](strings.join(''));
9739
9897
  }
9740
- // Use the new commitment-based system with provided or default model
9741
- return createAgentModelRequirementsWithCommitments(agentSource, modelName);
9742
- }
9743
- /**
9744
- * Selects the best model using the preparePersona function
9745
- * This directly uses preparePersona to ensure DRY principle
9746
- *
9747
- * @param agentSource The agent source to derive persona description from
9748
- * @param llmTools LLM tools for preparing persona
9749
- * @returns The name of the best selected model
9750
- * @private function of `createAgentModelRequirements`
9751
- */
9752
- async function selectBestModelUsingPersona(agentSource, llmTools) {
9753
- var _a;
9754
- // Parse agent source to get persona description
9898
+ const stringsWithHiddenParameters = strings.map((stringsItem) =>
9899
+ // TODO: [0] DRY
9900
+ stringsItem.split('{').join(`${REPLACING_NONCE}beginbracket`).split('}').join(`${REPLACING_NONCE}endbracket`));
9901
+ const placeholderParameterNames = values.map((value, i) => `${REPLACING_NONCE}${i}`);
9902
+ const parameters = Object.fromEntries(values.map((value, i) => [placeholderParameterNames[i], value]));
9903
+ // Combine strings and values
9904
+ let pipelineString = stringsWithHiddenParameters.reduce((result, stringsItem, i) => placeholderParameterNames[i] === undefined
9905
+ ? `${result}${stringsItem}`
9906
+ : `${result}${stringsItem}{${placeholderParameterNames[i]}}`, '');
9907
+ pipelineString = spaceTrim__default["default"](pipelineString);
9908
+ try {
9909
+ pipelineString = templateParameters(pipelineString, parameters);
9910
+ }
9911
+ catch (error) {
9912
+ if (!(error instanceof PipelineExecutionError)) {
9913
+ throw error;
9914
+ }
9915
+ console.error({ pipelineString, parameters, placeholderParameterNames, error });
9916
+ throw new UnexpectedError(spaceTrim__default["default"]((block) => `
9917
+ Internal error in prompt template literal
9918
+
9919
+ ${block(JSON.stringify({ strings, values }, null, 4))}}
9920
+
9921
+ `));
9922
+ }
9923
+ // TODO: [0] DRY
9924
+ pipelineString = pipelineString
9925
+ .split(`${REPLACING_NONCE}beginbracket`)
9926
+ .join('{')
9927
+ .split(`${REPLACING_NONCE}endbracket`)
9928
+ .join('}');
9929
+ return pipelineString;
9930
+ }
9931
+ /**
9932
+ * TODO: [🧠][🈓] Where is the best location for this file
9933
+ * Note: [šŸ’ž] Ignore a discrepancy between file name and entity name
9934
+ */
9935
+
9936
+ /**
9937
+ * Detects if the code is running in a browser environment in main thread (Not in a web worker)
9938
+ *
9939
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
9940
+ *
9941
+ * @public exported from `@promptbook/utils`
9942
+ */
9943
+ const $isRunningInBrowser = new Function(`
9944
+ try {
9945
+ return this === window;
9946
+ } catch (e) {
9947
+ return false;
9948
+ }
9949
+ `);
9950
+ /**
9951
+ * TODO: [šŸŽŗ]
9952
+ */
9953
+
9954
+ /**
9955
+ * Detects if the code is running in jest environment
9956
+ *
9957
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
9958
+ *
9959
+ * @public exported from `@promptbook/utils`
9960
+ */
9961
+ const $isRunningInJest = new Function(`
9962
+ try {
9963
+ return process.env.JEST_WORKER_ID !== undefined;
9964
+ } catch (e) {
9965
+ return false;
9966
+ }
9967
+ `);
9968
+ /**
9969
+ * TODO: [šŸŽŗ]
9970
+ */
9971
+
9972
+ /**
9973
+ * Detects if the code is running in a Node.js environment
9974
+ *
9975
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
9976
+ *
9977
+ * @public exported from `@promptbook/utils`
9978
+ */
9979
+ const $isRunningInNode = new Function(`
9980
+ try {
9981
+ return this === global;
9982
+ } catch (e) {
9983
+ return false;
9984
+ }
9985
+ `);
9986
+ /**
9987
+ * TODO: [šŸŽŗ]
9988
+ */
9989
+
9990
+ /**
9991
+ * Detects if the code is running in a web worker
9992
+ *
9993
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
9994
+ *
9995
+ * @public exported from `@promptbook/utils`
9996
+ */
9997
+ const $isRunningInWebWorker = new Function(`
9998
+ try {
9999
+ if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
10000
+ return true;
10001
+ } else {
10002
+ return false;
10003
+ }
10004
+ } catch (e) {
10005
+ return false;
10006
+ }
10007
+ `);
10008
+ /**
10009
+ * TODO: [šŸŽŗ]
10010
+ */
10011
+
10012
+ /**
10013
+ * Returns information about the current runtime environment
10014
+ *
10015
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environments
10016
+ *
10017
+ * @public exported from `@promptbook/utils`
10018
+ */
10019
+ function $detectRuntimeEnvironment() {
10020
+ return {
10021
+ isRunningInBrowser: $isRunningInBrowser(),
10022
+ isRunningInJest: $isRunningInJest(),
10023
+ isRunningInNode: $isRunningInNode(),
10024
+ isRunningInWebWorker: $isRunningInWebWorker(),
10025
+ };
10026
+ }
10027
+ /**
10028
+ * TODO: [šŸŽŗ] Also detect and report node version here
10029
+ */
10030
+
10031
+ /**
10032
+ * Simple wrapper `new Date().toISOString()`
10033
+ *
10034
+ * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
10035
+ *
10036
+ * @returns string_date branded type
10037
+ * @public exported from `@promptbook/utils`
10038
+ */
10039
+ function $getCurrentDate() {
10040
+ return new Date().toISOString();
10041
+ }
10042
+
10043
+ /**
10044
+ * Function parseNumber will parse number from string
10045
+ *
10046
+ * Note: [šŸ”‚] This function is idempotent.
10047
+ * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
10048
+ * Note: it also works only with decimal numbers
10049
+ *
10050
+ * @returns parsed number
10051
+ * @throws {ParseError} if the value is not a number
10052
+ *
10053
+ * @public exported from `@promptbook/utils`
10054
+ */
10055
+ function parseNumber(value) {
10056
+ const originalValue = value;
10057
+ if (typeof value === 'number') {
10058
+ value = value.toString(); // <- TODO: Maybe more efficient way to do this
10059
+ }
10060
+ if (typeof value !== 'string') {
10061
+ return 0;
10062
+ }
10063
+ value = value.trim();
10064
+ if (value.startsWith('+')) {
10065
+ return parseNumber(value.substring(1));
10066
+ }
10067
+ if (value.startsWith('-')) {
10068
+ const number = parseNumber(value.substring(1));
10069
+ if (number === 0) {
10070
+ return 0; // <- Note: To prevent -0
10071
+ }
10072
+ return -number;
10073
+ }
10074
+ value = value.replace(/,/g, '.');
10075
+ value = value.toUpperCase();
10076
+ if (value === '') {
10077
+ return 0;
10078
+ }
10079
+ if (value === '♾' || value.startsWith('INF')) {
10080
+ return Infinity;
10081
+ }
10082
+ if (value.includes('/')) {
10083
+ const [numerator_, denominator_] = value.split('/');
10084
+ const numerator = parseNumber(numerator_);
10085
+ const denominator = parseNumber(denominator_);
10086
+ if (denominator === 0) {
10087
+ throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
10088
+ }
10089
+ return numerator / denominator;
10090
+ }
10091
+ if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
10092
+ return 0;
10093
+ }
10094
+ if (value.includes('E')) {
10095
+ const [significand, exponent] = value.split('E');
10096
+ return parseNumber(significand) * 10 ** parseNumber(exponent);
10097
+ }
10098
+ if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
10099
+ throw new ParseError(`Unable to parse number from "${originalValue}"`);
10100
+ }
10101
+ const num = parseFloat(value);
10102
+ if (isNaN(num)) {
10103
+ throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
10104
+ }
10105
+ return num;
10106
+ }
10107
+ /**
10108
+ * TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
10109
+ * TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
10110
+ */
10111
+
10112
+ /**
10113
+ * Removes quotes from a string
10114
+ *
10115
+ * Note: [šŸ”‚] This function is idempotent.
10116
+ * Tip: This is very useful for post-processing of the result of the LLM model
10117
+ * Note: This function removes only the same quotes from the beginning and the end of the string
10118
+ * Note: There are two similar functions:
10119
+ * - `removeQuotes` which removes only bounding quotes
10120
+ * - `unwrapResult` which removes whole introduce sentence
10121
+ *
10122
+ * @param text optionally quoted text
10123
+ * @returns text without quotes
10124
+ * @public exported from `@promptbook/utils`
10125
+ */
10126
+ function removeQuotes(text) {
10127
+ if (text.startsWith('"') && text.endsWith('"')) {
10128
+ return text.slice(1, -1);
10129
+ }
10130
+ if (text.startsWith("'") && text.endsWith("'")) {
10131
+ return text.slice(1, -1);
10132
+ }
10133
+ return text;
10134
+ }
10135
+
10136
+ /**
10137
+ * Trims string from all 4 sides
10138
+ *
10139
+ * Note: This is a re-exported function from the `spacetrim` package which is
10140
+ * Developed by same author @hejny as this package
10141
+ *
10142
+ * @public exported from `@promptbook/utils`
10143
+ * @see https://github.com/hejny/spacetrim#usage
10144
+ */
10145
+ const spaceTrim = spaceTrim$1.spaceTrim;
10146
+
10147
+ /**
10148
+ * Checks if the given value is a valid JavaScript identifier name.
10149
+ *
10150
+ * @param javascriptName The value to check for JavaScript identifier validity.
10151
+ * @returns `true` if the value is a valid JavaScript name, false otherwise.
10152
+ * @public exported from `@promptbook/utils`
10153
+ */
10154
+ function isValidJavascriptName(javascriptName) {
10155
+ if (typeof javascriptName !== 'string') {
10156
+ return false;
10157
+ }
10158
+ return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
10159
+ }
10160
+
10161
+ /**
10162
+ * Normalizes agent name from arbitrary string to valid agent name
10163
+ *
10164
+ * Note: [šŸ”‚] This function is idempotent.
10165
+ *
10166
+ * @public exported from `@promptbook/core`
10167
+ */
10168
+ function normalizeAgentName(rawAgentName) {
10169
+ return titleToName(spaceTrim__default["default"](rawAgentName));
10170
+ }
10171
+
10172
+ /**
10173
+ * Creates temporary default agent name based on agent source hash
10174
+ *
10175
+ * @public exported from `@promptbook/core`
10176
+ */
10177
+ function createDefaultAgentName(agentSource) {
10178
+ const agentHash = computeAgentHash(agentSource);
10179
+ return normalizeAgentName(`Agent ${agentHash.substring(0, 6)}`);
10180
+ }
10181
+
10182
+ /**
10183
+ * Parses basic information from agent source
10184
+ *
10185
+ * There are 2 similar functions:
10186
+ * - `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.
10187
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronously.
10188
+ *
10189
+ * @public exported from `@promptbook/core`
10190
+ */
10191
+ function parseAgentSource(agentSource) {
10192
+ const parseResult = parseAgentSourceWithCommitments(agentSource);
10193
+ // Find PERSONA and META commitments
10194
+ let personaDescription = null;
10195
+ for (const commitment of parseResult.commitments) {
10196
+ if (commitment.type !== 'PERSONA') {
10197
+ continue;
10198
+ }
10199
+ if (personaDescription === null) {
10200
+ personaDescription = '';
10201
+ }
10202
+ else {
10203
+ personaDescription += `\n\n${personaDescription}`;
10204
+ }
10205
+ personaDescription += commitment.content;
10206
+ }
10207
+ let initialMessage = null;
10208
+ for (const commitment of parseResult.commitments) {
10209
+ if (commitment.type !== 'INITIAL MESSAGE') {
10210
+ continue;
10211
+ }
10212
+ // Note: Initial message override logic - later overrides earlier
10213
+ // Or should it append? Usually initial message is just one block.
10214
+ // Let's stick to "later overrides earlier" for simplicity, or just take the last one.
10215
+ initialMessage = commitment.content;
10216
+ }
10217
+ const meta = {};
10218
+ const links = [];
10219
+ for (const commitment of parseResult.commitments) {
10220
+ if (commitment.type === 'META LINK') {
10221
+ links.push(spaceTrim__default["default"](commitment.content));
10222
+ continue;
10223
+ }
10224
+ if (commitment.type !== 'META') {
10225
+ continue;
10226
+ }
10227
+ // Parse META commitments - format is "META TYPE content"
10228
+ const metaTypeRaw = commitment.content.split(' ')[0] || 'NONE';
10229
+ if (metaTypeRaw === 'LINK') {
10230
+ links.push(spaceTrim__default["default"](commitment.content.substring(metaTypeRaw.length)));
10231
+ }
10232
+ const metaType = normalizeTo_camelCase(metaTypeRaw);
10233
+ meta[metaType] = spaceTrim__default["default"](commitment.content.substring(metaTypeRaw.length));
10234
+ }
10235
+ // Generate gravatar fallback if no meta image specified
10236
+ if (!meta.image) {
10237
+ meta.image = generatePlaceholderAgentProfileImageUrl(parseResult.agentName || '!!');
10238
+ }
10239
+ // Parse parameters using unified approach - both @Parameter and {parameter} notations
10240
+ // are treated as the same syntax feature with unified representation
10241
+ const parameters = parseParameters(agentSource);
10242
+ const agentHash = computeAgentHash(agentSource);
10243
+ return {
10244
+ agentName: normalizeAgentName(parseResult.agentName || createDefaultAgentName(agentSource)),
10245
+ agentHash,
10246
+ personaDescription,
10247
+ initialMessage,
10248
+ meta,
10249
+ links,
10250
+ parameters,
10251
+ };
10252
+ }
10253
+ /**
10254
+ * TODO: [šŸ•›] Unite `AgentBasicInformation`, `ChatParticipant`, `LlmExecutionTools` + `LlmToolsMetadata`
10255
+ */
10256
+
10257
+ /**
10258
+ * Creates model requirements for an agent based on its source
10259
+ *
10260
+ * There are 2 similar functions:
10261
+ * - `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.
10262
+ * - `createAgentModelRequirements` which is an asynchronous function that creates model requirements it applies each commitment one by one and works asynchronous.
10263
+ *
10264
+ * @public exported from `@promptbook/core`
10265
+ */
10266
+ async function createAgentModelRequirements(agentSource, modelName, availableModels, llmTools) {
10267
+ // If availableModels are provided and no specific modelName is given,
10268
+ // use preparePersona to select the best model
10269
+ if (availableModels && !modelName && llmTools) {
10270
+ const selectedModelName = await selectBestModelUsingPersona(agentSource, llmTools);
10271
+ return createAgentModelRequirementsWithCommitments(agentSource, selectedModelName);
10272
+ }
10273
+ // Use the new commitment-based system with provided or default model
10274
+ return createAgentModelRequirementsWithCommitments(agentSource, modelName);
10275
+ }
10276
+ /**
10277
+ * Selects the best model using the preparePersona function
10278
+ * This directly uses preparePersona to ensure DRY principle
10279
+ *
10280
+ * @param agentSource The agent source to derive persona description from
10281
+ * @param llmTools LLM tools for preparing persona
10282
+ * @returns The name of the best selected model
10283
+ * @private function of `createAgentModelRequirements`
10284
+ */
10285
+ async function selectBestModelUsingPersona(agentSource, llmTools) {
10286
+ var _a;
10287
+ // Parse agent source to get persona description
9755
10288
  const { agentName, personaDescription } = parseAgentSource(agentSource);
9756
10289
  // Use agent name as fallback if no persona description is available
9757
10290
  const description = personaDescription || agentName || 'AI Agent';
@@ -9884,37 +10417,28 @@
9884
10417
  PERSONA A friendly AI assistant that helps you with your tasks
9885
10418
  `)));
9886
10419
  // <- Note: Not using book`...` notation to avoid strange error in jest unit tests `TypeError: (0 , book_notation_1.book) is not a function`
9887
- // <- TODO: !!! `GENESIS_BOOK` / `ADAM_BOOK` in `/agents/adam.book`
9888
- // <- !!! Buttons into genesis book
9889
- // <- TODO: !!! generateBookBoilerplate and deprecate `DEFAULT_BOOK`
9890
-
9891
- /**
9892
- * Trims string from all 4 sides
9893
- *
9894
- * Note: This is a re-exported function from the `spacetrim` package which is
9895
- * Developed by same author @hejny as this package
9896
- *
9897
- * @public exported from `@promptbook/utils`
9898
- * @see https://github.com/hejny/spacetrim#usage
9899
- */
9900
- const spaceTrim = spaceTrim$1.spaceTrim;
10420
+ // <- TODO: [šŸ±ā€šŸš€] `GENESIS_BOOK` / `ADAM_BOOK` in `/agents/adam.book`
10421
+ // <- [šŸ±ā€šŸš€] Buttons into genesis book
10422
+ // <- TODO: [šŸ±ā€šŸš€] generateBookBoilerplate and deprecate `DEFAULT_BOOK`
9901
10423
 
10424
+ // import { getTableName } from '../../../../../apps/agents-server/src/database/getTableName';
10425
+ // <- TODO: [šŸ±ā€šŸš€] Prevent imports from `/apps` -> `/src`
9902
10426
  /**
9903
10427
  * Agent collection stored in Supabase table
9904
10428
  *
9905
10429
  * Note: This object can work both from Node.js and browser environment depending on the Supabase client provided
9906
10430
  *
9907
10431
  * @public exported from `@promptbook/core`
9908
- * <- TODO: !!! Move to `@promptbook/supabase` package
10432
+ * <- TODO: [šŸ±ā€šŸš€] Move to `@promptbook/supabase` package
9909
10433
  */
9910
- class AgentCollectionInSupabase /* TODO: !!!! implements AgentCollection */ {
10434
+ class AgentCollectionInSupabase /* TODO: [šŸ±ā€šŸš€] implements Agent */ {
9911
10435
  /**
9912
10436
  * @param rootPath - path to the directory with agents
9913
- * @param tools - Execution tools to be used in !!! `Agent` itself and listing the agents
10437
+ * @param tools - Execution tools to be used in [šŸ±ā€šŸš€] `Agent` itself and listing the agents
9914
10438
  * @param options - Options for the collection creation
9915
10439
  */
9916
10440
  constructor(supabaseClient,
9917
- /// TODO: !!! Remove> private readonly tools?: Pick<ExecutionTools, 'llm' | 'fs' | 'scrapers'>,
10441
+ /// TODO: [šŸ±ā€šŸš€] Remove> private readonly tools?: Pick<ExecutionTools, 'llm' | 'fs' | 'scrapers'>,
9918
10442
  options) {
9919
10443
  this.supabaseClient = supabaseClient;
9920
10444
  this.options = options;
@@ -9929,8 +10453,8 @@
9929
10453
  async listAgents( /* TODO: [🧠] Allow to pass some condition here */) {
9930
10454
  const { isVerbose = exports.DEFAULT_IS_VERBOSE } = this.options || {};
9931
10455
  const selectResult = await this.supabaseClient
9932
- .from('AgentCollection' /* <- TODO: !!!! Change to `Agent` */)
9933
- .select('agentProfile');
10456
+ .from(this.getTableName('Agent'))
10457
+ .select('agentName,agentProfile');
9934
10458
  if (selectResult.error) {
9935
10459
  throw new DatabaseError(spaceTrim((block) => `
9936
10460
 
@@ -9942,14 +10466,27 @@
9942
10466
  if (isVerbose) {
9943
10467
  console.info(`Found ${selectResult.data.length} agents in directory`);
9944
10468
  }
9945
- return selectResult.data.map((row) => row.agentProfile);
10469
+ return selectResult.data.map(({ agentName, agentProfile }) => {
10470
+ if (isVerbose && agentProfile.agentName !== agentName) {
10471
+ console.warn(spaceTrim(`
10472
+ Agent name mismatch for agent "${agentName}". Using name from database.
10473
+
10474
+ agentName: "${agentName}"
10475
+ agentProfile.agentName: "${agentProfile.agentName}"
10476
+ `));
10477
+ }
10478
+ return {
10479
+ ...agentProfile,
10480
+ agentName,
10481
+ };
10482
+ });
9946
10483
  }
9947
10484
  /**
9948
- * !!!@@@
10485
+ * [šŸ±ā€šŸš€]@@@
9949
10486
  */
9950
10487
  async getAgentSource(agentName) {
9951
10488
  const selectResult = await this.supabaseClient
9952
- .from('AgentCollection' /* <- TODO: !!!! Change to `Agent` */)
10489
+ .from(this.getTableName('Agent'))
9953
10490
  .select('agentSource')
9954
10491
  .eq('agentName', agentName)
9955
10492
  .single();
@@ -9965,7 +10502,7 @@
9965
10502
 
9966
10503
  ${block(selectResult.error.message)}
9967
10504
  `));
9968
- // <- TODO: !!! First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
10505
+ // <- TODO: [šŸ±ā€šŸš€] First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
9969
10506
  }
9970
10507
  return selectResult.data.agentSource;
9971
10508
  }
@@ -9977,67 +10514,90 @@
9977
10514
  async createAgent(agentSource) {
9978
10515
  const agentProfile = parseAgentSource(agentSource);
9979
10516
  // <- TODO: [šŸ•›]
9980
- const selectResult = await this.supabaseClient
9981
- .from('AgentCollection' /* <- TODO: !!!! Change to `Agent` */)
9982
- .insert({
9983
- agentName: agentProfile.agentName || '!!!!!' /* <- TODO: !!!! Remove */,
10517
+ const { agentName, agentHash } = agentProfile;
10518
+ const insertAgentResult = await this.supabaseClient.from(this.getTableName('Agent')).insert({
10519
+ agentName,
10520
+ agentHash,
9984
10521
  agentProfile,
9985
10522
  createdAt: new Date().toISOString(),
9986
10523
  updatedAt: null,
9987
- agentVersion: 0,
9988
10524
  promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
9989
10525
  usage: ZERO_USAGE,
9990
10526
  agentSource: agentSource,
9991
10527
  });
9992
- if (selectResult.error) {
10528
+ if (insertAgentResult.error) {
9993
10529
  throw new DatabaseError(spaceTrim((block) => `
9994
10530
  Error creating agent "${agentProfile.agentName}" in Supabase:
9995
10531
 
9996
- ${block(selectResult.error.message)}
10532
+ ${block(insertAgentResult.error.message)}
9997
10533
  `));
9998
10534
  }
10535
+ await this.supabaseClient.from(this.getTableName('AgentHistory')).insert({
10536
+ createdAt: new Date().toISOString(),
10537
+ agentName,
10538
+ agentHash,
10539
+ previousAgentHash: null,
10540
+ agentSource,
10541
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
10542
+ });
10543
+ // <- TODO: [🧠] What to do with `insertAgentHistoryResult.error`, ignore? wait?
9999
10544
  return agentProfile;
10000
10545
  }
10001
10546
  /**
10002
10547
  * Updates an existing agent in the collection
10003
10548
  */
10004
10549
  async updateAgentSource(agentName, agentSource) {
10005
- const selectResult = await this.supabaseClient
10006
- .from('AgentCollection' /* <- TODO: !!!! Change to `Agent` */)
10007
- .select('agentVersion')
10550
+ const selectPreviousAgentResult = await this.supabaseClient
10551
+ .from(this.getTableName('Agent'))
10552
+ .select('agentHash,agentName')
10008
10553
  .eq('agentName', agentName)
10009
10554
  .single();
10010
- if (!selectResult.data) {
10011
- throw new NotFoundError(`Agent "${agentName}" not found`);
10555
+ if (selectPreviousAgentResult.error) {
10556
+ throw new DatabaseError(spaceTrim((block) => `
10557
+
10558
+ Error fetching agent "${agentName}" from Supabase:
10559
+
10560
+ ${block(selectPreviousAgentResult.error.message)}
10561
+ `));
10562
+ // <- TODO: [šŸ±ā€šŸš€] First check if the error is "not found" and throw `NotFoundError` instead then throw `DatabaseError`
10012
10563
  }
10564
+ selectPreviousAgentResult.data.agentName;
10565
+ const previousAgentHash = selectPreviousAgentResult.data.agentHash;
10013
10566
  const agentProfile = parseAgentSource(agentSource);
10014
- // TODO: !!!!!! What about agentName change
10015
- console.log('!!! agentName', agentName);
10016
- const oldAgentSource = await this.getAgentSource(agentName);
10017
- const updateResult = await this.supabaseClient
10018
- .from('AgentCollection' /* <- TODO: !!!! Change to `Agent` */)
10567
+ // <- TODO: [šŸ•›]
10568
+ const { agentHash } = agentProfile;
10569
+ const updateAgentResult = await this.supabaseClient
10570
+ .from(this.getTableName('Agent'))
10019
10571
  .update({
10020
- // TODO: !!!! Compare not update> agentName: agentProfile.agentName || '!!!!!' /* <- TODO: !!!! Remove */,
10572
+ // TODO: [šŸ±ā€šŸš€] Compare not update> agentName: agentProfile.agentName || '[šŸ±ā€šŸš€]' /* <- TODO: [šŸ±ā€šŸš€] Remove */,
10021
10573
  agentProfile,
10022
10574
  updatedAt: new Date().toISOString(),
10023
- agentVersion: selectResult.data.agentVersion + 1,
10575
+ agentHash: agentProfile.agentHash,
10024
10576
  agentSource,
10025
10577
  promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
10026
10578
  })
10027
10579
  .eq('agentName', agentName);
10028
- const newAgentSource = await this.getAgentSource(agentName);
10029
- console.log('!!! updateAgent', updateResult);
10030
- console.log('!!! old', oldAgentSource);
10031
- console.log('!!! new', newAgentSource);
10032
- if (updateResult.error) {
10580
+ // console.log('[šŸ±ā€šŸš€] updateAgent', updateResult);
10581
+ // console.log('[šŸ±ā€šŸš€] old', oldAgentSource);
10582
+ // console.log('[šŸ±ā€šŸš€] new', newAgentSource);
10583
+ if (updateAgentResult.error) {
10033
10584
  throw new DatabaseError(spaceTrim((block) => `
10034
10585
  Error updating agent "${agentName}" in Supabase:
10035
10586
 
10036
- ${block(updateResult.error.message)}
10587
+ ${block(updateAgentResult.error.message)}
10037
10588
  `));
10038
10589
  }
10590
+ await this.supabaseClient.from(this.getTableName('AgentHistory')).insert({
10591
+ createdAt: new Date().toISOString(),
10592
+ agentName,
10593
+ agentHash,
10594
+ previousAgentHash,
10595
+ agentSource,
10596
+ promptbookEngineVersion: PROMPTBOOK_ENGINE_VERSION,
10597
+ });
10598
+ // <- TODO: [🧠] What to do with `insertAgentHistoryResult.error`, ignore? wait?
10039
10599
  }
10040
- // TODO: !!!! public async getAgentSourceSubject(agentName: string_agent_name): Promise<BehaviorSubject<string_book>>
10600
+ // TODO: [šŸ±ā€šŸš€] public async getAgentSourceSubject(agentName: string_agent_name): Promise<BehaviorSubject<string_book>>
10041
10601
  // Use Supabase realtime logic
10042
10602
  /**
10043
10603
  * Deletes an agent from the collection
@@ -10045,9 +10605,19 @@
10045
10605
  async deleteAgent(agentName) {
10046
10606
  throw new NotYetImplementedError('Method not implemented.');
10047
10607
  }
10608
+ /**
10609
+ * Get the Supabase table name with prefix
10610
+ *
10611
+ * @param tableName - The original table name
10612
+ * @returns The prefixed table name
10613
+ */
10614
+ getTableName(tableName) {
10615
+ const { tablePrefix = '' } = this.options || {};
10616
+ return `${tablePrefix}${tableName}`;
10617
+ }
10048
10618
  }
10049
10619
  /**
10050
- * TODO: !!!! Implement it here correctly and update JSDoc comments here, and on interface + other implementations
10620
+ * TODO: [šŸ±ā€šŸš€] Implement it here correctly and update JSDoc comments here, and on interface + other implementations
10051
10621
  * TODO: Write unit test
10052
10622
  * TODO: [🧠][šŸš™] `AgentXxx` vs `AgentsXxx` naming convention
10053
10623
  */
@@ -10658,106 +11228,37 @@
10658
11228
  *
10659
11229
  * Note: `$` is used to indicate that this function mutates given `pipelineJson`
10660
11230
  */
10661
- $applyToPipelineJson(command, $pipelineJson) {
10662
- // TODO: Warn if the version is overridden
10663
- $pipelineJson.bookVersion = command.bookVersion;
10664
- },
10665
- /**
10666
- * Converts the BOOK_VERSION command back to string
10667
- *
10668
- * Note: This is used in `pipelineJsonToString` utility
10669
- */
10670
- stringify(command) {
10671
- return `---`; // <- TODO: [šŸ›‹] Implement
10672
- },
10673
- /**
10674
- * Reads the BOOK_VERSION command from the `PipelineJson`
10675
- *
10676
- * Note: This is used in `pipelineJsonToString` utility
10677
- */
10678
- takeFromPipelineJson(pipelineJson) {
10679
- throw new NotYetImplementedError(`[šŸ›‹] Not implemented yet`); // <- TODO: [šŸ›‹] Implement
10680
- },
10681
- };
10682
-
10683
- /**
10684
- * Units of text measurement
10685
- *
10686
- * @see https://github.com/webgptorg/promptbook/discussions/30
10687
- * @public exported from `@promptbook/core`
10688
- */
10689
- const EXPECTATION_UNITS = ['CHARACTERS', 'WORDS', 'SENTENCES', 'LINES', 'PARAGRAPHS', 'PAGES'];
10690
- /**
10691
- * TODO: [šŸ’] Unite object for expecting amount and format - remove format
10692
- */
10693
-
10694
- /**
10695
- * Function parseNumber will parse number from string
10696
- *
10697
- * Note: [šŸ”‚] This function is idempotent.
10698
- * Unlike Number.parseInt, Number.parseFloat it will never ever result in NaN
10699
- * Note: it also works only with decimal numbers
10700
- *
10701
- * @returns parsed number
10702
- * @throws {ParseError} if the value is not a number
10703
- *
10704
- * @public exported from `@promptbook/utils`
10705
- */
10706
- function parseNumber(value) {
10707
- const originalValue = value;
10708
- if (typeof value === 'number') {
10709
- value = value.toString(); // <- TODO: Maybe more efficient way to do this
10710
- }
10711
- if (typeof value !== 'string') {
10712
- return 0;
10713
- }
10714
- value = value.trim();
10715
- if (value.startsWith('+')) {
10716
- return parseNumber(value.substring(1));
10717
- }
10718
- if (value.startsWith('-')) {
10719
- const number = parseNumber(value.substring(1));
10720
- if (number === 0) {
10721
- return 0; // <- Note: To prevent -0
10722
- }
10723
- return -number;
10724
- }
10725
- value = value.replace(/,/g, '.');
10726
- value = value.toUpperCase();
10727
- if (value === '') {
10728
- return 0;
10729
- }
10730
- if (value === '♾' || value.startsWith('INF')) {
10731
- return Infinity;
10732
- }
10733
- if (value.includes('/')) {
10734
- const [numerator_, denominator_] = value.split('/');
10735
- const numerator = parseNumber(numerator_);
10736
- const denominator = parseNumber(denominator_);
10737
- if (denominator === 0) {
10738
- throw new ParseError(`Unable to parse number from "${originalValue}" because denominator is zero`);
10739
- }
10740
- return numerator / denominator;
10741
- }
10742
- if (/^(NAN|NULL|NONE|UNDEFINED|ZERO|NO.*)$/.test(value)) {
10743
- return 0;
10744
- }
10745
- if (value.includes('E')) {
10746
- const [significand, exponent] = value.split('E');
10747
- return parseNumber(significand) * 10 ** parseNumber(exponent);
10748
- }
10749
- if (!/^[0-9.]+$/.test(value) || value.split('.').length > 2) {
10750
- throw new ParseError(`Unable to parse number from "${originalValue}"`);
10751
- }
10752
- const num = parseFloat(value);
10753
- if (isNaN(num)) {
10754
- throw new ParseError(`Unexpected NaN when parsing number from "${originalValue}"`);
10755
- }
10756
- return num;
10757
- }
11231
+ $applyToPipelineJson(command, $pipelineJson) {
11232
+ // TODO: Warn if the version is overridden
11233
+ $pipelineJson.bookVersion = command.bookVersion;
11234
+ },
11235
+ /**
11236
+ * Converts the BOOK_VERSION command back to string
11237
+ *
11238
+ * Note: This is used in `pipelineJsonToString` utility
11239
+ */
11240
+ stringify(command) {
11241
+ return `---`; // <- TODO: [šŸ›‹] Implement
11242
+ },
11243
+ /**
11244
+ * Reads the BOOK_VERSION command from the `PipelineJson`
11245
+ *
11246
+ * Note: This is used in `pipelineJsonToString` utility
11247
+ */
11248
+ takeFromPipelineJson(pipelineJson) {
11249
+ throw new NotYetImplementedError(`[šŸ›‹] Not implemented yet`); // <- TODO: [šŸ›‹] Implement
11250
+ },
11251
+ };
11252
+
10758
11253
  /**
10759
- * TODO: Maybe use sth. like safe-eval in fraction/calculation case @see https://www.npmjs.com/package/safe-eval
10760
- * TODO: [🧠][🌻] Maybe export through `@promptbook/markdown-utils` not `@promptbook/utils`
11254
+ * Units of text measurement
11255
+ *
11256
+ * @see https://github.com/webgptorg/promptbook/discussions/30
11257
+ * @public exported from `@promptbook/core`
11258
+ */
11259
+ const EXPECTATION_UNITS = ['CHARACTERS', 'WORDS', 'SENTENCES', 'LINES', 'PARAGRAPHS', 'PAGES'];
11260
+ /**
11261
+ * TODO: [šŸ’] Unite object for expecting amount and format - remove format
10761
11262
  */
10762
11263
 
10763
11264
  /**
@@ -10902,30 +11403,6 @@
10902
11403
  },
10903
11404
  };
10904
11405
 
10905
- /**
10906
- * Removes quotes from a string
10907
- *
10908
- * Note: [šŸ”‚] This function is idempotent.
10909
- * Tip: This is very useful for post-processing of the result of the LLM model
10910
- * Note: This function removes only the same quotes from the beginning and the end of the string
10911
- * Note: There are two similar functions:
10912
- * - `removeQuotes` which removes only bounding quotes
10913
- * - `unwrapResult` which removes whole introduce sentence
10914
- *
10915
- * @param text optionally quoted text
10916
- * @returns text without quotes
10917
- * @public exported from `@promptbook/utils`
10918
- */
10919
- function removeQuotes(text) {
10920
- if (text.startsWith('"') && text.endsWith('"')) {
10921
- return text.slice(1, -1);
10922
- }
10923
- if (text.startsWith("'") && text.endsWith("'")) {
10924
- return text.slice(1, -1);
10925
- }
10926
- return text;
10927
- }
10928
-
10929
11406
  /**
10930
11407
  * Function `validateParameterName` will normalize and validate a parameter name for use in pipelines.
10931
11408
  * It removes diacritics, emojis, and quotes, normalizes to camelCase, and checks for reserved names and invalid characters.
@@ -12112,20 +12589,6 @@
12112
12589
  persona.description += spaceTrim__default["default"]('\n\n' + personaDescription);
12113
12590
  }
12114
12591
 
12115
- /**
12116
- * Checks if the given value is a valid JavaScript identifier name.
12117
- *
12118
- * @param javascriptName The value to check for JavaScript identifier validity.
12119
- * @returns `true` if the value is a valid JavaScript name, false otherwise.
12120
- * @public exported from `@promptbook/utils`
12121
- */
12122
- function isValidJavascriptName(javascriptName) {
12123
- if (typeof javascriptName !== 'string') {
12124
- return false;
12125
- }
12126
- return /^[a-zA-Z_$][0-9a-zA-Z_$]*$/i.test(javascriptName);
12127
- }
12128
-
12129
12592
  /**
12130
12593
  * Parses the postprocess command
12131
12594
  *
@@ -13694,114 +14157,6 @@
13694
14157
  * TODO: [šŸ›] This can be part of markdown builder
13695
14158
  */
13696
14159
 
13697
- /**
13698
- * Creates a Mermaid graph based on the promptbook
13699
- *
13700
- * Note: The result is not wrapped in a Markdown code block
13701
- *
13702
- * @public exported from `@promptbook/utils`
13703
- */
13704
- function renderPromptbookMermaid(pipelineJson, options) {
13705
- const { linkTask = () => null } = options || {};
13706
- const MERMAID_PREFIX = 'pipeline_';
13707
- const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
13708
- const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
13709
- const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
13710
- const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
13711
- const parameterNameToTaskName = (parameterName) => {
13712
- if (parameterName === 'knowledge') {
13713
- return MERMAID_KNOWLEDGE_NAME;
13714
- }
13715
- else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
13716
- return MERMAID_RESERVED_NAME;
13717
- }
13718
- const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
13719
- if (!parameter) {
13720
- throw new UnexpectedError(`Could not find {${parameterName}}`);
13721
- // <- TODO: This causes problems when {knowledge} and other reserved parameters are used
13722
- }
13723
- if (parameter.isInput) {
13724
- return MERMAID_INPUT_NAME;
13725
- }
13726
- const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
13727
- if (!task) {
13728
- throw new Error(`Could not find task for {${parameterName}}`);
13729
- }
13730
- return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
13731
- };
13732
- const inputAndIntermediateParametersMermaid = pipelineJson.tasks
13733
- .flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
13734
- `${parameterNameToTaskName(resultingParameterName)}("${title}")`,
13735
- ...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
13736
- ])
13737
- .join('\n');
13738
- const outputParametersMermaid = pipelineJson.parameters
13739
- .filter(({ isOutput }) => isOutput)
13740
- .map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
13741
- .join('\n');
13742
- const linksMermaid = pipelineJson.tasks
13743
- .map((task) => {
13744
- const link = linkTask(task);
13745
- if (link === null) {
13746
- return '';
13747
- }
13748
- const { href, title } = link;
13749
- const taskName = parameterNameToTaskName(task.resultingParameterName);
13750
- return `click ${taskName} href "${href}" "${title}";`;
13751
- })
13752
- .filter((line) => line !== '')
13753
- .join('\n');
13754
- const interactionPointsMermaid = Object.entries({
13755
- [MERMAID_INPUT_NAME]: 'Input',
13756
- [MERMAID_OUTPUT_NAME]: 'Output',
13757
- [MERMAID_RESERVED_NAME]: 'Other',
13758
- [MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
13759
- })
13760
- .filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
13761
- .map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
13762
- .join('\n');
13763
- const promptbookMermaid = spaceTrim$1.spaceTrim((block) => `
13764
-
13765
- %% šŸ”® Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
13766
-
13767
- flowchart LR
13768
- subgraph "${pipelineJson.title}"
13769
-
13770
- %% Basic configuration
13771
- direction TB
13772
-
13773
- %% Interaction points from pipeline to outside
13774
- ${block(interactionPointsMermaid)}
13775
-
13776
- %% Input and intermediate parameters
13777
- ${block(inputAndIntermediateParametersMermaid)}
13778
-
13779
-
13780
- %% Output parameters
13781
- ${block(outputParametersMermaid)}
13782
-
13783
- %% Links
13784
- ${block(linksMermaid)}
13785
-
13786
- %% Styles
13787
- classDef ${MERMAID_INPUT_NAME} color: grey;
13788
- classDef ${MERMAID_OUTPUT_NAME} color: grey;
13789
- classDef ${MERMAID_RESERVED_NAME} color: grey;
13790
- classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
13791
-
13792
- end;
13793
-
13794
- `);
13795
- return promptbookMermaid;
13796
- }
13797
- /**
13798
- * TODO: [🧠] FOREACH in mermaid graph
13799
- * TODO: [🧠] Knowledge in mermaid graph
13800
- * TODO: [🧠] Personas in mermaid graph
13801
- * TODO: Maybe use some Mermaid package instead of string templating
13802
- * TODO: [šŸ•Œ] When more than 2 functionalities, split into separate functions
13803
- */
13804
-
13805
14160
  /**
13806
14161
  * Prettyfies Promptbook string and adds Mermaid graph
13807
14162
  *
@@ -14362,64 +14717,6 @@
14362
14717
  * TODO: [Ā®] DRY Register logic
14363
14718
  */
14364
14719
 
14365
- /**
14366
- * Detects if the code is running in a browser environment in main thread (Not in a web worker)
14367
- *
14368
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
14369
- *
14370
- * @public exported from `@promptbook/utils`
14371
- */
14372
- const $isRunningInBrowser = new Function(`
14373
- try {
14374
- return this === window;
14375
- } catch (e) {
14376
- return false;
14377
- }
14378
- `);
14379
- /**
14380
- * TODO: [šŸŽŗ]
14381
- */
14382
-
14383
- /**
14384
- * Detects if the code is running in a Node.js environment
14385
- *
14386
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
14387
- *
14388
- * @public exported from `@promptbook/utils`
14389
- */
14390
- const $isRunningInNode = new Function(`
14391
- try {
14392
- return this === global;
14393
- } catch (e) {
14394
- return false;
14395
- }
14396
- `);
14397
- /**
14398
- * TODO: [šŸŽŗ]
14399
- */
14400
-
14401
- /**
14402
- * Detects if the code is running in a web worker
14403
- *
14404
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
14405
- *
14406
- * @public exported from `@promptbook/utils`
14407
- */
14408
- const $isRunningInWebWorker = new Function(`
14409
- try {
14410
- if (typeof WorkerGlobalScope !== 'undefined' && self instanceof WorkerGlobalScope) {
14411
- return true;
14412
- } else {
14413
- return false;
14414
- }
14415
- } catch (e) {
14416
- return false;
14417
- }
14418
- `);
14419
- /**
14420
- * TODO: [šŸŽŗ]
14421
- */
14422
-
14423
14720
  /**
14424
14721
  * Creates a message with all registered LLM tools
14425
14722
  *
@@ -14653,18 +14950,6 @@
14653
14950
  }
14654
14951
  }
14655
14952
 
14656
- /**
14657
- * Simple wrapper `new Date().toISOString()`
14658
- *
14659
- * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
14660
- *
14661
- * @returns string_date branded type
14662
- * @public exported from `@promptbook/utils`
14663
- */
14664
- function $getCurrentDate() {
14665
- return new Date().toISOString();
14666
- }
14667
-
14668
14953
  /**
14669
14954
  * Intercepts LLM tools and counts total usage of the tools
14670
14955
  *
@@ -15290,18 +15575,18 @@
15290
15575
  },
15291
15576
  },
15292
15577
  /**/
15293
- /*/
15294
- {
15295
- modelTitle: 'tts-1-hd-1106',
15296
- modelName: 'tts-1-hd-1106',
15297
- },
15298
- /**/
15299
- /*/
15300
- {
15301
- modelTitle: 'tts-1-hd',
15302
- modelName: 'tts-1-hd',
15303
- },
15304
- /**/
15578
+ /*/
15579
+ {
15580
+ modelTitle: 'tts-1-hd-1106',
15581
+ modelName: 'tts-1-hd-1106',
15582
+ },
15583
+ /**/
15584
+ /*/
15585
+ {
15586
+ modelTitle: 'tts-1-hd',
15587
+ modelName: 'tts-1-hd',
15588
+ },
15589
+ /**/
15305
15590
  /**/
15306
15591
  {
15307
15592
  modelVariant: 'CHAT',
@@ -16487,11 +16772,12 @@
16487
16772
  *
16488
16773
  * This is useful for calling OpenAI API with a single assistant, for more wide usage use `OpenAiExecutionTools`.
16489
16774
  *
16490
- * !!! Note: [šŸ¦–] There are several different things in Promptbook:
16775
+ * Note: [šŸ¦–] There are several different things in Promptbook:
16491
16776
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
16492
16777
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
16493
16778
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
16494
16779
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
16780
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
16495
16781
  *
16496
16782
  * @public exported from `@promptbook/openai`
16497
16783
  */
@@ -16526,6 +16812,12 @@
16526
16812
  * Calls OpenAI API to use a chat model.
16527
16813
  */
16528
16814
  async callChatModel(prompt) {
16815
+ return this.callChatModelStream(prompt, () => { });
16816
+ }
16817
+ /**
16818
+ * Calls OpenAI API to use a chat model with streaming.
16819
+ */
16820
+ async callChatModelStream(prompt, onProgress) {
16529
16821
  var _a, _b, _c;
16530
16822
  if (this.options.isVerbose) {
16531
16823
  console.info('šŸ’¬ OpenAI callChatModel call', { prompt });
@@ -16593,21 +16885,24 @@
16593
16885
  console.info('connect', stream.currentEvent);
16594
16886
  }
16595
16887
  });
16596
- /*
16597
- stream.on('messageDelta', (messageDelta) => {
16598
- if (
16599
- this.options.isVerbose &&
16600
- messageDelta &&
16601
- messageDelta.content &&
16602
- messageDelta.content[0] &&
16603
- messageDelta.content[0].type === 'text'
16604
- ) {
16605
- console.info('messageDelta', messageDelta.content[0].text?.value);
16888
+ stream.on('textDelta', (textDelta, snapshot) => {
16889
+ if (this.options.isVerbose && textDelta.value) {
16890
+ console.info('textDelta', textDelta.value);
16606
16891
  }
16607
-
16608
- // <- TODO: [🐚] Make streaming and running tasks working
16892
+ const chunk = {
16893
+ content: textDelta.value || '',
16894
+ modelName: 'assistant',
16895
+ timing: {
16896
+ start,
16897
+ complete: $getCurrentDate(),
16898
+ },
16899
+ usage: UNCERTAIN_USAGE,
16900
+ rawPromptContent,
16901
+ rawRequest,
16902
+ rawResponse: snapshot,
16903
+ };
16904
+ onProgress(chunk);
16609
16905
  });
16610
- */
16611
16906
  stream.on('messageCreated', (message) => {
16612
16907
  if (this.options.isVerbose) {
16613
16908
  console.info('messageCreated', message);
@@ -16643,7 +16938,7 @@
16643
16938
  }
16644
16939
  return exportJson({
16645
16940
  name: 'promptResult',
16646
- message: `Result of \`OpenAiAssistantExecutionTools.callChatModel\``,
16941
+ message: `Result of \`OpenAiAssistantExecutionTools.callChatModelStream\``,
16647
16942
  order: [],
16648
16943
  value: {
16649
16944
  content: resultContent,
@@ -16662,15 +16957,19 @@
16662
16957
  },
16663
16958
  });
16664
16959
  }
16665
- async playground() {
16960
+ /*
16961
+ public async playground() {
16666
16962
  const client = await this.getClient();
16963
+
16667
16964
  // List all assistants
16668
16965
  const assistants = await client.beta.assistants.list();
16669
16966
  console.log('!!! Assistants:', assistants);
16967
+
16670
16968
  // Get details of a specific assistant
16671
16969
  const assistantId = 'asst_MO8fhZf4dGloCfXSHeLcIik0';
16672
16970
  const assistant = await client.beta.assistants.retrieve(assistantId);
16673
16971
  console.log('!!! Assistant Details:', assistant);
16972
+
16674
16973
  // Update an assistant
16675
16974
  const updatedAssistant = await client.beta.assistants.update(assistantId, {
16676
16975
  name: assistant.name + '(M)',
@@ -16680,7 +16979,18 @@
16680
16979
  },
16681
16980
  });
16682
16981
  console.log('!!! Updated Assistant:', updatedAssistant);
16683
- await waitasecond.forEver();
16982
+
16983
+ await forEver();
16984
+ }
16985
+ */
16986
+ /**
16987
+ * Get an existing assistant tool wrapper
16988
+ */
16989
+ getAssistant(assistantId) {
16990
+ return new OpenAiAssistantExecutionTools({
16991
+ ...this.options,
16992
+ assistantId,
16993
+ });
16684
16994
  }
16685
16995
  async createNewAssistant(options) {
16686
16996
  if (!this.isCreatingNewAssistantsAllowed) {
@@ -16767,9 +17077,98 @@
16767
17077
  }
16768
17078
  const assistant = await client.beta.assistants.create(assistantConfig);
16769
17079
  console.log(`āœ… Assistant created: ${assistant.id}`);
16770
- // TODO: !!!! Try listing existing assistants
16771
- // TODO: !!!! Try marking existing assistants by DISCRIMINANT
16772
- // TODO: !!!! Allow to update and reconnect to existing assistants
17080
+ // TODO: [šŸ±ā€šŸš€] Try listing existing assistants
17081
+ // TODO: [šŸ±ā€šŸš€] Try marking existing assistants by DISCRIMINANT
17082
+ // TODO: [šŸ±ā€šŸš€] Allow to update and reconnect to existing assistants
17083
+ return new OpenAiAssistantExecutionTools({
17084
+ ...this.options,
17085
+ isCreatingNewAssistantsAllowed: false,
17086
+ assistantId: assistant.id,
17087
+ });
17088
+ }
17089
+ async updateAssistant(options) {
17090
+ if (!this.isCreatingNewAssistantsAllowed) {
17091
+ throw new NotAllowed(`Updating assistants is not allowed. Set \`isCreatingNewAssistantsAllowed: true\` in options to enable this feature.`);
17092
+ }
17093
+ const { assistantId, name, instructions, knowledgeSources } = options;
17094
+ const client = await this.getClient();
17095
+ let vectorStoreId;
17096
+ // If knowledge sources are provided, create a vector store with them
17097
+ // TODO: [🧠] Reuse vector store creation logic from createNewAssistant
17098
+ if (knowledgeSources && knowledgeSources.length > 0) {
17099
+ if (this.options.isVerbose) {
17100
+ console.info(`šŸ“š Creating vector store for update with ${knowledgeSources.length} knowledge sources...`);
17101
+ }
17102
+ // Create a vector store
17103
+ const vectorStore = await client.beta.vectorStores.create({
17104
+ name: `${name} Knowledge Base`,
17105
+ });
17106
+ vectorStoreId = vectorStore.id;
17107
+ if (this.options.isVerbose) {
17108
+ console.info(`āœ… Vector store created: ${vectorStoreId}`);
17109
+ }
17110
+ // Upload files from knowledge sources to the vector store
17111
+ const fileStreams = [];
17112
+ for (const source of knowledgeSources) {
17113
+ try {
17114
+ // Check if it's a URL
17115
+ if (source.startsWith('http://') || source.startsWith('https://')) {
17116
+ // Download the file
17117
+ const response = await fetch(source);
17118
+ if (!response.ok) {
17119
+ console.error(`Failed to download ${source}: ${response.statusText}`);
17120
+ continue;
17121
+ }
17122
+ const buffer = await response.arrayBuffer();
17123
+ const filename = source.split('/').pop() || 'downloaded-file';
17124
+ const blob = new Blob([buffer]);
17125
+ const file = new File([blob], filename);
17126
+ fileStreams.push(file);
17127
+ }
17128
+ else {
17129
+ // Assume it's a local file path
17130
+ // Note: This will work in Node.js environment
17131
+ // For browser environments, this would need different handling
17132
+ const fs = await import('fs');
17133
+ const fileStream = fs.createReadStream(source);
17134
+ fileStreams.push(fileStream);
17135
+ }
17136
+ }
17137
+ catch (error) {
17138
+ console.error(`Error processing knowledge source ${source}:`, error);
17139
+ }
17140
+ }
17141
+ // Batch upload files to the vector store
17142
+ if (fileStreams.length > 0) {
17143
+ try {
17144
+ await client.beta.vectorStores.fileBatches.uploadAndPoll(vectorStoreId, {
17145
+ files: fileStreams,
17146
+ });
17147
+ if (this.options.isVerbose) {
17148
+ console.info(`āœ… Uploaded ${fileStreams.length} files to vector store`);
17149
+ }
17150
+ }
17151
+ catch (error) {
17152
+ console.error('Error uploading files to vector store:', error);
17153
+ }
17154
+ }
17155
+ }
17156
+ const assistantUpdate = {
17157
+ name,
17158
+ instructions,
17159
+ tools: [/* TODO: [🧠] Maybe add { type: 'code_interpreter' }, */ { type: 'file_search' }],
17160
+ };
17161
+ if (vectorStoreId) {
17162
+ assistantUpdate.tool_resources = {
17163
+ file_search: {
17164
+ vector_store_ids: [vectorStoreId],
17165
+ },
17166
+ };
17167
+ }
17168
+ const assistant = await client.beta.assistants.update(assistantId, assistantUpdate);
17169
+ if (this.options.isVerbose) {
17170
+ console.log(`āœ… Assistant updated: ${assistant.id}`);
17171
+ }
16773
17172
  return new OpenAiAssistantExecutionTools({
16774
17173
  ...this.options,
16775
17174
  isCreatingNewAssistantsAllowed: false,
@@ -16808,11 +17207,12 @@
16808
17207
  * Execution Tools for calling LLM models with a predefined agent "soul"
16809
17208
  * This wraps underlying LLM execution tools and applies agent-specific system prompts and requirements
16810
17209
  *
16811
- * !!! Note: [šŸ¦–] There are several different things in Promptbook:
17210
+ * Note: [šŸ¦–] There are several different things in Promptbook:
16812
17211
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
16813
17212
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
16814
17213
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
16815
17214
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
17215
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
16816
17216
  *
16817
17217
  * @public exported from `@promptbook/core`
16818
17218
  */
@@ -16906,9 +17306,12 @@
16906
17306
  * Calls the chat model with agent-specific system prompt and requirements
16907
17307
  */
16908
17308
  async callChatModel(prompt) {
16909
- if (!this.options.llmTools.callChatModel) {
16910
- throw new Error('Underlying LLM execution tools do not support chat model calls');
16911
- }
17309
+ return this.callChatModelStream(prompt, () => { });
17310
+ }
17311
+ /**
17312
+ * Calls the chat model with agent-specific system prompt and requirements with streaming
17313
+ */
17314
+ async callChatModelStream(prompt, onProgress) {
16912
17315
  // Ensure we're working with a chat prompt
16913
17316
  if (prompt.modelRequirements.modelVariant !== 'CHAT') {
16914
17317
  throw new Error('AgentLlmExecutionTools only supports chat prompts');
@@ -16917,27 +17320,58 @@
16917
17320
  const chatPrompt = prompt;
16918
17321
  let underlyingLlmResult;
16919
17322
  if (OpenAiAssistantExecutionTools.isOpenAiAssistantExecutionTools(this.options.llmTools)) {
16920
- if (this.options.isVerbose) {
16921
- console.log(`Creating new OpenAI Assistant for agent ${this.title}...`);
17323
+ const requirementsHash = cryptoJs.SHA256(JSON.stringify(modelRequirements)).toString();
17324
+ const cached = AgentLlmExecutionTools.assistantCache.get(this.title);
17325
+ let assistant;
17326
+ if (cached) {
17327
+ if (cached.requirementsHash === requirementsHash) {
17328
+ if (this.options.isVerbose) {
17329
+ console.log(`1ļøāƒ£ Using cached OpenAI Assistant for agent ${this.title}...`);
17330
+ }
17331
+ assistant = this.options.llmTools.getAssistant(cached.assistantId);
17332
+ }
17333
+ else {
17334
+ if (this.options.isVerbose) {
17335
+ console.log(`1ļøāƒ£ Updating OpenAI Assistant for agent ${this.title}...`);
17336
+ }
17337
+ assistant = await this.options.llmTools.updateAssistant({
17338
+ assistantId: cached.assistantId,
17339
+ name: this.title,
17340
+ instructions: modelRequirements.systemMessage,
17341
+ knowledgeSources: modelRequirements.knowledgeSources,
17342
+ });
17343
+ AgentLlmExecutionTools.assistantCache.set(this.title, {
17344
+ assistantId: assistant.assistantId,
17345
+ requirementsHash,
17346
+ });
17347
+ }
16922
17348
  }
16923
- // <- TODO: !!! Check also `isCreatingNewAssistantsAllowed` and warn about it
16924
- const assistant = await this.options.llmTools.createNewAssistant({
16925
- name: this.title,
16926
- instructions: modelRequirements.systemMessage,
16927
- knowledgeSources: modelRequirements.knowledgeSources,
16928
- /*
16929
- !!!
16930
- metadata: {
16931
- agentModelName: this.modelName,
17349
+ else {
17350
+ if (this.options.isVerbose) {
17351
+ console.log(`1ļøāƒ£ Creating new OpenAI Assistant for agent ${this.title}...`);
16932
17352
  }
16933
- */
16934
- });
16935
- // <- TODO: !!! Cache the assistant in prepareCache
16936
- underlyingLlmResult = await assistant.callChatModel(chatPrompt);
17353
+ // <- TODO: [šŸ±ā€šŸš€] Check also `isCreatingNewAssistantsAllowed` and warn about it
17354
+ assistant = await this.options.llmTools.createNewAssistant({
17355
+ name: this.title,
17356
+ instructions: modelRequirements.systemMessage,
17357
+ knowledgeSources: modelRequirements.knowledgeSources,
17358
+ /*
17359
+ !!!
17360
+ metadata: {
17361
+ agentModelName: this.modelName,
17362
+ }
17363
+ */
17364
+ });
17365
+ AgentLlmExecutionTools.assistantCache.set(this.title, {
17366
+ assistantId: assistant.assistantId,
17367
+ requirementsHash,
17368
+ });
17369
+ }
17370
+ underlyingLlmResult = await assistant.callChatModelStream(chatPrompt, onProgress);
16937
17371
  }
16938
17372
  else {
16939
17373
  if (this.options.isVerbose) {
16940
- console.log(`Creating Assistant ${this.title} on generic LLM execution tools...`);
17374
+ console.log(`2ļøāƒ£ Creating Assistant ${this.title} on generic LLM execution tools...`);
16941
17375
  }
16942
17376
  // Create modified chat prompt with agent system message
16943
17377
  const modifiedChatPrompt = {
@@ -16952,7 +17386,16 @@
16952
17386
  : ''),
16953
17387
  },
16954
17388
  };
16955
- underlyingLlmResult = await this.options.llmTools.callChatModel(modifiedChatPrompt);
17389
+ if (this.options.llmTools.callChatModelStream) {
17390
+ underlyingLlmResult = await this.options.llmTools.callChatModelStream(modifiedChatPrompt, onProgress);
17391
+ }
17392
+ else if (this.options.llmTools.callChatModel) {
17393
+ underlyingLlmResult = await this.options.llmTools.callChatModel(modifiedChatPrompt);
17394
+ onProgress(underlyingLlmResult);
17395
+ }
17396
+ else {
17397
+ throw new Error('Underlying LLM execution tools do not support chat model calls');
17398
+ }
16956
17399
  }
16957
17400
  let content = underlyingLlmResult.content;
16958
17401
  // Note: Cleanup the AI artifacts from the content
@@ -16967,6 +17410,10 @@
16967
17410
  return agentResult;
16968
17411
  }
16969
17412
  }
17413
+ /**
17414
+ * Cache of OpenAI assistants to avoid creating duplicates
17415
+ */
17416
+ AgentLlmExecutionTools.assistantCache = new Map();
16970
17417
  /**
16971
17418
  * TODO: [šŸš] Implement Destroyable pattern to free resources
16972
17419
  * TODO: [🧠] Adding parameter substitution support (here or should be responsibility of the underlying LLM Tools)
@@ -16975,15 +17422,28 @@
16975
17422
  /**
16976
17423
  * Represents one AI Agent
16977
17424
  *
16978
- * !!! Note: [šŸ¦–] There are several different things in Promptbook:
17425
+ * Note: [šŸ¦–] There are several different things in Promptbook:
16979
17426
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
16980
17427
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
16981
17428
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
16982
17429
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
17430
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
16983
17431
  *
16984
17432
  * @public exported from `@promptbook/core`
16985
17433
  */
16986
17434
  class Agent extends AgentLlmExecutionTools {
17435
+ /**
17436
+ * Name of the agent
17437
+ */
17438
+ get agentName() {
17439
+ return this._agentName || createDefaultAgentName(this.agentSource.value);
17440
+ }
17441
+ /**
17442
+ * Computed hash of the agent source for integrity verification
17443
+ */
17444
+ get agentHash() {
17445
+ return computeAgentHash(this.agentSource.value);
17446
+ }
16987
17447
  /**
16988
17448
  * Not used in Agent, always returns empty array
16989
17449
  */
@@ -16997,34 +17457,40 @@
16997
17457
  super({
16998
17458
  isVerbose: options.isVerbose,
16999
17459
  llmTools: getSingleLlmExecutionTools(options.executionTools.llm),
17000
- agentSource: agentSource.value, // <- TODO: !!!! Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
17460
+ agentSource: agentSource.value, // <- TODO: [šŸ±ā€šŸš€] Allow to pass BehaviorSubject<string_book> OR refresh llmExecutionTools.callChat on agentSource change
17001
17461
  });
17002
- /**
17003
- * Name of the agent
17004
- */
17005
- this.agentName = null;
17462
+ this._agentName = undefined;
17006
17463
  /**
17007
17464
  * Description of the agent
17008
17465
  */
17009
17466
  this.personaDescription = null;
17467
+ /**
17468
+ * The initial message shown to the user when the chat starts
17469
+ */
17470
+ this.initialMessage = null;
17471
+ /**
17472
+ * Links found in the agent source
17473
+ */
17474
+ this.links = [];
17010
17475
  /**
17011
17476
  * Metadata like image or color
17012
17477
  */
17013
17478
  this.meta = {};
17014
- // TODO: !!!! Add `Agent` simple "mocked" learning by appending to agent source
17015
- // TODO: !!!! Add `Agent` learning by promptbookAgent
17479
+ // TODO: [šŸ±ā€šŸš€] Add `Agent` simple "mocked" learning by appending to agent source
17480
+ // TODO: [šŸ±ā€šŸš€] Add `Agent` learning by promptbookAgent
17016
17481
  this.agentSource = agentSource;
17017
17482
  this.agentSource.subscribe((source) => {
17018
- const { agentName, personaDescription, meta } = parseAgentSource(source);
17019
- this.agentName = agentName;
17483
+ const { agentName, personaDescription, initialMessage, links, meta } = parseAgentSource(source);
17484
+ this._agentName = agentName;
17020
17485
  this.personaDescription = personaDescription;
17486
+ this.initialMessage = initialMessage;
17487
+ this.links = links;
17021
17488
  this.meta = { ...this.meta, ...meta };
17022
17489
  });
17023
17490
  }
17024
17491
  }
17025
17492
  /**
17026
17493
  * TODO: [🧠][😰]Agent is not working with the parameters, should it be?
17027
- * TODO: !!! Agent on remote server
17028
17494
  */
17029
17495
 
17030
17496
  /**
@@ -17090,24 +17556,24 @@
17090
17556
  /**
17091
17557
  * Represents one AI Agent
17092
17558
  *
17093
- * !!! Note: [šŸ¦–] There are several different things in Promptbook:
17559
+ * Note: [šŸ¦–] There are several different things in Promptbook:
17094
17560
  * - `Agent` - which represents an AI Agent with its source, memories, actions, etc. Agent is a higher-level abstraction which is internally using:
17095
- * !!!! `RemoteAgent`
17096
17561
  * - `LlmExecutionTools` - which wraps one or more LLM models and provides an interface to execute them
17097
17562
  * - `AgentLlmExecutionTools` - which is a specific implementation of `LlmExecutionTools` that wraps another LlmExecutionTools and applies agent-specific system prompts and requirements
17098
17563
  * - `OpenAiAssistantExecutionTools` - which is a specific implementation of `LlmExecutionTools` for OpenAI models with assistant capabilities, recommended for usage in `Agent` or `AgentLlmExecutionTools`
17564
+ * - `RemoteAgent` - which is an `Agent` that connects to a Promptbook Agents Server
17099
17565
  *
17100
17566
  * @public exported from `@promptbook/core`
17101
17567
  */
17102
17568
  class RemoteAgent extends Agent {
17103
17569
  static async connect(options) {
17104
- console.log('!!!!!', `${options.agentUrl}/api/book`);
17570
+ console.log('[šŸ±ā€šŸš€]', `${options.agentUrl}/api/book`);
17105
17571
  const bookResponse = await fetch(`${options.agentUrl}/api/book`);
17106
- // <- TODO: !!!! What about closed-source agents?
17107
- // <- TODO: !!!! Maybe use promptbookFetch
17572
+ // <- TODO: [šŸ±ā€šŸš€] What about closed-source agents?
17573
+ // <- TODO: [šŸ±ā€šŸš€] Maybe use promptbookFetch
17108
17574
  const agentSourceValue = (await bookResponse.text());
17109
17575
  const agentSource = new rxjs.BehaviorSubject(agentSourceValue);
17110
- // <- TODO: !!!!!! Support updating
17576
+ // <- TODO: [šŸ±ā€šŸš€] Support updating and self-updating
17111
17577
  return new RemoteAgent({
17112
17578
  ...options,
17113
17579
  executionTools: {
@@ -17134,13 +17600,29 @@
17134
17600
  * Calls the agent on agents remote server
17135
17601
  */
17136
17602
  async callChatModel(prompt) {
17603
+ return this.callChatModelStream(prompt, () => { });
17604
+ }
17605
+ /**
17606
+ * Calls the agent on agents remote server with streaming
17607
+ */
17608
+ async callChatModelStream(prompt, onProgress) {
17137
17609
  // Ensure we're working with a chat prompt
17138
17610
  if (prompt.modelRequirements.modelVariant !== 'CHAT') {
17139
17611
  throw new Error('Agents only supports chat prompts');
17140
17612
  }
17141
- const bookResponse = await fetch(`${this.agentUrl}/api/chat?message=${encodeURIComponent(prompt.content)}`);
17142
- // <- TODO: !!!! What about closed-source agents?
17143
- // <- TODO: !!!! Maybe use promptbookFetch
17613
+ const chatPrompt = prompt;
17614
+ const bookResponse = await fetch(`${this.agentUrl}/api/chat`, {
17615
+ method: 'POST',
17616
+ headers: {
17617
+ 'Content-Type': 'application/json',
17618
+ },
17619
+ body: JSON.stringify({
17620
+ message: prompt.content,
17621
+ thread: chatPrompt.thread,
17622
+ }),
17623
+ });
17624
+ // <- TODO: [šŸ±ā€šŸš€] What about closed-source agents?
17625
+ // <- TODO: [šŸ±ā€šŸš€] Maybe use promptbookFetch
17144
17626
  let content = '';
17145
17627
  if (!bookResponse.body) {
17146
17628
  content = await bookResponse.text();
@@ -17159,16 +17641,37 @@
17159
17641
  const textChunk = decoder.decode(value, { stream: true });
17160
17642
  // console.debug('RemoteAgent chunk:', textChunk);
17161
17643
  content += textChunk;
17644
+ onProgress({
17645
+ content,
17646
+ modelName: this.modelName,
17647
+ timing: {},
17648
+ usage: {},
17649
+ rawPromptContent: {},
17650
+ rawRequest: {},
17651
+ rawResponse: {},
17652
+ });
17162
17653
  }
17163
17654
  }
17164
17655
  // Flush any remaining decoder internal state
17165
- content += decoder.decode();
17656
+ const lastChunk = decoder.decode();
17657
+ if (lastChunk) {
17658
+ content += lastChunk;
17659
+ onProgress({
17660
+ content: lastChunk,
17661
+ modelName: this.modelName,
17662
+ timing: {},
17663
+ usage: {},
17664
+ rawPromptContent: {},
17665
+ rawRequest: {},
17666
+ rawResponse: {},
17667
+ });
17668
+ }
17166
17669
  }
17167
17670
  finally {
17168
17671
  reader.releaseLock();
17169
17672
  }
17170
17673
  }
17171
- // <- TODO: !!!!!!!! Transfer metadata
17674
+ // <- TODO: [šŸ±ā€šŸš€] Transfer metadata
17172
17675
  const agentResult = {
17173
17676
  content,
17174
17677
  modelName: this.modelName,
@@ -17177,7 +17680,7 @@
17177
17680
  rawPromptContent: {},
17178
17681
  rawRequest: {},
17179
17682
  rawResponse: {},
17180
- // <- TODO: !!!!!!!! Transfer and proxy the metadata
17683
+ // <- TODO: [šŸ±ā€šŸš€] Transfer and proxy the metadata
17181
17684
  };
17182
17685
  return agentResult;
17183
17686
  }
@@ -17308,24 +17811,6 @@
17308
17811
  * Note: [šŸ’ž] Ignore a discrepancy between file name and entity name
17309
17812
  */
17310
17813
 
17311
- /**
17312
- * Detects if the code is running in jest environment
17313
- *
17314
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
17315
- *
17316
- * @public exported from `@promptbook/utils`
17317
- */
17318
- const $isRunningInJest = new Function(`
17319
- try {
17320
- return process.env.JEST_WORKER_ID !== undefined;
17321
- } catch (e) {
17322
- return false;
17323
- }
17324
- `);
17325
- /**
17326
- * TODO: [šŸŽŗ]
17327
- */
17328
-
17329
17814
  /**
17330
17815
  * Registration of LLM provider metadata
17331
17816
  *
@@ -17678,61 +18163,6 @@
17678
18163
  * TODO: [🧠][🈓] Where is the best location for this file
17679
18164
  */
17680
18165
 
17681
- /**
17682
- * Tag function for notating a prompt as template literal
17683
- *
17684
- * Note: There are 3 similar functions:
17685
- * 1) `prompt` for notating single prompt exported from `@promptbook/utils`
17686
- * 2) `promptTemplate` alias for `prompt`
17687
- * 3) `book` for notating and validating entire books exported from `@promptbook/utils`
17688
- *
17689
- * @param strings
17690
- * @param values
17691
- * @returns the prompt string
17692
- * @public exported from `@promptbook/utils`
17693
- */
17694
- function prompt(strings, ...values) {
17695
- if (values.length === 0) {
17696
- return spaceTrim__default["default"](strings.join(''));
17697
- }
17698
- const stringsWithHiddenParameters = strings.map((stringsItem) =>
17699
- // TODO: [0] DRY
17700
- stringsItem.split('{').join(`${REPLACING_NONCE}beginbracket`).split('}').join(`${REPLACING_NONCE}endbracket`));
17701
- const placeholderParameterNames = values.map((value, i) => `${REPLACING_NONCE}${i}`);
17702
- const parameters = Object.fromEntries(values.map((value, i) => [placeholderParameterNames[i], value]));
17703
- // Combine strings and values
17704
- let pipelineString = stringsWithHiddenParameters.reduce((result, stringsItem, i) => placeholderParameterNames[i] === undefined
17705
- ? `${result}${stringsItem}`
17706
- : `${result}${stringsItem}{${placeholderParameterNames[i]}}`, '');
17707
- pipelineString = spaceTrim__default["default"](pipelineString);
17708
- try {
17709
- pipelineString = templateParameters(pipelineString, parameters);
17710
- }
17711
- catch (error) {
17712
- if (!(error instanceof PipelineExecutionError)) {
17713
- throw error;
17714
- }
17715
- console.error({ pipelineString, parameters, placeholderParameterNames, error });
17716
- throw new UnexpectedError(spaceTrim__default["default"]((block) => `
17717
- Internal error in prompt template literal
17718
-
17719
- ${block(JSON.stringify({ strings, values }, null, 4))}}
17720
-
17721
- `));
17722
- }
17723
- // TODO: [0] DRY
17724
- pipelineString = pipelineString
17725
- .split(`${REPLACING_NONCE}beginbracket`)
17726
- .join('{')
17727
- .split(`${REPLACING_NONCE}endbracket`)
17728
- .join('}');
17729
- return pipelineString;
17730
- }
17731
- /**
17732
- * TODO: [🧠][🈓] Where is the best location for this file
17733
- * Note: [šŸ’ž] Ignore a discrepancy between file name and entity name
17734
- */
17735
-
17736
18166
  /**
17737
18167
  * Tag function for notating a pipeline with a book\`...\ notation as template literal
17738
18168
  *
@@ -18268,7 +18698,7 @@
18268
18698
  });
18269
18699
 
18270
18700
  const answer = response.choices[0].message.content;
18271
- console.log('\\n🧠 ${agentName}:', answer, '\\n');
18701
+ console.log('\\n🧠 ${agentName /* <- TODO: [šŸ•›] There should be `agentFullname` not `agentName` */}:', answer, '\\n');
18272
18702
 
18273
18703
  chatHistory.push({ role: 'assistant', content: answer });
18274
18704
  promptUser();
@@ -18287,7 +18717,7 @@
18287
18717
 
18288
18718
  (async () => {
18289
18719
  await setupKnowledge();
18290
- console.log("šŸ¤– Chat with ${agentName} (type 'exit' to quit)\\n");
18720
+ console.log("šŸ¤– Chat with ${agentName /* <- TODO: [šŸ•›] There should be `agentFullname` not `agentName` */} (type 'exit' to quit)\\n");
18291
18721
  promptUser();
18292
18722
  })();
18293
18723
  `);
@@ -18334,7 +18764,7 @@
18334
18764
  });
18335
18765
 
18336
18766
  const answer = response.choices[0].message.content;
18337
- console.log('\\n🧠 ${agentName}:', answer, '\\n');
18767
+ console.log('\\n🧠 ${agentName /* <- TODO: [šŸ•›] There should be `agentFullname` not `agentName` */}:', answer, '\\n');
18338
18768
 
18339
18769
  chatHistory.push({ role: 'assistant', content: answer });
18340
18770
  promptUser();
@@ -18351,7 +18781,7 @@
18351
18781
  });
18352
18782
  }
18353
18783
 
18354
- console.log("šŸ¤– Chat with ${agentName} (type 'exit' to quit)\\n");
18784
+ console.log("šŸ¤– Chat with ${agentName /* <- TODO: [šŸ•›] There should be `agentFullname` not `agentName` */} (type 'exit' to quit)\\n");
18355
18785
  promptUser();
18356
18786
 
18357
18787
  `);
@@ -18359,25 +18789,6 @@
18359
18789
  },
18360
18790
  };
18361
18791
 
18362
- /**
18363
- * Returns information about the current runtime environment
18364
- *
18365
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environments
18366
- *
18367
- * @public exported from `@promptbook/utils`
18368
- */
18369
- function $detectRuntimeEnvironment() {
18370
- return {
18371
- isRunningInBrowser: $isRunningInBrowser(),
18372
- isRunningInJest: $isRunningInJest(),
18373
- isRunningInNode: $isRunningInNode(),
18374
- isRunningInWebWorker: $isRunningInWebWorker(),
18375
- };
18376
- }
18377
- /**
18378
- * TODO: [šŸŽŗ] Also detect and report node version here
18379
- */
18380
-
18381
18792
  /**
18382
18793
  * Provide information about Promptbook, engine version, book language version, servers, ...
18383
18794
  *
@@ -18556,7 +18967,7 @@
18556
18967
  const agentSource = validateBook(spaceTrim__default["default"]((block) => `
18557
18968
  ${agentName}
18558
18969
 
18559
- META COLOR ${color || '#3498db' /* <- TODO: !!!! Best default color */}
18970
+ META COLOR ${color || '#3498db' /* <- TODO: [🧠] [šŸ±ā€šŸš€] Best default color */}
18560
18971
  PERSONA ${block(personaDescription)}
18561
18972
  `));
18562
18973
  return agentSource;
@@ -18699,12 +19110,14 @@
18699
19110
  exports.book = book;
18700
19111
  exports.cacheLlmTools = cacheLlmTools;
18701
19112
  exports.compilePipeline = compilePipeline;
19113
+ exports.computeAgentHash = computeAgentHash;
18702
19114
  exports.computeCosineSimilarity = computeCosineSimilarity;
18703
19115
  exports.countUsage = countUsage;
18704
19116
  exports.createAgentLlmExecutionTools = createAgentLlmExecutionTools;
18705
19117
  exports.createAgentModelRequirements = createAgentModelRequirements;
18706
19118
  exports.createAgentModelRequirementsWithCommitments = createAgentModelRequirementsWithCommitments;
18707
19119
  exports.createBasicAgentModelRequirements = createBasicAgentModelRequirements;
19120
+ exports.createDefaultAgentName = createDefaultAgentName;
18708
19121
  exports.createEmptyAgentModelRequirements = createEmptyAgentModelRequirements;
18709
19122
  exports.createLlmToolsFromConfiguration = createLlmToolsFromConfiguration;
18710
19123
  exports.createPipelineCollectionFromJson = createPipelineCollectionFromJson;
@@ -18734,6 +19147,7 @@
18734
19147
  exports.limitTotalUsage = limitTotalUsage;
18735
19148
  exports.makeKnowledgeSourceHandler = makeKnowledgeSourceHandler;
18736
19149
  exports.migratePipeline = migratePipeline;
19150
+ exports.normalizeAgentName = normalizeAgentName;
18737
19151
  exports.padBook = padBook;
18738
19152
  exports.parseAgentSource = parseAgentSource;
18739
19153
  exports.parseParameters = parseParameters;