@promptbook/node 0.105.0-21 → 0.105.0-23

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 (43) hide show
  1. package/esm/index.es.js +2487 -1499
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/typings/src/_packages/browser.index.d.ts +2 -0
  4. package/esm/typings/src/_packages/core.index.d.ts +9 -13
  5. package/esm/typings/src/_packages/node.index.d.ts +2 -0
  6. package/esm/typings/src/_packages/types.index.d.ts +12 -2
  7. package/esm/typings/src/book-2.0/agent-source/AgentBasicInformation.d.ts +1 -1
  8. package/esm/typings/src/book-components/Chat/AgentChip/AgentChip.d.ts +67 -0
  9. package/esm/typings/src/book-components/Chat/AgentChip/index.d.ts +2 -0
  10. package/esm/typings/src/book-components/Chat/Chat/ChatMessageItem.d.ts +15 -0
  11. package/esm/typings/src/book-components/Chat/Chat/ChatProps.d.ts +10 -0
  12. package/esm/typings/src/book-components/Chat/LlmChat/FriendlyErrorMessage.d.ts +20 -0
  13. package/esm/typings/src/book-components/Chat/LlmChat/LlmChatProps.d.ts +8 -0
  14. package/esm/typings/src/book-components/Chat/SourceChip/SourceChip.d.ts +35 -0
  15. package/esm/typings/src/book-components/Chat/SourceChip/index.d.ts +2 -0
  16. package/esm/typings/src/book-components/Chat/types/ChatMessage.d.ts +21 -0
  17. package/esm/typings/src/book-components/Chat/utils/getToolCallChipletText.d.ts +21 -0
  18. package/esm/typings/src/book-components/Chat/utils/parseCitationsFromContent.d.ts +53 -0
  19. package/esm/typings/src/commitments/TEMPLATE/TEMPLATE.d.ts +44 -0
  20. package/esm/typings/src/commitments/TEMPLATE/TEMPLATE.test.d.ts +1 -0
  21. package/esm/typings/src/commitments/USE_BROWSER/USE_BROWSER.d.ts +16 -2
  22. package/esm/typings/src/commitments/USE_BROWSER/fetchUrlContent.d.ts +22 -0
  23. package/esm/typings/src/commitments/USE_BROWSER/fetchUrlContentViaBrowser.d.ts +13 -0
  24. package/esm/typings/src/commitments/USE_EMAIL/USE_EMAIL.d.ts +47 -0
  25. package/esm/typings/src/commitments/_common/getAllCommitmentDefinitions.d.ts +8 -0
  26. package/esm/typings/src/commitments/_common/getAllCommitmentTypes.d.ts +8 -0
  27. package/esm/typings/src/commitments/_common/getAllCommitmentsToolFunctionsForBrowser.d.ts +10 -0
  28. package/esm/typings/src/commitments/_common/getAllCommitmentsToolFunctionsForNode.d.ts +14 -0
  29. package/esm/typings/src/commitments/_common/getAllCommitmentsToolTitles.d.ts +7 -0
  30. package/esm/typings/src/commitments/_common/getCommitmentDefinition.d.ts +10 -0
  31. package/esm/typings/src/commitments/_common/getGroupedCommitmentDefinitions.d.ts +17 -0
  32. package/esm/typings/src/commitments/_common/isCommitmentSupported.d.ts +9 -0
  33. package/esm/typings/src/commitments/index.d.ts +3 -64
  34. package/esm/typings/src/executables/$provideExecutablesForNode.d.ts +1 -0
  35. package/esm/typings/src/execution/utils/$provideExecutionToolsForNode.d.ts +1 -0
  36. package/esm/typings/src/scrapers/_common/register/$provideFilesystemForNode.d.ts +1 -0
  37. package/esm/typings/src/scrapers/_common/register/$provideScrapersForNode.d.ts +1 -0
  38. package/esm/typings/src/scrapers/_common/register/$provideScriptingForNode.d.ts +1 -0
  39. package/esm/typings/src/version.d.ts +1 -1
  40. package/esm/typings/src/wizard/wizard.d.ts +1 -4
  41. package/package.json +5 -2
  42. package/umd/index.umd.js +2487 -1501
  43. package/umd/index.umd.js.map +1 -1
package/esm/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import colors from 'colors';
2
- import { stat, access, constants, readFile, writeFile, readdir, mkdir, watch, unlink } from 'fs/promises';
2
+ import { mkdir, rm, stat, access, constants, readFile, writeFile, readdir, watch, unlink } from 'fs/promises';
3
3
  import { basename, join, dirname, isAbsolute, relative } from 'path';
4
4
  import spaceTrim$2, { spaceTrim as spaceTrim$1 } from 'spacetrim';
5
5
  import JSZip from 'jszip';
@@ -11,9 +11,12 @@ import hexEncoder from 'crypto-js/enc-hex';
11
11
  import sha256 from 'crypto-js/sha256';
12
12
  import { SHA256 } from 'crypto-js';
13
13
  import { lookup, extension } from 'mime-types';
14
+ import { Readability } from '@mozilla/readability';
15
+ import { JSDOM } from 'jsdom';
16
+ import { Converter } from 'showdown';
17
+ import moment from 'moment';
14
18
  import { spawn } from 'child_process';
15
19
  import * as dotenv from 'dotenv';
16
- import moment from 'moment';
17
20
 
18
21
  // ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
19
22
  /**
@@ -29,7 +32,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
29
32
  * @generated
30
33
  * @see https://github.com/webgptorg/promptbook
31
34
  */
32
- const PROMPTBOOK_ENGINE_VERSION = '0.105.0-21';
35
+ const PROMPTBOOK_ENGINE_VERSION = '0.105.0-23';
33
36
  /**
34
37
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
35
38
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -10857,15 +10860,15 @@ async function compilePipeline(pipelineString, tools, options) {
10857
10860
  */
10858
10861
 
10859
10862
  /**
10860
- * Detects if the code is running in a Node.js environment
10863
+ * Detects if the code is running in a browser environment in main thread (Not in a web worker)
10861
10864
  *
10862
10865
  * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
10863
10866
  *
10864
10867
  * @public exported from `@promptbook/utils`
10865
10868
  */
10866
- function $isRunningInNode() {
10869
+ function $isRunningInBrowser() {
10867
10870
  try {
10868
- return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
10871
+ return typeof window !== 'undefined' && typeof window.document !== 'undefined';
10869
10872
  }
10870
10873
  catch (e) {
10871
10874
  return false;
@@ -10876,1535 +10879,1274 @@ function $isRunningInNode() {
10876
10879
  */
10877
10880
 
10878
10881
  /**
10879
- * Normalize options for `execCommand` and `execCommands`
10882
+ * Detects if the code is running in a Node.js environment
10880
10883
  *
10881
- * Note: `$` is used to indicate that this function behaves differently according to `process.platform`
10884
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
10882
10885
  *
10883
- * @private internal utility of `execCommand` and `execCommands`
10886
+ * @public exported from `@promptbook/utils`
10884
10887
  */
10885
- function $execCommandNormalizeOptions(options) {
10886
- var _a, _b, _c, _d;
10887
- let command;
10888
- let cwd;
10889
- let crashOnError;
10890
- let args = [];
10891
- let timeout;
10892
- let isVerbose;
10893
- let env;
10894
- if (typeof options === 'string') {
10895
- // TODO: [1] DRY default values
10896
- command = options;
10897
- cwd = process.cwd();
10898
- crashOnError = true;
10899
- timeout = Infinity; // <- TODO: [⏳]
10900
- isVerbose = DEFAULT_IS_VERBOSE;
10901
- env = undefined;
10902
- }
10903
- else {
10904
- /*
10905
- TODO:
10906
- if ((options as any).commands !== undefined) {
10907
- commands = (options as any).commands;
10908
- } else {
10909
- commands = [(options as any).command];
10910
- }
10911
- */
10912
- // TODO: [1] DRY default values
10913
- command = options.command;
10914
- cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
10915
- crashOnError = (_b = options.crashOnError) !== null && _b !== void 0 ? _b : true;
10916
- timeout = (_c = options.timeout) !== null && _c !== void 0 ? _c : Infinity;
10917
- isVerbose = (_d = options.isVerbose) !== null && _d !== void 0 ? _d : DEFAULT_IS_VERBOSE;
10918
- env = options.env;
10919
- }
10920
- // TODO: /(-[a-zA-Z0-9-]+\s+[^\s]*)|[^\s]*/g
10921
- const _ = Array.from(command.matchAll(/(".*")|([^\s]*)/g))
10922
- .map(([match]) => match)
10923
- .filter((arg) => arg !== '');
10924
- if (_.length > 1) {
10925
- [command, ...args] = _;
10926
- }
10927
- if (options.args) {
10928
- args = [...args, ...options.args];
10929
- }
10930
- let humanReadableCommand = !['npx', 'npm'].includes(command) ? command : args[0];
10931
- if (['ts-node'].includes(humanReadableCommand)) {
10932
- humanReadableCommand += ` ${args[1]}`;
10933
- }
10934
- if (/^win/.test(process.platform) && ['npm', 'npx'].includes(command)) {
10935
- command = `${command}.cmd`;
10888
+ function $isRunningInNode() {
10889
+ try {
10890
+ return typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
10936
10891
  }
10937
- return { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose, env };
10938
- }
10939
- // TODO: This should show type error> execCommandNormalizeOptions({ command: '', commands: [''] });
10940
-
10941
- /**
10942
- * Run one command in a shell
10943
- *
10944
- *
10945
- * Note: There are 2 similar functions in the codebase:
10946
- * - `$execCommand` which runs a single command
10947
- * - `$execCommands` which runs multiple commands
10948
- * Note: `$` is used to indicate that this function is not a pure function - it runs a command in a shell
10949
- *
10950
- * @public exported from `@promptbook/node`
10951
- */
10952
- function $execCommand(options) {
10953
- if (!$isRunningInNode()) {
10954
- throw new EnvironmentMismatchError('Function `$execCommand` can run only in Node environment.js');
10892
+ catch (e) {
10893
+ return false;
10955
10894
  }
10956
- return new Promise((resolve, reject) => {
10957
- // eslint-disable-next-line prefer-const
10958
- const { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose = DEFAULT_IS_VERBOSE, env, } = $execCommandNormalizeOptions(options);
10959
- if (timeout !== Infinity) {
10960
- // TODO: In waitasecond forTime(Infinity) should be equivalent to forEver()
10961
- forTime(timeout).then(() => {
10962
- if (crashOnError) {
10963
- reject(new Error(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms`));
10964
- }
10965
- else {
10966
- console.warn(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms but continues running`);
10967
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
10968
- resolve('Command exceeded time limit');
10969
- }
10970
- });
10971
- }
10972
- if (isVerbose) {
10973
- console.info(colors.yellow(cwd) + ' ' + colors.green(command) + ' ' + colors.blue(args.join(' ')));
10974
- }
10975
- try {
10976
- const commandProcess = spawn(command, args, {
10977
- cwd,
10978
- shell: true,
10979
- env: env ? { ...process.env, ...env } : process.env,
10980
- });
10981
- if (isVerbose) {
10982
- commandProcess.on('message', (message) => {
10983
- console.info({ message });
10984
- });
10985
- }
10986
- const output = [];
10987
- commandProcess.stdout.on('data', (stdout) => {
10988
- output.push(stdout.toString());
10989
- if (isVerbose) {
10990
- console.info(stdout.toString());
10991
- }
10992
- });
10993
- commandProcess.stderr.on('data', (stderr) => {
10994
- output.push(stderr.toString());
10995
- if (isVerbose && stderr.toString().trim()) {
10996
- console.warn(stderr.toString());
10997
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
10998
- }
10999
- });
11000
- const finishWithCode = (code) => {
11001
- if (code !== 0) {
11002
- if (crashOnError) {
11003
- reject(new Error(output.join('\n').trim() ||
11004
- `Command "${humanReadableCommand}" exited with code ${code}`));
11005
- }
11006
- else {
11007
- if (isVerbose) {
11008
- console.warn(`Command "${humanReadableCommand}" exited with code ${code}`);
11009
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
11010
- }
11011
- resolve(spaceTrim$1(output.join('\n')));
11012
- }
11013
- }
11014
- else {
11015
- resolve(spaceTrim$1(output.join('\n')));
11016
- }
11017
- };
11018
- commandProcess.on('close', finishWithCode);
11019
- commandProcess.on('exit', finishWithCode);
11020
- commandProcess.on('disconnect', () => {
11021
- // Note: Unexpected disconnection should always result in rejection
11022
- reject(new Error(`Command "${humanReadableCommand}" disconnected`));
11023
- });
11024
- commandProcess.on('error', (error) => {
11025
- if (crashOnError) {
11026
- reject(new Error(`Command "${humanReadableCommand}" failed: \n${error.message}`));
11027
- }
11028
- else {
11029
- if (isVerbose) {
11030
- console.warn(error);
11031
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
11032
- }
11033
- resolve(spaceTrim$1(output.join('\n')));
11034
- }
11035
- });
11036
- }
11037
- catch (error) {
11038
- // Note: Unexpected error in sync code should always result in rejection
11039
- reject(error);
11040
- }
11041
- });
11042
10895
  }
11043
10896
  /**
11044
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
10897
+ * TODO: [🎺]
11045
10898
  */
11046
10899
 
11047
10900
  /**
11048
- * Attempts to locate the specified application on a Linux system using the 'which' command.
11049
- * Returns the path to the executable if found, or null otherwise.
10901
+ * Detects if the code is running in a web worker
11050
10902
  *
11051
- * @private within the repository
10903
+ * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
10904
+ *
10905
+ * @public exported from `@promptbook/utils`
11052
10906
  */
11053
- async function locateAppOnLinux({ linuxWhich, }) {
10907
+ function $isRunningInWebWorker() {
11054
10908
  try {
11055
- const result = await $execCommand({ crashOnError: true, command: `which ${linuxWhich}` });
11056
- return result.trim();
10909
+ // Note: Check for importScripts which is specific to workers
10910
+ // and not available in the main browser thread
10911
+ return (typeof self !== 'undefined' &&
10912
+ typeof self.importScripts === 'function');
11057
10913
  }
11058
- catch (error) {
11059
- assertsError(error);
11060
- return null;
10914
+ catch (e) {
10915
+ return false;
11061
10916
  }
11062
10917
  }
11063
10918
  /**
11064
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
11065
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
10919
+ * TODO: [🎺]
11066
10920
  */
11067
10921
 
11068
10922
  /**
11069
- * Provides filesystem access (for example for Node.js-based scrapers)
11070
- * Creates a standardized filesystem interface that scrapers can use for file operations.
10923
+ * Computes SHA-256 hash of the given object
11071
10924
  *
11072
- * @public exported from `@promptbook/node`
10925
+ * @public exported from `@promptbook/utils`
11073
10926
  */
11074
- function $provideFilesystemForNode(options) {
11075
- if (!$isRunningInNode()) {
11076
- throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
11077
- }
11078
- return {
11079
- stat,
11080
- access,
11081
- constants,
11082
- readFile,
11083
- writeFile,
11084
- readdir,
11085
- mkdir,
11086
- watch,
11087
- };
10927
+ function computeHash(value) {
10928
+ return SHA256(hexEncoder.parse(spaceTrim$2(valueToString(value)))).toString( /* hex */);
11088
10929
  }
11089
10930
  /**
11090
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
10931
+ * TODO: [🥬][🥬] Use this ACRY
11091
10932
  */
11092
10933
 
11093
10934
  /**
11094
- * Checks if the file is executable
10935
+ * Makes first letter of a string lowercase
11095
10936
  *
11096
- * @private within the repository
10937
+ * Note: [🔂] This function is idempotent.
10938
+ *
10939
+ * @public exported from `@promptbook/utils`
11097
10940
  */
11098
- async function isExecutable(path, fs) {
11099
- try {
11100
- await fs.access(path, fs.constants.X_OK);
11101
- return true;
11102
- }
11103
- catch (error) {
11104
- return false;
11105
- }
10941
+ function decapitalize(word) {
10942
+ return word.substring(0, 1).toLowerCase() + word.substring(1);
11106
10943
  }
11107
- /**
11108
- * Note: Not [~🟢~] because it is not directly dependent on `fs
11109
- * TODO: [🖇] What about symlinks?
11110
- */
11111
10944
 
11112
- // Note: Module `userhome` has no types available, so it is imported using `require`
11113
- // @see https://stackoverflow.com/questions/37000981/how-to-import-node-module-in-typescript-without-type-definitions
11114
- // eslint-disable-next-line @typescript-eslint/no-var-requires
11115
- const userhome = require('userhome');
11116
10945
  /**
11117
- * Attempts to locate the specified application on a macOS system by checking standard application paths and using mdfind.
11118
- * Returns the path to the executable if found, or null otherwise.
10946
+ * Parses keywords from a string
11119
10947
  *
11120
- * @private within the repository
10948
+ * @param {string} input
10949
+ * @returns {Set} of keywords without diacritics in lowercase
10950
+ * @public exported from `@promptbook/utils`
11121
10951
  */
11122
- async function locateAppOnMacOs({ macOsName, }) {
11123
- try {
11124
- const toExec = `/Contents/MacOS/${macOsName}`;
11125
- const regPath = `/Applications/${macOsName}.app` + toExec;
11126
- const altPath = userhome(regPath.slice(1));
11127
- if (await isExecutable(regPath, $provideFilesystemForNode())) {
11128
- return regPath;
11129
- }
11130
- else if (await isExecutable(altPath, $provideFilesystemForNode())) {
11131
- return altPath;
11132
- }
11133
- const result = await $execCommand({
11134
- crashOnError: true,
11135
- command: `mdfind 'kMDItemDisplayName == "${macOsName}" && kMDItemKind == Application'`,
11136
- });
11137
- return result.trim() + toExec;
11138
- }
11139
- catch (error) {
11140
- assertsError(error);
11141
- return null;
11142
- }
10952
+ function parseKeywordsFromString(input) {
10953
+ const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
10954
+ .toLowerCase()
10955
+ .split(/[^a-z0-9]+/gs)
10956
+ .filter((value) => value);
10957
+ return new Set(keywords);
11143
10958
  }
10959
+
11144
10960
  /**
11145
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
11146
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
10961
+ * Converts a name string into a URI-compatible format.
10962
+ *
10963
+ * @param name The string to be converted to a URI-compatible format.
10964
+ * @returns A URI-compatible string derived from the input name.
10965
+ * @example 'Hello World' -> 'hello-world'
10966
+ * @public exported from `@promptbook/utils`
11147
10967
  */
10968
+ function nameToUriPart(name) {
10969
+ let uriPart = name;
10970
+ uriPart = uriPart.toLowerCase();
10971
+ uriPart = removeDiacritics(uriPart);
10972
+ uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
10973
+ uriPart = uriPart.replace(/^-+/, '');
10974
+ uriPart = uriPart.replace(/-+$/, '');
10975
+ return uriPart;
10976
+ }
11148
10977
 
11149
10978
  /**
11150
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
11151
- * Returns the path to the executable if found, or null otherwise.
10979
+ * Converts a given name into URI-compatible parts.
11152
10980
  *
11153
- * @private within the repository
10981
+ * @param name The name to be converted into URI parts.
10982
+ * @returns An array of URI-compatible parts derived from the name.
10983
+ * @example 'Example Name' -> ['example', 'name']
10984
+ * @public exported from `@promptbook/utils`
11154
10985
  */
11155
- async function locateAppOnWindows({ appName, windowsSuffix, }) {
11156
- try {
11157
- const prefixes = [
11158
- process.env.LOCALAPPDATA,
11159
- join(process.env.LOCALAPPDATA || '', 'Programs'),
11160
- process.env.PROGRAMFILES,
11161
- process.env['PROGRAMFILES(X86)'],
11162
- ];
11163
- for (const prefix of prefixes) {
11164
- const path = prefix + windowsSuffix;
11165
- if (await isExecutable(path, $provideFilesystemForNode())) {
11166
- return path;
11167
- }
11168
- }
11169
- throw new Error(`Can not locate app ${appName} on Windows.`);
11170
- }
11171
- catch (error) {
11172
- assertsError(error);
11173
- return null;
11174
- }
10986
+ function nameToUriParts(name) {
10987
+ return nameToUriPart(name)
10988
+ .split('-')
10989
+ .filter((value) => value !== '');
11175
10990
  }
10991
+
11176
10992
  /**
11177
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
11178
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
10993
+ * Normalizes a given text to PascalCase format.
10994
+ *
10995
+ * Note: [🔂] This function is idempotent.
10996
+ *
10997
+ * @param text @public exported from `@promptbook/utils`
10998
+ * @returns
10999
+ * @example 'HelloWorld'
11000
+ * @example 'ILovePromptbook'
11001
+ * @public exported from `@promptbook/utils`
11179
11002
  */
11003
+ function normalizeTo_PascalCase(text) {
11004
+ return normalizeTo_camelCase(text, true);
11005
+ }
11180
11006
 
11181
11007
  /**
11182
- * Locates an application on the system
11008
+ * Take every whitespace (space, new line, tab) and replace it with a single space
11183
11009
  *
11184
- * @private within the repository
11010
+ * Note: [🔂] This function is idempotent.
11011
+ *
11012
+ * @public exported from `@promptbook/utils`
11185
11013
  */
11186
- function locateApp(options) {
11187
- if (!$isRunningInNode()) {
11188
- throw new EnvironmentMismatchError('Locating apps works only in Node.js environment');
11014
+ function normalizeWhitespaces(sentence) {
11015
+ return sentence.replace(/\s+/gs, ' ').trim();
11016
+ }
11017
+
11018
+ /**
11019
+ * Removes quotes and optional introduce text from a string
11020
+ *
11021
+ * Tip: This is very useful for post-processing of the result of the LLM model
11022
+ * Note: This function trims the text and removes whole introduce sentence if it is present
11023
+ * Note: There are two similar functions:
11024
+ * - `removeQuotes` which removes only bounding quotes
11025
+ * - `unwrapResult` which removes whole introduce sentence
11026
+ *
11027
+ * @param text optionally quoted text
11028
+ * @returns text without quotes
11029
+ * @public exported from `@promptbook/utils`
11030
+ */
11031
+ function unwrapResult(text, options) {
11032
+ const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
11033
+ let trimmedText = text;
11034
+ // Remove leading and trailing spaces and newlines
11035
+ if (isTrimmed) {
11036
+ trimmedText = spaceTrim$1(trimmedText);
11189
11037
  }
11190
- const { appName, linuxWhich, windowsSuffix, macOsName } = options;
11191
- if (process.platform === 'win32') {
11192
- if (windowsSuffix) {
11193
- return locateAppOnWindows({ appName, windowsSuffix });
11194
- }
11195
- else {
11196
- throw new Error(`${appName} is not available on Windows.`);
11038
+ let processedText = trimmedText;
11039
+ // Check for markdown code block
11040
+ const codeBlockRegex = /^```[a-z]*\n([\s\S]*?)\n```\s*$/;
11041
+ const codeBlockMatch = processedText.match(codeBlockRegex);
11042
+ if (codeBlockMatch && codeBlockMatch[1] !== undefined) {
11043
+ // Check if there's only one code block
11044
+ const codeBlockCount = (processedText.match(/```/g) || []).length / 2;
11045
+ if (codeBlockCount === 1) {
11046
+ return unwrapResult(codeBlockMatch[1], { isTrimmed: false, isIntroduceSentenceRemoved: false });
11197
11047
  }
11198
11048
  }
11199
- else if (process.platform === 'darwin') {
11200
- if (macOsName) {
11201
- return locateAppOnMacOs({ macOsName });
11049
+ if (isIntroduceSentenceRemoved) {
11050
+ const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
11051
+ if (introduceSentenceRegex.test(text)) {
11052
+ // Remove the introduce sentence and quotes by replacing it with an empty string
11053
+ processedText = processedText.replace(introduceSentenceRegex, '');
11202
11054
  }
11203
- else {
11204
- throw new Error(`${appName} is not available on macOS.`);
11055
+ processedText = spaceTrim$1(processedText);
11056
+ // Check again for code block after removing introduce sentence
11057
+ const codeBlockMatch2 = processedText.match(codeBlockRegex);
11058
+ if (codeBlockMatch2 && codeBlockMatch2[1] !== undefined) {
11059
+ const codeBlockCount = (processedText.match(/```/g) || []).length / 2;
11060
+ if (codeBlockCount === 1) {
11061
+ return unwrapResult(codeBlockMatch2[1], { isTrimmed: false, isIntroduceSentenceRemoved: false });
11062
+ }
11205
11063
  }
11206
11064
  }
11207
- else {
11208
- if (linuxWhich) {
11209
- return locateAppOnLinux({ linuxWhich });
11065
+ if (processedText.length < 3) {
11066
+ return trimmedText;
11067
+ }
11068
+ if (processedText.includes('\n')) {
11069
+ return trimmedText;
11070
+ }
11071
+ // Remove the quotes by extracting the substring without the first and last characters
11072
+ const unquotedText = processedText.slice(1, -1);
11073
+ // Check if the text starts and ends with quotes
11074
+ if ([
11075
+ ['"', '"'],
11076
+ ["'", "'"],
11077
+ ['`', '`'],
11078
+ ['*', '*'],
11079
+ ['_', '_'],
11080
+ ['„', '“'],
11081
+ ['«', '»'] /* <- QUOTES to config */,
11082
+ ].some(([startQuote, endQuote]) => {
11083
+ if (!processedText.startsWith(startQuote)) {
11084
+ return false;
11210
11085
  }
11211
- else {
11212
- throw new Error(`${appName} is not available on Linux.`);
11086
+ if (!processedText.endsWith(endQuote)) {
11087
+ return false;
11088
+ }
11089
+ if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
11090
+ return false;
11091
+ }
11092
+ if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
11093
+ return false;
11213
11094
  }
11095
+ return true;
11096
+ })) {
11097
+ return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
11098
+ }
11099
+ else {
11100
+ return processedText;
11214
11101
  }
11215
11102
  }
11216
11103
  /**
11217
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
11218
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11104
+ * TODO: [🧠] Should this also unwrap the (parenthesis)
11219
11105
  */
11220
11106
 
11107
+ // <- TODO: Auto convert to type `import { ... } from 'type-fest';`
11221
11108
  /**
11222
- * Locates the LibreOffice executable on the current system by searching platform-specific paths.
11223
- * Returns the path to the executable if found, or null otherwise.
11109
+ * Tests if the value is [🚉] serializable as JSON
11224
11110
  *
11225
- * @private within the repository
11226
- */
11227
- function locateLibreoffice() {
11228
- return locateApp({
11229
- appName: 'Libreoffice',
11230
- linuxWhich: 'libreoffice',
11231
- windowsSuffix: '\\LibreOffice\\program\\soffice.exe',
11232
- macOsName: 'LibreOffice',
11233
- });
11234
- }
11235
- /**
11236
- * TODO: [🧠][♿] Maybe export through `@promptbook/node` OR `@promptbook/legacy-documents`
11237
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11238
- */
11239
-
11240
- /**
11241
- * Locates the Pandoc executable on the current system by searching platform-specific paths.
11242
- * Returns the path to the executable if found, or null otherwise.
11111
+ * - Almost all primitives are serializable BUT:
11112
+ * - `undefined` is not serializable
11113
+ * - `NaN` is not serializable
11114
+ * - Objects and arrays are serializable if all their properties are serializable
11115
+ * - Functions are not serializable
11116
+ * - Circular references are not serializable
11117
+ * - `Date` objects are not serializable
11118
+ * - `Map` and `Set` objects are not serializable
11119
+ * - `RegExp` objects are not serializable
11120
+ * - `Error` objects are not serializable
11121
+ * - `Symbol` objects are not serializable
11122
+ * - And much more...
11243
11123
  *
11244
- * @private within the repository
11124
+ *
11125
+ * @public exported from `@promptbook/utils`
11245
11126
  */
11246
- function locatePandoc() {
11247
- return locateApp({
11248
- appName: 'Pandoc',
11249
- linuxWhich: 'pandoc',
11250
- windowsSuffix: '\\Pandoc\\pandoc.exe',
11251
- macOsName: 'Pandoc',
11252
- });
11127
+ function isSerializableAsJson(value) {
11128
+ try {
11129
+ checkSerializableAsJson({ value });
11130
+ return true;
11131
+ }
11132
+ catch (error) {
11133
+ return false;
11134
+ }
11253
11135
  }
11254
11136
  /**
11255
- * TODO: [🧠][] Maybe export through `@promptbook/node` OR `@promptbook/documents`
11256
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11137
+ * TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
11138
+ * TODO: [🧠][💺] Can be done this on type-level?
11257
11139
  */
11258
11140
 
11259
11141
  /**
11260
- * Provides paths to required executables (i.e. as Pandoc and LibreOffice) for Node.js environments.
11142
+ * Determines if the given path is a root path.
11261
11143
  *
11262
- * @public exported from `@promptbook/node`
11144
+ * Note: This does not check if the file exists only if the path is valid
11145
+ * @public exported from `@promptbook/utils`
11263
11146
  */
11264
- async function $provideExecutablesForNode(options) {
11265
- if (!$isRunningInNode()) {
11266
- throw new EnvironmentMismatchError('Function `$getScrapersForNode` works only in Node.js environment');
11147
+ function isRootPath(value) {
11148
+ if (value === '/') {
11149
+ return true;
11267
11150
  }
11268
- return {
11269
- pandocPath: (await locatePandoc()) || undefined,
11270
- libreOfficePath: (await locateLibreoffice()) || undefined,
11271
- // <- TODO: [🧠] `null` vs `undefined`
11272
- };
11151
+ if (/^[A-Z]:\\$/i.test(value)) {
11152
+ return true;
11153
+ }
11154
+ return false;
11273
11155
  }
11274
11156
  /**
11275
- * TODO: [🧠] Allow to override the executables without need to call `locatePandoc` / `locateLibreoffice` in case of provided
11276
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11157
+ * TODO: [🍏] Make for MacOS paths
11277
11158
  */
11278
11159
 
11279
11160
  /**
11280
- * Register for LLM tools metadata.
11161
+ * Tests if given string is valid agent URL
11281
11162
  *
11282
- * Note: `$` is used to indicate that this interacts with the global scope
11283
- * @singleton Only one instance of each register is created per build, but there can be more instances across different builds or environments.
11284
- * @public exported from `@promptbook/core`
11163
+ * Note: There are few similar functions:
11164
+ * - `isValidUrl` which tests any URL
11165
+ * - `isValidAgentUrl` *(this one)* which tests just agent URL
11166
+ * - `isValidPipelineUrl` which tests just pipeline URL
11167
+ *
11168
+ * @public exported from `@promptbook/utils`
11285
11169
  */
11286
- const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
11170
+ function isValidAgentUrl(url) {
11171
+ if (!isValidUrl(url)) {
11172
+ return false;
11173
+ }
11174
+ if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
11175
+ return false;
11176
+ }
11177
+ if (url.includes('#')) {
11178
+ // TODO: [🐠]
11179
+ return false;
11180
+ }
11181
+ /*
11182
+ Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
11183
+ if (isUrlOnPrivateNetwork(url)) {
11184
+ return false;
11185
+ }
11186
+ */
11187
+ return true;
11188
+ }
11287
11189
  /**
11288
- * TODO: [®] DRY Register logic
11190
+ * TODO: [🐠] Maybe more info why the URL is invalid
11289
11191
  */
11290
11192
 
11291
11193
  /**
11292
- * Determines if the given path is a root path.
11194
+ * Retrieves an intermediate source for a scraper based on the knowledge source.
11195
+ * Manages the caching and retrieval of intermediate scraper results for optimized performance.
11293
11196
  *
11294
- * Note: This does not check if the file exists only if the path is valid
11295
- * @public exported from `@promptbook/utils`
11197
+ * @private as internal utility for scrapers
11296
11198
  */
11297
- function isRootPath(value) {
11298
- if (value === '/') {
11299
- return true;
11199
+ async function getScraperIntermediateSource(source, options) {
11200
+ const { filename: sourceFilename, url } = source;
11201
+ const { rootDirname, cacheDirname, intermediateFilesStrategy, extension, isVerbose } = options;
11202
+ // TODO: [👬] DRY
11203
+ const hash = SHA256(
11204
+ // <- TODO: [🥬] Encapsulate sha256 to some private utility function
11205
+ hexEncoder.parse(sourceFilename || url || 'untitled'))
11206
+ .toString( /* hex */)
11207
+ .substring(0, 20);
11208
+ // <- TODO: [🥬] Make some system for hashes and ids of promptbook
11209
+ const semanticName = normalizeToKebabCase(titleToName((sourceFilename || url || '').split('intermediate').join(''))).substring(0, 20);
11210
+ // <- TODO: [🐱‍🐉]
11211
+ const pieces = ['intermediate', semanticName, hash].filter((piece) => piece !== '');
11212
+ const name = pieces.join('-').split('--').join('-');
11213
+ const cacheFilename = join(process.cwd(), cacheDirname, ...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), name)
11214
+ .split('\\')
11215
+ .join('/') +
11216
+ '.' +
11217
+ extension;
11218
+ // Note: Try to create cache directory, but don't fail if filesystem has issues
11219
+ try {
11220
+ await mkdir(dirname(cacheFilename), { recursive: true });
11300
11221
  }
11301
- if (/^[A-Z]:\\$/i.test(value)) {
11302
- return true;
11222
+ catch (error) {
11223
+ // Note: If we can't create cache directory, continue without it
11224
+ // This handles read-only filesystems, permission issues, and missing parent directories
11225
+ if (error instanceof Error &&
11226
+ (error.message.includes('EROFS') ||
11227
+ error.message.includes('read-only') ||
11228
+ error.message.includes('EACCES') ||
11229
+ error.message.includes('EPERM') ||
11230
+ error.message.includes('ENOENT'))) ;
11231
+ else {
11232
+ // Re-throw other unexpected errors
11233
+ throw error;
11234
+ }
11303
11235
  }
11304
- return false;
11236
+ let isDestroyed = true;
11237
+ const fileHandler = {
11238
+ filename: cacheFilename,
11239
+ get isDestroyed() {
11240
+ return isDestroyed;
11241
+ },
11242
+ async destroy() {
11243
+ if (intermediateFilesStrategy === 'HIDE_AND_CLEAN') {
11244
+ if (isVerbose) {
11245
+ console.info('legacyDocumentScraper: Clening cache');
11246
+ }
11247
+ await rm(cacheFilename);
11248
+ // TODO: [🐿][🧠] Maybe remove empty folders
11249
+ }
11250
+ isDestroyed = true;
11251
+ },
11252
+ };
11253
+ return fileHandler;
11305
11254
  }
11306
11255
  /**
11307
- * TODO: [🍏] Make for MacOS paths
11256
+ * Note: Not using `FileCacheStorage` for two reasons:
11257
+ * 1) Need to store more than serialized JSONs
11258
+ * 2) Need to switch between a `rootDirname` and `cacheDirname` <- TODO: [😡]
11259
+ * TODO: [🐱‍🐉][🧠] Make some smart crop
11260
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11308
11261
  */
11309
11262
 
11310
11263
  /**
11311
- * Register for LLM tools.
11264
+ * Metadata of the scraper
11265
+ *
11266
+ * @private within the scraper directory
11267
+ */
11268
+ const markdownScraperMetadata = $deepFreeze({
11269
+ title: 'Markdown scraper',
11270
+ packageName: '@promptbook/markdown-utils',
11271
+ className: 'MarkdownScraper',
11272
+ mimeTypes: ['text/markdown', 'text/plain'],
11273
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/@@',
11274
+ isAvailableInBrowser: true,
11275
+ // <- Note: [🌏] This is the only scraper which makes sense to be available in the browser, for scraping non-markdown sources in the browser use a remote server
11276
+ requiredExecutables: [],
11277
+ }); /* <- Note: [🤛] */
11278
+ /**
11279
+ * Registration of known scraper metadata
11280
+ *
11281
+ * Warning: This is not useful for the end user, it is just a side effect of the mechanism that handles all available known scrapers
11312
11282
  *
11313
- * Note: `$` is used to indicate that this interacts with the global scope
11314
- * @singleton Only one instance of each register is created per build, but there can be more instances across different builds or environments.
11315
11283
  * @public exported from `@promptbook/core`
11284
+ * @public exported from `@promptbook/wizard`
11285
+ * @public exported from `@promptbook/cli`
11316
11286
  */
11317
- const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
11287
+ $scrapersMetadataRegister.register(markdownScraperMetadata);
11318
11288
  /**
11319
- * TODO: [®] DRY Register logic
11289
+ * Note: [💞] Ignore a discrepancy between file name and entity name
11320
11290
  */
11321
11291
 
11322
11292
  /**
11323
- * Path to the `.env` file which was used to configure LLM tools
11293
+ * Scraper for markdown files
11324
11294
  *
11325
- * Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
11295
+ * @see `documentationUrl` for more details
11296
+ * @public exported from `@promptbook/markdown-utils`
11326
11297
  */
11327
- let $usedEnvFilename = null;
11298
+ class MarkdownScraper {
11299
+ /**
11300
+ * Metadata of the scraper which includes title, mime types, etc.
11301
+ */
11302
+ get metadata() {
11303
+ return markdownScraperMetadata;
11304
+ }
11305
+ constructor(tools, options) {
11306
+ this.tools = tools;
11307
+ this.options = options;
11308
+ }
11309
+ /**
11310
+ * Scrapes the markdown file and returns the knowledge pieces or `null` if it can't scrape it
11311
+ */
11312
+ async scrape(source) {
11313
+ const { maxParallelCount = DEFAULT_MAX_PARALLEL_COUNT, isVerbose = DEFAULT_IS_VERBOSE } = this.options;
11314
+ const { llm } = this.tools;
11315
+ if (llm === undefined) {
11316
+ throw new MissingToolsError('LLM tools are required for scraping external files');
11317
+ // <- Note: This scraper is used in all other scrapers, so saying "external files" not "markdown files"
11318
+ }
11319
+ const llmTools = getSingleLlmExecutionTools(llm);
11320
+ // TODO: [🌼] In future use `ptbk make` and made getPipelineCollection
11321
+ const collection = createPipelineCollectionFromJson(...PipelineCollection);
11322
+ const prepareKnowledgeFromMarkdownExecutor = createPipelineExecutor({
11323
+ pipeline: await collection.getPipelineByUrl('https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book'),
11324
+ tools: {
11325
+ llm: llm,
11326
+ },
11327
+ });
11328
+ const prepareTitleExecutor = createPipelineExecutor({
11329
+ pipeline: await collection.getPipelineByUrl('https://promptbook.studio/promptbook/prepare-knowledge-title.book'),
11330
+ tools: {
11331
+ llm: llm,
11332
+ },
11333
+ });
11334
+ const prepareKeywordsExecutor = createPipelineExecutor({
11335
+ pipeline: await collection.getPipelineByUrl('https://promptbook.studio/promptbook/prepare-knowledge-keywords.book'),
11336
+ tools: {
11337
+ llm: llm,
11338
+ },
11339
+ });
11340
+ const knowledgeContent = await source.asText();
11341
+ const result = await prepareKnowledgeFromMarkdownExecutor({ knowledgeContent }).asPromise({
11342
+ isCrashedOnError: true,
11343
+ });
11344
+ const { outputParameters } = result;
11345
+ const { knowledgePieces: knowledgePiecesRaw } = outputParameters;
11346
+ const knowledgeTextPieces = (knowledgePiecesRaw || '').split('\n---\n');
11347
+ // <- TODO: [main] Smarter split and filter out empty pieces
11348
+ if (isVerbose) {
11349
+ console.info('knowledgeTextPieces:', knowledgeTextPieces);
11350
+ }
11351
+ // const usage = ;
11352
+ const knowledge = await Promise.all(
11353
+ // TODO: [🪂] Do not send all at once but in chunks
11354
+ knowledgeTextPieces.map(async (knowledgeTextPiece, i) => {
11355
+ // Note: These are just default values, they will be overwritten by the actual values:
11356
+ let name = `piece-${i}`;
11357
+ let title = spaceTrim$2(knowledgeTextPiece.substring(0, 100));
11358
+ const knowledgePieceContent = spaceTrim$2(knowledgeTextPiece);
11359
+ let keywords = [];
11360
+ const index = [];
11361
+ /*
11362
+ TODO: [☀] Track line and column of the source
11363
+ const sources: KnowledgePiecePreparedJson['sources'] = [
11364
+ ];
11365
+ */
11366
+ try {
11367
+ const titleResult = await prepareTitleExecutor({ knowledgePieceContent }).asPromise({
11368
+ isCrashedOnError: true,
11369
+ });
11370
+ const { title: titleRaw = 'Untitled' } = titleResult.outputParameters;
11371
+ title = spaceTrim$2(titleRaw) /* <- TODO: Maybe do in pipeline */;
11372
+ name = titleToName(title);
11373
+ // --- Keywords
11374
+ const keywordsResult = await prepareKeywordsExecutor({ knowledgePieceContent }).asPromise({
11375
+ isCrashedOnError: true,
11376
+ });
11377
+ const { keywords: keywordsRaw = '' } = keywordsResult.outputParameters;
11378
+ keywords = (keywordsRaw || '')
11379
+ .split(',')
11380
+ .map((keyword) => keyword.trim())
11381
+ .filter((keyword) => keyword !== '');
11382
+ if (isVerbose) {
11383
+ console.info(`Keywords for "${title}":`, keywords);
11384
+ }
11385
+ // ---
11386
+ if (!llmTools.callEmbeddingModel) {
11387
+ // TODO: [🟥] Detect browser / node and make it colorful
11388
+ console.error('No callEmbeddingModel function provided');
11389
+ }
11390
+ else {
11391
+ // TODO: [🧠][🎛] Embedding via multiple models
11392
+ const embeddingResult = await llmTools.callEmbeddingModel({
11393
+ title: `Embedding for ${title}` /* <- Note: No impact on embedding result itself, just for logging */,
11394
+ parameters: {},
11395
+ content: knowledgePieceContent,
11396
+ modelRequirements: {
11397
+ modelVariant: 'EMBEDDING',
11398
+ },
11399
+ });
11400
+ index.push({
11401
+ modelName: embeddingResult.modelName,
11402
+ position: [...embeddingResult.content],
11403
+ // <- TODO: [🪓] Here should be no need for spreading new array, just `position: embeddingResult.content`
11404
+ });
11405
+ }
11406
+ }
11407
+ catch (error) {
11408
+ // Note: Here is expected error:
11409
+ // > PipelineExecutionError: You have not provided any `LlmExecutionTools` that support model variant "EMBEDDING
11410
+ if (!(error instanceof PipelineExecutionError)) {
11411
+ throw error;
11412
+ }
11413
+ // TODO: [🟥] Detect browser / node and make it colorful
11414
+ console.error(error, "<- Note: This error is not critical to prepare the pipeline, just knowledge pieces won't have embeddings");
11415
+ }
11416
+ return {
11417
+ name,
11418
+ title,
11419
+ content: knowledgePieceContent,
11420
+ keywords,
11421
+ index,
11422
+ // <- TODO: [☀] sources,
11423
+ };
11424
+ }));
11425
+ return knowledge;
11426
+ }
11427
+ }
11328
11428
  /**
11329
- * Pass the `.env` file which was used to configure LLM tools
11429
+ * TODO: [🪂] Do it in parallel 11:11
11430
+ * Note: No need to aggregate usage here, it is done by intercepting the llmTools
11431
+ */
11432
+
11433
+ /**
11434
+ * Metadata of the scraper
11330
11435
  *
11331
- * Note: `$` is used to indicate that this variable is making side effect
11436
+ * @private within the scraper directory
11437
+ */
11438
+ const websiteScraperMetadata = $deepFreeze({
11439
+ title: 'Website scraper',
11440
+ packageName: '@promptbook/website-crawler',
11441
+ className: 'WebsiteScraper',
11442
+ mimeTypes: ['text/html'],
11443
+ documentationUrl: 'https://github.com/webgptorg/promptbook/discussions/@@',
11444
+ isAvailableInBrowser: false,
11445
+ // <- Note: [🌏] Only `MarkdownScraper` makes sense to be available in the browser, for scraping non-markdown sources in the browser use a remote server
11446
+ requiredExecutables: [],
11447
+ }); /* <- Note: [🤛] */
11448
+ /**
11449
+ * Registration of known scraper metadata
11332
11450
  *
11333
- * @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
11451
+ * Warning: This is not useful for the end user, it is just a side effect of the mechanism that handles all available known scrapers
11452
+ *
11453
+ * @public exported from `@promptbook/core`
11454
+ * @public exported from `@promptbook/wizard`
11455
+ * @public exported from `@promptbook/cli`
11334
11456
  */
11335
- function $setUsedEnvFilename(filepath) {
11336
- $usedEnvFilename = filepath;
11337
- }
11457
+ $scrapersMetadataRegister.register(websiteScraperMetadata);
11338
11458
  /**
11339
- * Creates a message with all registered LLM tools
11459
+ * Note: [💞] Ignore a discrepancy between file name and entity name
11460
+ */
11461
+
11462
+ /**
11463
+ * Create a new showdown converter instance
11340
11464
  *
11341
- * Note: This function is used to create a (error) message when there is no constructor for some LLM provider
11465
+ * @private utility of `WebsiteScraper`
11466
+ */
11467
+ function createShowdownConverter() {
11468
+ return new Converter({
11469
+ flavor: 'github',
11470
+ /*
11471
+ > import showdownHighlight from 'showdown-highlight';
11472
+ > extensions: [
11473
+ > showdownHighlight({
11474
+ > // Whether to add the classes to the <pre> tag, default is false
11475
+ > pre: true,
11476
+ > // Whether to use hljs' auto language detection, default is true
11477
+ > auto_detection: true,
11478
+ > }),
11479
+ > ],
11480
+ */
11481
+ });
11482
+ }
11483
+
11484
+ // TODO: [🏳‍🌈] Finally take pick of .json vs .ts
11485
+ /**
11486
+ * Scraper for websites
11342
11487
  *
11343
- * @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
11488
+ * @see `documentationUrl` for more details
11489
+ * @public exported from `@promptbook/website-crawler`
11344
11490
  */
11345
- function $registeredLlmToolsMessage() {
11346
- let env;
11347
- if ($isRunningInNode()) {
11348
- env = process.env;
11349
- // <- TODO: [⚛] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
11491
+ class WebsiteScraper {
11492
+ /**
11493
+ * Metadata of the scraper which includes title, mime types, etc.
11494
+ */
11495
+ get metadata() {
11496
+ return websiteScraperMetadata;
11350
11497
  }
11351
- else {
11352
- env = {};
11498
+ constructor(tools, options) {
11499
+ this.tools = tools;
11500
+ this.options = options;
11501
+ this.markdownScraper = new MarkdownScraper(tools, options);
11502
+ this.showdownConverter = createShowdownConverter();
11353
11503
  }
11354
11504
  /**
11355
- * Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
11505
+ * Convert the website to `.md` file and returns intermediate source
11506
+ *
11507
+ * Note: `$` is used to indicate that this function is not a pure function - it leaves files on the disk and you are responsible for cleaning them by calling `destroy` method of returned object
11356
11508
  */
11357
- const all = [];
11358
- for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
11359
- if (all.some((item) => item.packageName === packageName && item.className === className)) {
11360
- continue;
11509
+ async $convert(source) {
11510
+ const {
11511
+ // TODO: [🧠] Maybe in node use headless browser not just JSDOM
11512
+ rootDirname = process.cwd(), cacheDirname = DEFAULT_SCRAPE_CACHE_DIRNAME, intermediateFilesStrategy = DEFAULT_INTERMEDIATE_FILES_STRATEGY, isVerbose = DEFAULT_IS_VERBOSE, } = this.options;
11513
+ if (source.url === null) {
11514
+ throw new KnowledgeScrapeError('Website scraper requires URL');
11361
11515
  }
11362
- all.push({ title, packageName, className, envVariables });
11363
- }
11364
- for (const { packageName, className } of $llmToolsRegister.list()) {
11365
- if (all.some((item) => item.packageName === packageName && item.className === className)) {
11366
- continue;
11516
+ if (this.tools.fs === undefined) {
11517
+ throw new EnvironmentMismatchError('Can not scrape websites without filesystem tools');
11367
11518
  }
11368
- all.push({ packageName, className });
11519
+ const jsdom = new JSDOM(await source.asText(), {
11520
+ url: source.url,
11521
+ });
11522
+ const reader = new Readability(jsdom.window.document);
11523
+ const article = reader.parse();
11524
+ // console.log(article);
11525
+ // await forTime(10000);
11526
+ let html = (article === null || article === void 0 ? void 0 : article.content) || (article === null || article === void 0 ? void 0 : article.textContent) || jsdom.window.document.body.innerHTML;
11527
+ // Note: Unwrap html such as it is convertable by `markdownConverter`
11528
+ for (let i = 0; i < 2; i++) {
11529
+ html = html.replace(/<div\s*(?:id="readability-page-\d+"\s+class="page")?>(.*)<\/div>/is, '$1');
11530
+ }
11531
+ if (html.includes('<div')) {
11532
+ html = (article === null || article === void 0 ? void 0 : article.textContent) || '';
11533
+ }
11534
+ const cacheFilehandler = await getScraperIntermediateSource(source, {
11535
+ rootDirname,
11536
+ cacheDirname,
11537
+ intermediateFilesStrategy,
11538
+ extension: 'html',
11539
+ isVerbose,
11540
+ });
11541
+ // Note: Try to cache the scraped content, but don't fail if the filesystem is read-only
11542
+ try {
11543
+ await this.tools.fs.writeFile(cacheFilehandler.filename, html, 'utf-8');
11544
+ }
11545
+ catch (error) {
11546
+ // Note: If we can't write to cache, we'll continue without caching
11547
+ // This handles read-only filesystems like Vercel
11548
+ if (error instanceof Error &&
11549
+ (error.message.includes('EROFS') ||
11550
+ error.message.includes('read-only') ||
11551
+ error.message.includes('EACCES') ||
11552
+ error.message.includes('EPERM') ||
11553
+ error.message.includes('ENOENT'))) ;
11554
+ else {
11555
+ // Re-throw other unexpected errors
11556
+ throw error;
11557
+ }
11558
+ }
11559
+ const markdown = this.showdownConverter.makeMarkdown(html, jsdom.window.document);
11560
+ return { ...cacheFilehandler, markdown };
11369
11561
  }
11370
- const metadata = all.map((metadata) => {
11371
- var _a, _b;
11372
- const isMetadataAviailable = $llmToolsMetadataRegister
11373
- .list()
11374
- .some(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
11375
- const isInstalled = $llmToolsRegister
11376
- .list()
11377
- .some(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
11378
- const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
11379
- const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
11380
- // <- Note: [🗨]
11381
- return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
11382
- });
11383
- const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
11384
- if (metadata.length === 0) {
11385
- return spaceTrim$2((block) => `
11386
- No LLM providers are available.
11387
-
11388
- ${block(usedEnvMessage)}
11389
- `);
11562
+ /**
11563
+ * Scrapes the website and returns the knowledge pieces or `null` if it can't scrape it
11564
+ */
11565
+ async scrape(source) {
11566
+ const cacheFilehandler = await this.$convert(source);
11567
+ const markdownSource = {
11568
+ source: source.source,
11569
+ filename: cacheFilehandler.filename,
11570
+ url: null,
11571
+ mimeType: 'text/markdown',
11572
+ asText() {
11573
+ return cacheFilehandler.markdown;
11574
+ },
11575
+ asJson() {
11576
+ throw new UnexpectedError('Did not expect that `markdownScraper` would need to get the content `asJson`');
11577
+ },
11578
+ /*
11579
+ TODO: [🥽]
11580
+ > asBlob() {
11581
+ > throw new UnexpectedError(
11582
+ > 'Did not expect that `markdownScraper` would need to get the content `asBlob`',
11583
+ > );
11584
+ > },
11585
+ */
11586
+ };
11587
+ const knowledge = this.markdownScraper.scrape(markdownSource);
11588
+ await cacheFilehandler.destroy();
11589
+ return knowledge;
11390
11590
  }
11391
- return spaceTrim$2((block) => `
11392
-
11393
- ${block(usedEnvMessage)}
11394
-
11395
- Relevant environment variables:
11396
- ${block(Object.keys(env)
11397
- .filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
11398
- .map((envVariableName) => `- \`${envVariableName}\``)
11399
- .join('\n'))}
11400
-
11401
- Available LLM providers are:
11402
- ${block(metadata
11403
- .map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
11404
- const morePieces = [];
11405
- if (just(false)) ;
11406
- else if (!isMetadataAviailable && !isInstalled) {
11407
- // TODO: [�][�] Maybe do allow to do auto-install if package not registered and not found
11408
- morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
11409
- }
11410
- else if (isMetadataAviailable && !isInstalled) {
11411
- // TODO: [�][�]
11412
- morePieces.push(`Not installed`);
11413
- }
11414
- else if (!isMetadataAviailable && isInstalled) {
11415
- morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
11416
- }
11417
- else if (isMetadataAviailable && isInstalled) {
11418
- morePieces.push(`Installed`);
11419
- }
11420
- else {
11421
- morePieces.push(`unknown state, looks like a unexpected behavior`);
11422
- } /* not else */
11423
- if (isFullyConfigured) {
11424
- morePieces.push(`Configured`);
11425
- }
11426
- else if (isPartiallyConfigured) {
11427
- morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
11428
- }
11429
- else {
11430
- if (envVariables !== null) {
11431
- morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
11432
- }
11433
- else {
11434
- morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
11435
- }
11436
- }
11437
- let providerMessage = spaceTrim$2(`
11438
- ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
11439
- ${morePieces.join('; ')}
11440
- `);
11441
- if ($isRunningInNode()) {
11442
- if (isInstalled && isFullyConfigured) {
11443
- providerMessage = colors.green(providerMessage);
11444
- }
11445
- else if (isInstalled && isPartiallyConfigured) {
11446
- providerMessage = colors.yellow(providerMessage);
11447
- }
11448
- else {
11449
- providerMessage = colors.gray(providerMessage);
11450
- }
11451
- }
11452
- return providerMessage;
11453
- })
11454
- .join('\n'))}
11455
- `);
11456
11591
  }
11457
11592
  /**
11458
- * TODO: [®] DRY Register logic
11459
- * TODO: [🧠][⚛] Maybe pass env as argument
11593
+ * TODO: [👣] Scraped website in .md can act as cache item - there is no need to run conversion each time
11594
+ * TODO: [🪂] Do it in parallel 11:11
11595
+ * Note: No need to aggregate usage here, it is done by intercepting the llmTools
11596
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11460
11597
  */
11461
11598
 
11462
11599
  /**
11463
- * Provides the path to the `.env` file
11600
+ * Fetches and scrapes content from a URL (SERVER-SIDE ONLY)
11464
11601
  *
11465
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
11602
+ * This function:
11603
+ * 1. Fetches the URL content using promptbookFetch
11604
+ * 2. Determines the content type (HTML, PDF, etc.)
11605
+ * 3. Uses the appropriate scraper to convert to markdown
11606
+ * 4. Returns the scraped markdown content
11466
11607
  *
11467
- * @private within the repository - for CLI utils
11608
+ * @param url The URL to fetch and scrape
11609
+ * @returns Markdown content from the URL
11610
+ *
11611
+ * @private internal utility for USE BROWSER commitment
11612
+ *
11613
+ * WARNING: This function should NOT be used directly in browser environments.
11614
+ * For browser environments, use fetchUrlContentViaBrowser which proxies through
11615
+ * the Agents Server API endpoint at /api/scrape
11468
11616
  */
11469
- async function $provideEnvFilename() {
11470
- if (!$isRunningInNode()) {
11471
- throw new EnvironmentMismatchError('Function `$provideEnvFilename` works only in Node.js environment');
11472
- }
11473
- const envFilePatterns = [
11474
- '.env',
11475
- '.env.test',
11476
- '.env.local',
11477
- '.env.development.local',
11478
- '.env.development',
11479
- '.env.production.local',
11480
- '.env.production',
11481
- '.env.prod.local',
11482
- '.env.prod',
11483
- // <- TODO: Maybe add more patterns
11484
- ];
11485
- let rootDirname = process.cwd();
11486
- up_to_root: for (let i = 0; i < LOOP_LIMIT; i++) {
11487
- for (const pattern of envFilePatterns) {
11488
- const envFilename = join(rootDirname, pattern);
11489
- if (await isFileExisting(envFilename, $provideFilesystemForNode())) {
11490
- $setUsedEnvFilename(envFilename);
11491
- return envFilename;
11492
- }
11617
+ async function fetchUrlContent(url) {
11618
+ try {
11619
+ // Validate URL
11620
+ let parsedUrl;
11621
+ try {
11622
+ parsedUrl = new URL(url);
11493
11623
  }
11494
- if (isRootPath(rootDirname)) {
11495
- break up_to_root;
11624
+ catch (error) {
11625
+ throw new Error(`Invalid URL: ${url}`);
11496
11626
  }
11497
- // Note: If the directory does not exist, try the parent directory
11498
- rootDirname = join(rootDirname, '..');
11499
- }
11500
- return null;
11501
- }
11502
- /**
11503
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11504
- */
11627
+ // Fetch the URL content
11628
+ const response = await promptbookFetch(url);
11629
+ if (!response.ok) {
11630
+ throw new Error(`Failed to fetch URL: ${response.status} ${response.statusText}`);
11631
+ }
11632
+ // Get content type
11633
+ const contentType = response.headers.get('content-type') || 'text/html';
11634
+ const content = await response.text();
11635
+ // Determine the appropriate scraper based on content type
11636
+ const mimeType = (contentType.split(';')[0] || 'text/html').trim();
11637
+ // Handle PDFs
11638
+ if (mimeType === 'application/pdf' || url.toLowerCase().endsWith('.pdf')) {
11639
+ // TODO: [🔰] Implement PDF scraping using PdfScraper
11640
+ // For now, return a message indicating PDF support is coming
11641
+ return spaceTrim$1(`
11642
+ # PDF Document: ${parsedUrl.pathname.split('/').pop()}
11643
+
11644
+ **Note:** PDF scraping is not yet fully implemented.
11645
+
11646
+ **URL:** ${url}
11647
+
11648
+ The PDF scraper will be integrated to provide full document content extraction.
11649
+ For now, please try to find an HTML version of this content or use a different source.
11650
+ `);
11651
+ }
11652
+ // Handle HTML/websites
11653
+ if (mimeType === 'text/html' || mimeType === 'application/xhtml+xml' || !mimeType.includes('/')) {
11654
+ // Create a scraper source handler
11655
+ const sourceHandler = {
11656
+ source: url,
11657
+ filename: null,
11658
+ url,
11659
+ mimeType: 'text/html',
11660
+ asText: () => content,
11661
+ asJson: () => {
11662
+ throw new Error('JSON conversion not supported for HTML content');
11663
+ },
11664
+ };
11665
+ // Use WebsiteScraper to convert HTML to markdown
11666
+ // Note: We need minimal tools for the scraper
11667
+ const scraper = new WebsiteScraper(// <- TODO: !!!!! Things marked with [🟢] should never be never released in packages that could be imported into browser environment
11668
+ {
11669
+ // Minimal tools - fs and llm are optional for basic scraping
11670
+ fs: undefined,
11671
+ llm: undefined,
11672
+ }, {
11673
+ isVerbose: false,
11674
+ });
11675
+ // Convert to markdown
11676
+ const intermediateSource = await scraper.$convert(sourceHandler);
11677
+ const markdown = intermediateSource.markdown;
11678
+ // Clean up intermediate files
11679
+ await intermediateSource.destroy();
11680
+ // Add URL header to the content
11681
+ return spaceTrim$1(`
11682
+ # Content from: ${url}
11505
11683
 
11506
- /**
11507
- * Provides LLM tools configuration by reading environment variables.
11508
- *
11509
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
11510
- *
11511
- * It looks for environment variables:
11512
- * - `process.env.OPENAI_API_KEY`
11513
- * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
11514
- * - ...
11515
- *
11516
- * @see Environment variables documentation or .env file for required variables.
11517
- * @returns A promise that resolves to the LLM tools configuration, or null if configuration is incomplete or missing.
11518
- * @public exported from `@promptbook/node`
11519
- */
11520
- async function $provideLlmToolsConfigurationFromEnv() {
11521
- if (!$isRunningInNode()) {
11522
- throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
11684
+ ${markdown}
11685
+
11686
+ ---
11687
+
11688
+ *Source: ${url}*
11689
+ `);
11690
+ }
11691
+ // For other content types, return the raw content with a note
11692
+ return spaceTrim$1(`
11693
+ # Content from: ${url}
11694
+
11695
+ **Content Type:** ${contentType}
11696
+
11697
+ ${content}
11698
+
11699
+ ---
11700
+
11701
+ *Source: ${url}*
11702
+ `);
11523
11703
  }
11524
- const envFilepath = await $provideEnvFilename();
11525
- if (envFilepath !== null) {
11526
- dotenv.config({ path: envFilepath });
11704
+ catch (error) {
11705
+ // Handle errors gracefully
11706
+ const errorMessage = error instanceof Error ? error.message : String(error);
11707
+ return spaceTrim$1(`
11708
+ # Error fetching content from URL
11709
+
11710
+ **URL:** ${url}
11711
+
11712
+ **Error:** ${errorMessage}
11713
+
11714
+ Unable to fetch and scrape the content from this URL.
11715
+ Please verify the URL is correct and accessible.
11716
+ `);
11527
11717
  }
11528
- const llmToolsConfiguration = $llmToolsMetadataRegister
11529
- .list()
11530
- .map((metadata) => metadata.createConfigurationFromEnv(process.env))
11531
- .filter((configuration) => configuration !== null);
11532
- return llmToolsConfiguration;
11533
11718
  }
11534
11719
  /**
11535
11720
  * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11536
11721
  */
11537
11722
 
11538
11723
  /**
11539
- * Detects if the code is running in a browser environment in main thread (Not in a web worker)
11724
+ * Generates a regex pattern to match a specific commitment
11540
11725
  *
11541
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
11726
+ * Note: It always creates new Regex object
11727
+ * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
11542
11728
  *
11543
- * @public exported from `@promptbook/utils`
11729
+ * @private - TODO: [🧠] Maybe should be public?
11544
11730
  */
11545
- function $isRunningInBrowser() {
11546
- try {
11547
- return typeof window !== 'undefined' && typeof window.document !== 'undefined';
11731
+ function createCommitmentRegex(commitment, aliases = [], requiresContent = true) {
11732
+ const allCommitments = [commitment, ...aliases];
11733
+ const patterns = allCommitments.map((commitment) => {
11734
+ const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
11735
+ return escapedCommitment.split(/\s+/).join('\\s+');
11736
+ });
11737
+ const keywordPattern = patterns.join('|');
11738
+ if (requiresContent) {
11739
+ return new RegExp(`^\\s*(?<type>${keywordPattern})\\b\\s+(?<contents>.+)$`, 'gim');
11548
11740
  }
11549
- catch (e) {
11550
- return false;
11741
+ else {
11742
+ return new RegExp(`^\\s*(?<type>${keywordPattern})\\b(?:\\s+(?<contents>.+))?$`, 'gim');
11551
11743
  }
11552
11744
  }
11553
11745
  /**
11554
- * TODO: [🎺]
11555
- */
11556
-
11557
- /**
11558
- * Detects if the code is running in a web worker
11746
+ * Generates a regex pattern to match a specific commitment type
11559
11747
  *
11560
- * Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
11748
+ * Note: It just matches the type part of the commitment
11749
+ * Note: It always creates new Regex object
11750
+ * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
11561
11751
  *
11562
- * @public exported from `@promptbook/utils`
11752
+ * @private
11563
11753
  */
11564
- function $isRunningInWebWorker() {
11565
- try {
11566
- // Note: Check for importScripts which is specific to workers
11567
- // and not available in the main browser thread
11568
- return (typeof self !== 'undefined' &&
11569
- typeof self.importScripts === 'function');
11570
- }
11571
- catch (e) {
11572
- return false;
11573
- }
11754
+ function createCommitmentTypeRegex(commitment, aliases = []) {
11755
+ const allCommitments = [commitment, ...aliases];
11756
+ const patterns = allCommitments.map((commitment) => {
11757
+ const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
11758
+ return escapedCommitment.split(/\s+/).join('\\s+');
11759
+ });
11760
+ const keywordPattern = patterns.join('|');
11761
+ const regex = new RegExp(`^\\s*(?<type>${keywordPattern})\\b`, 'gim');
11762
+ return regex;
11574
11763
  }
11575
- /**
11576
- * TODO: [🎺]
11577
- */
11578
11764
 
11579
11765
  /**
11580
- * Creates LLM execution tools from provided configuration objects
11581
- *
11582
- * Instantiates and configures LLM tool instances for each configuration entry,
11583
- * combining them into a unified interface via MultipleLlmExecutionTools.
11584
- *
11585
- * Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
11766
+ * Base implementation of CommitmentDefinition that provides common functionality
11767
+ * Most commitments can extend this class and only override the applyToAgentModelRequirements method
11586
11768
  *
11587
- * @param configuration Array of LLM tool configurations to instantiate
11588
- * @param options Additional options for configuring the LLM tools
11589
- * @returns A unified interface combining all successfully instantiated LLM tools
11590
- * @public exported from `@promptbook/core`
11769
+ * @private
11591
11770
  */
11592
- function createLlmToolsFromConfiguration(configuration, options = {}) {
11593
- const { title = 'LLM Tools from Configuration', isVerbose = DEFAULT_IS_VERBOSE, userId } = options;
11594
- const llmTools = configuration.map((llmConfiguration) => {
11595
- const registeredItem = $llmToolsRegister
11596
- .list()
11597
- .find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
11598
- if (registeredItem === undefined) {
11599
- // console.log('$llmToolsRegister.list()', $llmToolsRegister.list());
11600
- throw new Error(spaceTrim$2((block) => `
11601
- There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
11602
- Running in ${!$isRunningInBrowser() ? '' : 'browser environment'}${!$isRunningInNode() ? '' : 'node environment'}${!$isRunningInWebWorker() ? '' : 'worker environment'}
11603
-
11604
- You have probably forgotten install and import the provider package.
11605
- To fix this issue, you can:
11606
-
11607
- Install:
11608
-
11609
- > npm install ${llmConfiguration.packageName}
11610
-
11611
- And import:
11612
-
11613
- > import '${llmConfiguration.packageName}';
11614
-
11615
-
11616
- ${block($registeredLlmToolsMessage())}
11617
- `));
11618
- }
11619
- return registeredItem({
11620
- isVerbose,
11621
- userId,
11622
- ...llmConfiguration.options,
11771
+ class BaseCommitmentDefinition {
11772
+ constructor(type, aliases = []) {
11773
+ this.type = type;
11774
+ this.aliases = aliases;
11775
+ }
11776
+ /**
11777
+ * Whether this commitment requires content.
11778
+ * If true, regex will match only if there is content after the commitment keyword.
11779
+ * If false, regex will match even if there is no content.
11780
+ */
11781
+ get requiresContent() {
11782
+ return true;
11783
+ }
11784
+ /**
11785
+ * Creates a regex pattern to match this commitment in agent source
11786
+ * Uses the existing createCommitmentRegex function as internal helper
11787
+ */
11788
+ createRegex() {
11789
+ return createCommitmentRegex(this.type, this.aliases, this.requiresContent);
11790
+ }
11791
+ /**
11792
+ * Creates a regex pattern to match just the commitment type
11793
+ * Uses the existing createCommitmentTypeRegex function as internal helper
11794
+ */
11795
+ createTypeRegex() {
11796
+ return createCommitmentTypeRegex(this.type, this.aliases);
11797
+ }
11798
+ /**
11799
+ * Helper method to create a new requirements object with updated system message
11800
+ * This is commonly used by many commitments
11801
+ */
11802
+ updateSystemMessage(requirements, messageUpdate) {
11803
+ const newMessage = typeof messageUpdate === 'string' ? messageUpdate : messageUpdate(requirements.systemMessage);
11804
+ return {
11805
+ ...requirements,
11806
+ systemMessage: newMessage,
11807
+ };
11808
+ }
11809
+ /**
11810
+ * Helper method to append content to the system message
11811
+ */
11812
+ appendToSystemMessage(requirements, content, separator = '\n\n') {
11813
+ return this.updateSystemMessage(requirements, (currentMessage) => {
11814
+ if (!currentMessage.trim()) {
11815
+ return content;
11816
+ }
11817
+ return currentMessage + separator + content;
11623
11818
  });
11624
- });
11625
- return joinLlmExecutionTools(title, ...llmTools);
11819
+ }
11820
+ /**
11821
+ * Helper method to add a comment section to the system message
11822
+ * Comments are lines starting with # that will be removed from the final system message
11823
+ * but can be useful for organizing and structuring the message during processing
11824
+ */
11825
+ addCommentSection(requirements, commentTitle, content, position = 'end') {
11826
+ const commentSection = `# ${commentTitle.toUpperCase()}\n${content}`;
11827
+ if (position === 'beginning') {
11828
+ return this.updateSystemMessage(requirements, (currentMessage) => {
11829
+ if (!currentMessage.trim()) {
11830
+ return commentSection;
11831
+ }
11832
+ return commentSection + '\n\n' + currentMessage;
11833
+ });
11834
+ }
11835
+ else {
11836
+ return this.appendToSystemMessage(requirements, commentSection);
11837
+ }
11838
+ }
11839
+ /**
11840
+ * Gets tool function implementations provided by this commitment
11841
+ *
11842
+ * When the `applyToAgentModelRequirements` adds tools to the requirements, this method should return the corresponding function definitions.
11843
+ */
11844
+ getToolFunctions() {
11845
+ return {};
11846
+ }
11847
+ /**
11848
+ * Gets human-readable titles for tool functions provided by this commitment
11849
+ *
11850
+ * This is used in the UI to show a user-friendly name instead of the technical function name.
11851
+ */
11852
+ getToolTitles() {
11853
+ return {};
11854
+ }
11626
11855
  }
11627
- /**
11628
- * TODO: [🎌] Together with `createLlmToolsFromConfiguration` + 'EXECUTION_TOOLS_CLASSES' gets to `@promptbook/core` ALL model providers, make this more efficient
11629
- * TODO: [🧠][🎌] Dynamically install required providers
11630
- * TODO: We should implement an interactive configuration wizard that would:
11631
- * 1. Detect which LLM providers are available in the environment
11632
- * 2. Guide users through required configuration settings for each provider
11633
- * 3. Allow testing connections before completing setup
11634
- * 4. Generate appropriate configuration code for application integration
11635
- * TODO: [🧠][🍛] Which name is better `createLlmToolsFromConfig` or `createLlmToolsFromConfiguration`?
11636
- * TODO: [🧠] Is there some meaningfull way how to test this util
11637
- * TODO: This should be maybe not under `_common` but under `utils`
11638
- * TODO: [®] DRY Register logic
11639
- */
11640
11856
 
11641
11857
  /**
11642
- * Automatically configures LLM tools from environment variables in Node.js
11643
- *
11644
- * This utility function detects available LLM providers based on environment variables
11645
- * and creates properly configured LLM execution tools for each detected provider.
11858
+ * ACTION commitment definition
11646
11859
  *
11647
- * Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
11860
+ * The ACTION commitment defines specific actions or capabilities that the agent can perform.
11861
+ * This helps define what the agent is capable of doing and how it should approach tasks.
11648
11862
  *
11649
- * Supports environment variables from .env files when dotenv is configured
11650
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
11863
+ * Example usage in agent source:
11651
11864
  *
11652
- * It looks for environment variables:
11653
- * - `process.env.OPENAI_API_KEY`
11654
- * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
11655
- * - ...
11865
+ * ```book
11866
+ * ACTION Can generate code snippets and explain programming concepts
11867
+ * ACTION Able to analyze data and provide insights
11868
+ * ```
11656
11869
  *
11657
- * @param options Configuration options for the LLM tools
11658
- * @returns A unified interface containing all detected and configured LLM tools
11659
- * @public exported from `@promptbook/node`
11870
+ * @private [🪔] Maybe export the commitments through some package
11660
11871
  */
11661
- async function $provideLlmToolsFromEnv(options = {}) {
11662
- if (!$isRunningInNode()) {
11663
- throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
11872
+ class ActionCommitmentDefinition extends BaseCommitmentDefinition {
11873
+ constructor(type = 'ACTION') {
11874
+ super(type);
11664
11875
  }
11665
- const configuration = await $provideLlmToolsConfigurationFromEnv();
11666
- if (configuration.length === 0) {
11667
- if ($llmToolsMetadataRegister.list().length === 0) {
11668
- throw new UnexpectedError(spaceTrim$2((block) => `
11669
- No LLM tools registered, this is probably a bug in the Promptbook library
11876
+ /**
11877
+ * Short one-line description of ACTION.
11878
+ */
11879
+ get description() {
11880
+ return 'Define agent capabilities and actions it can perform.';
11881
+ }
11882
+ /**
11883
+ * Icon for this commitment.
11884
+ */
11885
+ get icon() {
11886
+ return '⚡';
11887
+ }
11888
+ /**
11889
+ * Markdown documentation for ACTION commitment.
11890
+ */
11891
+ get documentation() {
11892
+ return spaceTrim$1(`
11893
+ # ${this.type}
11670
11894
 
11671
- ${block($registeredLlmToolsMessage())}}
11672
- `));
11673
- }
11674
- // TODO: [🥃]
11675
- throw new Error(spaceTrim$2((block) => `
11676
- No LLM tools found in the environment
11895
+ Defines specific actions or capabilities that the agent can perform.
11677
11896
 
11678
- ${block($registeredLlmToolsMessage())}}
11679
- `));
11680
- }
11681
- return createLlmToolsFromConfiguration(configuration, options);
11682
- }
11683
- /**
11684
- * TODO: The architecture for LLM tools configuration consists of three key functions:
11685
- * 1. `$provideLlmToolsFromEnv` - High-level function that detects available providers from env vars and returns ready-to-use LLM tools
11686
- * 2. `$provideLlmToolsConfigurationFromEnv` - Middle layer that extracts configuration objects from environment variables
11687
- * 3. `createLlmToolsFromConfiguration` - Low-level function that instantiates LLM tools from explicit configuration
11688
- *
11689
- * This layered approach allows flexibility in how tools are configured:
11690
- * - Use $provideLlmToolsFromEnv for automatic detection and setup in Node.js environments
11691
- * - Use $provideLlmToolsConfigurationFromEnv to extract config objects for modification before instantiation
11692
- * - Use createLlmToolsFromConfiguration for explicit control over tool configurations
11693
- *
11694
- * TODO: [🧠][🍛] Which name is better `$provideLlmToolsFromEnv` or `$provideLlmToolsFromEnvironment`?
11695
- * TODO: [🧠] Is there some meaningfull way how to test this util
11696
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11697
- * TODO: [🥃] Allow `ptbk make` without llm tools
11698
- * TODO: This should be maybe not under `_common` but under `utils`
11699
- * TODO: [®] DRY Register logic
11700
- */
11897
+ ## Key aspects
11701
11898
 
11702
- /**
11703
- * Provides a collection of scrapers optimized for Node.js environment.
11704
- * 1) `provideScrapersForNode` use as default
11705
- * 2) `provideScrapersForBrowser` use in limited browser environment *
11706
- * @public exported from `@promptbook/node`
11707
- */
11708
- async function $provideScrapersForNode(tools, options) {
11709
- if (!$isRunningInNode()) {
11710
- throw new EnvironmentMismatchError('Function `$getScrapersForNode` works only in Node.js environment');
11899
+ - Both terms work identically and can be used interchangeably.
11900
+ - Each action adds to the agent's capability list.
11901
+ - Actions help users understand what the agent can do.
11902
+
11903
+ ## Examples
11904
+
11905
+ \`\`\`book
11906
+ Code Assistant
11907
+
11908
+ PERSONA You are a programming assistant
11909
+ ACTION Can generate code snippets and explain programming concepts
11910
+ ACTION Able to debug existing code and suggest improvements
11911
+ ACTION Can create unit tests for functions
11912
+ \`\`\`
11913
+
11914
+ \`\`\`book
11915
+ Data Scientist
11916
+
11917
+ PERSONA You are a data analysis expert
11918
+ ACTION Able to analyze data and provide insights
11919
+ ACTION Can create visualizations and charts
11920
+ ACTION Capable of statistical analysis and modeling
11921
+ KNOWLEDGE Data analysis best practices and statistical methods
11922
+ \`\`\`
11923
+ `);
11711
11924
  }
11712
- // TODO: [🔱] Do here auto-installation + auto-include of missing scrapers - use all from $scrapersMetadataRegister.list()
11713
- // TODO: [🔱][🧠] What is the best strategy for auto-install - install them all?
11714
- const scrapers = [];
11715
- for (const scraperFactory of $scrapersRegister.list()) {
11716
- const scraper = await scraperFactory(tools, options || {});
11717
- if (scraper.metadata.packageName === '@promptbook/boilerplate' ||
11718
- scraper.metadata.mimeTypes.some((mimeType) => mimeType.includes('DISABLED'))) {
11719
- continue;
11925
+ applyToAgentModelRequirements(requirements, content) {
11926
+ const trimmedContent = content.trim();
11927
+ if (!trimmedContent) {
11928
+ return requirements;
11720
11929
  }
11721
- scrapers.push(scraper);
11930
+ // Add action capability to the system message
11931
+ const actionSection = `Capability: ${trimmedContent}`;
11932
+ return this.appendToSystemMessage(requirements, actionSection, '\n\n');
11722
11933
  }
11723
- return scrapers;
11724
11934
  }
11725
11935
  /**
11726
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
11936
+ * Note: [💞] Ignore a discrepancy between file name and entity name
11727
11937
  */
11728
11938
 
11729
11939
  /**
11730
- * Computes SHA-256 hash of the given object
11940
+ * CLOSED commitment definition
11731
11941
  *
11732
- * @public exported from `@promptbook/utils`
11733
- */
11734
- function computeHash(value) {
11735
- return SHA256(hexEncoder.parse(spaceTrim$2(valueToString(value)))).toString( /* hex */);
11736
- }
11737
- /**
11738
- * TODO: [🥬][🥬] Use this ACRY
11739
- */
11740
-
11741
- /**
11742
- * Makes first letter of a string lowercase
11942
+ * The CLOSED commitment specifies that the agent CANNOT be modified by conversation.
11943
+ * It prevents the agent from learning from interactions and updating its source code.
11743
11944
  *
11744
- * Note: [🔂] This function is idempotent.
11945
+ * Example usage in agent source:
11745
11946
  *
11746
- * @public exported from `@promptbook/utils`
11747
- */
11748
- function decapitalize(word) {
11749
- return word.substring(0, 1).toLowerCase() + word.substring(1);
11750
- }
11751
-
11752
- /**
11753
- * Parses keywords from a string
11947
+ * ```book
11948
+ * CLOSED
11949
+ * ```
11754
11950
  *
11755
- * @param {string} input
11756
- * @returns {Set} of keywords without diacritics in lowercase
11757
- * @public exported from `@promptbook/utils`
11951
+ * @private [🪔] Maybe export the commitments through some package
11758
11952
  */
11759
- function parseKeywordsFromString(input) {
11760
- const keywords = normalizeTo_SCREAMING_CASE(removeDiacritics(input))
11761
- .toLowerCase()
11762
- .split(/[^a-z0-9]+/gs)
11763
- .filter((value) => value);
11764
- return new Set(keywords);
11765
- }
11953
+ class ClosedCommitmentDefinition extends BaseCommitmentDefinition {
11954
+ constructor() {
11955
+ super('CLOSED');
11956
+ }
11957
+ /**
11958
+ * The `CLOSED` commitment is standalone.
11959
+ */
11960
+ get requiresContent() {
11961
+ return false;
11962
+ }
11963
+ /**
11964
+ * Short one-line description of CLOSED.
11965
+ */
11966
+ get description() {
11967
+ return 'Prevent the agent from being modified by conversation.';
11968
+ }
11969
+ /**
11970
+ * Icon for this commitment.
11971
+ */
11972
+ get icon() {
11973
+ return '🔒';
11974
+ }
11975
+ /**
11976
+ * Markdown documentation for CLOSED commitment.
11977
+ */
11978
+ get documentation() {
11979
+ return spaceTrim$1(`
11980
+ # CLOSED
11766
11981
 
11767
- /**
11768
- * Converts a name string into a URI-compatible format.
11769
- *
11770
- * @param name The string to be converted to a URI-compatible format.
11771
- * @returns A URI-compatible string derived from the input name.
11772
- * @example 'Hello World' -> 'hello-world'
11773
- * @public exported from `@promptbook/utils`
11774
- */
11775
- function nameToUriPart(name) {
11776
- let uriPart = name;
11777
- uriPart = uriPart.toLowerCase();
11778
- uriPart = removeDiacritics(uriPart);
11779
- uriPart = uriPart.replace(/[^a-zA-Z0-9]+/g, '-');
11780
- uriPart = uriPart.replace(/^-+/, '');
11781
- uriPart = uriPart.replace(/-+$/, '');
11782
- return uriPart;
11783
- }
11982
+ Specifies that the agent **cannot** be modified by conversation with it.
11983
+ This means the agent will **not** learn from interactions and its source code will remain static during conversation.
11784
11984
 
11785
- /**
11786
- * Converts a given name into URI-compatible parts.
11787
- *
11788
- * @param name The name to be converted into URI parts.
11789
- * @returns An array of URI-compatible parts derived from the name.
11790
- * @example 'Example Name' -> ['example', 'name']
11791
- * @public exported from `@promptbook/utils`
11792
- */
11793
- function nameToUriParts(name) {
11794
- return nameToUriPart(name)
11795
- .split('-')
11796
- .filter((value) => value !== '');
11797
- }
11985
+ By default (if not specified), agents are \`OPEN\` to modification.
11798
11986
 
11799
- /**
11800
- * Normalizes a given text to PascalCase format.
11801
- *
11802
- * Note: [🔂] This function is idempotent.
11803
- *
11804
- * @param text @public exported from `@promptbook/utils`
11805
- * @returns
11806
- * @example 'HelloWorld'
11807
- * @example 'ILovePromptbook'
11808
- * @public exported from `@promptbook/utils`
11809
- */
11810
- function normalizeTo_PascalCase(text) {
11811
- return normalizeTo_camelCase(text, true);
11812
- }
11987
+ > See also [OPEN](/docs/OPEN)
11988
+
11989
+ ## Example
11813
11990
 
11991
+ \`\`\`book
11992
+ CLOSED
11993
+ \`\`\`
11994
+ `);
11995
+ }
11996
+ applyToAgentModelRequirements(requirements, _content) {
11997
+ const updatedMetadata = {
11998
+ ...requirements.metadata,
11999
+ isClosed: true,
12000
+ };
12001
+ return {
12002
+ ...requirements,
12003
+ metadata: updatedMetadata,
12004
+ };
12005
+ }
12006
+ }
11814
12007
  /**
11815
- * Take every whitespace (space, new line, tab) and replace it with a single space
11816
- *
11817
- * Note: [🔂] This function is idempotent.
11818
- *
11819
- * @public exported from `@promptbook/utils`
12008
+ * Note: [💞] Ignore a discrepancy between file name and entity name
11820
12009
  */
11821
- function normalizeWhitespaces(sentence) {
11822
- return sentence.replace(/\s+/gs, ' ').trim();
11823
- }
11824
12010
 
11825
12011
  /**
11826
- * Removes quotes and optional introduce text from a string
12012
+ * COMPONENT commitment definition
11827
12013
  *
11828
- * Tip: This is very useful for post-processing of the result of the LLM model
11829
- * Note: This function trims the text and removes whole introduce sentence if it is present
11830
- * Note: There are two similar functions:
11831
- * - `removeQuotes` which removes only bounding quotes
11832
- * - `unwrapResult` which removes whole introduce sentence
12014
+ * The COMPONENT commitment defines a UI component that the agent can render in the chat.
11833
12015
  *
11834
- * @param text optionally quoted text
11835
- * @returns text without quotes
11836
- * @public exported from `@promptbook/utils`
12016
+ * @private [🪔] Maybe export the commitments through some package
11837
12017
  */
11838
- function unwrapResult(text, options) {
11839
- const { isTrimmed = true, isIntroduceSentenceRemoved = true } = options || {};
11840
- let trimmedText = text;
11841
- // Remove leading and trailing spaces and newlines
11842
- if (isTrimmed) {
11843
- trimmedText = spaceTrim$1(trimmedText);
11844
- }
11845
- let processedText = trimmedText;
11846
- // Check for markdown code block
11847
- const codeBlockRegex = /^```[a-z]*\n([\s\S]*?)\n```\s*$/;
11848
- const codeBlockMatch = processedText.match(codeBlockRegex);
11849
- if (codeBlockMatch && codeBlockMatch[1] !== undefined) {
11850
- // Check if there's only one code block
11851
- const codeBlockCount = (processedText.match(/```/g) || []).length / 2;
11852
- if (codeBlockCount === 1) {
11853
- return unwrapResult(codeBlockMatch[1], { isTrimmed: false, isIntroduceSentenceRemoved: false });
11854
- }
12018
+ class ComponentCommitmentDefinition extends BaseCommitmentDefinition {
12019
+ constructor() {
12020
+ super('COMPONENT');
11855
12021
  }
11856
- if (isIntroduceSentenceRemoved) {
11857
- const introduceSentenceRegex = /^[a-zěščřžýáíéúů:\s]*:\s*/i;
11858
- if (introduceSentenceRegex.test(text)) {
11859
- // Remove the introduce sentence and quotes by replacing it with an empty string
11860
- processedText = processedText.replace(introduceSentenceRegex, '');
11861
- }
11862
- processedText = spaceTrim$1(processedText);
11863
- // Check again for code block after removing introduce sentence
11864
- const codeBlockMatch2 = processedText.match(codeBlockRegex);
11865
- if (codeBlockMatch2 && codeBlockMatch2[1] !== undefined) {
11866
- const codeBlockCount = (processedText.match(/```/g) || []).length / 2;
11867
- if (codeBlockCount === 1) {
11868
- return unwrapResult(codeBlockMatch2[1], { isTrimmed: false, isIntroduceSentenceRemoved: false });
11869
- }
11870
- }
12022
+ /**
12023
+ * Short one-line description of COMPONENT.
12024
+ */
12025
+ get description() {
12026
+ return 'Define a UI component that the agent can render in the chat.';
11871
12027
  }
11872
- if (processedText.length < 3) {
11873
- return trimmedText;
12028
+ /**
12029
+ * Icon for this commitment.
12030
+ */
12031
+ get icon() {
12032
+ return '🧩';
11874
12033
  }
11875
- if (processedText.includes('\n')) {
11876
- return trimmedText;
12034
+ /**
12035
+ * Markdown documentation for COMPONENT commitment.
12036
+ */
12037
+ get documentation() {
12038
+ return spaceTrim$1(`
12039
+ # COMPONENT
12040
+
12041
+ Defines a UI component that the agent can render in the chat.
12042
+
12043
+ ## Key aspects
12044
+
12045
+ - Tells the agent that a specific component is available.
12046
+ - Provides syntax for using the component.
12047
+
12048
+ ## Example
12049
+
12050
+ \`\`\`book
12051
+ COMPONENT Arrow
12052
+ The agent should render an arrow component in the chat UI.
12053
+ Syntax:
12054
+ <Arrow direction="up" color="red" />
12055
+ \`\`\`
12056
+ `);
11877
12057
  }
11878
- // Remove the quotes by extracting the substring without the first and last characters
11879
- const unquotedText = processedText.slice(1, -1);
11880
- // Check if the text starts and ends with quotes
11881
- if ([
11882
- ['"', '"'],
11883
- ["'", "'"],
11884
- ['`', '`'],
11885
- ['*', '*'],
11886
- ['_', '_'],
11887
- ['„', '“'],
11888
- ['«', '»'] /* <- QUOTES to config */,
11889
- ].some(([startQuote, endQuote]) => {
11890
- if (!processedText.startsWith(startQuote)) {
11891
- return false;
11892
- }
11893
- if (!processedText.endsWith(endQuote)) {
11894
- return false;
11895
- }
11896
- if (unquotedText.includes(startQuote) && !unquotedText.includes(endQuote)) {
11897
- return false;
11898
- }
11899
- if (!unquotedText.includes(startQuote) && unquotedText.includes(endQuote)) {
11900
- return false;
12058
+ applyToAgentModelRequirements(requirements, content) {
12059
+ const trimmedContent = content.trim();
12060
+ if (!trimmedContent) {
12061
+ return requirements;
11901
12062
  }
11902
- return true;
11903
- })) {
11904
- return unwrapResult(unquotedText, { isTrimmed: false, isIntroduceSentenceRemoved: false });
11905
- }
11906
- else {
11907
- return processedText;
12063
+ // Add component capability to the system message
12064
+ const componentSection = `Component: ${trimmedContent}`;
12065
+ return this.appendToSystemMessage(requirements, componentSection, '\n\n');
11908
12066
  }
11909
12067
  }
11910
12068
  /**
11911
- * TODO: [🧠] Should this also unwrap the (parenthesis)
12069
+ * Note: [💞] Ignore a discrepancy between file name and entity name
11912
12070
  */
11913
12071
 
11914
- // <- TODO: Auto convert to type `import { ... } from 'type-fest';`
11915
12072
  /**
11916
- * Tests if the value is [🚉] serializable as JSON
12073
+ * DELETE commitment definition
11917
12074
  *
11918
- * - Almost all primitives are serializable BUT:
11919
- * - `undefined` is not serializable
11920
- * - `NaN` is not serializable
11921
- * - Objects and arrays are serializable if all their properties are serializable
11922
- * - Functions are not serializable
11923
- * - Circular references are not serializable
11924
- * - `Date` objects are not serializable
11925
- * - `Map` and `Set` objects are not serializable
11926
- * - `RegExp` objects are not serializable
11927
- * - `Error` objects are not serializable
11928
- * - `Symbol` objects are not serializable
11929
- * - And much more...
11930
- *
11931
- *
11932
- * @public exported from `@promptbook/utils`
11933
- */
11934
- function isSerializableAsJson(value) {
11935
- try {
11936
- checkSerializableAsJson({ value });
11937
- return true;
11938
- }
11939
- catch (error) {
11940
- return false;
11941
- }
11942
- }
11943
- /**
11944
- * TODO: [🧠][main] !!3 In-memory cache of same values to prevent multiple checks
11945
- * TODO: [🧠][💺] Can be done this on type-level?
11946
- */
11947
-
11948
- /**
11949
- * Tests if given string is valid agent URL
11950
- *
11951
- * Note: There are few similar functions:
11952
- * - `isValidUrl` which tests any URL
11953
- * - `isValidAgentUrl` *(this one)* which tests just agent URL
11954
- * - `isValidPipelineUrl` which tests just pipeline URL
11955
- *
11956
- * @public exported from `@promptbook/utils`
11957
- */
11958
- function isValidAgentUrl(url) {
11959
- if (!isValidUrl(url)) {
11960
- return false;
11961
- }
11962
- if (!url.startsWith('https://') && !url.startsWith('http://') /* <- Note: [👣] */) {
11963
- return false;
11964
- }
11965
- if (url.includes('#')) {
11966
- // TODO: [🐠]
11967
- return false;
11968
- }
11969
- /*
11970
- Note: [👣][🧠] Is it secure to allow pipeline URLs on private and unsecured networks?
11971
- if (isUrlOnPrivateNetwork(url)) {
11972
- return false;
11973
- }
11974
- */
11975
- return true;
11976
- }
11977
- /**
11978
- * TODO: [🐠] Maybe more info why the URL is invalid
11979
- */
11980
-
11981
- /**
11982
- * Generates a regex pattern to match a specific commitment
11983
- *
11984
- * Note: It always creates new Regex object
11985
- * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
11986
- *
11987
- * @private - TODO: [🧠] Maybe should be public?
11988
- */
11989
- function createCommitmentRegex(commitment, aliases = [], requiresContent = true) {
11990
- const allCommitments = [commitment, ...aliases];
11991
- const patterns = allCommitments.map((commitment) => {
11992
- const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
11993
- return escapedCommitment.split(/\s+/).join('\\s+');
11994
- });
11995
- const keywordPattern = patterns.join('|');
11996
- if (requiresContent) {
11997
- return new RegExp(`^\\s*(?<type>${keywordPattern})\\b\\s+(?<contents>.+)$`, 'gim');
11998
- }
11999
- else {
12000
- return new RegExp(`^\\s*(?<type>${keywordPattern})\\b(?:\\s+(?<contents>.+))?$`, 'gim');
12001
- }
12002
- }
12003
- /**
12004
- * Generates a regex pattern to match a specific commitment type
12005
- *
12006
- * Note: It just matches the type part of the commitment
12007
- * Note: It always creates new Regex object
12008
- * Note: Uses word boundaries to ensure only full words are matched (e.g., "PERSONA" matches but "PERSONALITY" does not)
12009
- *
12010
- * @private
12011
- */
12012
- function createCommitmentTypeRegex(commitment, aliases = []) {
12013
- const allCommitments = [commitment, ...aliases];
12014
- const patterns = allCommitments.map((commitment) => {
12015
- const escapedCommitment = commitment.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
12016
- return escapedCommitment.split(/\s+/).join('\\s+');
12017
- });
12018
- const keywordPattern = patterns.join('|');
12019
- const regex = new RegExp(`^\\s*(?<type>${keywordPattern})\\b`, 'gim');
12020
- return regex;
12021
- }
12022
-
12023
- /**
12024
- * Base implementation of CommitmentDefinition that provides common functionality
12025
- * Most commitments can extend this class and only override the applyToAgentModelRequirements method
12026
- *
12027
- * @private
12028
- */
12029
- class BaseCommitmentDefinition {
12030
- constructor(type, aliases = []) {
12031
- this.type = type;
12032
- this.aliases = aliases;
12033
- }
12034
- /**
12035
- * Whether this commitment requires content.
12036
- * If true, regex will match only if there is content after the commitment keyword.
12037
- * If false, regex will match even if there is no content.
12038
- */
12039
- get requiresContent() {
12040
- return true;
12041
- }
12042
- /**
12043
- * Creates a regex pattern to match this commitment in agent source
12044
- * Uses the existing createCommitmentRegex function as internal helper
12045
- */
12046
- createRegex() {
12047
- return createCommitmentRegex(this.type, this.aliases, this.requiresContent);
12048
- }
12049
- /**
12050
- * Creates a regex pattern to match just the commitment type
12051
- * Uses the existing createCommitmentTypeRegex function as internal helper
12052
- */
12053
- createTypeRegex() {
12054
- return createCommitmentTypeRegex(this.type, this.aliases);
12055
- }
12056
- /**
12057
- * Helper method to create a new requirements object with updated system message
12058
- * This is commonly used by many commitments
12059
- */
12060
- updateSystemMessage(requirements, messageUpdate) {
12061
- const newMessage = typeof messageUpdate === 'string' ? messageUpdate : messageUpdate(requirements.systemMessage);
12062
- return {
12063
- ...requirements,
12064
- systemMessage: newMessage,
12065
- };
12066
- }
12067
- /**
12068
- * Helper method to append content to the system message
12069
- */
12070
- appendToSystemMessage(requirements, content, separator = '\n\n') {
12071
- return this.updateSystemMessage(requirements, (currentMessage) => {
12072
- if (!currentMessage.trim()) {
12073
- return content;
12074
- }
12075
- return currentMessage + separator + content;
12076
- });
12077
- }
12078
- /**
12079
- * Helper method to add a comment section to the system message
12080
- * Comments are lines starting with # that will be removed from the final system message
12081
- * but can be useful for organizing and structuring the message during processing
12082
- */
12083
- addCommentSection(requirements, commentTitle, content, position = 'end') {
12084
- const commentSection = `# ${commentTitle.toUpperCase()}\n${content}`;
12085
- if (position === 'beginning') {
12086
- return this.updateSystemMessage(requirements, (currentMessage) => {
12087
- if (!currentMessage.trim()) {
12088
- return commentSection;
12089
- }
12090
- return commentSection + '\n\n' + currentMessage;
12091
- });
12092
- }
12093
- else {
12094
- return this.appendToSystemMessage(requirements, commentSection);
12095
- }
12096
- }
12097
- /**
12098
- * Gets tool function implementations provided by this commitment
12099
- *
12100
- * When the `applyToAgentModelRequirements` adds tools to the requirements, this method should return the corresponding function definitions.
12101
- */
12102
- getToolFunctions() {
12103
- return {};
12104
- }
12105
- /**
12106
- * Gets human-readable titles for tool functions provided by this commitment
12107
- *
12108
- * This is used in the UI to show a user-friendly name instead of the technical function name.
12109
- */
12110
- getToolTitles() {
12111
- return {};
12112
- }
12113
- }
12114
-
12115
- /**
12116
- * ACTION commitment definition
12117
- *
12118
- * The ACTION commitment defines specific actions or capabilities that the agent can perform.
12119
- * This helps define what the agent is capable of doing and how it should approach tasks.
12075
+ * The DELETE commitment (and its aliases CANCEL, DISCARD, REMOVE) is used to
12076
+ * remove or disregard certain information or context. This can be useful for
12077
+ * overriding previous commitments or removing unwanted behaviors.
12120
12078
  *
12121
12079
  * Example usage in agent source:
12122
12080
  *
12123
12081
  * ```book
12124
- * ACTION Can generate code snippets and explain programming concepts
12125
- * ACTION Able to analyze data and provide insights
12082
+ * DELETE Previous formatting requirements
12083
+ * CANCEL All emotional responses
12084
+ * DISCARD Technical jargon explanations
12085
+ * REMOVE Casual conversational style
12126
12086
  * ```
12127
12087
  *
12128
12088
  * @private [🪔] Maybe export the commitments through some package
12129
12089
  */
12130
- class ActionCommitmentDefinition extends BaseCommitmentDefinition {
12131
- constructor(type = 'ACTION') {
12090
+ class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
12091
+ constructor(type) {
12132
12092
  super(type);
12133
12093
  }
12134
12094
  /**
12135
- * Short one-line description of ACTION.
12095
+ * Short one-line description of DELETE/CANCEL/DISCARD/REMOVE.
12136
12096
  */
12137
12097
  get description() {
12138
- return 'Define agent capabilities and actions it can perform.';
12098
+ return 'Remove or **disregard** certain information, context, or previous commitments.';
12139
12099
  }
12140
12100
  /**
12141
12101
  * Icon for this commitment.
12142
12102
  */
12143
12103
  get icon() {
12144
- return '';
12104
+ return '🗑️';
12145
12105
  }
12146
12106
  /**
12147
- * Markdown documentation for ACTION commitment.
12107
+ * Markdown documentation for DELETE commitment.
12148
12108
  */
12149
12109
  get documentation() {
12150
12110
  return spaceTrim$1(`
12151
- # ${this.type}
12111
+ # DELETE (CANCEL, DISCARD, REMOVE)
12152
12112
 
12153
- Defines specific actions or capabilities that the agent can perform.
12113
+ A commitment to remove or disregard certain information or context. This can be useful for overriding previous commitments or removing unwanted behaviors.
12114
+
12115
+ ## Aliases
12116
+
12117
+ - \`DELETE\` - Remove or eliminate something
12118
+ - \`CANCEL\` - Cancel or nullify something
12119
+ - \`DISCARD\` - Discard or ignore something
12120
+ - \`REMOVE\` - Remove or take away something
12154
12121
 
12155
12122
  ## Key aspects
12156
12123
 
12157
- - Both terms work identically and can be used interchangeably.
12158
- - Each action adds to the agent's capability list.
12159
- - Actions help users understand what the agent can do.
12124
+ - Multiple delete commitments can be used to remove different aspects.
12125
+ - Useful for overriding previous commitments in the same agent definition.
12126
+ - Can be used to remove inherited behaviors from base personas.
12127
+ - Helps fine-tune agent behavior by explicitly removing unwanted elements.
12128
+
12129
+ ## Use cases
12130
+
12131
+ - Overriding inherited persona characteristics
12132
+ - Removing conflicting or outdated instructions
12133
+ - Disabling specific response patterns
12134
+ - Canceling previous formatting or style requirements
12160
12135
 
12161
12136
  ## Examples
12162
12137
 
12163
12138
  \`\`\`book
12164
- Code Assistant
12139
+ Serious Business Assistant
12165
12140
 
12166
- PERSONA You are a programming assistant
12167
- ACTION Can generate code snippets and explain programming concepts
12168
- ACTION Able to debug existing code and suggest improvements
12169
- ACTION Can create unit tests for functions
12141
+ PERSONA You are a friendly and casual assistant who uses emojis
12142
+ DELETE Casual conversational style
12143
+ REMOVE All emoji usage
12144
+ GOAL Provide professional business communications
12145
+ STYLE Use formal language and proper business etiquette
12170
12146
  \`\`\`
12171
12147
 
12172
12148
  \`\`\`book
12173
- Data Scientist
12174
-
12175
- PERSONA You are a data analysis expert
12176
- ACTION Able to analyze data and provide insights
12177
- ACTION Can create visualizations and charts
12178
- ACTION Capable of statistical analysis and modeling
12179
- KNOWLEDGE Data analysis best practices and statistical methods
12180
- \`\`\`
12181
- `);
12182
- }
12183
- applyToAgentModelRequirements(requirements, content) {
12184
- const trimmedContent = content.trim();
12185
- if (!trimmedContent) {
12186
- return requirements;
12187
- }
12188
- // Add action capability to the system message
12189
- const actionSection = `Capability: ${trimmedContent}`;
12190
- return this.appendToSystemMessage(requirements, actionSection, '\n\n');
12191
- }
12192
- }
12193
- /**
12194
- * Note: [💞] Ignore a discrepancy between file name and entity name
12195
- */
12196
-
12197
- /**
12198
- * CLOSED commitment definition
12199
- *
12200
- * The CLOSED commitment specifies that the agent CANNOT be modified by conversation.
12201
- * It prevents the agent from learning from interactions and updating its source code.
12202
- *
12203
- * Example usage in agent source:
12204
- *
12205
- * ```book
12206
- * CLOSED
12207
- * ```
12208
- *
12209
- * @private [🪔] Maybe export the commitments through some package
12210
- */
12211
- class ClosedCommitmentDefinition extends BaseCommitmentDefinition {
12212
- constructor() {
12213
- super('CLOSED');
12214
- }
12215
- /**
12216
- * The `CLOSED` commitment is standalone.
12217
- */
12218
- get requiresContent() {
12219
- return false;
12220
- }
12221
- /**
12222
- * Short one-line description of CLOSED.
12223
- */
12224
- get description() {
12225
- return 'Prevent the agent from being modified by conversation.';
12226
- }
12227
- /**
12228
- * Icon for this commitment.
12229
- */
12230
- get icon() {
12231
- return '🔒';
12232
- }
12233
- /**
12234
- * Markdown documentation for CLOSED commitment.
12235
- */
12236
- get documentation() {
12237
- return spaceTrim$1(`
12238
- # CLOSED
12239
-
12240
- Specifies that the agent **cannot** be modified by conversation with it.
12241
- This means the agent will **not** learn from interactions and its source code will remain static during conversation.
12242
-
12243
- By default (if not specified), agents are \`OPEN\` to modification.
12244
-
12245
- > See also [OPEN](/docs/OPEN)
12246
-
12247
- ## Example
12248
-
12249
- \`\`\`book
12250
- CLOSED
12251
- \`\`\`
12252
- `);
12253
- }
12254
- applyToAgentModelRequirements(requirements, _content) {
12255
- const updatedMetadata = {
12256
- ...requirements.metadata,
12257
- isClosed: true,
12258
- };
12259
- return {
12260
- ...requirements,
12261
- metadata: updatedMetadata,
12262
- };
12263
- }
12264
- }
12265
- /**
12266
- * Note: [💞] Ignore a discrepancy between file name and entity name
12267
- */
12268
-
12269
- /**
12270
- * COMPONENT commitment definition
12271
- *
12272
- * The COMPONENT commitment defines a UI component that the agent can render in the chat.
12273
- *
12274
- * @private [🪔] Maybe export the commitments through some package
12275
- */
12276
- class ComponentCommitmentDefinition extends BaseCommitmentDefinition {
12277
- constructor() {
12278
- super('COMPONENT');
12279
- }
12280
- /**
12281
- * Short one-line description of COMPONENT.
12282
- */
12283
- get description() {
12284
- return 'Define a UI component that the agent can render in the chat.';
12285
- }
12286
- /**
12287
- * Icon for this commitment.
12288
- */
12289
- get icon() {
12290
- return '🧩';
12291
- }
12292
- /**
12293
- * Markdown documentation for COMPONENT commitment.
12294
- */
12295
- get documentation() {
12296
- return spaceTrim$1(`
12297
- # COMPONENT
12298
-
12299
- Defines a UI component that the agent can render in the chat.
12300
-
12301
- ## Key aspects
12302
-
12303
- - Tells the agent that a specific component is available.
12304
- - Provides syntax for using the component.
12305
-
12306
- ## Example
12307
-
12308
- \`\`\`book
12309
- COMPONENT Arrow
12310
- The agent should render an arrow component in the chat UI.
12311
- Syntax:
12312
- <Arrow direction="up" color="red" />
12313
- \`\`\`
12314
- `);
12315
- }
12316
- applyToAgentModelRequirements(requirements, content) {
12317
- const trimmedContent = content.trim();
12318
- if (!trimmedContent) {
12319
- return requirements;
12320
- }
12321
- // Add component capability to the system message
12322
- const componentSection = `Component: ${trimmedContent}`;
12323
- return this.appendToSystemMessage(requirements, componentSection, '\n\n');
12324
- }
12325
- }
12326
- /**
12327
- * Note: [💞] Ignore a discrepancy between file name and entity name
12328
- */
12329
-
12330
- /**
12331
- * DELETE commitment definition
12332
- *
12333
- * The DELETE commitment (and its aliases CANCEL, DISCARD, REMOVE) is used to
12334
- * remove or disregard certain information or context. This can be useful for
12335
- * overriding previous commitments or removing unwanted behaviors.
12336
- *
12337
- * Example usage in agent source:
12338
- *
12339
- * ```book
12340
- * DELETE Previous formatting requirements
12341
- * CANCEL All emotional responses
12342
- * DISCARD Technical jargon explanations
12343
- * REMOVE Casual conversational style
12344
- * ```
12345
- *
12346
- * @private [🪔] Maybe export the commitments through some package
12347
- */
12348
- class DeleteCommitmentDefinition extends BaseCommitmentDefinition {
12349
- constructor(type) {
12350
- super(type);
12351
- }
12352
- /**
12353
- * Short one-line description of DELETE/CANCEL/DISCARD/REMOVE.
12354
- */
12355
- get description() {
12356
- return 'Remove or **disregard** certain information, context, or previous commitments.';
12357
- }
12358
- /**
12359
- * Icon for this commitment.
12360
- */
12361
- get icon() {
12362
- return '🗑️';
12363
- }
12364
- /**
12365
- * Markdown documentation for DELETE commitment.
12366
- */
12367
- get documentation() {
12368
- return spaceTrim$1(`
12369
- # DELETE (CANCEL, DISCARD, REMOVE)
12370
-
12371
- A commitment to remove or disregard certain information or context. This can be useful for overriding previous commitments or removing unwanted behaviors.
12372
-
12373
- ## Aliases
12374
-
12375
- - \`DELETE\` - Remove or eliminate something
12376
- - \`CANCEL\` - Cancel or nullify something
12377
- - \`DISCARD\` - Discard or ignore something
12378
- - \`REMOVE\` - Remove or take away something
12379
-
12380
- ## Key aspects
12381
-
12382
- - Multiple delete commitments can be used to remove different aspects.
12383
- - Useful for overriding previous commitments in the same agent definition.
12384
- - Can be used to remove inherited behaviors from base personas.
12385
- - Helps fine-tune agent behavior by explicitly removing unwanted elements.
12386
-
12387
- ## Use cases
12388
-
12389
- - Overriding inherited persona characteristics
12390
- - Removing conflicting or outdated instructions
12391
- - Disabling specific response patterns
12392
- - Canceling previous formatting or style requirements
12393
-
12394
- ## Examples
12395
-
12396
- \`\`\`book
12397
- Serious Business Assistant
12398
-
12399
- PERSONA You are a friendly and casual assistant who uses emojis
12400
- DELETE Casual conversational style
12401
- REMOVE All emoji usage
12402
- GOAL Provide professional business communications
12403
- STYLE Use formal language and proper business etiquette
12404
- \`\`\`
12405
-
12406
- \`\`\`book
12407
- Simplified Technical Support
12149
+ Simplified Technical Support
12408
12150
 
12409
12151
  PERSONA You are a technical support specialist with deep expertise
12410
12152
  KNOWLEDGE Extensive database of technical specifications
@@ -15354,50 +15096,173 @@ function stripToolCallLines(text) {
15354
15096
  */
15355
15097
 
15356
15098
  /**
15357
- * USE commitment definition
15358
- *
15359
- * The USE commitment indicates that the agent should utilize specific tools or capabilities
15360
- * to access and interact with external systems when necessary.
15361
- *
15362
- * Supported USE types:
15363
- * - USE BROWSER: Enables the agent to use a web browser tool
15364
- * - USE SEARCH ENGINE (future): Enables search engine access
15365
- * - USE FILE SYSTEM (future): Enables file system operations
15366
- * - USE MCP (future): Enables MCP server connections
15099
+ * TEMPLATE commitment definition
15367
15100
  *
15368
- * The content following the USE commitment is ignored (similar to NOTE).
15101
+ * The TEMPLATE commitment enforces a specific response structure or template
15102
+ * that the agent must follow when generating responses. This helps ensure
15103
+ * consistent message formatting across all agent interactions.
15369
15104
  *
15370
15105
  * Example usage in agent source:
15371
15106
  *
15372
15107
  * ```book
15373
- * USE BROWSER
15374
- * USE SEARCH ENGINE
15108
+ * TEMPLATE Always structure your response with: 1) Summary, 2) Details, 3) Next steps
15109
+ * TEMPLATE Use the following format: **Question:** [user question] | **Answer:** [your answer]
15375
15110
  * ```
15376
15111
  *
15112
+ * When used without content, it enables template mode which instructs the agent
15113
+ * to follow any template patterns defined in other commitments or context.
15114
+ *
15377
15115
  * @private [🪔] Maybe export the commitments through some package
15378
15116
  */
15379
- class UseCommitmentDefinition extends BaseCommitmentDefinition {
15380
- constructor() {
15381
- super('USE');
15117
+ class TemplateCommitmentDefinition extends BaseCommitmentDefinition {
15118
+ constructor(type = 'TEMPLATE') {
15119
+ super(type);
15382
15120
  }
15383
15121
  /**
15384
- * Short one-line description of USE commitments.
15122
+ * Short one-line description of TEMPLATE.
15385
15123
  */
15386
15124
  get description() {
15387
- return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, etc.).';
15125
+ return 'Enforce a specific message structure or response template.';
15388
15126
  }
15389
15127
  /**
15390
15128
  * Icon for this commitment.
15391
15129
  */
15392
15130
  get icon() {
15393
- return '🔧';
15131
+ return '📋';
15394
15132
  }
15395
15133
  /**
15396
- * Markdown documentation for USE commitment.
15134
+ * Markdown documentation for TEMPLATE commitment.
15397
15135
  */
15398
15136
  get documentation() {
15399
15137
  return spaceTrim$1(`
15400
- # USE
15138
+ # ${this.type}
15139
+
15140
+ Enforces a specific response structure or template that the agent must follow when generating responses.
15141
+
15142
+ ## Key aspects
15143
+
15144
+ - Both terms work identically and can be used interchangeably.
15145
+ - Can be used with or without content.
15146
+ - When used without content, enables template mode for structured responses.
15147
+ - When used with content, defines the specific template structure to follow.
15148
+ - Multiple templates can be combined, with later ones taking precedence.
15149
+
15150
+ ## Examples
15151
+
15152
+ \`\`\`book
15153
+ Customer Support Agent
15154
+
15155
+ PERSONA You are a helpful customer support representative
15156
+ TEMPLATE Always structure your response with: 1) Acknowledgment, 2) Solution, 3) Follow-up question
15157
+ STYLE Be professional and empathetic
15158
+ \`\`\`
15159
+
15160
+ \`\`\`book
15161
+ Technical Documentation Assistant
15162
+
15163
+ PERSONA You are a technical writing expert
15164
+ TEMPLATE Use the following format: **Topic:** [topic] | **Explanation:** [details] | **Example:** [code]
15165
+ FORMAT Use markdown with clear headings
15166
+ \`\`\`
15167
+
15168
+ \`\`\`book
15169
+ Simple Agent
15170
+
15171
+ PERSONA You are a virtual assistant
15172
+ TEMPLATE
15173
+ \`\`\`
15174
+ `);
15175
+ }
15176
+ /**
15177
+ * TEMPLATE can be used with or without content.
15178
+ */
15179
+ get requiresContent() {
15180
+ return false;
15181
+ }
15182
+ applyToAgentModelRequirements(requirements, content) {
15183
+ var _a;
15184
+ const trimmedContent = content.trim();
15185
+ // If no content is provided, enable template mode
15186
+ if (!trimmedContent) {
15187
+ // Store template mode flag in metadata
15188
+ const updatedMetadata = {
15189
+ ...requirements.metadata,
15190
+ templateMode: true,
15191
+ };
15192
+ // Add a general instruction about using structured templates
15193
+ const templateModeInstruction = spaceTrim$1(`
15194
+ Use a clear, structured template format for your responses.
15195
+ Maintain consistency in how you organize and present information.
15196
+ `);
15197
+ return {
15198
+ ...this.appendToSystemMessage(requirements, templateModeInstruction, '\n\n'),
15199
+ metadata: updatedMetadata,
15200
+ };
15201
+ }
15202
+ // If content is provided, add the specific template instructions
15203
+ const templateSection = `Response Template: ${trimmedContent}`;
15204
+ // Store the template in metadata for potential programmatic access
15205
+ const existingTemplates = ((_a = requirements.metadata) === null || _a === void 0 ? void 0 : _a.templates) || [];
15206
+ const updatedMetadata = {
15207
+ ...requirements.metadata,
15208
+ templates: [...existingTemplates, trimmedContent],
15209
+ templateMode: true,
15210
+ };
15211
+ return {
15212
+ ...this.appendToSystemMessage(requirements, templateSection, '\n\n'),
15213
+ metadata: updatedMetadata,
15214
+ };
15215
+ }
15216
+ }
15217
+ /**
15218
+ * Note: [💞] Ignore a discrepancy between file name and entity name
15219
+ */
15220
+
15221
+ /**
15222
+ * USE commitment definition
15223
+ *
15224
+ * The USE commitment indicates that the agent should utilize specific tools or capabilities
15225
+ * to access and interact with external systems when necessary.
15226
+ *
15227
+ * Supported USE types:
15228
+ * - USE BROWSER: Enables the agent to use a web browser tool
15229
+ * - USE SEARCH ENGINE (future): Enables search engine access
15230
+ * - USE FILE SYSTEM (future): Enables file system operations
15231
+ * - USE MCP (future): Enables MCP server connections
15232
+ *
15233
+ * The content following the USE commitment is ignored (similar to NOTE).
15234
+ *
15235
+ * Example usage in agent source:
15236
+ *
15237
+ * ```book
15238
+ * USE BROWSER
15239
+ * USE SEARCH ENGINE
15240
+ * ```
15241
+ *
15242
+ * @private [🪔] Maybe export the commitments through some package
15243
+ */
15244
+ class UseCommitmentDefinition extends BaseCommitmentDefinition {
15245
+ constructor() {
15246
+ super('USE');
15247
+ }
15248
+ /**
15249
+ * Short one-line description of USE commitments.
15250
+ */
15251
+ get description() {
15252
+ return 'Enable the agent to use specific tools or capabilities (BROWSER, SEARCH ENGINE, etc.).';
15253
+ }
15254
+ /**
15255
+ * Icon for this commitment.
15256
+ */
15257
+ get icon() {
15258
+ return '🔧';
15259
+ }
15260
+ /**
15261
+ * Markdown documentation for USE commitment.
15262
+ */
15263
+ get documentation() {
15264
+ return spaceTrim$1(`
15265
+ # USE
15401
15266
 
15402
15267
  Enables the agent to use specific tools or capabilities for interacting with external systems.
15403
15268
 
@@ -15467,12 +15332,56 @@ class UseCommitmentDefinition extends BaseCommitmentDefinition {
15467
15332
  * Note: [💞] Ignore a discrepancy between file name and entity name
15468
15333
  */
15469
15334
 
15335
+ /**
15336
+ * Client-side safe wrapper for fetching URL content
15337
+ *
15338
+ * This function proxies requests to the Agents Server API endpoint for scraping,
15339
+ * making it safe to use in browser environments.
15340
+ *
15341
+ * @param url The URL to fetch and scrape
15342
+ * @param agentsServerUrl The base URL of the agents server (defaults to current origin)
15343
+ * @returns Markdown content from the URL
15344
+ *
15345
+ * @private internal utility for USE BROWSER commitment
15346
+ */
15347
+ async function fetchUrlContentViaBrowser(url, agentsServerUrl) {
15348
+ try {
15349
+ // Determine the agents server URL
15350
+ const baseUrl = agentsServerUrl || (typeof window !== 'undefined' ? window.location.origin : '');
15351
+ if (!baseUrl) {
15352
+ throw new Error('Agents server URL is required in non-browser environments');
15353
+ }
15354
+ // Build the API endpoint URL
15355
+ const apiUrl = new URL('/api/scrape', baseUrl);
15356
+ apiUrl.searchParams.set('url', url);
15357
+ // Fetch from the API endpoint
15358
+ const response = await fetch(apiUrl.toString());
15359
+ if (!response.ok) {
15360
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
15361
+ throw new Error(`Failed to scrape URL: ${errorData.error || response.statusText}`);
15362
+ }
15363
+ const data = await response.json();
15364
+ if (!data.success || !data.content) {
15365
+ throw new Error(`Scraping failed: ${data.error || 'No content returned'}`);
15366
+ }
15367
+ return data.content;
15368
+ }
15369
+ catch (error) {
15370
+ const errorMessage = error instanceof Error ? error.message : String(error);
15371
+ throw new Error(`Error fetching URL content via browser: ${errorMessage}`);
15372
+ }
15373
+ }
15374
+
15470
15375
  /**
15471
15376
  * USE BROWSER commitment definition
15472
15377
  *
15473
- * The `USE BROWSER` commitment indicates that the agent should utilize a web browser tool
15378
+ * The `USE BROWSER` commitment indicates that the agent should utilize browser tools
15474
15379
  * to access and retrieve up-to-date information from the internet when necessary.
15475
15380
  *
15381
+ * This commitment provides two levels of browser access:
15382
+ * 1. One-shot URL fetching: Simple function to fetch and scrape URL content
15383
+ * 2. Running browser: For complex tasks like scrolling, clicking, etc. (prepared but not active yet)
15384
+ *
15476
15385
  * The content following `USE BROWSER` is ignored (similar to NOTE).
15477
15386
  *
15478
15387
  * Example usage in agent source:
@@ -15498,7 +15407,7 @@ class UseBrowserCommitmentDefinition extends BaseCommitmentDefinition {
15498
15407
  * Short one-line description of USE BROWSER.
15499
15408
  */
15500
15409
  get description() {
15501
- return 'Enable the agent to use a web browser tool for accessing internet information.';
15410
+ return 'Enable the agent to use browser tools for accessing internet information.';
15502
15411
  }
15503
15412
  /**
15504
15413
  * Icon for this commitment.
@@ -15513,14 +15422,18 @@ class UseBrowserCommitmentDefinition extends BaseCommitmentDefinition {
15513
15422
  return spaceTrim$1(`
15514
15423
  # USE BROWSER
15515
15424
 
15516
- Enables the agent to use a web browser tool to access and retrieve up-to-date information from the internet.
15425
+ Enables the agent to use browser tools to access and retrieve up-to-date information from the internet.
15517
15426
 
15518
15427
  ## Key aspects
15519
15428
 
15520
15429
  - The content following \`USE BROWSER\` is ignored (similar to NOTE)
15430
+ - Provides two levels of browser access:
15431
+ 1. **One-shot URL fetching**: Simple function to fetch and scrape URL content (active)
15432
+ 2. **Running browser**: For complex tasks like scrolling, clicking, etc. (prepared but not active yet)
15521
15433
  - The actual browser tool usage is handled by the agent runtime
15522
- - Allows the agent to fetch current information from websites
15434
+ - Allows the agent to fetch current information from websites and documents
15523
15435
  - Useful for research tasks, fact-checking, and accessing dynamic content
15436
+ - Supports various content types including HTML pages and PDF documents
15524
15437
 
15525
15438
  ## Examples
15526
15439
 
@@ -15556,48 +15469,306 @@ class UseBrowserCommitmentDefinition extends BaseCommitmentDefinition {
15556
15469
  */
15557
15470
  getToolTitles() {
15558
15471
  return {
15559
- web_browser: 'Web browser',
15472
+ fetch_url_content: 'Fetch URL content',
15473
+ run_browser: 'Run browser',
15474
+ };
15475
+ }
15476
+ applyToAgentModelRequirements(requirements, content) {
15477
+ // Get existing tools array or create new one
15478
+ const existingTools = requirements.tools || [];
15479
+ // Add browser tools if not already present
15480
+ const toolsToAdd = [];
15481
+ // Tool 1: One-shot URL content fetching
15482
+ if (!existingTools.some((tool) => tool.name === 'fetch_url_content')) {
15483
+ toolsToAdd.push({
15484
+ name: 'fetch_url_content',
15485
+ description: spaceTrim$1(`
15486
+ Fetches and scrapes the content from a URL (webpage or document).
15487
+ This tool retrieves the content of the specified URL and converts it to markdown format.
15488
+ Use this when you need to access information from a specific website or document.
15489
+ Supports various content types including HTML pages and PDF documents.
15490
+ `),
15491
+ parameters: {
15492
+ type: 'object',
15493
+ properties: {
15494
+ url: {
15495
+ type: 'string',
15496
+ description: 'The URL to fetch and scrape (e.g., "https://example.com" or "https://example.com/document.pdf")',
15497
+ },
15498
+ },
15499
+ required: ['url'],
15500
+ },
15501
+ });
15502
+ }
15503
+ // Tool 2: Running browser (prepared but not active yet)
15504
+ if (!existingTools.some((tool) => tool.name === 'run_browser')) {
15505
+ toolsToAdd.push({
15506
+ name: 'run_browser',
15507
+ description: spaceTrim$1(`
15508
+ Launches a browser session for complex interactions.
15509
+ This tool is for advanced browser automation tasks like scrolling, clicking, form filling, etc.
15510
+ Note: This tool is prepared but not yet active. It will be implemented in a future update.
15511
+ `),
15512
+ parameters: {
15513
+ type: 'object',
15514
+ properties: {
15515
+ url: {
15516
+ type: 'string',
15517
+ description: 'The initial URL to navigate to',
15518
+ },
15519
+ actions: {
15520
+ type: 'array',
15521
+ description: 'Array of actions to perform in the browser',
15522
+ items: {
15523
+ type: 'object',
15524
+ properties: {
15525
+ type: {
15526
+ type: 'string',
15527
+ enum: ['navigate', 'click', 'scroll', 'type', 'wait'],
15528
+ },
15529
+ selector: {
15530
+ type: 'string',
15531
+ description: 'CSS selector for the element (for click, type actions)',
15532
+ },
15533
+ value: {
15534
+ type: 'string',
15535
+ description: 'Value to type or navigate to',
15536
+ },
15537
+ },
15538
+ },
15539
+ },
15540
+ },
15541
+ required: ['url'],
15542
+ },
15543
+ });
15544
+ }
15545
+ const updatedTools = [...existingTools, ...toolsToAdd];
15546
+ // Return requirements with updated tools and metadata
15547
+ return this.appendToSystemMessage({
15548
+ ...requirements,
15549
+ tools: updatedTools,
15550
+ metadata: {
15551
+ ...requirements.metadata,
15552
+ useBrowser: true,
15553
+ },
15554
+ }, spaceTrim$1(`
15555
+ You have access to browser tools to fetch and access content from the internet.
15556
+ - Use "fetch_url_content" to retrieve content from specific URLs (webpages or documents)
15557
+ - Use "run_browser" for complex browser interactions (note: not yet active)
15558
+ When you need to know information from a specific website or document, use the fetch_url_content tool.
15559
+ `));
15560
+ }
15561
+ /**
15562
+ * Gets the browser tool function implementations.
15563
+ *
15564
+ * This method automatically detects the environment and uses:
15565
+ * - Server-side: Direct scraping via fetchUrlContent (Node.js)
15566
+ * - Browser: Proxy through Agents Server API via fetchUrlContentViaBrowser
15567
+ */
15568
+ getToolFunctions() {
15569
+ return {
15570
+ /**
15571
+ * @@@
15572
+ *
15573
+ * Note: [🛺] This function has implementation both for browser and node, this is the proxied one for browser
15574
+ */
15575
+ async fetch_url_content(args) {
15576
+ console.log('!!!! [Tool] fetch_url_content called', { args });
15577
+ const { url } = args;
15578
+ return await fetchUrlContentViaBrowser(url);
15579
+ },
15580
+ /**
15581
+ * @@@
15582
+ */
15583
+ async run_browser(args) {
15584
+ console.log('!!!! [Tool] run_browser called', { args });
15585
+ const { url } = args;
15586
+ // This tool is prepared but not active yet
15587
+ return spaceTrim$1(`
15588
+ # Running browser
15589
+
15590
+ The running browser tool is not yet active.
15591
+
15592
+ Requested URL: ${url}
15593
+
15594
+ This feature will be implemented in a future update to support:
15595
+ - Complex browser interactions
15596
+ - Scrolling and navigation
15597
+ - Clicking and form filling
15598
+ - Taking screenshots
15599
+
15600
+ For now, please use the "fetch_url_content" tool instead.
15601
+ `);
15602
+ },
15560
15603
  };
15561
15604
  }
15605
+ }
15606
+ /**
15607
+ * Note: [💞] Ignore a discrepancy between file name and entity name
15608
+ */
15609
+
15610
+ /**
15611
+ * @@@
15612
+ *
15613
+ * @private utility for commitments
15614
+ */
15615
+ function formatOptionalInstructionBlock(label, content) {
15616
+ const trimmedContent = spaceTrim$1(content);
15617
+ if (!trimmedContent) {
15618
+ return '';
15619
+ }
15620
+ return spaceTrim$1((block) => `
15621
+ - ${label}:
15622
+ ${block(trimmedContent
15623
+ .split('\n')
15624
+ .map((line) => `- ${line}`)
15625
+ .join('\n'))}
15626
+ `);
15627
+ }
15628
+
15629
+ /**
15630
+ * USE EMAIL commitment definition
15631
+ *
15632
+ * The `USE EMAIL` commitment enables the agent to send emails.
15633
+ *
15634
+ * Example usage in agent source:
15635
+ *
15636
+ * ```book
15637
+ * USE EMAIL
15638
+ * USE EMAIL Write always formal and polite emails, always greet.
15639
+ * ```
15640
+ *
15641
+ * @private [🪔] Maybe export the commitments through some package
15642
+ */
15643
+ class UseEmailCommitmentDefinition extends BaseCommitmentDefinition {
15644
+ constructor() {
15645
+ super('USE EMAIL', ['EMAIL', 'MAIL']);
15646
+ }
15647
+ get requiresContent() {
15648
+ return false;
15649
+ }
15650
+ /**
15651
+ * Short one-line description of USE EMAIL.
15652
+ */
15653
+ get description() {
15654
+ return 'Enable the agent to send emails.';
15655
+ }
15656
+ /**
15657
+ * Icon for this commitment.
15658
+ */
15659
+ get icon() {
15660
+ return '📧';
15661
+ }
15662
+ /**
15663
+ * Markdown documentation for USE EMAIL commitment.
15664
+ */
15665
+ get documentation() {
15666
+ return spaceTrim$1(`
15667
+ # USE EMAIL
15668
+
15669
+ Enables the agent to send emails through the email service.
15670
+
15671
+ ## Key aspects
15672
+
15673
+ - The agent can send emails to specified recipients.
15674
+ - Supports multiple recipients, CC, subject, and markdown content.
15675
+ - Emails are queued and sent through configured email providers.
15676
+ - The content following \`USE EMAIL\` can provide additional instructions for email composition (e.g., style, tone, formatting preferences).
15677
+
15678
+ ## Examples
15679
+
15680
+ \`\`\`book
15681
+ Email Assistant
15682
+
15683
+ PERSONA You are a helpful assistant who can send emails.
15684
+ USE EMAIL
15685
+ \`\`\`
15686
+
15687
+ \`\`\`book
15688
+ Formal Email Assistant
15689
+
15690
+ PERSONA You help with professional communication.
15691
+ USE EMAIL Write always formal and polite emails, always greet.
15692
+ \`\`\`
15693
+ `);
15694
+ }
15562
15695
  applyToAgentModelRequirements(requirements, content) {
15696
+ const extraInstructions = formatOptionalInstructionBlock('Email instructions', content);
15563
15697
  // Get existing tools array or create new one
15564
15698
  const existingTools = requirements.tools || [];
15565
- // Add 'web_browser' to tools if not already present
15566
- const updatedTools = existingTools.some((tool) => tool.name === 'web_browser')
15699
+ // Add 'send_email' to tools if not already present
15700
+ const updatedTools = existingTools.some((tool) => tool.name === 'send_email')
15567
15701
  ? existingTools
15568
- : ([
15569
- // TODO: [🔰] Use through proper MCP server
15702
+ : [
15570
15703
  ...existingTools,
15571
15704
  {
15572
- name: 'web_browser',
15573
- description: spaceTrim$1(`
15574
- A tool that can browse the web.
15575
- Use this tool when you need to access specific websites or browse the internet.
15576
- `),
15705
+ name: 'send_email',
15706
+ description: `Send an email to one or more recipients. ${!content ? '' : `Style instructions: ${content}`}`,
15577
15707
  parameters: {
15578
15708
  type: 'object',
15579
15709
  properties: {
15580
- url: {
15710
+ to: {
15711
+ type: 'array',
15712
+ items: { type: 'string' },
15713
+ description: 'Array of recipient email addresses (e.g., ["user@example.com", "Jane Doe <jane@example.com>"])',
15714
+ },
15715
+ cc: {
15716
+ type: 'array',
15717
+ items: { type: 'string' },
15718
+ description: 'Optional array of CC email addresses',
15719
+ },
15720
+ subject: {
15581
15721
  type: 'string',
15582
- description: 'The URL to browse',
15722
+ description: 'Email subject line',
15723
+ },
15724
+ body: {
15725
+ type: 'string',
15726
+ description: 'Email body content in markdown format',
15583
15727
  },
15584
15728
  },
15585
- required: ['url'],
15729
+ required: ['to', 'subject', 'body'],
15586
15730
  },
15587
15731
  },
15588
- ]);
15732
+ // <- TODO: !!!! define the function in LLM tools
15733
+ ];
15589
15734
  // Return requirements with updated tools and metadata
15590
15735
  return this.appendToSystemMessage({
15591
15736
  ...requirements,
15592
15737
  tools: updatedTools,
15593
15738
  metadata: {
15594
15739
  ...requirements.metadata,
15595
- useBrowser: true,
15740
+ useEmail: content || true,
15596
15741
  },
15597
- }, spaceTrim$1(`
15598
- You have access to the web browser. Use it to access specific websites or browse the internet.
15599
- When you need to know some information from a specific website, use the tool provided to you.
15600
- `));
15742
+ }, spaceTrim$1((block) => `
15743
+ Email tool:
15744
+ - You have access to send emails via the tool "send_email".
15745
+ - Use it when you need to send emails to users or other recipients.
15746
+ - The email body should be written in markdown format.
15747
+ - Always ensure the email content is clear, professional, and appropriate.
15748
+ ${block(extraInstructions)}
15749
+ `));
15750
+ }
15751
+ /**
15752
+ * Gets human-readable titles for tool functions provided by this commitment.
15753
+ */
15754
+ getToolTitles() {
15755
+ return {
15756
+ send_email: 'Send email',
15757
+ };
15758
+ }
15759
+ /**
15760
+ * Gets the `send_email` tool function implementation.
15761
+ * Note: This is a placeholder - the actual implementation is provided by the agent server.
15762
+ */
15763
+ getToolFunctions() {
15764
+ return {
15765
+ async send_email(args) {
15766
+ console.log('!!!! [Tool] send_email called', { args });
15767
+ // This is a placeholder implementation
15768
+ // The actual implementation should be provided by the agent server
15769
+ throw new Error('send_email tool not implemented. This commitment requires integration with an email service.');
15770
+ },
15771
+ };
15601
15772
  }
15602
15773
  }
15603
15774
  /**
@@ -15866,25 +16037,6 @@ class SerpSearchEngine {
15866
16037
  }
15867
16038
  }
15868
16039
 
15869
- /**
15870
- * @@@
15871
- *
15872
- * @private utility for commitments
15873
- */
15874
- function formatOptionalInstructionBlock(label, content) {
15875
- const trimmedContent = spaceTrim$1(content);
15876
- if (!trimmedContent) {
15877
- return '';
15878
- }
15879
- return spaceTrim$1((block) => `
15880
- - ${label}:
15881
- ${block(trimmedContent
15882
- .split('\n')
15883
- .map((line) => `- ${line}`)
15884
- .join('\n'))}
15885
- `);
15886
- }
15887
-
15888
16040
  /**
15889
16041
  * USE SEARCH ENGINE commitment definition
15890
16042
  *
@@ -16253,151 +16405,991 @@ class NotYetImplementedCommitmentDefinition extends BaseCommitmentDefinition {
16253
16405
  return spaceTrim$1(`
16254
16406
  # ${this.type}
16255
16407
 
16256
- This commitment is not yet fully implemented.
16408
+ This commitment is not yet fully implemented.
16409
+
16410
+ ## Key aspects
16411
+
16412
+ - Content is appended directly to the system message.
16413
+ - No special processing or validation is performed.
16414
+ - Behavior preserved until proper implementation is added.
16415
+
16416
+ ## Status
16417
+
16418
+ - **Status:** Placeholder implementation
16419
+ - **Effect:** Appends content prefixed by commitment type
16420
+ - **Future:** Will be replaced with specialized logic
16421
+
16422
+ ## Examples
16423
+
16424
+ \`\`\`book
16425
+ Example Agent
16426
+
16427
+ PERSONA You are a helpful assistant
16428
+ ${this.type} Your content here
16429
+ RULE Always be helpful
16430
+ \`\`\`
16431
+ `);
16432
+ }
16433
+ applyToAgentModelRequirements(requirements, content) {
16434
+ const trimmedContent = content.trim();
16435
+ if (!trimmedContent) {
16436
+ return requirements;
16437
+ }
16438
+ // Add the commitment content 1:1 to the system message
16439
+ const commitmentLine = `${this.type} ${trimmedContent}`;
16440
+ return this.appendToSystemMessage(requirements, commitmentLine, '\n\n');
16441
+ }
16442
+ }
16443
+
16444
+ /**
16445
+ * Registry of all available commitment definitions
16446
+ * This array contains instances of all commitment definitions
16447
+ * This is the single source of truth for all commitments in the system
16448
+ *
16449
+ * @private Use functions to access commitments instead of this array directly
16450
+ */
16451
+ const COMMITMENT_REGISTRY = [
16452
+ // Fully implemented commitments
16453
+ new PersonaCommitmentDefinition('PERSONA'),
16454
+ new PersonaCommitmentDefinition('PERSONAE'),
16455
+ new KnowledgeCommitmentDefinition(),
16456
+ new MemoryCommitmentDefinition('MEMORY'),
16457
+ new MemoryCommitmentDefinition('MEMORIES'),
16458
+ new StyleCommitmentDefinition('STYLE'),
16459
+ new StyleCommitmentDefinition('STYLES'),
16460
+ new RuleCommitmentDefinition('RULES'),
16461
+ new RuleCommitmentDefinition('RULE'),
16462
+ new LanguageCommitmentDefinition('LANGUAGES'),
16463
+ new LanguageCommitmentDefinition('LANGUAGE'),
16464
+ new SampleCommitmentDefinition('SAMPLE'),
16465
+ new SampleCommitmentDefinition('EXAMPLE'),
16466
+ new FormatCommitmentDefinition('FORMAT'),
16467
+ new FormatCommitmentDefinition('FORMATS'),
16468
+ new TemplateCommitmentDefinition('TEMPLATE'),
16469
+ new TemplateCommitmentDefinition('TEMPLATES'),
16470
+ new FromCommitmentDefinition('FROM'),
16471
+ new ImportCommitmentDefinition('IMPORT'),
16472
+ new ImportCommitmentDefinition('IMPORTS'),
16473
+ new ModelCommitmentDefinition('MODEL'),
16474
+ new ModelCommitmentDefinition('MODELS'),
16475
+ new ActionCommitmentDefinition('ACTION'),
16476
+ new ActionCommitmentDefinition('ACTIONS'),
16477
+ new ComponentCommitmentDefinition(),
16478
+ new MetaImageCommitmentDefinition(),
16479
+ new MetaColorCommitmentDefinition(),
16480
+ new MetaFontCommitmentDefinition(),
16481
+ new MetaLinkCommitmentDefinition(),
16482
+ new MetaCommitmentDefinition(),
16483
+ new NoteCommitmentDefinition('NOTE'),
16484
+ new NoteCommitmentDefinition('NOTES'),
16485
+ new NoteCommitmentDefinition('COMMENT'),
16486
+ new NoteCommitmentDefinition('NONCE'),
16487
+ new NoteCommitmentDefinition('TODO'),
16488
+ new GoalCommitmentDefinition('GOAL'),
16489
+ new GoalCommitmentDefinition('GOALS'),
16490
+ new InitialMessageCommitmentDefinition(),
16491
+ new UserMessageCommitmentDefinition(),
16492
+ new AgentMessageCommitmentDefinition(),
16493
+ new MessageCommitmentDefinition('MESSAGE'),
16494
+ new MessageCommitmentDefinition('MESSAGES'),
16495
+ new ScenarioCommitmentDefinition('SCENARIO'),
16496
+ new ScenarioCommitmentDefinition('SCENARIOS'),
16497
+ new DeleteCommitmentDefinition('DELETE'),
16498
+ new DeleteCommitmentDefinition('CANCEL'),
16499
+ new DeleteCommitmentDefinition('DISCARD'),
16500
+ new DeleteCommitmentDefinition('REMOVE'),
16501
+ new DictionaryCommitmentDefinition(),
16502
+ new OpenCommitmentDefinition(),
16503
+ new ClosedCommitmentDefinition(),
16504
+ new TeamCommitmentDefinition(),
16505
+ new UseBrowserCommitmentDefinition(),
16506
+ new UseSearchEngineCommitmentDefinition(),
16507
+ new UseTimeCommitmentDefinition(),
16508
+ new UseEmailCommitmentDefinition(),
16509
+ new UseImageGeneratorCommitmentDefinition('USE IMAGE GENERATOR'),
16510
+ new UseImageGeneratorCommitmentDefinition('USE IMAGE GENERATION' /* <- TODO: Remove any */),
16511
+ new UseImageGeneratorCommitmentDefinition('IMAGE GENERATOR' /* <- TODO: Remove any */),
16512
+ new UseImageGeneratorCommitmentDefinition('IMAGE GENERATION' /* <- TODO: Remove any */),
16513
+ new UseImageGeneratorCommitmentDefinition('USE IMAGE' /* <- TODO: Remove any */),
16514
+ // <- Note: [⛹️] How to deal with commitment aliases with defined functions
16515
+ new UseMcpCommitmentDefinition(),
16516
+ new UseCommitmentDefinition(),
16517
+ // Not yet implemented commitments (using placeholder)
16518
+ new NotYetImplementedCommitmentDefinition('EXPECT'),
16519
+ new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
16520
+ new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
16521
+ new NotYetImplementedCommitmentDefinition('AVOID'),
16522
+ new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
16523
+ new NotYetImplementedCommitmentDefinition('CONTEXT'),
16524
+ // <- TODO: Prompt: Leverage aliases instead of duplicating commitment definitions
16525
+ ];
16526
+ /**
16527
+ * TODO: [🧠] Maybe create through standardized $register
16528
+ * Note: [💞] Ignore a discrepancy between file name and entity name
16529
+ */
16530
+
16531
+ /**
16532
+ * Gets all available commitment definitions
16533
+ * @returns Array of all commitment definitions
16534
+ *
16535
+ * @public exported from `@promptbook/core`
16536
+ */
16537
+ function getAllCommitmentDefinitions() {
16538
+ return $deepFreeze([...COMMITMENT_REGISTRY]);
16539
+ }
16540
+
16541
+ /**
16542
+ * Gets all function implementations provided by all commitments
16543
+ *
16544
+ * Note: This function is intended for browser use, there is also equivalent `getAllCommitmentsToolFunctionsForNode` for server use
16545
+ *
16546
+ * @public exported from `@promptbook/browser`
16547
+ */
16548
+ function getAllCommitmentsToolFunctionsForBrowser() {
16549
+ const allToolFunctions = {};
16550
+ for (const commitmentDefinition of getAllCommitmentDefinitions()) {
16551
+ const toolFunctions = commitmentDefinition.getToolFunctions();
16552
+ for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
16553
+ if (allToolFunctions[funcName] !== undefined &&
16554
+ just(false) /* <- Note: [⛹️] How to deal with commitment aliases */) {
16555
+ throw new UnexpectedError(`Duplicate tool function name detected: \`${funcName}\` provided by commitment \`${commitmentDefinition.type}\``);
16556
+ }
16557
+ allToolFunctions[funcName] = funcImpl;
16558
+ }
16559
+ }
16560
+ return allToolFunctions;
16561
+ }
16562
+
16563
+ /**
16564
+ * Gets all function implementations provided by all commitments
16565
+ *
16566
+ * Note: This function is intended for server use, there is also equivalent `getAllCommitmentsToolFunctionsForBrowser` for browser use
16567
+ *
16568
+ * @public exported from `@promptbook/node`
16569
+ */
16570
+ function getAllCommitmentsToolFunctionsForNode() {
16571
+ if (!$isRunningInNode()) {
16572
+ throw new EnvironmentMismatchError(spaceTrim(`
16573
+ Function getAllCommitmentsToolFunctionsForNode should be run in Node.js environment.
16574
+
16575
+ - In browser use getAllCommitmentsToolFunctionsForBrowser instead.
16576
+ - This function can include server-only tools which cannot run in browser environment.
16577
+
16578
+ `));
16579
+ }
16580
+ const allToolFunctionsInBrowser = getAllCommitmentsToolFunctionsForBrowser();
16581
+ const allToolFunctionsInNode = {
16582
+ /**
16583
+ * @@@
16584
+ *
16585
+ * Note: [🛺] This function has implementation both for browser and node, this is the full one for node
16586
+ */
16587
+ async fetch_url_content(args) {
16588
+ console.log('!!!! [Tool] fetch_url_content called', { args });
16589
+ const { url } = args;
16590
+ return await fetchUrlContent(url);
16591
+ },
16592
+ // TODO: !!!! Unhardcode, make proper server function register from definitions
16593
+ };
16594
+ return { ...allToolFunctionsInBrowser, ...allToolFunctionsInNode };
16595
+ }
16596
+ /**
16597
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16598
+ * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
16599
+ */
16600
+
16601
+ /**
16602
+ * Normalize options for `execCommand` and `execCommands`
16603
+ *
16604
+ * Note: `$` is used to indicate that this function behaves differently according to `process.platform`
16605
+ *
16606
+ * @private internal utility of `execCommand` and `execCommands`
16607
+ */
16608
+ function $execCommandNormalizeOptions(options) {
16609
+ var _a, _b, _c, _d;
16610
+ let command;
16611
+ let cwd;
16612
+ let crashOnError;
16613
+ let args = [];
16614
+ let timeout;
16615
+ let isVerbose;
16616
+ let env;
16617
+ if (typeof options === 'string') {
16618
+ // TODO: [1] DRY default values
16619
+ command = options;
16620
+ cwd = process.cwd();
16621
+ crashOnError = true;
16622
+ timeout = Infinity; // <- TODO: [⏳]
16623
+ isVerbose = DEFAULT_IS_VERBOSE;
16624
+ env = undefined;
16625
+ }
16626
+ else {
16627
+ /*
16628
+ TODO:
16629
+ if ((options as any).commands !== undefined) {
16630
+ commands = (options as any).commands;
16631
+ } else {
16632
+ commands = [(options as any).command];
16633
+ }
16634
+ */
16635
+ // TODO: [1] DRY default values
16636
+ command = options.command;
16637
+ cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
16638
+ crashOnError = (_b = options.crashOnError) !== null && _b !== void 0 ? _b : true;
16639
+ timeout = (_c = options.timeout) !== null && _c !== void 0 ? _c : Infinity;
16640
+ isVerbose = (_d = options.isVerbose) !== null && _d !== void 0 ? _d : DEFAULT_IS_VERBOSE;
16641
+ env = options.env;
16642
+ }
16643
+ // TODO: /(-[a-zA-Z0-9-]+\s+[^\s]*)|[^\s]*/g
16644
+ const _ = Array.from(command.matchAll(/(".*")|([^\s]*)/g))
16645
+ .map(([match]) => match)
16646
+ .filter((arg) => arg !== '');
16647
+ if (_.length > 1) {
16648
+ [command, ...args] = _;
16649
+ }
16650
+ if (options.args) {
16651
+ args = [...args, ...options.args];
16652
+ }
16653
+ let humanReadableCommand = !['npx', 'npm'].includes(command) ? command : args[0];
16654
+ if (['ts-node'].includes(humanReadableCommand)) {
16655
+ humanReadableCommand += ` ${args[1]}`;
16656
+ }
16657
+ if (/^win/.test(process.platform) && ['npm', 'npx'].includes(command)) {
16658
+ command = `${command}.cmd`;
16659
+ }
16660
+ return { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose, env };
16661
+ }
16662
+ // TODO: This should show type error> execCommandNormalizeOptions({ command: '', commands: [''] });
16663
+
16664
+ /**
16665
+ * Run one command in a shell
16666
+ *
16667
+ *
16668
+ * Note: There are 2 similar functions in the codebase:
16669
+ * - `$execCommand` which runs a single command
16670
+ * - `$execCommands` which runs multiple commands
16671
+ * Note: `$` is used to indicate that this function is not a pure function - it runs a command in a shell
16672
+ *
16673
+ * @public exported from `@promptbook/node`
16674
+ */
16675
+ function $execCommand(options) {
16676
+ if (!$isRunningInNode()) {
16677
+ throw new EnvironmentMismatchError('Function `$execCommand` can run only in Node environment.js');
16678
+ }
16679
+ return new Promise((resolve, reject) => {
16680
+ // eslint-disable-next-line prefer-const
16681
+ const { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose = DEFAULT_IS_VERBOSE, env, } = $execCommandNormalizeOptions(options);
16682
+ if (timeout !== Infinity) {
16683
+ // TODO: In waitasecond forTime(Infinity) should be equivalent to forEver()
16684
+ forTime(timeout).then(() => {
16685
+ if (crashOnError) {
16686
+ reject(new Error(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms`));
16687
+ }
16688
+ else {
16689
+ console.warn(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms but continues running`);
16690
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
16691
+ resolve('Command exceeded time limit');
16692
+ }
16693
+ });
16694
+ }
16695
+ if (isVerbose) {
16696
+ console.info(colors.yellow(cwd) + ' ' + colors.green(command) + ' ' + colors.blue(args.join(' ')));
16697
+ }
16698
+ try {
16699
+ const commandProcess = spawn(command, args, {
16700
+ cwd,
16701
+ shell: true,
16702
+ env: env ? { ...process.env, ...env } : process.env,
16703
+ });
16704
+ if (isVerbose) {
16705
+ commandProcess.on('message', (message) => {
16706
+ console.info({ message });
16707
+ });
16708
+ }
16709
+ const output = [];
16710
+ commandProcess.stdout.on('data', (stdout) => {
16711
+ output.push(stdout.toString());
16712
+ if (isVerbose) {
16713
+ console.info(stdout.toString());
16714
+ }
16715
+ });
16716
+ commandProcess.stderr.on('data', (stderr) => {
16717
+ output.push(stderr.toString());
16718
+ if (isVerbose && stderr.toString().trim()) {
16719
+ console.warn(stderr.toString());
16720
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
16721
+ }
16722
+ });
16723
+ const finishWithCode = (code) => {
16724
+ if (code !== 0) {
16725
+ if (crashOnError) {
16726
+ reject(new Error(output.join('\n').trim() ||
16727
+ `Command "${humanReadableCommand}" exited with code ${code}`));
16728
+ }
16729
+ else {
16730
+ if (isVerbose) {
16731
+ console.warn(`Command "${humanReadableCommand}" exited with code ${code}`);
16732
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
16733
+ }
16734
+ resolve(spaceTrim$1(output.join('\n')));
16735
+ }
16736
+ }
16737
+ else {
16738
+ resolve(spaceTrim$1(output.join('\n')));
16739
+ }
16740
+ };
16741
+ commandProcess.on('close', finishWithCode);
16742
+ commandProcess.on('exit', finishWithCode);
16743
+ commandProcess.on('disconnect', () => {
16744
+ // Note: Unexpected disconnection should always result in rejection
16745
+ reject(new Error(`Command "${humanReadableCommand}" disconnected`));
16746
+ });
16747
+ commandProcess.on('error', (error) => {
16748
+ if (crashOnError) {
16749
+ reject(new Error(`Command "${humanReadableCommand}" failed: \n${error.message}`));
16750
+ }
16751
+ else {
16752
+ if (isVerbose) {
16753
+ console.warn(error);
16754
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
16755
+ }
16756
+ resolve(spaceTrim$1(output.join('\n')));
16757
+ }
16758
+ });
16759
+ }
16760
+ catch (error) {
16761
+ // Note: Unexpected error in sync code should always result in rejection
16762
+ reject(error);
16763
+ }
16764
+ });
16765
+ }
16766
+ /**
16767
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16768
+ */
16769
+
16770
+ /**
16771
+ * Attempts to locate the specified application on a Linux system using the 'which' command.
16772
+ * Returns the path to the executable if found, or null otherwise.
16773
+ *
16774
+ * @private within the repository
16775
+ */
16776
+ async function locateAppOnLinux({ linuxWhich, }) {
16777
+ try {
16778
+ const result = await $execCommand({ crashOnError: true, command: `which ${linuxWhich}` });
16779
+ return result.trim();
16780
+ }
16781
+ catch (error) {
16782
+ assertsError(error);
16783
+ return null;
16784
+ }
16785
+ }
16786
+ /**
16787
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
16788
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16789
+ */
16790
+
16791
+ /**
16792
+ * Provides filesystem access (for example for Node.js-based scrapers)
16793
+ * Creates a standardized filesystem interface that scrapers can use for file operations.
16794
+ *
16795
+ * @public exported from `@promptbook/node`
16796
+ */
16797
+ function $provideFilesystemForNode(options) {
16798
+ if (!$isRunningInNode()) {
16799
+ throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
16800
+ }
16801
+ return {
16802
+ stat,
16803
+ access,
16804
+ constants,
16805
+ readFile,
16806
+ writeFile,
16807
+ readdir,
16808
+ mkdir,
16809
+ watch,
16810
+ };
16811
+ }
16812
+ /**
16813
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16814
+ * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
16815
+ */
16816
+
16817
+ /**
16818
+ * Checks if the file is executable
16819
+ *
16820
+ * @private within the repository
16821
+ */
16822
+ async function isExecutable(path, fs) {
16823
+ try {
16824
+ await fs.access(path, fs.constants.X_OK);
16825
+ return true;
16826
+ }
16827
+ catch (error) {
16828
+ return false;
16829
+ }
16830
+ }
16831
+ /**
16832
+ * Note: Not [~🟢~] because it is not directly dependent on `fs
16833
+ * TODO: [🖇] What about symlinks?
16834
+ */
16835
+
16836
+ // Note: Module `userhome` has no types available, so it is imported using `require`
16837
+ // @see https://stackoverflow.com/questions/37000981/how-to-import-node-module-in-typescript-without-type-definitions
16838
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
16839
+ const userhome = require('userhome');
16840
+ /**
16841
+ * Attempts to locate the specified application on a macOS system by checking standard application paths and using mdfind.
16842
+ * Returns the path to the executable if found, or null otherwise.
16843
+ *
16844
+ * @private within the repository
16845
+ */
16846
+ async function locateAppOnMacOs({ macOsName, }) {
16847
+ try {
16848
+ const toExec = `/Contents/MacOS/${macOsName}`;
16849
+ const regPath = `/Applications/${macOsName}.app` + toExec;
16850
+ const altPath = userhome(regPath.slice(1));
16851
+ if (await isExecutable(regPath, $provideFilesystemForNode())) {
16852
+ return regPath;
16853
+ }
16854
+ else if (await isExecutable(altPath, $provideFilesystemForNode())) {
16855
+ return altPath;
16856
+ }
16857
+ const result = await $execCommand({
16858
+ crashOnError: true,
16859
+ command: `mdfind 'kMDItemDisplayName == "${macOsName}" && kMDItemKind == Application'`,
16860
+ });
16861
+ return result.trim() + toExec;
16862
+ }
16863
+ catch (error) {
16864
+ assertsError(error);
16865
+ return null;
16866
+ }
16867
+ }
16868
+ /**
16869
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
16870
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16871
+ */
16872
+
16873
+ /**
16874
+ * Attempts to locate the specified application on a Windows system by searching common installation directories.
16875
+ * Returns the path to the executable if found, or null otherwise.
16876
+ *
16877
+ * @private within the repository
16878
+ */
16879
+ async function locateAppOnWindows({ appName, windowsSuffix, }) {
16880
+ try {
16881
+ const prefixes = [
16882
+ process.env.LOCALAPPDATA,
16883
+ join(process.env.LOCALAPPDATA || '', 'Programs'),
16884
+ process.env.PROGRAMFILES,
16885
+ process.env['PROGRAMFILES(X86)'],
16886
+ ];
16887
+ for (const prefix of prefixes) {
16888
+ const path = prefix + windowsSuffix;
16889
+ if (await isExecutable(path, $provideFilesystemForNode())) {
16890
+ return path;
16891
+ }
16892
+ }
16893
+ throw new Error(`Can not locate app ${appName} on Windows.`);
16894
+ }
16895
+ catch (error) {
16896
+ assertsError(error);
16897
+ return null;
16898
+ }
16899
+ }
16900
+ /**
16901
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
16902
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16903
+ */
16904
+
16905
+ /**
16906
+ * Locates an application on the system
16907
+ *
16908
+ * @private within the repository
16909
+ */
16910
+ function locateApp(options) {
16911
+ if (!$isRunningInNode()) {
16912
+ throw new EnvironmentMismatchError('Locating apps works only in Node.js environment');
16913
+ }
16914
+ const { appName, linuxWhich, windowsSuffix, macOsName } = options;
16915
+ if (process.platform === 'win32') {
16916
+ if (windowsSuffix) {
16917
+ return locateAppOnWindows({ appName, windowsSuffix });
16918
+ }
16919
+ else {
16920
+ throw new Error(`${appName} is not available on Windows.`);
16921
+ }
16922
+ }
16923
+ else if (process.platform === 'darwin') {
16924
+ if (macOsName) {
16925
+ return locateAppOnMacOs({ macOsName });
16926
+ }
16927
+ else {
16928
+ throw new Error(`${appName} is not available on macOS.`);
16929
+ }
16930
+ }
16931
+ else {
16932
+ if (linuxWhich) {
16933
+ return locateAppOnLinux({ linuxWhich });
16934
+ }
16935
+ else {
16936
+ throw new Error(`${appName} is not available on Linux.`);
16937
+ }
16938
+ }
16939
+ }
16940
+ /**
16941
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
16942
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16943
+ */
16944
+
16945
+ /**
16946
+ * Locates the LibreOffice executable on the current system by searching platform-specific paths.
16947
+ * Returns the path to the executable if found, or null otherwise.
16948
+ *
16949
+ * @private within the repository
16950
+ */
16951
+ function locateLibreoffice() {
16952
+ return locateApp({
16953
+ appName: 'Libreoffice',
16954
+ linuxWhich: 'libreoffice',
16955
+ windowsSuffix: '\\LibreOffice\\program\\soffice.exe',
16956
+ macOsName: 'LibreOffice',
16957
+ });
16958
+ }
16959
+ /**
16960
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node` OR `@promptbook/legacy-documents`
16961
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16962
+ */
16963
+
16964
+ /**
16965
+ * Locates the Pandoc executable on the current system by searching platform-specific paths.
16966
+ * Returns the path to the executable if found, or null otherwise.
16967
+ *
16968
+ * @private within the repository
16969
+ */
16970
+ function locatePandoc() {
16971
+ return locateApp({
16972
+ appName: 'Pandoc',
16973
+ linuxWhich: 'pandoc',
16974
+ windowsSuffix: '\\Pandoc\\pandoc.exe',
16975
+ macOsName: 'Pandoc',
16976
+ });
16977
+ }
16978
+ /**
16979
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node` OR `@promptbook/documents`
16980
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
16981
+ */
16982
+
16983
+ /**
16984
+ * Provides paths to required executables (i.e. as Pandoc and LibreOffice) for Node.js environments.
16985
+ *
16986
+ * @public exported from `@promptbook/node`
16987
+ */
16988
+ async function $provideExecutablesForNode(options) {
16989
+ if (!$isRunningInNode()) {
16990
+ throw new EnvironmentMismatchError('Function `$getScrapersForNode` works only in Node.js environment');
16991
+ }
16992
+ return {
16993
+ pandocPath: (await locatePandoc()) || undefined,
16994
+ libreOfficePath: (await locateLibreoffice()) || undefined,
16995
+ // <- TODO: [🧠] `null` vs `undefined`
16996
+ };
16997
+ }
16998
+ /**
16999
+ * TODO: [🧠] Allow to override the executables without need to call `locatePandoc` / `locateLibreoffice` in case of provided
17000
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17001
+ * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
17002
+ */
17003
+
17004
+ /**
17005
+ * Register for LLM tools metadata.
17006
+ *
17007
+ * Note: `$` is used to indicate that this interacts with the global scope
17008
+ * @singleton Only one instance of each register is created per build, but there can be more instances across different builds or environments.
17009
+ * @public exported from `@promptbook/core`
17010
+ */
17011
+ const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
17012
+ /**
17013
+ * TODO: [®] DRY Register logic
17014
+ */
17015
+
17016
+ /**
17017
+ * Register for LLM tools.
17018
+ *
17019
+ * Note: `$` is used to indicate that this interacts with the global scope
17020
+ * @singleton Only one instance of each register is created per build, but there can be more instances across different builds or environments.
17021
+ * @public exported from `@promptbook/core`
17022
+ */
17023
+ const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
17024
+ /**
17025
+ * TODO: [®] DRY Register logic
17026
+ */
17027
+
17028
+ /**
17029
+ * Path to the `.env` file which was used to configure LLM tools
17030
+ *
17031
+ * Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
17032
+ */
17033
+ let $usedEnvFilename = null;
17034
+ /**
17035
+ * Pass the `.env` file which was used to configure LLM tools
17036
+ *
17037
+ * Note: `$` is used to indicate that this variable is making side effect
17038
+ *
17039
+ * @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
17040
+ */
17041
+ function $setUsedEnvFilename(filepath) {
17042
+ $usedEnvFilename = filepath;
17043
+ }
17044
+ /**
17045
+ * Creates a message with all registered LLM tools
17046
+ *
17047
+ * Note: This function is used to create a (error) message when there is no constructor for some LLM provider
17048
+ *
17049
+ * @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
17050
+ */
17051
+ function $registeredLlmToolsMessage() {
17052
+ let env;
17053
+ if ($isRunningInNode()) {
17054
+ env = process.env;
17055
+ // <- TODO: [⚛] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
17056
+ }
17057
+ else {
17058
+ env = {};
17059
+ }
17060
+ /**
17061
+ * Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
17062
+ */
17063
+ const all = [];
17064
+ for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
17065
+ if (all.some((item) => item.packageName === packageName && item.className === className)) {
17066
+ continue;
17067
+ }
17068
+ all.push({ title, packageName, className, envVariables });
17069
+ }
17070
+ for (const { packageName, className } of $llmToolsRegister.list()) {
17071
+ if (all.some((item) => item.packageName === packageName && item.className === className)) {
17072
+ continue;
17073
+ }
17074
+ all.push({ packageName, className });
17075
+ }
17076
+ const metadata = all.map((metadata) => {
17077
+ var _a, _b;
17078
+ const isMetadataAviailable = $llmToolsMetadataRegister
17079
+ .list()
17080
+ .some(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
17081
+ const isInstalled = $llmToolsRegister
17082
+ .list()
17083
+ .some(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
17084
+ const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
17085
+ const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
17086
+ // <- Note: [🗨]
17087
+ return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
17088
+ });
17089
+ const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
17090
+ if (metadata.length === 0) {
17091
+ return spaceTrim$2((block) => `
17092
+ No LLM providers are available.
17093
+
17094
+ ${block(usedEnvMessage)}
17095
+ `);
17096
+ }
17097
+ return spaceTrim$2((block) => `
17098
+
17099
+ ${block(usedEnvMessage)}
17100
+
17101
+ Relevant environment variables:
17102
+ ${block(Object.keys(env)
17103
+ .filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
17104
+ .map((envVariableName) => `- \`${envVariableName}\``)
17105
+ .join('\n'))}
17106
+
17107
+ Available LLM providers are:
17108
+ ${block(metadata
17109
+ .map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
17110
+ const morePieces = [];
17111
+ if (just(false)) ;
17112
+ else if (!isMetadataAviailable && !isInstalled) {
17113
+ // TODO: [�][�] Maybe do allow to do auto-install if package not registered and not found
17114
+ morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
17115
+ }
17116
+ else if (isMetadataAviailable && !isInstalled) {
17117
+ // TODO: [�][�]
17118
+ morePieces.push(`Not installed`);
17119
+ }
17120
+ else if (!isMetadataAviailable && isInstalled) {
17121
+ morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
17122
+ }
17123
+ else if (isMetadataAviailable && isInstalled) {
17124
+ morePieces.push(`Installed`);
17125
+ }
17126
+ else {
17127
+ morePieces.push(`unknown state, looks like a unexpected behavior`);
17128
+ } /* not else */
17129
+ if (isFullyConfigured) {
17130
+ morePieces.push(`Configured`);
17131
+ }
17132
+ else if (isPartiallyConfigured) {
17133
+ morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
17134
+ }
17135
+ else {
17136
+ if (envVariables !== null) {
17137
+ morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
17138
+ }
17139
+ else {
17140
+ morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
17141
+ }
17142
+ }
17143
+ let providerMessage = spaceTrim$2(`
17144
+ ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
17145
+ ${morePieces.join('; ')}
17146
+ `);
17147
+ if ($isRunningInNode()) {
17148
+ if (isInstalled && isFullyConfigured) {
17149
+ providerMessage = colors.green(providerMessage);
17150
+ }
17151
+ else if (isInstalled && isPartiallyConfigured) {
17152
+ providerMessage = colors.yellow(providerMessage);
17153
+ }
17154
+ else {
17155
+ providerMessage = colors.gray(providerMessage);
17156
+ }
17157
+ }
17158
+ return providerMessage;
17159
+ })
17160
+ .join('\n'))}
17161
+ `);
17162
+ }
17163
+ /**
17164
+ * TODO: [®] DRY Register logic
17165
+ * TODO: [🧠][⚛] Maybe pass env as argument
17166
+ */
17167
+
17168
+ /**
17169
+ * Provides the path to the `.env` file
17170
+ *
17171
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
17172
+ *
17173
+ * @private within the repository - for CLI utils
17174
+ */
17175
+ async function $provideEnvFilename() {
17176
+ if (!$isRunningInNode()) {
17177
+ throw new EnvironmentMismatchError('Function `$provideEnvFilename` works only in Node.js environment');
17178
+ }
17179
+ const envFilePatterns = [
17180
+ '.env',
17181
+ '.env.test',
17182
+ '.env.local',
17183
+ '.env.development.local',
17184
+ '.env.development',
17185
+ '.env.production.local',
17186
+ '.env.production',
17187
+ '.env.prod.local',
17188
+ '.env.prod',
17189
+ // <- TODO: Maybe add more patterns
17190
+ ];
17191
+ let rootDirname = process.cwd();
17192
+ up_to_root: for (let i = 0; i < LOOP_LIMIT; i++) {
17193
+ for (const pattern of envFilePatterns) {
17194
+ const envFilename = join(rootDirname, pattern);
17195
+ if (await isFileExisting(envFilename, $provideFilesystemForNode())) {
17196
+ $setUsedEnvFilename(envFilename);
17197
+ return envFilename;
17198
+ }
17199
+ }
17200
+ if (isRootPath(rootDirname)) {
17201
+ break up_to_root;
17202
+ }
17203
+ // Note: If the directory does not exist, try the parent directory
17204
+ rootDirname = join(rootDirname, '..');
17205
+ }
17206
+ return null;
17207
+ }
17208
+ /**
17209
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17210
+ */
17211
+
17212
+ /**
17213
+ * Provides LLM tools configuration by reading environment variables.
17214
+ *
17215
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
17216
+ *
17217
+ * It looks for environment variables:
17218
+ * - `process.env.OPENAI_API_KEY`
17219
+ * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
17220
+ * - ...
17221
+ *
17222
+ * @see Environment variables documentation or .env file for required variables.
17223
+ * @returns A promise that resolves to the LLM tools configuration, or null if configuration is incomplete or missing.
17224
+ * @public exported from `@promptbook/node`
17225
+ */
17226
+ async function $provideLlmToolsConfigurationFromEnv() {
17227
+ if (!$isRunningInNode()) {
17228
+ throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
17229
+ }
17230
+ const envFilepath = await $provideEnvFilename();
17231
+ if (envFilepath !== null) {
17232
+ dotenv.config({ path: envFilepath });
17233
+ }
17234
+ const llmToolsConfiguration = $llmToolsMetadataRegister
17235
+ .list()
17236
+ .map((metadata) => metadata.createConfigurationFromEnv(process.env))
17237
+ .filter((configuration) => configuration !== null);
17238
+ return llmToolsConfiguration;
17239
+ }
17240
+ /**
17241
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17242
+ */
17243
+
17244
+ /**
17245
+ * Creates LLM execution tools from provided configuration objects
17246
+ *
17247
+ * Instantiates and configures LLM tool instances for each configuration entry,
17248
+ * combining them into a unified interface via MultipleLlmExecutionTools.
17249
+ *
17250
+ * Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
17251
+ *
17252
+ * @param configuration Array of LLM tool configurations to instantiate
17253
+ * @param options Additional options for configuring the LLM tools
17254
+ * @returns A unified interface combining all successfully instantiated LLM tools
17255
+ * @public exported from `@promptbook/core`
17256
+ */
17257
+ function createLlmToolsFromConfiguration(configuration, options = {}) {
17258
+ const { title = 'LLM Tools from Configuration', isVerbose = DEFAULT_IS_VERBOSE, userId } = options;
17259
+ const llmTools = configuration.map((llmConfiguration) => {
17260
+ const registeredItem = $llmToolsRegister
17261
+ .list()
17262
+ .find(({ packageName, className }) => llmConfiguration.packageName === packageName && llmConfiguration.className === className);
17263
+ if (registeredItem === undefined) {
17264
+ // console.log('$llmToolsRegister.list()', $llmToolsRegister.list());
17265
+ throw new Error(spaceTrim$2((block) => `
17266
+ There is no constructor for LLM provider \`${llmConfiguration.className}\` from \`${llmConfiguration.packageName}\`
17267
+ Running in ${!$isRunningInBrowser() ? '' : 'browser environment'}${!$isRunningInNode() ? '' : 'node environment'}${!$isRunningInWebWorker() ? '' : 'worker environment'}
16257
17268
 
16258
- ## Key aspects
17269
+ You have probably forgotten install and import the provider package.
17270
+ To fix this issue, you can:
16259
17271
 
16260
- - Content is appended directly to the system message.
16261
- - No special processing or validation is performed.
16262
- - Behavior preserved until proper implementation is added.
17272
+ Install:
16263
17273
 
16264
- ## Status
17274
+ > npm install ${llmConfiguration.packageName}
16265
17275
 
16266
- - **Status:** Placeholder implementation
16267
- - **Effect:** Appends content prefixed by commitment type
16268
- - **Future:** Will be replaced with specialized logic
17276
+ And import:
16269
17277
 
16270
- ## Examples
17278
+ > import '${llmConfiguration.packageName}';
16271
17279
 
16272
- \`\`\`book
16273
- Example Agent
16274
17280
 
16275
- PERSONA You are a helpful assistant
16276
- ${this.type} Your content here
16277
- RULE Always be helpful
16278
- \`\`\`
16279
- `);
16280
- }
16281
- applyToAgentModelRequirements(requirements, content) {
16282
- const trimmedContent = content.trim();
16283
- if (!trimmedContent) {
16284
- return requirements;
17281
+ ${block($registeredLlmToolsMessage())}
17282
+ `));
16285
17283
  }
16286
- // Add the commitment content 1:1 to the system message
16287
- const commitmentLine = `${this.type} ${trimmedContent}`;
16288
- return this.appendToSystemMessage(requirements, commitmentLine, '\n\n');
16289
- }
17284
+ return registeredItem({
17285
+ isVerbose,
17286
+ userId,
17287
+ ...llmConfiguration.options,
17288
+ });
17289
+ });
17290
+ return joinLlmExecutionTools(title, ...llmTools);
16290
17291
  }
16291
-
16292
17292
  /**
16293
- * Registry of all available commitment definitions
16294
- * This array contains instances of all commitment definitions
16295
- * This is the single source of truth for all commitments in the system
16296
- *
16297
- * @private Use functions to access commitments instead of this array directly
17293
+ * TODO: [🎌] Together with `createLlmToolsFromConfiguration` + 'EXECUTION_TOOLS_CLASSES' gets to `@promptbook/core` ALL model providers, make this more efficient
17294
+ * TODO: [🧠][🎌] Dynamically install required providers
17295
+ * TODO: We should implement an interactive configuration wizard that would:
17296
+ * 1. Detect which LLM providers are available in the environment
17297
+ * 2. Guide users through required configuration settings for each provider
17298
+ * 3. Allow testing connections before completing setup
17299
+ * 4. Generate appropriate configuration code for application integration
17300
+ * TODO: [🧠][🍛] Which name is better `createLlmToolsFromConfig` or `createLlmToolsFromConfiguration`?
17301
+ * TODO: [🧠] Is there some meaningfull way how to test this util
17302
+ * TODO: This should be maybe not under `_common` but under `utils`
17303
+ * TODO: [®] DRY Register logic
16298
17304
  */
16299
- const COMMITMENT_REGISTRY = [
16300
- // Fully implemented commitments
16301
- new PersonaCommitmentDefinition('PERSONA'),
16302
- new PersonaCommitmentDefinition('PERSONAE'),
16303
- new KnowledgeCommitmentDefinition(),
16304
- new MemoryCommitmentDefinition('MEMORY'),
16305
- new MemoryCommitmentDefinition('MEMORIES'),
16306
- new StyleCommitmentDefinition('STYLE'),
16307
- new StyleCommitmentDefinition('STYLES'),
16308
- new RuleCommitmentDefinition('RULES'),
16309
- new RuleCommitmentDefinition('RULE'),
16310
- new LanguageCommitmentDefinition('LANGUAGES'),
16311
- new LanguageCommitmentDefinition('LANGUAGE'),
16312
- new SampleCommitmentDefinition('SAMPLE'),
16313
- new SampleCommitmentDefinition('EXAMPLE'),
16314
- new FormatCommitmentDefinition('FORMAT'),
16315
- new FormatCommitmentDefinition('FORMATS'),
16316
- new FromCommitmentDefinition('FROM'),
16317
- new ImportCommitmentDefinition('IMPORT'),
16318
- new ImportCommitmentDefinition('IMPORTS'),
16319
- new ModelCommitmentDefinition('MODEL'),
16320
- new ModelCommitmentDefinition('MODELS'),
16321
- new ActionCommitmentDefinition('ACTION'),
16322
- new ActionCommitmentDefinition('ACTIONS'),
16323
- new ComponentCommitmentDefinition(),
16324
- new MetaImageCommitmentDefinition(),
16325
- new MetaColorCommitmentDefinition(),
16326
- new MetaFontCommitmentDefinition(),
16327
- new MetaLinkCommitmentDefinition(),
16328
- new MetaCommitmentDefinition(),
16329
- new NoteCommitmentDefinition('NOTE'),
16330
- new NoteCommitmentDefinition('NOTES'),
16331
- new NoteCommitmentDefinition('COMMENT'),
16332
- new NoteCommitmentDefinition('NONCE'),
16333
- new NoteCommitmentDefinition('TODO'),
16334
- new GoalCommitmentDefinition('GOAL'),
16335
- new GoalCommitmentDefinition('GOALS'),
16336
- new InitialMessageCommitmentDefinition(),
16337
- new UserMessageCommitmentDefinition(),
16338
- new AgentMessageCommitmentDefinition(),
16339
- new MessageCommitmentDefinition('MESSAGE'),
16340
- new MessageCommitmentDefinition('MESSAGES'),
16341
- new ScenarioCommitmentDefinition('SCENARIO'),
16342
- new ScenarioCommitmentDefinition('SCENARIOS'),
16343
- new DeleteCommitmentDefinition('DELETE'),
16344
- new DeleteCommitmentDefinition('CANCEL'),
16345
- new DeleteCommitmentDefinition('DISCARD'),
16346
- new DeleteCommitmentDefinition('REMOVE'),
16347
- new DictionaryCommitmentDefinition(),
16348
- new OpenCommitmentDefinition(),
16349
- new ClosedCommitmentDefinition(),
16350
- new TeamCommitmentDefinition(),
16351
- new UseBrowserCommitmentDefinition(),
16352
- new UseSearchEngineCommitmentDefinition(),
16353
- new UseTimeCommitmentDefinition(),
16354
- new UseImageGeneratorCommitmentDefinition('USE IMAGE GENERATOR'),
16355
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16356
- new UseImageGeneratorCommitmentDefinition('USE IMAGE GENERATION'),
16357
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16358
- new UseImageGeneratorCommitmentDefinition('IMAGE GENERATOR'),
16359
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16360
- new UseImageGeneratorCommitmentDefinition('IMAGE GENERATION'),
16361
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
16362
- new UseImageGeneratorCommitmentDefinition('USE IMAGE'),
16363
- new UseMcpCommitmentDefinition(),
16364
- new UseCommitmentDefinition(),
16365
- // Not yet implemented commitments (using placeholder)
16366
- new NotYetImplementedCommitmentDefinition('EXPECT'),
16367
- new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
16368
- new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
16369
- new NotYetImplementedCommitmentDefinition('AVOID'),
16370
- new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
16371
- new NotYetImplementedCommitmentDefinition('CONTEXT'),
16372
- // <- TODO: Prompt: Leverage aliases instead of duplicating commitment definitions
16373
- ];
17305
+
16374
17306
  /**
16375
- * Gets all available commitment definitions
16376
- * @returns Array of all commitment definitions
17307
+ * Automatically configures LLM tools from environment variables in Node.js
16377
17308
  *
16378
- * @public exported from `@promptbook/core`
17309
+ * This utility function detects available LLM providers based on environment variables
17310
+ * and creates properly configured LLM execution tools for each detected provider.
17311
+ *
17312
+ * Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
17313
+ *
17314
+ * Supports environment variables from .env files when dotenv is configured
17315
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
17316
+ *
17317
+ * It looks for environment variables:
17318
+ * - `process.env.OPENAI_API_KEY`
17319
+ * - `process.env.ANTHROPIC_CLAUDE_API_KEY`
17320
+ * - ...
17321
+ *
17322
+ * @param options Configuration options for the LLM tools
17323
+ * @returns A unified interface containing all detected and configured LLM tools
17324
+ * @public exported from `@promptbook/node`
16379
17325
  */
16380
- function getAllCommitmentDefinitions() {
16381
- return $deepFreeze([...COMMITMENT_REGISTRY]);
17326
+ async function $provideLlmToolsFromEnv(options = {}) {
17327
+ if (!$isRunningInNode()) {
17328
+ throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
17329
+ }
17330
+ const configuration = await $provideLlmToolsConfigurationFromEnv();
17331
+ if (configuration.length === 0) {
17332
+ if ($llmToolsMetadataRegister.list().length === 0) {
17333
+ throw new UnexpectedError(spaceTrim$2((block) => `
17334
+ No LLM tools registered, this is probably a bug in the Promptbook library
17335
+
17336
+ ${block($registeredLlmToolsMessage())}}
17337
+ `));
17338
+ }
17339
+ // TODO: [🥃]
17340
+ throw new Error(spaceTrim$2((block) => `
17341
+ No LLM tools found in the environment
17342
+
17343
+ ${block($registeredLlmToolsMessage())}}
17344
+ `));
17345
+ }
17346
+ return createLlmToolsFromConfiguration(configuration, options);
16382
17347
  }
16383
17348
  /**
16384
- * Gets all function implementations provided by all commitments
17349
+ * TODO: The architecture for LLM tools configuration consists of three key functions:
17350
+ * 1. `$provideLlmToolsFromEnv` - High-level function that detects available providers from env vars and returns ready-to-use LLM tools
17351
+ * 2. `$provideLlmToolsConfigurationFromEnv` - Middle layer that extracts configuration objects from environment variables
17352
+ * 3. `createLlmToolsFromConfiguration` - Low-level function that instantiates LLM tools from explicit configuration
16385
17353
  *
16386
- * @public exported from `@promptbook/core`
17354
+ * This layered approach allows flexibility in how tools are configured:
17355
+ * - Use $provideLlmToolsFromEnv for automatic detection and setup in Node.js environments
17356
+ * - Use $provideLlmToolsConfigurationFromEnv to extract config objects for modification before instantiation
17357
+ * - Use createLlmToolsFromConfiguration for explicit control over tool configurations
17358
+ *
17359
+ * TODO: [🧠][🍛] Which name is better `$provideLlmToolsFromEnv` or `$provideLlmToolsFromEnvironment`?
17360
+ * TODO: [🧠] Is there some meaningfull way how to test this util
17361
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17362
+ * TODO: [🥃] Allow `ptbk make` without llm tools
17363
+ * TODO: This should be maybe not under `_common` but under `utils`
17364
+ * TODO: [®] DRY Register logic
16387
17365
  */
16388
- function getAllCommitmentsToolFunctions() {
16389
- const allToolFunctions = {};
16390
- for (const commitmentDefinition of getAllCommitmentDefinitions()) {
16391
- const toolFunctions = commitmentDefinition.getToolFunctions();
16392
- for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
16393
- allToolFunctions[funcName] = funcImpl;
17366
+
17367
+ /**
17368
+ * Provides a collection of scrapers optimized for Node.js environment.
17369
+ * 1) `provideScrapersForNode` use as default
17370
+ * 2) `provideScrapersForBrowser` use in limited browser environment *
17371
+ * @public exported from `@promptbook/node`
17372
+ */
17373
+ async function $provideScrapersForNode(tools, options) {
17374
+ if (!$isRunningInNode()) {
17375
+ throw new EnvironmentMismatchError('Function `$getScrapersForNode` works only in Node.js environment');
17376
+ }
17377
+ // TODO: [🔱] Do here auto-installation + auto-include of missing scrapers - use all from $scrapersMetadataRegister.list()
17378
+ // TODO: [🔱][🧠] What is the best strategy for auto-install - install them all?
17379
+ const scrapers = [];
17380
+ for (const scraperFactory of $scrapersRegister.list()) {
17381
+ const scraper = await scraperFactory(tools, options || {});
17382
+ if (scraper.metadata.packageName === '@promptbook/boilerplate' ||
17383
+ scraper.metadata.mimeTypes.some((mimeType) => mimeType.includes('DISABLED'))) {
17384
+ continue;
16394
17385
  }
17386
+ scrapers.push(scraper);
16395
17387
  }
16396
- return allToolFunctions;
17388
+ return scrapers;
16397
17389
  }
16398
17390
  /**
16399
- * TODO: [🧠] Maybe create through standardized $register
16400
- * Note: [💞] Ignore a discrepancy between file name and entity name
17391
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17392
+ * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
16401
17393
  */
16402
17394
 
16403
17395
  /**
@@ -16580,13 +17572,6 @@ class JavascriptEvalExecutionTools {
16580
17572
  `const ${functionName} = buildinFunctions.${functionName};`)
16581
17573
  .join('\n');
16582
17574
  // TODO: DRY [🍯]
16583
- const commitmentsFunctions = getAllCommitmentsToolFunctions();
16584
- const commitmentsFunctionsStatement = Object.keys(commitmentsFunctions)
16585
- .map((functionName) =>
16586
- // Note: Custom functions are exposed to the current scope as variables
16587
- `const ${functionName} = commitmentsFunctions.${functionName};`)
16588
- .join('\n');
16589
- // TODO: DRY [🍯]
16590
17575
  const customFunctions = this.options.functions || {};
16591
17576
  const customFunctionsStatement = Object.keys(customFunctions)
16592
17577
  .map((functionName) =>
@@ -16600,10 +17585,6 @@ class JavascriptEvalExecutionTools {
16600
17585
  // Build-in functions:
16601
17586
  ${block(buildinFunctionsStatement)}
16602
17587
 
16603
- // Commitments functions:
16604
- ${block(commitmentsFunctionsStatement)}
16605
-
16606
-
16607
17588
  // Custom functions:
16608
17589
  ${block(customFunctionsStatement || '// -- No custom functions --')}
16609
17590
 
@@ -16710,12 +17691,18 @@ async function $provideExecutionToolsForNode(options) {
16710
17691
  fs,
16711
17692
  executables,
16712
17693
  scrapers: await $provideScrapersForNode({ fs, llm, executables }, options),
16713
- script: [new JavascriptExecutionTools(options)],
17694
+ script: [
17695
+ new JavascriptExecutionTools({
17696
+ ...options,
17697
+ functions: { ...getAllCommitmentsToolFunctionsForNode() },
17698
+ }),
17699
+ ],
16714
17700
  };
16715
17701
  return tools;
16716
17702
  }
16717
17703
  /**
16718
17704
  * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17705
+ * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
16719
17706
  */
16720
17707
 
16721
17708
  /**
@@ -17037,10 +18024,11 @@ async function $provideScriptingForNode(options) {
17037
18024
  throw new EnvironmentMismatchError('Function `$provideScriptingForNode` works only in Node.js environment');
17038
18025
  }
17039
18026
  // TODO: [🔱] Do here auto-installation
17040
- return [new JavascriptExecutionTools(options)];
18027
+ return [new JavascriptExecutionTools({ ...options, functions: { ...getAllCommitmentsToolFunctionsForNode() } })];
17041
18028
  }
17042
18029
  /**
17043
18030
  * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
18031
+ * TODO: [🏓] Unite `xxxForServer` and `xxxForNode` naming
17044
18032
  */
17045
18033
 
17046
18034
  /**
@@ -17188,5 +18176,5 @@ async function $execCommands({ commands, cwd, crashOnError, }) {
17188
18176
  * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
17189
18177
  */
17190
18178
 
17191
- export { $execCommand, $execCommands, $provideExecutablesForNode, $provideExecutionToolsForNode, $provideFilesystemForNode, $provideLlmToolsConfigurationFromEnv, $provideLlmToolsFromEnv, $provideScrapersForNode, $provideScriptingForNode, BOOK_LANGUAGE_VERSION, FileCacheStorage, PROMPTBOOK_ENGINE_VERSION, createPipelineCollectionFromDirectory };
18179
+ export { $execCommand, $execCommands, $provideExecutablesForNode, $provideExecutionToolsForNode, $provideFilesystemForNode, $provideLlmToolsConfigurationFromEnv, $provideLlmToolsFromEnv, $provideScrapersForNode, $provideScriptingForNode, BOOK_LANGUAGE_VERSION, FileCacheStorage, PROMPTBOOK_ENGINE_VERSION, createPipelineCollectionFromDirectory, getAllCommitmentsToolFunctionsForNode };
17192
18180
  //# sourceMappingURL=index.es.js.map