@promptbook/node 0.112.0-11 → 0.112.0-12

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 (75) hide show
  1. package/esm/index.es.js +2402 -1154
  2. package/esm/index.es.js.map +1 -1
  3. package/esm/src/book-2.0/agent-source/AgentBasicInformation.d.ts +1 -1
  4. package/esm/src/book-components/Chat/Chat/ChatToolCallModal.test.d.ts +2 -0
  5. package/esm/src/book-components/Chat/Chat/renderToolCallDetails.d.ts +4 -0
  6. package/esm/src/book-components/Chat/utils/getToolCallChipletInfo.timeout.test.d.ts +1 -0
  7. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +123 -0
  8. package/esm/src/book-components/Chat/utils/timeoutToolCallPresentation.test.d.ts +1 -0
  9. package/esm/src/commitments/USE_CALENDAR/USE_CALENDAR.d.ts +42 -0
  10. package/esm/src/commitments/USE_CALENDAR/USE_CALENDAR.test.d.ts +1 -0
  11. package/esm/src/commitments/USE_CALENDAR/UseCalendarToolNames.d.ts +13 -0
  12. package/esm/src/commitments/USE_CALENDAR/UseCalendarWallet.d.ts +9 -0
  13. package/esm/src/commitments/USE_CALENDAR/calendarReference.d.ts +68 -0
  14. package/esm/src/commitments/USE_CALENDAR/callGoogleCalendarApi.d.ts +19 -0
  15. package/esm/src/commitments/USE_CALENDAR/createUseCalendarToolFunctions.d.ts +8 -0
  16. package/esm/src/commitments/USE_CALENDAR/createUseCalendarTools.d.ts +7 -0
  17. package/esm/src/commitments/USE_CALENDAR/getUseCalendarToolTitles.d.ts +7 -0
  18. package/esm/src/commitments/USE_CALENDAR/normalizeConfiguredCalendars.d.ts +19 -0
  19. package/esm/src/commitments/USE_CALENDAR/resolveUseCalendarToolRuntimeOrWalletCredentialResult.d.ts +34 -0
  20. package/esm/src/commitments/_common/toolRuntimeContext.d.ts +9 -0
  21. package/esm/src/commitments/index.d.ts +2 -1
  22. package/esm/src/executables/apps/locateVscode.d.ts +7 -0
  23. package/esm/src/executables/apps/locateVscode.test.d.ts +1 -0
  24. package/esm/src/executables/browsers/locateBrowser.d.ts +10 -0
  25. package/esm/src/executables/browsers/locateBrowser.test.d.ts +1 -0
  26. package/esm/src/executables/browsers/locateChrome.d.ts +7 -0
  27. package/esm/src/executables/browsers/locateChrome.test.d.ts +1 -0
  28. package/esm/src/executables/browsers/locateDefaultSystemBrowser.d.ts +10 -0
  29. package/esm/src/executables/browsers/locateDefaultSystemBrowser.test.d.ts +1 -0
  30. package/esm/src/executables/browsers/locateEdge.d.ts +7 -0
  31. package/esm/src/executables/browsers/locateEdge.test.d.ts +1 -0
  32. package/esm/src/executables/browsers/locateFirefox.d.ts +7 -0
  33. package/esm/src/executables/browsers/locateFirefox.test.d.ts +1 -0
  34. package/esm/src/executables/browsers/locateInternetExplorer.d.ts +7 -0
  35. package/esm/src/executables/browsers/locateInternetExplorer.test.d.ts +1 -0
  36. package/esm/src/executables/browsers/locateSafari.d.ts +7 -0
  37. package/esm/src/version.d.ts +1 -1
  38. package/package.json +2 -2
  39. package/umd/index.umd.js +2407 -1158
  40. package/umd/index.umd.js.map +1 -1
  41. package/umd/src/book-2.0/agent-source/AgentBasicInformation.d.ts +1 -1
  42. package/umd/src/book-components/Chat/Chat/ChatToolCallModal.test.d.ts +2 -0
  43. package/umd/src/book-components/Chat/Chat/renderToolCallDetails.d.ts +4 -0
  44. package/umd/src/book-components/Chat/utils/getToolCallChipletInfo.timeout.test.d.ts +1 -0
  45. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.d.ts +123 -0
  46. package/umd/src/book-components/Chat/utils/timeoutToolCallPresentation.test.d.ts +1 -0
  47. package/umd/src/commitments/USE_CALENDAR/USE_CALENDAR.d.ts +42 -0
  48. package/umd/src/commitments/USE_CALENDAR/USE_CALENDAR.test.d.ts +1 -0
  49. package/umd/src/commitments/USE_CALENDAR/UseCalendarToolNames.d.ts +13 -0
  50. package/umd/src/commitments/USE_CALENDAR/UseCalendarWallet.d.ts +9 -0
  51. package/umd/src/commitments/USE_CALENDAR/calendarReference.d.ts +68 -0
  52. package/umd/src/commitments/USE_CALENDAR/callGoogleCalendarApi.d.ts +19 -0
  53. package/umd/src/commitments/USE_CALENDAR/createUseCalendarToolFunctions.d.ts +8 -0
  54. package/umd/src/commitments/USE_CALENDAR/createUseCalendarTools.d.ts +7 -0
  55. package/umd/src/commitments/USE_CALENDAR/getUseCalendarToolTitles.d.ts +7 -0
  56. package/umd/src/commitments/USE_CALENDAR/normalizeConfiguredCalendars.d.ts +19 -0
  57. package/umd/src/commitments/USE_CALENDAR/resolveUseCalendarToolRuntimeOrWalletCredentialResult.d.ts +34 -0
  58. package/umd/src/commitments/_common/toolRuntimeContext.d.ts +9 -0
  59. package/umd/src/commitments/index.d.ts +2 -1
  60. package/umd/src/executables/apps/locateVscode.d.ts +7 -0
  61. package/umd/src/executables/apps/locateVscode.test.d.ts +1 -0
  62. package/umd/src/executables/browsers/locateBrowser.d.ts +10 -0
  63. package/umd/src/executables/browsers/locateBrowser.test.d.ts +1 -0
  64. package/umd/src/executables/browsers/locateChrome.d.ts +7 -0
  65. package/umd/src/executables/browsers/locateChrome.test.d.ts +1 -0
  66. package/umd/src/executables/browsers/locateDefaultSystemBrowser.d.ts +10 -0
  67. package/umd/src/executables/browsers/locateDefaultSystemBrowser.test.d.ts +1 -0
  68. package/umd/src/executables/browsers/locateEdge.d.ts +7 -0
  69. package/umd/src/executables/browsers/locateEdge.test.d.ts +1 -0
  70. package/umd/src/executables/browsers/locateFirefox.d.ts +7 -0
  71. package/umd/src/executables/browsers/locateFirefox.test.d.ts +1 -0
  72. package/umd/src/executables/browsers/locateInternetExplorer.d.ts +7 -0
  73. package/umd/src/executables/browsers/locateInternetExplorer.test.d.ts +1 -0
  74. package/umd/src/executables/browsers/locateSafari.d.ts +7 -0
  75. package/umd/src/version.d.ts +1 -1
package/esm/index.es.js CHANGED
@@ -11,7 +11,6 @@ import { JSDOM } from 'jsdom';
11
11
  import { Converter } from 'showdown';
12
12
  import { tmpdir } from 'os';
13
13
  import { ConfigChecker } from 'configchecker';
14
- import { locateChrome } from 'locate-app';
15
14
  import { chromium } from 'playwright';
16
15
  import { spawn } from 'child_process';
17
16
  import { forTime } from 'waitasecond';
@@ -39,7 +38,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
39
38
  * @generated
40
39
  * @see https://github.com/webgptorg/promptbook
41
40
  */
42
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-11';
41
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-12';
43
42
  /**
44
43
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
45
44
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -14804,117 +14803,449 @@ const runBrowserResultFormatting = {
14804
14803
  };
14805
14804
 
14806
14805
  /**
14807
- * Creates one standard abort error for cancelled retry loops.
14806
+ * Normalize options for `execCommand` and `execCommands`
14808
14807
  *
14809
- * @private utility for Agents Server runtime retries
14810
- */
14811
- function createAbortError$1() {
14812
- const error = new Error('Operation was aborted.');
14813
- error.name = 'AbortError';
14814
- return error;
14815
- }
14816
- /**
14817
- * Throws when the supplied signal is already aborted.
14808
+ * Note: `$` is used to indicate that this function behaves differently according to `process.platform`
14818
14809
  *
14819
- * @private utility for Agents Server runtime retries
14810
+ * @private internal utility of `execCommand` and `execCommands`
14820
14811
  */
14821
- function assertNotAborted(signal) {
14822
- if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
14823
- throw createAbortError$1();
14812
+ function $execCommandNormalizeOptions(options) {
14813
+ var _a, _b, _c, _d;
14814
+ let command;
14815
+ let cwd;
14816
+ let crashOnError;
14817
+ let args = [];
14818
+ let timeout;
14819
+ let isVerbose;
14820
+ let env;
14821
+ if (typeof options === 'string') {
14822
+ // TODO: [1] DRY default values
14823
+ command = options;
14824
+ cwd = process.cwd();
14825
+ crashOnError = true;
14826
+ timeout = Infinity; // <- TODO: [⏳]
14827
+ isVerbose = DEFAULT_IS_VERBOSE;
14828
+ env = undefined;
14824
14829
  }
14825
- }
14826
- /**
14827
- * Waits for a duration while respecting cancellation.
14828
- *
14829
- * @private utility for Agents Server runtime retries
14830
- */
14831
- async function sleepWithAbort(delayMs, signal) {
14832
- if (delayMs <= 0) {
14833
- assertNotAborted(signal);
14834
- return;
14830
+ else {
14831
+ /*
14832
+ TODO:
14833
+ if ((options as any).commands !== undefined) {
14834
+ commands = (options as any).commands;
14835
+ } else {
14836
+ commands = [(options as any).command];
14837
+ }
14838
+ */
14839
+ // TODO: [1] DRY default values
14840
+ command = options.command;
14841
+ cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
14842
+ crashOnError = (_b = options.crashOnError) !== null && _b !== void 0 ? _b : true;
14843
+ timeout = (_c = options.timeout) !== null && _c !== void 0 ? _c : Infinity;
14844
+ isVerbose = (_d = options.isVerbose) !== null && _d !== void 0 ? _d : DEFAULT_IS_VERBOSE;
14845
+ env = options.env;
14835
14846
  }
14836
- await new Promise((resolve, reject) => {
14837
- const timeout = setTimeout(() => {
14838
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
14839
- resolve();
14840
- }, delayMs);
14841
- const onAbort = () => {
14842
- clearTimeout(timeout);
14843
- signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
14844
- reject(createAbortError$1());
14845
- };
14846
- signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
14847
- });
14847
+ // TODO: /(-[a-zA-Z0-9-]+\s+[^\s]*)|[^\s]*/g
14848
+ const _ = Array.from(command.matchAll(/(".*")|([^\s]*)/g))
14849
+ .map(([match]) => match)
14850
+ .filter((arg) => arg !== '');
14851
+ if (_.length > 1) {
14852
+ [command, ...args] = _;
14853
+ }
14854
+ if (options.args) {
14855
+ args = [...args, ...options.args];
14856
+ }
14857
+ let humanReadableCommand = !['npx', 'npm'].includes(command) ? command : args[0];
14858
+ if (['ts-node'].includes(humanReadableCommand)) {
14859
+ humanReadableCommand += ` ${args[1]}`;
14860
+ }
14861
+ if (/^win/.test(process.platform) && ['npm', 'npx'].includes(command)) {
14862
+ command = `${command}.cmd`;
14863
+ }
14864
+ return { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose, env };
14848
14865
  }
14866
+ // TODO: This should show type error> execCommandNormalizeOptions({ command: '', commands: [''] });
14867
+
14849
14868
  /**
14850
- * Resolves the retry wait duration for one failed attempt.
14869
+ * Run one command in a shell
14851
14870
  *
14852
- * @private utility for Agents Server runtime retries
14853
- */
14854
- function resolveBackoffDelayMs(options) {
14855
- const exponentialDelay = options.initialDelayMs * Math.pow(options.backoffFactor, Math.max(0, options.attempt - 1));
14856
- const boundedDelay = Math.min(exponentialDelay, options.maxDelayMs);
14857
- const jitterDelay = boundedDelay * options.jitterRatio * Math.max(0, options.random());
14858
- return Math.max(0, Math.round(boundedDelay + jitterDelay));
14859
- }
14860
- /**
14861
- * Retries one async operation with exponential backoff and jitter.
14862
14871
  *
14863
- * @private utility for Agents Server runtime retries
14872
+ * Note: There are 2 similar functions in the codebase:
14873
+ * - `$execCommand` which runs a single command
14874
+ * - `$execCommands` which runs multiple commands
14875
+ * Note: `$` is used to indicate that this function is not a pure function - it runs a command in a shell
14876
+ *
14877
+ * @public exported from `@promptbook/node`
14864
14878
  */
14865
- async function retryWithBackoff(operation, options) {
14866
- var _a, _b, _c;
14867
- const startedAt = Date.now();
14868
- const totalAttempts = Math.max(1, options.retries + 1);
14869
- const random = (_a = options.random) !== null && _a !== void 0 ? _a : Math.random;
14870
- const sleep = (_b = options.sleep) !== null && _b !== void 0 ? _b : sleepWithAbort;
14871
- for (let attempt = 1; attempt <= totalAttempts; attempt++) {
14872
- assertNotAborted(options.signal);
14873
- try {
14874
- const value = await operation(attempt);
14875
- return {
14876
- value,
14877
- attempts: attempt,
14878
- durationMs: Date.now() - startedAt,
14879
- };
14879
+ function $execCommand(options) {
14880
+ if (!$isRunningInNode()) {
14881
+ throw new EnvironmentMismatchError('Function `$execCommand` can run only in Node environment.js');
14882
+ }
14883
+ return new Promise((resolve, reject) => {
14884
+ // eslint-disable-next-line prefer-const
14885
+ const { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose = DEFAULT_IS_VERBOSE, env, } = $execCommandNormalizeOptions(options);
14886
+ if (timeout !== Infinity) {
14887
+ // TODO: In waitasecond forTime(Infinity) should be equivalent to forEver()
14888
+ forTime(timeout).then(() => {
14889
+ if (crashOnError) {
14890
+ reject(new Error(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms`));
14891
+ }
14892
+ else {
14893
+ console.warn(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms but continues running`);
14894
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
14895
+ resolve('Command exceeded time limit');
14896
+ }
14897
+ });
14880
14898
  }
14881
- catch (error) {
14882
- const isLastAttempt = attempt >= totalAttempts;
14883
- const isRetryable = options.shouldRetry ? options.shouldRetry(error, attempt) : true;
14884
- if (isLastAttempt || !isRetryable) {
14885
- throw error;
14899
+ if (isVerbose) {
14900
+ console.info(colors.yellow(cwd) + ' ' + colors.green(command) + ' ' + colors.blue(args.join(' ')));
14901
+ }
14902
+ try {
14903
+ const commandProcess = spawn(command, args, {
14904
+ cwd,
14905
+ shell: true,
14906
+ env: env ? { ...process.env, ...env } : process.env,
14907
+ });
14908
+ if (isVerbose) {
14909
+ commandProcess.on('message', (message) => {
14910
+ console.info({ message });
14911
+ });
14886
14912
  }
14887
- const delayMs = resolveBackoffDelayMs({
14888
- attempt,
14889
- initialDelayMs: options.initialDelayMs,
14890
- maxDelayMs: options.maxDelayMs,
14891
- backoffFactor: options.backoffFactor,
14892
- jitterRatio: options.jitterRatio,
14893
- random,
14913
+ const output = [];
14914
+ commandProcess.stdout.on('data', (stdout) => {
14915
+ output.push(stdout.toString());
14916
+ if (isVerbose) {
14917
+ console.info(stdout.toString());
14918
+ }
14894
14919
  });
14895
- (_c = options.onRetry) === null || _c === void 0 ? void 0 : _c.call(options, {
14896
- attempt,
14897
- retries: options.retries,
14898
- delayMs,
14899
- error,
14920
+ commandProcess.stderr.on('data', (stderr) => {
14921
+ output.push(stderr.toString());
14922
+ if (isVerbose && stderr.toString().trim()) {
14923
+ console.warn(stderr.toString());
14924
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
14925
+ }
14926
+ });
14927
+ const finishWithCode = (code) => {
14928
+ if (code !== 0) {
14929
+ if (crashOnError) {
14930
+ reject(new Error(output.join('\n').trim() ||
14931
+ `Command "${humanReadableCommand}" exited with code ${code}`));
14932
+ }
14933
+ else {
14934
+ if (isVerbose) {
14935
+ console.warn(`Command "${humanReadableCommand}" exited with code ${code}`);
14936
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
14937
+ }
14938
+ resolve(spaceTrim$1(output.join('\n')));
14939
+ }
14940
+ }
14941
+ else {
14942
+ resolve(spaceTrim$1(output.join('\n')));
14943
+ }
14944
+ };
14945
+ commandProcess.on('close', finishWithCode);
14946
+ commandProcess.on('exit', finishWithCode);
14947
+ commandProcess.on('disconnect', () => {
14948
+ // Note: Unexpected disconnection should always result in rejection
14949
+ reject(new Error(`Command "${humanReadableCommand}" disconnected`));
14950
+ });
14951
+ commandProcess.on('error', (error) => {
14952
+ if (crashOnError) {
14953
+ reject(new Error(`Command "${humanReadableCommand}" failed: \n${error.message}`));
14954
+ }
14955
+ else {
14956
+ if (isVerbose) {
14957
+ console.warn(error);
14958
+ // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
14959
+ }
14960
+ resolve(spaceTrim$1(output.join('\n')));
14961
+ }
14900
14962
  });
14901
- await sleep(delayMs, options.signal);
14902
14963
  }
14903
- }
14904
- throw new Error('Retry loop exited unexpectedly.');
14964
+ catch (error) {
14965
+ // Note: Unexpected error in sync code should always result in rejection
14966
+ reject(error);
14967
+ }
14968
+ });
14905
14969
  }
14906
-
14907
- const DEFAULT_BROWSER_USER_DATA_DIR = join(tmpdir(), 'promptbook', 'browser', 'user-data');
14908
14970
  /**
14909
- * Default remote browser connect timeout in milliseconds.
14971
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
14910
14972
  */
14911
- const DEFAULT_REMOTE_CONNECT_TIMEOUT_MS = 10000;
14973
+
14912
14974
  /**
14913
- * Default retry count for remote browser connection establishment.
14975
+ * Attempts to locate the specified application on a Linux system using the 'which' command.
14976
+ * Returns the path to the executable if found, or null otherwise.
14977
+ *
14978
+ * @private within the repository
14914
14979
  */
14915
- const DEFAULT_REMOTE_CONNECT_RETRIES = 2;
14980
+ async function locateAppOnLinux({ linuxWhich, }) {
14981
+ try {
14982
+ const result = await $execCommand({ crashOnError: true, command: `which ${linuxWhich}` });
14983
+ return result.trim();
14984
+ }
14985
+ catch (error) {
14986
+ assertsError(error);
14987
+ return null;
14988
+ }
14989
+ }
14916
14990
  /**
14917
- * Default initial retry delay for remote browser connection.
14991
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
14992
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
14993
+ */
14994
+
14995
+ /**
14996
+ * Checks if the file is executable
14997
+ *
14998
+ * @private within the repository
14999
+ */
15000
+ async function isExecutable(path, fs) {
15001
+ try {
15002
+ await fs.access(path, fs.constants.X_OK);
15003
+ return true;
15004
+ }
15005
+ catch (error) {
15006
+ return false;
15007
+ }
15008
+ }
15009
+ /**
15010
+ * Note: Not [~🟢~] because it is not directly dependent on `fs
15011
+ * TODO: [🖇] What about symlinks?
15012
+ */
15013
+
15014
+ // Note: Module `userhome` has no types available, so it is imported using `require`
15015
+ // @see https://stackoverflow.com/questions/37000981/how-to-import-node-module-in-typescript-without-type-definitions
15016
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
15017
+ const userhome = require('userhome');
15018
+ /**
15019
+ * Attempts to locate the specified application on a macOS system by checking standard application paths and using mdfind.
15020
+ * Returns the path to the executable if found, or null otherwise.
15021
+ *
15022
+ * @private within the repository
15023
+ */
15024
+ async function locateAppOnMacOs({ macOsName, }) {
15025
+ try {
15026
+ const toExec = `/Contents/MacOS/${macOsName}`;
15027
+ const regPath = `/Applications/${macOsName}.app` + toExec;
15028
+ const altPath = userhome(regPath.slice(1));
15029
+ if (await isExecutable(regPath, $provideFilesystemForNode())) {
15030
+ return regPath;
15031
+ }
15032
+ else if (await isExecutable(altPath, $provideFilesystemForNode())) {
15033
+ return altPath;
15034
+ }
15035
+ const result = await $execCommand({
15036
+ crashOnError: true,
15037
+ command: `mdfind 'kMDItemDisplayName == "${macOsName}" && kMDItemKind == Application'`,
15038
+ });
15039
+ return result.trim() + toExec;
15040
+ }
15041
+ catch (error) {
15042
+ assertsError(error);
15043
+ return null;
15044
+ }
15045
+ }
15046
+ /**
15047
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
15048
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
15049
+ */
15050
+
15051
+ /**
15052
+ * Attempts to locate the specified application on a Windows system by searching common installation directories.
15053
+ * Returns the path to the executable if found, or null otherwise.
15054
+ *
15055
+ * @private within the repository
15056
+ */
15057
+ async function locateAppOnWindows({ appName, windowsSuffix, }) {
15058
+ try {
15059
+ const prefixes = [
15060
+ process.env.LOCALAPPDATA,
15061
+ join(process.env.LOCALAPPDATA || '', 'Programs'),
15062
+ process.env.PROGRAMFILES,
15063
+ process.env['PROGRAMFILES(X86)'],
15064
+ ];
15065
+ for (const prefix of prefixes) {
15066
+ const path = prefix + windowsSuffix;
15067
+ if (await isExecutable(path, $provideFilesystemForNode())) {
15068
+ return path;
15069
+ }
15070
+ }
15071
+ throw new Error(`Can not locate app ${appName} on Windows.`);
15072
+ }
15073
+ catch (error) {
15074
+ assertsError(error);
15075
+ return null;
15076
+ }
15077
+ }
15078
+ /**
15079
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
15080
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
15081
+ */
15082
+
15083
+ /**
15084
+ * Locates an application on the system
15085
+ *
15086
+ * @private within the repository
15087
+ */
15088
+ function locateApp(options) {
15089
+ if (!$isRunningInNode()) {
15090
+ throw new EnvironmentMismatchError('Locating apps works only in Node.js environment');
15091
+ }
15092
+ const { appName, linuxWhich, windowsSuffix, macOsName } = options;
15093
+ if (process.platform === 'win32') {
15094
+ if (windowsSuffix) {
15095
+ return locateAppOnWindows({ appName, windowsSuffix });
15096
+ }
15097
+ else {
15098
+ throw new Error(`${appName} is not available on Windows.`);
15099
+ }
15100
+ }
15101
+ else if (process.platform === 'darwin') {
15102
+ if (macOsName) {
15103
+ return locateAppOnMacOs({ macOsName });
15104
+ }
15105
+ else {
15106
+ throw new Error(`${appName} is not available on macOS.`);
15107
+ }
15108
+ }
15109
+ else {
15110
+ if (linuxWhich) {
15111
+ return locateAppOnLinux({ linuxWhich });
15112
+ }
15113
+ else {
15114
+ throw new Error(`${appName} is not available on Linux.`);
15115
+ }
15116
+ }
15117
+ }
15118
+ /**
15119
+ * TODO: [🧠][♿] Maybe export through `@promptbook/node`
15120
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
15121
+ */
15122
+
15123
+ /**
15124
+ * @@@
15125
+ *
15126
+ * @private within the repository
15127
+ */
15128
+ function locateChrome() {
15129
+ return locateApp({
15130
+ appName: 'Chrome',
15131
+ linuxWhich: 'google-chrome',
15132
+ windowsSuffix: '\\Google\\Chrome\\Application\\chrome.exe',
15133
+ macOsName: 'Google Chrome',
15134
+ });
15135
+ }
15136
+
15137
+ /**
15138
+ * Creates one standard abort error for cancelled retry loops.
15139
+ *
15140
+ * @private utility for Agents Server runtime retries
15141
+ */
15142
+ function createAbortError$1() {
15143
+ const error = new Error('Operation was aborted.');
15144
+ error.name = 'AbortError';
15145
+ return error;
15146
+ }
15147
+ /**
15148
+ * Throws when the supplied signal is already aborted.
15149
+ *
15150
+ * @private utility for Agents Server runtime retries
15151
+ */
15152
+ function assertNotAborted(signal) {
15153
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
15154
+ throw createAbortError$1();
15155
+ }
15156
+ }
15157
+ /**
15158
+ * Waits for a duration while respecting cancellation.
15159
+ *
15160
+ * @private utility for Agents Server runtime retries
15161
+ */
15162
+ async function sleepWithAbort(delayMs, signal) {
15163
+ if (delayMs <= 0) {
15164
+ assertNotAborted(signal);
15165
+ return;
15166
+ }
15167
+ await new Promise((resolve, reject) => {
15168
+ const timeout = setTimeout(() => {
15169
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
15170
+ resolve();
15171
+ }, delayMs);
15172
+ const onAbort = () => {
15173
+ clearTimeout(timeout);
15174
+ signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
15175
+ reject(createAbortError$1());
15176
+ };
15177
+ signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
15178
+ });
15179
+ }
15180
+ /**
15181
+ * Resolves the retry wait duration for one failed attempt.
15182
+ *
15183
+ * @private utility for Agents Server runtime retries
15184
+ */
15185
+ function resolveBackoffDelayMs(options) {
15186
+ const exponentialDelay = options.initialDelayMs * Math.pow(options.backoffFactor, Math.max(0, options.attempt - 1));
15187
+ const boundedDelay = Math.min(exponentialDelay, options.maxDelayMs);
15188
+ const jitterDelay = boundedDelay * options.jitterRatio * Math.max(0, options.random());
15189
+ return Math.max(0, Math.round(boundedDelay + jitterDelay));
15190
+ }
15191
+ /**
15192
+ * Retries one async operation with exponential backoff and jitter.
15193
+ *
15194
+ * @private utility for Agents Server runtime retries
15195
+ */
15196
+ async function retryWithBackoff(operation, options) {
15197
+ var _a, _b, _c;
15198
+ const startedAt = Date.now();
15199
+ const totalAttempts = Math.max(1, options.retries + 1);
15200
+ const random = (_a = options.random) !== null && _a !== void 0 ? _a : Math.random;
15201
+ const sleep = (_b = options.sleep) !== null && _b !== void 0 ? _b : sleepWithAbort;
15202
+ for (let attempt = 1; attempt <= totalAttempts; attempt++) {
15203
+ assertNotAborted(options.signal);
15204
+ try {
15205
+ const value = await operation(attempt);
15206
+ return {
15207
+ value,
15208
+ attempts: attempt,
15209
+ durationMs: Date.now() - startedAt,
15210
+ };
15211
+ }
15212
+ catch (error) {
15213
+ const isLastAttempt = attempt >= totalAttempts;
15214
+ const isRetryable = options.shouldRetry ? options.shouldRetry(error, attempt) : true;
15215
+ if (isLastAttempt || !isRetryable) {
15216
+ throw error;
15217
+ }
15218
+ const delayMs = resolveBackoffDelayMs({
15219
+ attempt,
15220
+ initialDelayMs: options.initialDelayMs,
15221
+ maxDelayMs: options.maxDelayMs,
15222
+ backoffFactor: options.backoffFactor,
15223
+ jitterRatio: options.jitterRatio,
15224
+ random,
15225
+ });
15226
+ (_c = options.onRetry) === null || _c === void 0 ? void 0 : _c.call(options, {
15227
+ attempt,
15228
+ retries: options.retries,
15229
+ delayMs,
15230
+ error,
15231
+ });
15232
+ await sleep(delayMs, options.signal);
15233
+ }
15234
+ }
15235
+ throw new Error('Retry loop exited unexpectedly.');
15236
+ }
15237
+
15238
+ const DEFAULT_BROWSER_USER_DATA_DIR = join(tmpdir(), 'promptbook', 'browser', 'user-data');
15239
+ /**
15240
+ * Default remote browser connect timeout in milliseconds.
15241
+ */
15242
+ const DEFAULT_REMOTE_CONNECT_TIMEOUT_MS = 10000;
15243
+ /**
15244
+ * Default retry count for remote browser connection establishment.
15245
+ */
15246
+ const DEFAULT_REMOTE_CONNECT_RETRIES = 2;
15247
+ /**
15248
+ * Default initial retry delay for remote browser connection.
14918
15249
  */
14919
15250
  const DEFAULT_REMOTE_CONNECT_BACKOFF_INITIAL_MS = 250;
14920
15251
  /**
@@ -21357,27 +21688,154 @@ class UseBrowserCommitmentDefinition extends BaseCommitmentDefinition {
21357
21688
  */
21358
21689
 
21359
21690
  /**
21360
- * Lightweight email token matcher used for `USE EMAIL` first-line parsing.
21691
+ * Base Google Calendar API URL.
21361
21692
  *
21362
- * @private internal USE EMAIL constant
21693
+ * @private constant of callGoogleCalendarApi
21363
21694
  */
21364
- const EMAIL_TOKEN_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/;
21695
+ const GOOGLE_CALENDAR_API_BASE_URL = 'https://www.googleapis.com/calendar/v3';
21365
21696
  /**
21366
- * Parses `USE EMAIL` commitment content into optional sender email + additional instructions.
21697
+ * Runs one Google Calendar API request and parses JSON response payload.
21367
21698
  *
21368
- * Examples:
21369
- * - `agent@example.com`
21370
- * - `agent@example.com Keep emails concise`
21371
- * - `Keep emails concise`
21699
+ * @private function of UseCalendarCommitmentDefinition
21700
+ */
21701
+ async function callGoogleCalendarApi(accessToken, options) {
21702
+ const url = new URL(options.path, GOOGLE_CALENDAR_API_BASE_URL);
21703
+ if (options.query) {
21704
+ for (const [key, value] of Object.entries(options.query)) {
21705
+ if (value && value.trim()) {
21706
+ url.searchParams.set(key, value);
21707
+ }
21708
+ }
21709
+ }
21710
+ const response = await fetch(url.toString(), {
21711
+ method: options.method,
21712
+ headers: {
21713
+ Authorization: `Bearer ${accessToken}`,
21714
+ Accept: 'application/json',
21715
+ 'Content-Type': 'application/json',
21716
+ },
21717
+ body: options.body ? JSON.stringify(options.body) : undefined,
21718
+ });
21719
+ const textPayload = await response.text();
21720
+ const parsedPayload = tryParseJson$2(textPayload);
21721
+ if (options.allowNotFound && response.status === 404) {
21722
+ return null;
21723
+ }
21724
+ if (!response.ok) {
21725
+ throw new Error(spaceTrim$1(`
21726
+ Google Calendar API request failed (${response.status} ${response.statusText}):
21727
+ ${extractGoogleCalendarApiErrorMessage(parsedPayload, textPayload)}
21728
+ `));
21729
+ }
21730
+ return parsedPayload;
21731
+ }
21732
+ /**
21733
+ * Parses raw text into JSON when possible.
21372
21734
  *
21373
- * @private internal utility of USE EMAIL commitment
21735
+ * @private function of callGoogleCalendarApi
21374
21736
  */
21375
- function parseUseEmailCommitmentContent(content) {
21737
+ function tryParseJson$2(rawText) {
21738
+ if (!rawText.trim()) {
21739
+ return {};
21740
+ }
21741
+ try {
21742
+ return JSON.parse(rawText);
21743
+ }
21744
+ catch (_a) {
21745
+ return rawText;
21746
+ }
21747
+ }
21748
+ /**
21749
+ * Extracts a user-friendly Google Calendar API error message.
21750
+ *
21751
+ * @private function of callGoogleCalendarApi
21752
+ */
21753
+ function extractGoogleCalendarApiErrorMessage(parsedPayload, fallbackText) {
21754
+ if (parsedPayload && typeof parsedPayload === 'object') {
21755
+ const payload = parsedPayload;
21756
+ const errorPayload = payload.error;
21757
+ if (errorPayload && typeof errorPayload === 'object') {
21758
+ const normalizedErrorPayload = errorPayload;
21759
+ const message = typeof normalizedErrorPayload.message === 'string' ? normalizedErrorPayload.message : '';
21760
+ const errors = Array.isArray(normalizedErrorPayload.errors) ? normalizedErrorPayload.errors : [];
21761
+ const flattenedErrors = errors
21762
+ .map((errorEntry) => {
21763
+ if (!errorEntry || typeof errorEntry !== 'object') {
21764
+ return '';
21765
+ }
21766
+ const normalizedErrorEntry = errorEntry;
21767
+ const detailMessage = typeof normalizedErrorEntry.message === 'string' ? normalizedErrorEntry.message : '';
21768
+ const reason = typeof normalizedErrorEntry.reason === 'string' ? normalizedErrorEntry.reason : '';
21769
+ return [detailMessage, reason].filter(Boolean).join(' | ');
21770
+ })
21771
+ .filter(Boolean);
21772
+ if (message || flattenedErrors.length > 0) {
21773
+ return [message, ...flattenedErrors].filter(Boolean).join(' | ');
21774
+ }
21775
+ }
21776
+ }
21777
+ return fallbackText || 'Unknown Google Calendar API error';
21778
+ }
21779
+
21780
+ /**
21781
+ * Hostnames accepted for Google Calendar references.
21782
+ *
21783
+ * @private internal USE CALENDAR constant
21784
+ */
21785
+ const GOOGLE_CALENDAR_HOSTNAMES = new Set(['calendar.google.com', 'www.calendar.google.com']);
21786
+ /**
21787
+ * Default Google Calendar OAuth scopes when commitment content does not list any.
21788
+ *
21789
+ * @private internal USE CALENDAR constant
21790
+ */
21791
+ const DEFAULT_GOOGLE_CALENDAR_SCOPES = ['https://www.googleapis.com/auth/calendar'];
21792
+ /**
21793
+ * Parses one Google Calendar URL/reference into canonical details.
21794
+ *
21795
+ * Supported input forms:
21796
+ * - `https://calendar.google.com/...`
21797
+ * - `calendar.google.com/...`
21798
+ *
21799
+ * @private internal utility of USE CALENDAR commitment
21800
+ */
21801
+ function parseGoogleCalendarReference(rawReference) {
21802
+ const trimmedReference = rawReference.trim();
21803
+ if (!trimmedReference) {
21804
+ return null;
21805
+ }
21806
+ const normalizedReference = trimmedReference.startsWith('calendar.google.com/')
21807
+ ? `https://${trimmedReference}`
21808
+ : trimmedReference;
21809
+ let parsedUrl;
21810
+ try {
21811
+ parsedUrl = new URL(normalizedReference);
21812
+ }
21813
+ catch (_a) {
21814
+ return null;
21815
+ }
21816
+ if (!GOOGLE_CALENDAR_HOSTNAMES.has(parsedUrl.hostname.toLowerCase())) {
21817
+ return null;
21818
+ }
21819
+ parsedUrl.protocol = 'https:';
21820
+ parsedUrl.hash = '';
21821
+ return {
21822
+ provider: 'google',
21823
+ url: parsedUrl.toString(),
21824
+ calendarId: parseGoogleCalendarIdFromUrl(parsedUrl) || 'primary',
21825
+ scopes: [...DEFAULT_GOOGLE_CALENDAR_SCOPES],
21826
+ };
21827
+ }
21828
+ /**
21829
+ * Parses `USE CALENDAR` commitment content into calendar reference + optional instructions.
21830
+ *
21831
+ * @private internal utility of USE CALENDAR commitment
21832
+ */
21833
+ function parseUseCalendarCommitmentContent(content) {
21376
21834
  const trimmedContent = spaceTrim$1(content);
21377
21835
  if (!trimmedContent) {
21378
21836
  return {
21379
- senderEmail: null,
21380
- senderEmailRaw: null,
21837
+ calendar: null,
21838
+ calendarUrlRaw: null,
21381
21839
  instructions: '',
21382
21840
  };
21383
21841
  }
@@ -21387,718 +21845,1505 @@ function parseUseEmailCommitmentContent(content) {
21387
21845
  .filter(Boolean);
21388
21846
  if (lines.length === 0) {
21389
21847
  return {
21390
- senderEmail: null,
21391
- senderEmailRaw: null,
21848
+ calendar: null,
21849
+ calendarUrlRaw: null,
21392
21850
  instructions: '',
21393
21851
  };
21394
21852
  }
21853
+ let calendarReferenceRaw = null;
21854
+ let calendarReference = null;
21395
21855
  const firstLine = lines[0] || '';
21396
- const senderMatch = firstLine.match(EMAIL_TOKEN_PATTERN);
21397
- const senderEmailRaw = (senderMatch === null || senderMatch === void 0 ? void 0 : senderMatch[0]) || null;
21398
- const senderEmail = senderEmailRaw && isValidEmail(senderEmailRaw) ? senderEmailRaw : null;
21399
- let firstLineWithoutSender = firstLine;
21400
- if (senderEmailRaw) {
21401
- const matchIndex = firstLine.indexOf(senderEmailRaw);
21402
- const prefix = firstLine.slice(0, matchIndex).trim();
21403
- const suffix = firstLine.slice(matchIndex + senderEmailRaw.length).trim();
21404
- firstLineWithoutSender = [prefix, suffix].filter(Boolean).join(' ').trim();
21856
+ const firstLineTokens = firstLine.split(/\s+/).filter(Boolean);
21857
+ for (const token of firstLineTokens) {
21858
+ const cleanedToken = token.replace(/[),.;:!?]+$/g, '');
21859
+ const parsedReference = parseGoogleCalendarReference(cleanedToken);
21860
+ if (!parsedReference) {
21861
+ continue;
21862
+ }
21863
+ calendarReferenceRaw = cleanedToken;
21864
+ calendarReference = parsedReference;
21865
+ break;
21405
21866
  }
21406
- const instructionLines = [firstLineWithoutSender, ...lines.slice(1)].filter(Boolean);
21407
- const instructions = instructionLines.join('\n').trim();
21867
+ if (!calendarReference) {
21868
+ const firstUrl = findFirstUrlToken(trimmedContent);
21869
+ if (firstUrl) {
21870
+ calendarReferenceRaw = firstUrl;
21871
+ calendarReference = parseGoogleCalendarReference(firstUrl);
21872
+ }
21873
+ }
21874
+ const scopes = extractGoogleCalendarScopes(trimmedContent);
21875
+ if (calendarReference) {
21876
+ calendarReference = {
21877
+ ...calendarReference,
21878
+ scopes: scopes.length > 0 ? scopes : [...DEFAULT_GOOGLE_CALENDAR_SCOPES],
21879
+ tokenRef: extractTokenRef(trimmedContent),
21880
+ };
21881
+ }
21882
+ const instructionLines = [...lines];
21883
+ if (instructionLines.length > 0 && calendarReferenceRaw) {
21884
+ instructionLines[0] = removeTokenFromLine(instructionLines[0] || '', calendarReferenceRaw);
21885
+ }
21886
+ const filteredInstructionLines = instructionLines.filter((line) => !/^\s*SCOPES?\s+/i.test(line) && !line.trim().startsWith('https://www.googleapis.com/auth/'));
21408
21887
  return {
21409
- senderEmail,
21410
- senderEmailRaw,
21411
- instructions,
21888
+ calendar: calendarReference,
21889
+ calendarUrlRaw: calendarReferenceRaw,
21890
+ instructions: filteredInstructionLines.join('\n').trim(),
21412
21891
  };
21413
21892
  }
21414
-
21415
21893
  /**
21416
- * Client-side safe wrapper for sending emails.
21894
+ * Attempts to resolve one concrete Google Calendar id from URL.
21417
21895
  *
21418
- * This function proxies requests to the Agents Server API endpoint for email queuing,
21419
- * making it safe to use in browser environments.
21896
+ * @private internal utility of USE CALENDAR commitment
21897
+ */
21898
+ function parseGoogleCalendarIdFromUrl(url) {
21899
+ const rawCalendarId = url.searchParams.get('cid') || url.searchParams.get('src') || url.searchParams.get('calendarId');
21900
+ if (!rawCalendarId) {
21901
+ return null;
21902
+ }
21903
+ const decodedCalendarId = decodeURIComponent(rawCalendarId).trim();
21904
+ return decodedCalendarId || null;
21905
+ }
21906
+ /**
21907
+ * Finds the first URL-looking token in text.
21420
21908
  *
21421
- * @param args Email payload forwarded to the server-side `send_email` tool
21422
- * @param agentsServerUrl The base URL of the agents server (defaults to current origin)
21423
- * @returns Result string from the server-side send_email tool
21909
+ * @private utility of USE CALENDAR commitment
21910
+ */
21911
+ function findFirstUrlToken(text) {
21912
+ const match = text.match(/https?:\/\/[^\s)]+/i);
21913
+ return match ? match[0] || null : null;
21914
+ }
21915
+ /**
21916
+ * Extracts Google Calendar OAuth scopes declared in commitment text.
21424
21917
  *
21425
- * @private internal utility for USE EMAIL commitment
21918
+ * @private utility of USE CALENDAR commitment
21426
21919
  */
21427
- async function sendEmailViaBrowser(args, agentsServerUrl) {
21428
- try {
21429
- const baseUrl = agentsServerUrl || (typeof window !== 'undefined' ? window.location.origin : '');
21430
- if (!baseUrl) {
21431
- throw new Error('Agents server URL is required in non-browser environments');
21432
- }
21433
- const apiUrl = new URL('/api/send-email', baseUrl);
21434
- const response = await fetch(apiUrl.toString(), {
21435
- method: 'POST',
21436
- headers: {
21437
- 'Content-Type': 'application/json',
21438
- },
21439
- body: JSON.stringify(args),
21440
- });
21441
- if (!response.ok) {
21442
- const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
21443
- throw new Error(`Failed to send email: ${errorData.error || response.statusText}`);
21444
- }
21445
- const data = await response.json();
21446
- if (!data.success) {
21447
- throw new Error(`Email sending failed: ${data.error || 'Unknown error'}`);
21448
- }
21449
- return typeof data.result === 'string' ? data.result : JSON.stringify(data.result);
21450
- }
21451
- catch (error) {
21452
- const errorMessage = error instanceof Error ? error.message : String(error);
21453
- throw new Error(`Error sending email via browser: ${errorMessage}`);
21920
+ function extractGoogleCalendarScopes(content) {
21921
+ const scopesFromUrls = content.match(/https:\/\/www\.googleapis\.com\/auth\/[A-Za-z0-9._/-]+/gim) || [];
21922
+ const scopesFromKeywordLines = content
21923
+ .split(/\r?\n/)
21924
+ .map((line) => line.trim())
21925
+ .filter((line) => /^\s*SCOPES?\s+/i.test(line))
21926
+ .flatMap((line) => line
21927
+ .replace(/^\s*SCOPES?\s+/i, '')
21928
+ .split(/[,\s]+/)
21929
+ .map((scope) => scope.trim())
21930
+ .filter(Boolean))
21931
+ .filter((scope) => scope.startsWith('https://www.googleapis.com/auth/'));
21932
+ const uniqueScopes = new Set();
21933
+ for (const scope of [...scopesFromUrls, ...scopesFromKeywordLines]) {
21934
+ uniqueScopes.add(scope);
21454
21935
  }
21936
+ return [...uniqueScopes];
21455
21937
  }
21456
-
21457
21938
  /**
21458
- * Tool name used by USE EMAIL.
21939
+ * Extracts optional token reference marker from commitment text.
21459
21940
  *
21460
- * @private internal USE EMAIL constant
21941
+ * @private utility of USE CALENDAR commitment
21461
21942
  */
21462
- const SEND_EMAIL_TOOL_NAME = 'send_email';
21943
+ function extractTokenRef(content) {
21944
+ var _a;
21945
+ const tokenRefMatch = content.match(/@[\w.-]+/);
21946
+ const tokenRef = (_a = tokenRefMatch === null || tokenRefMatch === void 0 ? void 0 : tokenRefMatch[0]) === null || _a === void 0 ? void 0 : _a.trim();
21947
+ return tokenRef || undefined;
21948
+ }
21463
21949
  /**
21464
- * Wallet service used for SMTP credentials required by USE EMAIL.
21950
+ * Removes one specific token from one instruction line.
21465
21951
  *
21466
- * @private internal USE EMAIL constant
21952
+ * @private utility of USE CALENDAR commitment
21467
21953
  */
21468
- const USE_EMAIL_SMTP_WALLET_SERVICE = 'smtp';
21954
+ function removeTokenFromLine(line, token) {
21955
+ const tokens = line.split(/\s+/).filter(Boolean);
21956
+ const filteredTokens = tokens.filter((lineToken) => lineToken.replace(/[),.;:!?]+$/g, '') !== token);
21957
+ return filteredTokens.join(' ').trim();
21958
+ }
21469
21959
  /**
21470
- * Wallet key used for SMTP credentials required by USE EMAIL.
21960
+ * Note: [💞] Ignore a discrepancy between file name and entity name
21961
+ */
21962
+
21963
+ /**
21964
+ * Wallet metadata used by USE CALENDAR when resolving Google Calendar credentials.
21471
21965
  *
21472
- * @private internal USE EMAIL constant
21966
+ * @private constant of UseCalendarCommitmentDefinition
21473
21967
  */
21474
- const USE_EMAIL_SMTP_WALLET_KEY = 'use-email-smtp-credentials';
21968
+ const UseCalendarWallet = {
21969
+ service: 'google_calendar',
21970
+ key: 'use-calendar-google-token',
21971
+ };
21972
+
21475
21973
  /**
21476
- * USE EMAIL commitment definition.
21974
+ * Internal error used to signal missing wallet credentials.
21477
21975
  *
21478
- * The `USE EMAIL` commitment enables outbound email sending through the `send_email` tool.
21976
+ * @private class of resolveUseCalendarToolRuntimeOrWalletCredentialResult
21977
+ */
21978
+ class CalendarWalletCredentialRequiredError extends Error {
21979
+ constructor(options) {
21980
+ super('Google Calendar token is missing in wallet. Request it from user and store as ACCESS_TOKEN (service google_calendar, key use-calendar-google-token).');
21981
+ this.name = 'CalendarWalletCredentialRequiredError';
21982
+ this.service = UseCalendarWallet.service;
21983
+ this.key = UseCalendarWallet.key;
21984
+ this.calendarReference = options.calendarReference;
21985
+ }
21986
+ }
21987
+ /**
21988
+ * Resolves calendar runtime or returns a wallet-credential-required payload when missing.
21479
21989
  *
21480
- * @private [🪔] Maybe export the commitments through some package
21990
+ * @private function of UseCalendarCommitmentDefinition
21481
21991
  */
21482
- class UseEmailCommitmentDefinition extends BaseCommitmentDefinition {
21483
- constructor() {
21484
- super('USE EMAIL', ['EMAIL', 'MAIL']);
21992
+ function resolveUseCalendarToolRuntimeOrWalletCredentialResult(args) {
21993
+ try {
21994
+ return resolveUseCalendarToolRuntime(args);
21485
21995
  }
21486
- get requiresContent() {
21487
- return false;
21996
+ catch (error) {
21997
+ if (error instanceof CalendarWalletCredentialRequiredError) {
21998
+ return {
21999
+ walletResult: JSON.stringify(createCalendarWalletCredentialRequiredResult(error)),
22000
+ };
22001
+ }
22002
+ throw error;
21488
22003
  }
21489
- /**
21490
- * Short one-line description of USE EMAIL.
21491
- */
21492
- get description() {
21493
- return 'Enable outbound email sending through a wallet-backed SMTP configuration.';
22004
+ }
22005
+ /**
22006
+ * Resolves runtime calendar + token for a USE CALENDAR tool call.
22007
+ *
22008
+ * @private function of resolveUseCalendarToolRuntimeOrWalletCredentialResult
22009
+ */
22010
+ function resolveUseCalendarToolRuntime(args) {
22011
+ var _a, _b;
22012
+ const runtimeContext = (readToolRuntimeContextFromToolArgs(args) ||
22013
+ {});
22014
+ const configuredCalendars = normalizeConfiguredCalendars$1((_a = runtimeContext.calendars) === null || _a === void 0 ? void 0 : _a.connections);
22015
+ const calendarArgument = normalizeOptionalText$1(args.calendarUrl);
22016
+ let calendarReference = null;
22017
+ if (calendarArgument) {
22018
+ calendarReference = parseGoogleCalendarReference(calendarArgument);
22019
+ if (!calendarReference) {
22020
+ throw new Error(`Calendar URL "${calendarArgument}" is invalid.`);
22021
+ }
21494
22022
  }
21495
- /**
21496
- * Icon for this commitment.
21497
- */
21498
- get icon() {
21499
- return '📧';
22023
+ else if (configuredCalendars.length === 1) {
22024
+ calendarReference = configuredCalendars[0] || null;
21500
22025
  }
21501
- /**
21502
- * Markdown documentation for USE EMAIL commitment.
21503
- */
21504
- get documentation() {
21505
- return spaceTrim$1(`
21506
- # USE EMAIL
21507
-
21508
- Enables the agent to send outbound emails through SMTP.
21509
-
21510
- ## Key aspects
21511
-
21512
- - The agent sends email via the \`send_email\` tool.
21513
- - SMTP credentials are expected from wallet records (\`ACCESS_TOKEN\`, service \`smtp\`, key \`use-email-smtp-credentials\`).
21514
- - Commitment content can optionally begin with a default sender email address:
21515
- - \`USE EMAIL agent@example.com\`
21516
- - Remaining commitment content is treated as optional email-writing instructions.
21517
-
21518
- ## Examples
21519
-
21520
- \`\`\`book
21521
- Writing Agent
21522
- USE EMAIL agent@example.com
21523
- RULE Write emails to customers according to the instructions from user.
21524
- \`\`\`
21525
-
21526
- \`\`\`book
21527
- Formal Email Assistant
21528
- USE EMAIL agent@example.com Keep emails concise and formal.
21529
- \`\`\`
21530
- `);
22026
+ else if (configuredCalendars.length > 1) {
22027
+ throw new Error('Calendar is ambiguous. Provide "calendarUrl" argument with one calendar from USE CALENDAR commitments.');
21531
22028
  }
21532
- applyToAgentModelRequirements(requirements, content) {
21533
- const parsedCommitment = parseUseEmailCommitmentContent(content);
21534
- const extraInstructions = formatOptionalInstructionBlock('Email instructions', parsedCommitment.instructions);
21535
- const senderInstruction = parsedCommitment.senderEmail
21536
- ? `- Default sender address from commitment: "${parsedCommitment.senderEmail}".`
21537
- : '';
21538
- const updatedTools = addUseEmailTools(requirements.tools || []);
21539
- return this.appendToSystemMessage({
21540
- ...requirements,
21541
- tools: updatedTools,
21542
- _metadata: {
21543
- ...requirements._metadata,
21544
- useEmail: true,
21545
- ...(parsedCommitment.senderEmail ? { useEmailSender: parsedCommitment.senderEmail } : {}),
21546
- },
21547
- }, spaceTrim$1((block) => `
21548
- Email tool:
21549
- - Use "${SEND_EMAIL_TOOL_NAME}" to send outbound emails.
21550
- - Prefer \`message\` argument compatible with Promptbook \`Message\` type.
21551
- - Include subject in \`message.metadata.subject\` (or use legacy \`subject\` argument).
21552
- - USE EMAIL credentials are read from wallet records (ACCESS_TOKEN, service "${USE_EMAIL_SMTP_WALLET_SERVICE}", key "${USE_EMAIL_SMTP_WALLET_KEY}").
21553
- - Wallet secret must contain SMTP credentials in JSON format with fields \`host\`, \`port\`, \`secure\`, \`username\`, \`password\`.
21554
- - If credentials are missing, ask user to add wallet credentials.
21555
- ${block(senderInstruction)}
21556
- ${block(extraInstructions)}
21557
- `));
22029
+ else {
22030
+ throw new Error('Calendar is required. Provide "calendarUrl" argument in the tool call.');
21558
22031
  }
21559
- /**
21560
- * Gets human-readable titles for tool functions provided by this commitment.
21561
- */
21562
- getToolTitles() {
21563
- return {
21564
- [SEND_EMAIL_TOOL_NAME]: 'Send email',
21565
- };
22032
+ if (!calendarReference) {
22033
+ throw new Error('Calendar is required but was not resolved.');
21566
22034
  }
21567
- /**
21568
- * Gets the browser-safe `send_email` implementation.
21569
- *
21570
- * Node.js runtime overrides this via `getAllCommitmentsToolFunctionsForNode`.
21571
- */
21572
- getToolFunctions() {
21573
- return {
21574
- async [SEND_EMAIL_TOOL_NAME](args) {
21575
- return sendEmailViaBrowser(args);
21576
- },
21577
- };
22035
+ const accessToken = normalizeOptionalText$1((_b = runtimeContext.calendars) === null || _b === void 0 ? void 0 : _b.googleAccessToken) || '';
22036
+ if (!accessToken) {
22037
+ throw new CalendarWalletCredentialRequiredError({
22038
+ calendarReference,
22039
+ });
22040
+ }
22041
+ if (configuredCalendars.length > 0) {
22042
+ const allowedCalendarUrls = new Set(configuredCalendars.map((configuredCalendar) => configuredCalendar.url));
22043
+ if (!allowedCalendarUrls.has(calendarReference.url)) {
22044
+ throw new Error(`Calendar "${calendarReference.url}" is not configured by USE CALENDAR for this agent.`);
22045
+ }
21578
22046
  }
22047
+ return {
22048
+ calendarReference,
22049
+ accessToken,
22050
+ };
21579
22051
  }
21580
22052
  /**
21581
- * Adds USE EMAIL tool definition while keeping already registered tools untouched.
22053
+ * Normalizes optional calendar list from runtime context.
21582
22054
  *
21583
- * @private utility of USE EMAIL commitment
22055
+ * @private function of resolveUseCalendarToolRuntimeOrWalletCredentialResult
21584
22056
  */
21585
- function addUseEmailTools(existingTools) {
21586
- if (existingTools.some((tool) => tool.name === SEND_EMAIL_TOOL_NAME)) {
21587
- return [...existingTools];
22057
+ function normalizeConfiguredCalendars$1(rawCalendars) {
22058
+ if (!Array.isArray(rawCalendars)) {
22059
+ return [];
21588
22060
  }
21589
- return [
21590
- ...existingTools,
21591
- {
21592
- name: SEND_EMAIL_TOOL_NAME,
21593
- description: 'Send an outbound email through configured SMTP credentials. Prefer providing Message-like payload in `message`.',
21594
- parameters: {
21595
- type: 'object',
21596
- properties: {
21597
- message: {
21598
- type: 'object',
21599
- description: 'Preferred input payload compatible with Promptbook Message type. Use metadata.subject for subject line.',
21600
- },
21601
- to: {
21602
- type: 'string',
21603
- description: 'Legacy alias for recipients (use comma-separated emails or JSON array encoded as string).',
21604
- },
21605
- cc: {
21606
- type: 'string',
21607
- description: 'Optional CC recipients (use comma-separated emails or JSON array encoded as string).',
21608
- },
21609
- subject: {
21610
- type: 'string',
21611
- description: 'Legacy alias for subject.',
21612
- },
22061
+ const calendars = [];
22062
+ const knownCalendars = new Set();
22063
+ for (const rawCalendar of rawCalendars) {
22064
+ if (!rawCalendar || typeof rawCalendar !== 'object') {
22065
+ continue;
22066
+ }
22067
+ const calendar = rawCalendar;
22068
+ const rawUrl = normalizeOptionalText$1(calendar.url);
22069
+ if (!rawUrl) {
22070
+ continue;
22071
+ }
22072
+ const parsedReference = parseGoogleCalendarReference(rawUrl);
22073
+ if (!parsedReference) {
22074
+ continue;
22075
+ }
22076
+ const key = `${parsedReference.provider}|${parsedReference.url}`;
22077
+ if (knownCalendars.has(key)) {
22078
+ continue;
22079
+ }
22080
+ knownCalendars.add(key);
22081
+ const scopes = Array.isArray(calendar.scopes)
22082
+ ? calendar.scopes
22083
+ .filter((scope) => typeof scope === 'string')
22084
+ .map((scope) => scope.trim())
22085
+ .filter(Boolean)
22086
+ : [];
22087
+ calendars.push({
22088
+ ...parsedReference,
22089
+ scopes: scopes.length > 0 ? scopes : parsedReference.scopes,
22090
+ });
22091
+ }
22092
+ return calendars;
22093
+ }
22094
+ /**
22095
+ * Converts missing-wallet errors into structured tool result payloads.
22096
+ *
22097
+ * @private function of resolveUseCalendarToolRuntimeOrWalletCredentialResult
22098
+ */
22099
+ function createCalendarWalletCredentialRequiredResult(error) {
22100
+ return {
22101
+ action: 'calendar-auth',
22102
+ status: 'wallet-credential-required',
22103
+ recordType: 'ACCESS_TOKEN',
22104
+ service: error.service,
22105
+ key: error.key,
22106
+ isUserScoped: false,
22107
+ isGlobal: false,
22108
+ provider: 'google',
22109
+ calendarUrl: error.calendarReference.url,
22110
+ scopes: error.calendarReference.scopes,
22111
+ message: error.message,
22112
+ };
22113
+ }
22114
+ /**
22115
+ * Normalizes unknown text input to trimmed non-empty string.
22116
+ *
22117
+ * @private function of resolveUseCalendarToolRuntimeOrWalletCredentialResult
22118
+ */
22119
+ function normalizeOptionalText$1(value) {
22120
+ if (typeof value !== 'string') {
22121
+ return undefined;
22122
+ }
22123
+ const trimmedValue = value.trim();
22124
+ return trimmedValue || undefined;
22125
+ }
22126
+
22127
+ /**
22128
+ * Names of tools used by the USE CALENDAR commitment.
22129
+ *
22130
+ * @private constant of UseCalendarCommitmentDefinition
22131
+ */
22132
+ const UseCalendarToolNames = {
22133
+ listEvents: 'calendar_list_events',
22134
+ getEvent: 'calendar_get_event',
22135
+ createEvent: 'calendar_create_event',
22136
+ updateEvent: 'calendar_update_event',
22137
+ deleteEvent: 'calendar_delete_event',
22138
+ inviteGuests: 'calendar_invite_guests',
22139
+ };
22140
+
22141
+ /**
22142
+ * Gets Google Calendar tool function implementations.
22143
+ *
22144
+ * @private function of UseCalendarCommitmentDefinition
22145
+ */
22146
+ function createUseCalendarToolFunctions() {
22147
+ return {
22148
+ async [UseCalendarToolNames.listEvents](args) {
22149
+ return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
22150
+ const query = {};
22151
+ if (normalizeOptionalText(args.timeMin)) {
22152
+ query.timeMin = args.timeMin.trim();
22153
+ }
22154
+ if (normalizeOptionalText(args.timeMax)) {
22155
+ query.timeMax = args.timeMax.trim();
22156
+ }
22157
+ if (normalizeOptionalText(args.query)) {
22158
+ query.q = args.query.trim();
22159
+ }
22160
+ if (typeof args.maxResults === 'number' && Number.isFinite(args.maxResults) && args.maxResults > 0) {
22161
+ query.maxResults = String(Math.floor(args.maxResults));
22162
+ }
22163
+ if (args.singleEvents !== undefined) {
22164
+ query.singleEvents = args.singleEvents ? 'true' : 'false';
22165
+ }
22166
+ if (args.orderBy === 'startTime' || args.orderBy === 'updated') {
22167
+ query.orderBy = args.orderBy;
22168
+ }
22169
+ if (normalizeOptionalText(args.timeZone)) {
22170
+ query.timeZone = args.timeZone.trim();
22171
+ }
22172
+ const payload = await callGoogleCalendarApi(accessToken, {
22173
+ method: 'GET',
22174
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events`,
22175
+ query,
22176
+ });
22177
+ const events = ((payload === null || payload === void 0 ? void 0 : payload.items) || []).map((event) => mapGoogleCalendarEvent(event));
22178
+ return JSON.stringify({
22179
+ provider: calendarReference.provider,
22180
+ calendarUrl: calendarReference.url,
22181
+ calendarId: calendarReference.calendarId,
22182
+ events,
22183
+ nextPageToken: (payload === null || payload === void 0 ? void 0 : payload.nextPageToken) || null,
22184
+ nextSyncToken: (payload === null || payload === void 0 ? void 0 : payload.nextSyncToken) || null,
22185
+ });
22186
+ });
22187
+ },
22188
+ async [UseCalendarToolNames.getEvent](args) {
22189
+ return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
22190
+ const eventId = normalizeRequiredText(args.eventId, 'eventId');
22191
+ const payload = await callGoogleCalendarApi(accessToken, {
22192
+ method: 'GET',
22193
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
22194
+ });
22195
+ return JSON.stringify({
22196
+ provider: calendarReference.provider,
22197
+ calendarUrl: calendarReference.url,
22198
+ calendarId: calendarReference.calendarId,
22199
+ event: mapGoogleCalendarEvent(payload || {}),
22200
+ });
22201
+ });
22202
+ },
22203
+ async [UseCalendarToolNames.createEvent](args) {
22204
+ return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
22205
+ const requestBody = createGoogleCalendarEventPayload({
22206
+ summary: normalizeRequiredText(args.summary, 'summary'),
22207
+ description: normalizeOptionalText(args.description),
22208
+ location: normalizeOptionalText(args.location),
22209
+ start: normalizeRequiredText(args.start, 'start'),
22210
+ end: normalizeRequiredText(args.end, 'end'),
22211
+ timeZone: normalizeOptionalText(args.timeZone),
22212
+ attendees: normalizeAttendees(args.attendees),
22213
+ reminderMinutes: normalizeReminderMinutes(args.reminderMinutes),
22214
+ });
22215
+ const payload = await callGoogleCalendarApi(accessToken, {
22216
+ method: 'POST',
22217
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events`,
22218
+ query: createSendUpdatesQuery(args.sendUpdates),
22219
+ body: requestBody,
22220
+ });
22221
+ return JSON.stringify({
22222
+ provider: calendarReference.provider,
22223
+ calendarUrl: calendarReference.url,
22224
+ calendarId: calendarReference.calendarId,
22225
+ event: mapGoogleCalendarEvent(payload || {}),
22226
+ });
22227
+ });
22228
+ },
22229
+ async [UseCalendarToolNames.updateEvent](args) {
22230
+ return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
22231
+ const eventId = normalizeRequiredText(args.eventId, 'eventId');
22232
+ const requestBody = createGoogleCalendarEventPayload({
22233
+ summary: normalizeOptionalText(args.summary),
22234
+ description: normalizeOptionalText(args.description),
22235
+ location: normalizeOptionalText(args.location),
22236
+ start: normalizeOptionalText(args.start),
22237
+ end: normalizeOptionalText(args.end),
22238
+ timeZone: normalizeOptionalText(args.timeZone),
22239
+ attendees: normalizeAttendees(args.attendees),
22240
+ reminderMinutes: normalizeReminderMinutes(args.reminderMinutes),
22241
+ });
22242
+ const payload = await callGoogleCalendarApi(accessToken, {
22243
+ method: 'PATCH',
22244
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
22245
+ query: createSendUpdatesQuery(args.sendUpdates),
22246
+ body: requestBody,
22247
+ });
22248
+ return JSON.stringify({
22249
+ provider: calendarReference.provider,
22250
+ calendarUrl: calendarReference.url,
22251
+ calendarId: calendarReference.calendarId,
22252
+ event: mapGoogleCalendarEvent(payload || {}),
22253
+ });
22254
+ });
22255
+ },
22256
+ async [UseCalendarToolNames.deleteEvent](args) {
22257
+ return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
22258
+ const eventId = normalizeRequiredText(args.eventId, 'eventId');
22259
+ await callGoogleCalendarApi(accessToken, {
22260
+ method: 'DELETE',
22261
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
22262
+ query: createSendUpdatesQuery(args.sendUpdates),
22263
+ });
22264
+ return JSON.stringify({
22265
+ provider: calendarReference.provider,
22266
+ calendarUrl: calendarReference.url,
22267
+ calendarId: calendarReference.calendarId,
22268
+ eventId,
22269
+ status: 'deleted',
22270
+ });
22271
+ });
22272
+ },
22273
+ async [UseCalendarToolNames.inviteGuests](args) {
22274
+ return withUseCalendarRuntime(args, async ({ calendarReference, accessToken }) => {
22275
+ const eventId = normalizeRequiredText(args.eventId, 'eventId');
22276
+ const guests = normalizeAttendees(args.guests);
22277
+ if (guests.length === 0) {
22278
+ throw new Error('Tool "calendar_invite_guests" requires non-empty "guests".');
22279
+ }
22280
+ const existingEvent = await callGoogleCalendarApi(accessToken, {
22281
+ method: 'GET',
22282
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
22283
+ });
22284
+ const existingAttendees = ((existingEvent === null || existingEvent === void 0 ? void 0 : existingEvent.attendees) || [])
22285
+ .map((attendee) => normalizeOptionalText(attendee.email))
22286
+ .filter((email) => Boolean(email));
22287
+ const mergedAttendees = [...new Set([...existingAttendees, ...guests])];
22288
+ const payload = await callGoogleCalendarApi(accessToken, {
22289
+ method: 'PATCH',
22290
+ path: `/calendars/${encodeGoogleCalendarId(calendarReference.calendarId)}/events/${encodeURIComponent(eventId)}`,
22291
+ query: createSendUpdatesQuery(args.sendUpdates),
21613
22292
  body: {
21614
- type: 'string',
21615
- description: 'Legacy alias for markdown body content.',
22293
+ attendees: mergedAttendees.map((email) => ({ email })),
21616
22294
  },
21617
- },
21618
- required: [],
21619
- },
22295
+ });
22296
+ return JSON.stringify({
22297
+ provider: calendarReference.provider,
22298
+ calendarUrl: calendarReference.url,
22299
+ calendarId: calendarReference.calendarId,
22300
+ event: mapGoogleCalendarEvent(payload || {}),
22301
+ invitedGuests: guests,
22302
+ });
22303
+ });
21620
22304
  },
21621
- ];
22305
+ };
21622
22306
  }
21623
22307
  /**
21624
- * Note: [💞] Ignore a discrepancy between file name and entity name
22308
+ * Executes one tool operation with resolved USE CALENDAR runtime.
22309
+ *
22310
+ * @private function of createUseCalendarToolFunctions
21625
22311
  */
21626
-
22312
+ async function withUseCalendarRuntime(args, operation) {
22313
+ const runtime = resolveUseCalendarToolRuntimeOrWalletCredentialResult(args);
22314
+ if ('walletResult' in runtime) {
22315
+ return runtime.walletResult;
22316
+ }
22317
+ return operation(runtime);
22318
+ }
21627
22319
  /**
21628
- * USE IMAGE GENERATOR commitment definition
22320
+ * Encodes one Google calendar id for URL path usage.
21629
22321
  *
21630
- * The `USE IMAGE GENERATOR` commitment indicates that the agent can output
21631
- * markdown placeholders for UI-driven image generation.
22322
+ * @private function of createUseCalendarToolFunctions
22323
+ */
22324
+ function encodeGoogleCalendarId(calendarId) {
22325
+ return encodeURIComponent(calendarId);
22326
+ }
22327
+ /**
22328
+ * Normalizes one required textual input.
21632
22329
  *
21633
- * Example usage in agent source:
22330
+ * @private function of createUseCalendarToolFunctions
22331
+ */
22332
+ function normalizeRequiredText(value, fieldName) {
22333
+ const normalizedValue = normalizeOptionalText(value);
22334
+ if (!normalizedValue) {
22335
+ throw new Error(`Tool "${fieldName}" requires non-empty value.`);
22336
+ }
22337
+ return normalizedValue;
22338
+ }
22339
+ /**
22340
+ * Normalizes unknown text input to trimmed non-empty string.
21634
22341
  *
21635
- * ```book
21636
- * USE IMAGE GENERATOR
21637
- * USE IMAGE GENERATOR Create realistic images of nature
21638
- * ```
22342
+ * @private function of createUseCalendarToolFunctions
22343
+ */
22344
+ function normalizeOptionalText(value) {
22345
+ if (typeof value !== 'string') {
22346
+ return undefined;
22347
+ }
22348
+ const trimmedValue = value.trim();
22349
+ return trimmedValue || undefined;
22350
+ }
22351
+ /**
22352
+ * Normalizes optional attendee list from tool input.
21639
22353
  *
21640
- * @private [🪔] Maybe export the commitments through some package
22354
+ * @private function of createUseCalendarToolFunctions
21641
22355
  */
21642
- class UseImageGeneratorCommitmentDefinition extends BaseCommitmentDefinition {
21643
- constructor(type = 'USE IMAGE GENERATOR') {
21644
- super(type);
22356
+ function normalizeAttendees(value) {
22357
+ if (!Array.isArray(value)) {
22358
+ return [];
21645
22359
  }
21646
- get requiresContent() {
21647
- return false;
22360
+ const normalizedAttendees = value
22361
+ .filter((attendee) => typeof attendee === 'string')
22362
+ .map((attendee) => attendee.trim())
22363
+ .filter(Boolean);
22364
+ return [...new Set(normalizedAttendees)];
22365
+ }
22366
+ /**
22367
+ * Normalizes optional reminder-minute offsets from tool input.
22368
+ *
22369
+ * @private function of createUseCalendarToolFunctions
22370
+ */
22371
+ function normalizeReminderMinutes(value) {
22372
+ if (!Array.isArray(value)) {
22373
+ return [];
21648
22374
  }
21649
- /**
21650
- * Short one-line description of USE IMAGE GENERATOR.
21651
- */
21652
- get description() {
21653
- return 'Enable the agent to output markdown image placeholders that the UI turns into generated images.';
22375
+ const reminderMinutes = value
22376
+ .filter((minute) => typeof minute === 'number' && Number.isFinite(minute))
22377
+ .map((minute) => Math.max(0, Math.floor(minute)));
22378
+ return [...new Set(reminderMinutes)];
22379
+ }
22380
+ /**
22381
+ * Builds optional `sendUpdates` query for mutating Google Calendar requests.
22382
+ *
22383
+ * @private function of createUseCalendarToolFunctions
22384
+ */
22385
+ function createSendUpdatesQuery(sendUpdates) {
22386
+ if (sendUpdates === 'all' || sendUpdates === 'externalOnly' || sendUpdates === 'none') {
22387
+ return { sendUpdates };
21654
22388
  }
21655
- /**
21656
- * Icon for this commitment.
21657
- */
21658
- get icon() {
21659
- return '🖼️';
22389
+ return {};
22390
+ }
22391
+ /**
22392
+ * Creates one Google Calendar event payload from normalized tool arguments.
22393
+ *
22394
+ * @private function of createUseCalendarToolFunctions
22395
+ */
22396
+ function createGoogleCalendarEventPayload(options) {
22397
+ const payload = {};
22398
+ if (options.summary) {
22399
+ payload.summary = options.summary;
21660
22400
  }
21661
- /**
21662
- * Markdown documentation for USE IMAGE GENERATOR commitment.
21663
- */
21664
- get documentation() {
21665
- return spaceTrim$1(`
21666
- # USE IMAGE GENERATOR
21667
-
21668
- Enables the agent to output markdown image placeholders that trigger image generation in the user interface.
21669
-
21670
- ## Key aspects
21671
-
21672
- - The content following \`USE IMAGE GENERATOR\` is an arbitrary text that the agent should know (e.g. style instructions or safety guidelines).
21673
- - The agent does **not** call an image-generation tool directly.
21674
- - The agent inserts markdown notation: \`![alt](?image-prompt=...)\`.
21675
- - The user interface detects the notation and generates the image asynchronously.
21676
-
21677
- ## Examples
21678
-
21679
- \`\`\`book
21680
- Visual Artist
21681
-
21682
- PERSONA You are a creative visual artist.
21683
- USE IMAGE GENERATOR
21684
- RULE Always describe the generated image to the user.
21685
- \`\`\`
21686
-
21687
- \`\`\`book
21688
- Interior Designer
21689
-
21690
- PERSONA You are an interior designer who helps users visualize their space.
21691
- USE IMAGE GENERATOR Professional interior design renders.
21692
- ACTION Add one generated image placeholder whenever a user asks for a visual.
21693
- \`\`\`
21694
- `);
22401
+ if (options.description) {
22402
+ payload.description = options.description;
21695
22403
  }
21696
- applyToAgentModelRequirements(requirements, content) {
21697
- const extraInstructions = formatOptionalInstructionBlock('Image instructions', content);
21698
- return this.appendToSystemMessage({
21699
- ...requirements,
21700
- _metadata: {
21701
- ...requirements._metadata,
21702
- useImageGenerator: content || true,
21703
- },
21704
- }, spaceTrim$1((block) => `
21705
- Image generation:
21706
- - You do not generate images directly and you do not call any image tool.
21707
- - When the user asks for an image, include markdown notation in your message:
21708
- \`![<alt text>](?image-prompt=<prompt>)\`
21709
- - Keep \`<alt text>\` short and descriptive.
21710
- - Keep \`<prompt>\` detailed so the generated image matches the request.
21711
- - You can include normal explanatory text before and after the notation.
21712
- ${block(extraInstructions)}
21713
- `));
22404
+ if (options.location) {
22405
+ payload.location = options.location;
22406
+ }
22407
+ if (options.start) {
22408
+ payload.start = createGoogleCalendarDateValue(options.start, options.timeZone);
22409
+ }
22410
+ if (options.end) {
22411
+ payload.end = createGoogleCalendarDateValue(options.end, options.timeZone);
22412
+ }
22413
+ if (options.attendees && options.attendees.length > 0) {
22414
+ payload.attendees = options.attendees.map((email) => ({ email }));
21714
22415
  }
22416
+ if (options.reminderMinutes && options.reminderMinutes.length > 0) {
22417
+ payload.reminders = {
22418
+ useDefault: false,
22419
+ overrides: options.reminderMinutes.map((minutes) => ({
22420
+ method: 'popup',
22421
+ minutes,
22422
+ })),
22423
+ };
22424
+ }
22425
+ return payload;
21715
22426
  }
21716
22427
  /**
21717
- * Note: [💞] Ignore a discrepancy between file name and entity name
22428
+ * Converts date/dateTime input into a Google Calendar-compatible date object.
22429
+ *
22430
+ * @private function of createUseCalendarToolFunctions
21718
22431
  */
22432
+ function createGoogleCalendarDateValue(value, timeZone) {
22433
+ const isDateOnly = /^\d{4}-\d{2}-\d{2}$/.test(value);
22434
+ if (isDateOnly) {
22435
+ return {
22436
+ date: value,
22437
+ };
22438
+ }
22439
+ return {
22440
+ dateTime: value,
22441
+ ...(timeZone ? { timeZone } : {}),
22442
+ };
22443
+ }
22444
+ /**
22445
+ * Maps raw Google Calendar event payload to a compact tool result object.
22446
+ *
22447
+ * @private function of createUseCalendarToolFunctions
22448
+ */
22449
+ function mapGoogleCalendarEvent(event) {
22450
+ return {
22451
+ id: event.id || null,
22452
+ summary: event.summary || null,
22453
+ description: event.description || null,
22454
+ location: event.location || null,
22455
+ status: event.status || null,
22456
+ htmlLink: event.htmlLink || null,
22457
+ start: event.start || null,
22458
+ end: event.end || null,
22459
+ organizer: event.organizer || null,
22460
+ attendees: (event.attendees || []).map((attendee) => ({
22461
+ email: attendee.email || null,
22462
+ responseStatus: attendee.responseStatus || null,
22463
+ })),
22464
+ };
22465
+ }
21719
22466
 
21720
22467
  /**
21721
- * USE MCP commitment definition
22468
+ * Shared calendar URL argument description used in USE CALENDAR tool schemas.
21722
22469
  *
21723
- * The `USE MCP` commitment allows to specify an MCP server URL which the agent will connect to
21724
- * for retrieving additional instructions and actions.
22470
+ * @private constant of createUseCalendarTools
22471
+ */
22472
+ const CALENDAR_URL_PARAMETER_DESCRIPTION = 'Google Calendar URL configured by USE CALENDAR (for example "https://calendar.google.com/...").';
22473
+ /**
22474
+ * Adds USE CALENDAR tool definitions while keeping already registered tools untouched.
21725
22475
  *
21726
- * The content following `USE MCP` is the URL of the MCP server.
22476
+ * @private function of UseCalendarCommitmentDefinition
22477
+ */
22478
+ function createUseCalendarTools(existingTools) {
22479
+ const updatedTools = [...existingTools];
22480
+ const addToolIfMissing = (tool) => {
22481
+ if (!updatedTools.some((existingTool) => existingTool.name === tool.name)) {
22482
+ updatedTools.push(tool);
22483
+ }
22484
+ };
22485
+ addToolIfMissing({
22486
+ name: UseCalendarToolNames.listEvents,
22487
+ description: 'List events from a configured calendar for a time range.',
22488
+ parameters: {
22489
+ type: 'object',
22490
+ properties: {
22491
+ calendarUrl: {
22492
+ type: 'string',
22493
+ description: CALENDAR_URL_PARAMETER_DESCRIPTION,
22494
+ },
22495
+ timeMin: {
22496
+ type: 'string',
22497
+ description: 'Inclusive event start bound in ISO datetime.',
22498
+ },
22499
+ timeMax: {
22500
+ type: 'string',
22501
+ description: 'Exclusive event end bound in ISO datetime.',
22502
+ },
22503
+ query: {
22504
+ type: 'string',
22505
+ description: 'Optional free-text event search query.',
22506
+ },
22507
+ maxResults: {
22508
+ type: 'integer',
22509
+ description: 'Maximum number of events to return.',
22510
+ },
22511
+ singleEvents: {
22512
+ type: 'boolean',
22513
+ description: 'Expand recurring events into individual instances.',
22514
+ },
22515
+ orderBy: {
22516
+ type: 'string',
22517
+ description: 'Optional ordering ("startTime" or "updated").',
22518
+ },
22519
+ timeZone: {
22520
+ type: 'string',
22521
+ description: 'Optional IANA timezone for response rendering.',
22522
+ },
22523
+ },
22524
+ required: [],
22525
+ },
22526
+ });
22527
+ addToolIfMissing({
22528
+ name: UseCalendarToolNames.getEvent,
22529
+ description: 'Get one event by id from a configured calendar.',
22530
+ parameters: {
22531
+ type: 'object',
22532
+ properties: {
22533
+ calendarUrl: {
22534
+ type: 'string',
22535
+ description: CALENDAR_URL_PARAMETER_DESCRIPTION,
22536
+ },
22537
+ eventId: {
22538
+ type: 'string',
22539
+ description: 'Google Calendar event id.',
22540
+ },
22541
+ },
22542
+ required: ['eventId'],
22543
+ },
22544
+ });
22545
+ addToolIfMissing({
22546
+ name: UseCalendarToolNames.createEvent,
22547
+ description: 'Create one event in a configured calendar.',
22548
+ parameters: {
22549
+ type: 'object',
22550
+ properties: {
22551
+ calendarUrl: {
22552
+ type: 'string',
22553
+ description: CALENDAR_URL_PARAMETER_DESCRIPTION,
22554
+ },
22555
+ summary: {
22556
+ type: 'string',
22557
+ description: 'Event title/summary.',
22558
+ },
22559
+ description: {
22560
+ type: 'string',
22561
+ description: 'Optional event description.',
22562
+ },
22563
+ location: {
22564
+ type: 'string',
22565
+ description: 'Optional event location.',
22566
+ },
22567
+ start: {
22568
+ type: 'string',
22569
+ description: 'Event start as ISO datetime or date.',
22570
+ },
22571
+ end: {
22572
+ type: 'string',
22573
+ description: 'Event end as ISO datetime or date.',
22574
+ },
22575
+ timeZone: {
22576
+ type: 'string',
22577
+ description: 'Optional timezone for datetime values.',
22578
+ },
22579
+ attendees: {
22580
+ type: 'array',
22581
+ description: 'Optional guest email list.',
22582
+ },
22583
+ reminderMinutes: {
22584
+ type: 'array',
22585
+ description: 'Optional popup reminder minute offsets.',
22586
+ },
22587
+ sendUpdates: {
22588
+ type: 'string',
22589
+ description: 'Guest update policy ("all", "externalOnly", "none").',
22590
+ },
22591
+ },
22592
+ required: ['summary', 'start', 'end'],
22593
+ },
22594
+ });
22595
+ addToolIfMissing({
22596
+ name: UseCalendarToolNames.updateEvent,
22597
+ description: 'Update one existing event in a configured calendar.',
22598
+ parameters: {
22599
+ type: 'object',
22600
+ properties: {
22601
+ calendarUrl: {
22602
+ type: 'string',
22603
+ description: CALENDAR_URL_PARAMETER_DESCRIPTION,
22604
+ },
22605
+ eventId: {
22606
+ type: 'string',
22607
+ description: 'Google Calendar event id.',
22608
+ },
22609
+ summary: {
22610
+ type: 'string',
22611
+ description: 'Updated event summary.',
22612
+ },
22613
+ description: {
22614
+ type: 'string',
22615
+ description: 'Updated event description.',
22616
+ },
22617
+ location: {
22618
+ type: 'string',
22619
+ description: 'Updated event location.',
22620
+ },
22621
+ start: {
22622
+ type: 'string',
22623
+ description: 'Updated event start as ISO datetime or date.',
22624
+ },
22625
+ end: {
22626
+ type: 'string',
22627
+ description: 'Updated event end as ISO datetime or date.',
22628
+ },
22629
+ timeZone: {
22630
+ type: 'string',
22631
+ description: 'Optional timezone for datetime values.',
22632
+ },
22633
+ attendees: {
22634
+ type: 'array',
22635
+ description: 'Optional replacement guest email list.',
22636
+ },
22637
+ reminderMinutes: {
22638
+ type: 'array',
22639
+ description: 'Optional replacement popup reminder minute offsets.',
22640
+ },
22641
+ sendUpdates: {
22642
+ type: 'string',
22643
+ description: 'Guest update policy ("all", "externalOnly", "none").',
22644
+ },
22645
+ },
22646
+ required: ['eventId'],
22647
+ },
22648
+ });
22649
+ addToolIfMissing({
22650
+ name: UseCalendarToolNames.deleteEvent,
22651
+ description: 'Delete one event from a configured calendar.',
22652
+ parameters: {
22653
+ type: 'object',
22654
+ properties: {
22655
+ calendarUrl: {
22656
+ type: 'string',
22657
+ description: CALENDAR_URL_PARAMETER_DESCRIPTION,
22658
+ },
22659
+ eventId: {
22660
+ type: 'string',
22661
+ description: 'Google Calendar event id.',
22662
+ },
22663
+ sendUpdates: {
22664
+ type: 'string',
22665
+ description: 'Guest update policy ("all", "externalOnly", "none").',
22666
+ },
22667
+ },
22668
+ required: ['eventId'],
22669
+ },
22670
+ });
22671
+ addToolIfMissing({
22672
+ name: UseCalendarToolNames.inviteGuests,
22673
+ description: 'Add guests to an existing event in a configured calendar.',
22674
+ parameters: {
22675
+ type: 'object',
22676
+ properties: {
22677
+ calendarUrl: {
22678
+ type: 'string',
22679
+ description: CALENDAR_URL_PARAMETER_DESCRIPTION,
22680
+ },
22681
+ eventId: {
22682
+ type: 'string',
22683
+ description: 'Google Calendar event id.',
22684
+ },
22685
+ guests: {
22686
+ type: 'array',
22687
+ description: 'Guest email list to add to the event.',
22688
+ },
22689
+ sendUpdates: {
22690
+ type: 'string',
22691
+ description: 'Guest update policy ("all", "externalOnly", "none").',
22692
+ },
22693
+ },
22694
+ required: ['eventId', 'guests'],
22695
+ },
22696
+ });
22697
+ return updatedTools;
22698
+ }
22699
+
22700
+ /**
22701
+ * Gets human-readable tool labels for USE CALENDAR functions.
21727
22702
  *
21728
- * Example usage in agent source:
22703
+ * @private function of UseCalendarCommitmentDefinition
22704
+ */
22705
+ function getUseCalendarToolTitles() {
22706
+ return {
22707
+ [UseCalendarToolNames.listEvents]: 'List calendar events',
22708
+ [UseCalendarToolNames.getEvent]: 'Get calendar event',
22709
+ [UseCalendarToolNames.createEvent]: 'Create calendar event',
22710
+ [UseCalendarToolNames.updateEvent]: 'Update calendar event',
22711
+ [UseCalendarToolNames.deleteEvent]: 'Delete calendar event',
22712
+ [UseCalendarToolNames.inviteGuests]: 'Invite calendar guests',
22713
+ };
22714
+ }
22715
+
22716
+ /**
22717
+ * Normalizes unknown metadata payload into a typed list of configured calendars.
21729
22718
  *
21730
- * ```book
21731
- * USE MCP http://mcp-server-url.com
21732
- * ```
22719
+ * @private internal utility of USE CALENDAR commitment
22720
+ */
22721
+ function normalizeConfiguredCalendars(rawCalendars) {
22722
+ if (!Array.isArray(rawCalendars)) {
22723
+ return [];
22724
+ }
22725
+ const uniqueCalendars = new Set();
22726
+ const calendars = [];
22727
+ for (const rawCalendar of rawCalendars) {
22728
+ if (!rawCalendar || typeof rawCalendar !== 'object') {
22729
+ continue;
22730
+ }
22731
+ const calendar = rawCalendar;
22732
+ const provider = normalizeProvider(calendar.provider);
22733
+ const url = normalizeText(calendar.url);
22734
+ const calendarId = normalizeText(calendar.calendarId);
22735
+ if (!provider || !url || !calendarId) {
22736
+ continue;
22737
+ }
22738
+ const uniqueKey = `${provider}|${url}`;
22739
+ if (uniqueCalendars.has(uniqueKey)) {
22740
+ continue;
22741
+ }
22742
+ uniqueCalendars.add(uniqueKey);
22743
+ const scopes = Array.isArray(calendar.scopes)
22744
+ ? calendar.scopes
22745
+ .filter((scope) => typeof scope === 'string')
22746
+ .map((scope) => scope.trim())
22747
+ .filter(Boolean)
22748
+ : [];
22749
+ calendars.push({
22750
+ provider,
22751
+ url,
22752
+ calendarId,
22753
+ scopes,
22754
+ ...(normalizeText(calendar.tokenRef) ? { tokenRef: normalizeText(calendar.tokenRef) } : {}),
22755
+ });
22756
+ }
22757
+ return calendars;
22758
+ }
22759
+ /**
22760
+ * Normalizes optional provider text to one supported value.
22761
+ *
22762
+ * @private function of normalizeConfiguredCalendars
22763
+ */
22764
+ function normalizeProvider(value) {
22765
+ if (typeof value !== 'string') {
22766
+ return null;
22767
+ }
22768
+ const normalizedProvider = value.trim().toLowerCase();
22769
+ if (normalizedProvider === 'google') {
22770
+ return 'google';
22771
+ }
22772
+ return null;
22773
+ }
22774
+ /**
22775
+ * Normalizes unknown text input to trimmed non-empty string.
22776
+ *
22777
+ * @private function of normalizeConfiguredCalendars
22778
+ */
22779
+ function normalizeText(value) {
22780
+ return typeof value === 'string' ? value.trim() : '';
22781
+ }
22782
+
22783
+ /**
22784
+ * USE CALENDAR commitment definition.
22785
+ *
22786
+ * `USE CALENDAR` enables calendar tooling so the agent can read and manage events
22787
+ * in one configured Google Calendar.
22788
+ *
22789
+ * Authentication is expected through runtime context provided by the host app UI.
22790
+ * Hosts can provide manual wallet tokens or host-managed OAuth tokens.
21733
22791
  *
21734
22792
  * @private [🪔] Maybe export the commitments through some package
21735
22793
  */
21736
- class UseMcpCommitmentDefinition extends BaseCommitmentDefinition {
22794
+ class UseCalendarCommitmentDefinition extends BaseCommitmentDefinition {
21737
22795
  constructor() {
21738
- super('USE MCP', ['MCP']);
22796
+ super('USE CALENDAR', ['CALENDAR']);
21739
22797
  }
21740
22798
  /**
21741
- * Short one-line description of USE MCP.
22799
+ * Short one-line description of USE CALENDAR.
21742
22800
  */
21743
22801
  get description() {
21744
- return 'Connects the agent to an external MCP server for additional capabilities.';
22802
+ return 'Enable calendar tools for reading and managing events through Google Calendar.';
21745
22803
  }
21746
22804
  /**
21747
22805
  * Icon for this commitment.
21748
22806
  */
21749
22807
  get icon() {
21750
- return '🔌';
22808
+ return '📅';
21751
22809
  }
21752
22810
  /**
21753
- * Markdown documentation for USE MCP commitment.
22811
+ * Markdown documentation for USE CALENDAR commitment.
21754
22812
  */
21755
22813
  get documentation() {
21756
22814
  return spaceTrim$1(`
21757
- # USE MCP
22815
+ # USE CALENDAR
21758
22816
 
21759
- Connects the agent to an external Model Context Protocol (MCP) server.
22817
+ Enables the agent to access and manage one Google Calendar.
21760
22818
 
21761
22819
  ## Key aspects
21762
22820
 
21763
- - The content following \`USE MCP\` must be a valid URL
21764
- - Multiple MCP servers can be connected by using multiple \`USE MCP\` commitments
21765
- - The agent will have access to tools and resources provided by the MCP server
22821
+ - The first URL in the commitment should point to a Google Calendar URL.
22822
+ - Optional \`SCOPES\` lines can provide explicit OAuth scopes.
22823
+ - Optional extra instructions can follow calendar reference lines.
22824
+ - Runtime provides Google Calendar OAuth token (manual wallet token or host-managed OAuth token).
22825
+ - Tools support listing events, reading one event, creating events, updating events, deleting events, and inviting guests.
21766
22826
 
21767
- ## Example
22827
+ ## Examples
21768
22828
 
21769
22829
  \`\`\`book
21770
- Company Lawyer
22830
+ Scheduling Assistant
21771
22831
 
21772
- PERSONA You are a company lawyer.
21773
- USE MCP http://legal-db.example.com
22832
+ PERSONA You coordinate meetings and schedules.
22833
+ USE CALENDAR https://calendar.google.com/calendar/u/0/r
22834
+ \`\`\`
22835
+
22836
+ \`\`\`book
22837
+ Executive Assistant
22838
+
22839
+ USE CALENDAR https://calendar.google.com/calendar/u/0/r
22840
+ SCOPES https://www.googleapis.com/auth/calendar.readonly
22841
+ RULE Ask for confirmation before deleting events.
21774
22842
  \`\`\`
21775
22843
  `);
21776
22844
  }
21777
22845
  applyToAgentModelRequirements(requirements, content) {
21778
- const mcpServerUrl = content.trim();
21779
- if (!mcpServerUrl) {
21780
- return requirements;
21781
- }
21782
- const existingMcpServers = requirements.mcpServers || [];
21783
- // Avoid duplicates
21784
- if (existingMcpServers.includes(mcpServerUrl)) {
21785
- return requirements;
21786
- }
21787
- return {
22846
+ var _a;
22847
+ const parsedCommitment = parseUseCalendarCommitmentContent(content);
22848
+ const existingConfiguredCalendars = normalizeConfiguredCalendars((_a = requirements._metadata) === null || _a === void 0 ? void 0 : _a.useCalendars);
22849
+ if (parsedCommitment.calendar) {
22850
+ addConfiguredCalendarIfMissing(existingConfiguredCalendars, parsedCommitment.calendar);
22851
+ }
22852
+ const calendarsList = existingConfiguredCalendars.length > 0
22853
+ ? existingConfiguredCalendars
22854
+ .map((calendar) => [
22855
+ `- ${calendar.provider}: ${calendar.url}`,
22856
+ calendar.scopes.length > 0 ? ` scopes: ${calendar.scopes.join(', ')}` : '',
22857
+ ]
22858
+ .filter(Boolean)
22859
+ .join('\n'))
22860
+ .join('\n')
22861
+ : '- Calendar is resolved from runtime context';
22862
+ const extraInstructions = formatOptionalInstructionBlock('Calendar instructions', parsedCommitment.instructions);
22863
+ return this.appendToSystemMessage({
21788
22864
  ...requirements,
21789
- mcpServers: [...existingMcpServers, mcpServerUrl],
21790
- };
22865
+ tools: createUseCalendarTools(requirements.tools || []),
22866
+ _metadata: {
22867
+ ...requirements._metadata,
22868
+ useCalendar: true,
22869
+ useCalendars: existingConfiguredCalendars,
22870
+ },
22871
+ }, spaceTrim$1((block) => `
22872
+ Calendar tools:
22873
+ - You can inspect and manage events in configured calendars.
22874
+ - Supported operations include read, create, update, delete, invite guests, and reminders.
22875
+ - Configured calendars:
22876
+ ${block(calendarsList)}
22877
+ - USE CALENDAR credentials are read from wallet records (ACCESS_TOKEN, service "${UseCalendarWallet.service}", key "${UseCalendarWallet.key}").
22878
+ - If credentials are missing, ask user to connect calendar credentials in host UI and/or add them to wallet.
22879
+ ${block(extraInstructions)}
22880
+ `));
21791
22881
  }
22882
+ /**
22883
+ * Gets human-readable titles for tool functions provided by this commitment.
22884
+ */
22885
+ getToolTitles() {
22886
+ return getUseCalendarToolTitles();
22887
+ }
22888
+ /**
22889
+ * Gets calendar tool function implementations.
22890
+ */
22891
+ getToolFunctions() {
22892
+ return createUseCalendarToolFunctions();
22893
+ }
22894
+ }
22895
+ /**
22896
+ * Adds calendar into configured calendars list if it is not already present.
22897
+ *
22898
+ * @private function of UseCalendarCommitmentDefinition
22899
+ */
22900
+ function addConfiguredCalendarIfMissing(configuredCalendars, calendarReference) {
22901
+ if (configuredCalendars.some((calendar) => calendar.provider === calendarReference.provider && calendar.url === calendarReference.url)) {
22902
+ return;
22903
+ }
22904
+ configuredCalendars.push({
22905
+ provider: calendarReference.provider,
22906
+ url: calendarReference.url,
22907
+ calendarId: calendarReference.calendarId,
22908
+ scopes: [...calendarReference.scopes],
22909
+ ...(calendarReference.tokenRef ? { tokenRef: calendarReference.tokenRef } : {}),
22910
+ });
21792
22911
  }
21793
22912
  /**
21794
22913
  * Note: [💞] Ignore a discrepancy between file name and entity name
21795
22914
  */
21796
22915
 
21797
22916
  /**
21798
- * USE POPUP commitment definition
22917
+ * Lightweight email token matcher used for `USE EMAIL` first-line parsing.
21799
22918
  *
21800
- * The `USE POPUP` commitment indicates that the agent can open a popup window with a specific website.
21801
- * This is useful, for example, when the agent writes a post on Facebook but wants the user to post it on Facebook.
22919
+ * @private internal USE EMAIL constant
22920
+ */
22921
+ const EMAIL_TOKEN_PATTERN = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/;
22922
+ /**
22923
+ * Parses `USE EMAIL` commitment content into optional sender email + additional instructions.
21802
22924
  *
21803
- * Example usage in agent source:
22925
+ * Examples:
22926
+ * - `agent@example.com`
22927
+ * - `agent@example.com Keep emails concise`
22928
+ * - `Keep emails concise`
21804
22929
  *
21805
- * ```book
21806
- * USE POPUP Allow to open Facebook and Linkedin
21807
- * ```
22930
+ * @private internal utility of USE EMAIL commitment
22931
+ */
22932
+ function parseUseEmailCommitmentContent(content) {
22933
+ const trimmedContent = spaceTrim$1(content);
22934
+ if (!trimmedContent) {
22935
+ return {
22936
+ senderEmail: null,
22937
+ senderEmailRaw: null,
22938
+ instructions: '',
22939
+ };
22940
+ }
22941
+ const lines = trimmedContent
22942
+ .split(/\r?\n/)
22943
+ .map((line) => line.trim())
22944
+ .filter(Boolean);
22945
+ if (lines.length === 0) {
22946
+ return {
22947
+ senderEmail: null,
22948
+ senderEmailRaw: null,
22949
+ instructions: '',
22950
+ };
22951
+ }
22952
+ const firstLine = lines[0] || '';
22953
+ const senderMatch = firstLine.match(EMAIL_TOKEN_PATTERN);
22954
+ const senderEmailRaw = (senderMatch === null || senderMatch === void 0 ? void 0 : senderMatch[0]) || null;
22955
+ const senderEmail = senderEmailRaw && isValidEmail(senderEmailRaw) ? senderEmailRaw : null;
22956
+ let firstLineWithoutSender = firstLine;
22957
+ if (senderEmailRaw) {
22958
+ const matchIndex = firstLine.indexOf(senderEmailRaw);
22959
+ const prefix = firstLine.slice(0, matchIndex).trim();
22960
+ const suffix = firstLine.slice(matchIndex + senderEmailRaw.length).trim();
22961
+ firstLineWithoutSender = [prefix, suffix].filter(Boolean).join(' ').trim();
22962
+ }
22963
+ const instructionLines = [firstLineWithoutSender, ...lines.slice(1)].filter(Boolean);
22964
+ const instructions = instructionLines.join('\n').trim();
22965
+ return {
22966
+ senderEmail,
22967
+ senderEmailRaw,
22968
+ instructions,
22969
+ };
22970
+ }
22971
+
22972
+ /**
22973
+ * Client-side safe wrapper for sending emails.
22974
+ *
22975
+ * This function proxies requests to the Agents Server API endpoint for email queuing,
22976
+ * making it safe to use in browser environments.
22977
+ *
22978
+ * @param args Email payload forwarded to the server-side `send_email` tool
22979
+ * @param agentsServerUrl The base URL of the agents server (defaults to current origin)
22980
+ * @returns Result string from the server-side send_email tool
22981
+ *
22982
+ * @private internal utility for USE EMAIL commitment
22983
+ */
22984
+ async function sendEmailViaBrowser(args, agentsServerUrl) {
22985
+ try {
22986
+ const baseUrl = agentsServerUrl || (typeof window !== 'undefined' ? window.location.origin : '');
22987
+ if (!baseUrl) {
22988
+ throw new Error('Agents server URL is required in non-browser environments');
22989
+ }
22990
+ const apiUrl = new URL('/api/send-email', baseUrl);
22991
+ const response = await fetch(apiUrl.toString(), {
22992
+ method: 'POST',
22993
+ headers: {
22994
+ 'Content-Type': 'application/json',
22995
+ },
22996
+ body: JSON.stringify(args),
22997
+ });
22998
+ if (!response.ok) {
22999
+ const errorData = await response.json().catch(() => ({ error: 'Unknown error' }));
23000
+ throw new Error(`Failed to send email: ${errorData.error || response.statusText}`);
23001
+ }
23002
+ const data = await response.json();
23003
+ if (!data.success) {
23004
+ throw new Error(`Email sending failed: ${data.error || 'Unknown error'}`);
23005
+ }
23006
+ return typeof data.result === 'string' ? data.result : JSON.stringify(data.result);
23007
+ }
23008
+ catch (error) {
23009
+ const errorMessage = error instanceof Error ? error.message : String(error);
23010
+ throw new Error(`Error sending email via browser: ${errorMessage}`);
23011
+ }
23012
+ }
23013
+
23014
+ /**
23015
+ * Tool name used by USE EMAIL.
23016
+ *
23017
+ * @private internal USE EMAIL constant
23018
+ */
23019
+ const SEND_EMAIL_TOOL_NAME = 'send_email';
23020
+ /**
23021
+ * Wallet service used for SMTP credentials required by USE EMAIL.
23022
+ *
23023
+ * @private internal USE EMAIL constant
23024
+ */
23025
+ const USE_EMAIL_SMTP_WALLET_SERVICE = 'smtp';
23026
+ /**
23027
+ * Wallet key used for SMTP credentials required by USE EMAIL.
23028
+ *
23029
+ * @private internal USE EMAIL constant
23030
+ */
23031
+ const USE_EMAIL_SMTP_WALLET_KEY = 'use-email-smtp-credentials';
23032
+ /**
23033
+ * USE EMAIL commitment definition.
23034
+ *
23035
+ * The `USE EMAIL` commitment enables outbound email sending through the `send_email` tool.
21808
23036
  *
21809
23037
  * @private [🪔] Maybe export the commitments through some package
21810
23038
  */
21811
- class UsePopupCommitmentDefinition extends BaseCommitmentDefinition {
23039
+ class UseEmailCommitmentDefinition extends BaseCommitmentDefinition {
21812
23040
  constructor() {
21813
- super('USE POPUP', ['POPUP']);
23041
+ super('USE EMAIL', ['EMAIL', 'MAIL']);
21814
23042
  }
21815
- /**
21816
- * The `USE POPUP` commitment is standalone or with instructions.
21817
- */
21818
23043
  get requiresContent() {
21819
23044
  return false;
21820
23045
  }
21821
23046
  /**
21822
- * Short one-line description of USE POPUP.
23047
+ * Short one-line description of USE EMAIL.
21823
23048
  */
21824
23049
  get description() {
21825
- return 'Enable the agent to open a popup window with a specific website.';
23050
+ return 'Enable outbound email sending through a wallet-backed SMTP configuration.';
21826
23051
  }
21827
23052
  /**
21828
23053
  * Icon for this commitment.
21829
23054
  */
21830
23055
  get icon() {
21831
- return '🪟';
23056
+ return '📧';
21832
23057
  }
21833
23058
  /**
21834
- * Markdown documentation for USE POPUP commitment.
23059
+ * Markdown documentation for USE EMAIL commitment.
21835
23060
  */
21836
23061
  get documentation() {
21837
23062
  return spaceTrim$1(`
21838
- # USE POPUP
23063
+ # USE EMAIL
21839
23064
 
21840
- Enables the agent to open a popup window with a specific website.
23065
+ Enables the agent to send outbound emails through SMTP.
21841
23066
 
21842
23067
  ## Key aspects
21843
23068
 
21844
- - The content following \`USE POPUP\` is an arbitrary text that the agent should know (e.g. constraints or instructions).
21845
- - The actual popup opening is handled by the agent runtime (usually in the browser)
21846
- - Allows the agent to open websites for the user to interact with (e.g. social media posts)
23069
+ - The agent sends email via the \`send_email\` tool.
23070
+ - SMTP credentials are expected from wallet records (\`ACCESS_TOKEN\`, service \`smtp\`, key \`use-email-smtp-credentials\`).
23071
+ - Commitment content can optionally begin with a default sender email address:
23072
+ - \`USE EMAIL agent@example.com\`
23073
+ - Remaining commitment content is treated as optional email-writing instructions.
21847
23074
 
21848
23075
  ## Examples
21849
23076
 
21850
23077
  \`\`\`book
21851
- John the Copywriter
23078
+ Writing Agent
23079
+ USE EMAIL agent@example.com
23080
+ RULE Write emails to customers according to the instructions from user.
23081
+ \`\`\`
21852
23082
 
21853
- PERSONA You are a professional copywriter writing about CNC machines.
21854
- USE POPUP Allow to open Facebook and Linkedin
23083
+ \`\`\`book
23084
+ Formal Email Assistant
23085
+ USE EMAIL agent@example.com Keep emails concise and formal.
21855
23086
  \`\`\`
21856
23087
  `);
21857
23088
  }
21858
23089
  applyToAgentModelRequirements(requirements, content) {
21859
- const extraInstructions = formatOptionalInstructionBlock('Popup instructions', content);
21860
- // Get existing tools array or create new one
21861
- const existingTools = requirements.tools || [];
21862
- // Add 'open_popup' to tools if not already present
21863
- const updatedTools = existingTools.some((tool) => tool.name === 'open_popup')
21864
- ? existingTools
21865
- : [
21866
- ...existingTools,
21867
- {
21868
- name: 'open_popup',
21869
- description: spaceTrim$1(`
21870
- Opens a popup window with a specific URL.
21871
- Use this when you want to show a specific website to the user in a new window.
21872
- ${!content ? '' : `Constraints / instructions: ${content}`}
21873
- `),
21874
- parameters: {
21875
- type: 'object',
21876
- properties: {
21877
- url: {
21878
- type: 'string',
21879
- description: 'The URL to open in the popup window',
21880
- },
21881
- },
21882
- required: ['url'],
21883
- },
21884
- },
21885
- ];
21886
- // Return requirements with updated tools and metadata
23090
+ const parsedCommitment = parseUseEmailCommitmentContent(content);
23091
+ const extraInstructions = formatOptionalInstructionBlock('Email instructions', parsedCommitment.instructions);
23092
+ const senderInstruction = parsedCommitment.senderEmail
23093
+ ? `- Default sender address from commitment: "${parsedCommitment.senderEmail}".`
23094
+ : '';
23095
+ const updatedTools = addUseEmailTools(requirements.tools || []);
21887
23096
  return this.appendToSystemMessage({
21888
23097
  ...requirements,
21889
23098
  tools: updatedTools,
21890
23099
  _metadata: {
21891
23100
  ...requirements._metadata,
21892
- usePopup: content || true,
23101
+ useEmail: true,
23102
+ ...(parsedCommitment.senderEmail ? { useEmailSender: parsedCommitment.senderEmail } : {}),
21893
23103
  },
21894
23104
  }, spaceTrim$1((block) => `
21895
- Tool:
21896
- - You can open a popup window with a specific URL using the tool "open_popup".
21897
- - Use this when you want the user to see or interact with a specific website.
23105
+ Email tool:
23106
+ - Use "${SEND_EMAIL_TOOL_NAME}" to send outbound emails.
23107
+ - Prefer \`message\` argument compatible with Promptbook \`Message\` type.
23108
+ - Include subject in \`message.metadata.subject\` (or use legacy \`subject\` argument).
23109
+ - USE EMAIL credentials are read from wallet records (ACCESS_TOKEN, service "${USE_EMAIL_SMTP_WALLET_SERVICE}", key "${USE_EMAIL_SMTP_WALLET_KEY}").
23110
+ - Wallet secret must contain SMTP credentials in JSON format with fields \`host\`, \`port\`, \`secure\`, \`username\`, \`password\`.
23111
+ - If credentials are missing, ask user to add wallet credentials.
23112
+ ${block(senderInstruction)}
21898
23113
  ${block(extraInstructions)}
21899
- `));
23114
+ `));
21900
23115
  }
21901
23116
  /**
21902
23117
  * Gets human-readable titles for tool functions provided by this commitment.
21903
23118
  */
21904
23119
  getToolTitles() {
21905
23120
  return {
21906
- open_popup: 'Open popup',
23121
+ [SEND_EMAIL_TOOL_NAME]: 'Send email',
21907
23122
  };
21908
23123
  }
21909
23124
  /**
21910
- * Gets the `open_popup` tool function implementation.
23125
+ * Gets the browser-safe `send_email` implementation.
23126
+ *
23127
+ * Node.js runtime overrides this via `getAllCommitmentsToolFunctionsForNode`.
21911
23128
  */
21912
23129
  getToolFunctions() {
21913
23130
  return {
21914
- async open_popup(args) {
21915
- console.log('!!!! [Tool] open_popup called', { args });
21916
- const { url } = args;
21917
- if (typeof window !== 'undefined') {
21918
- window.open(url, '_blank');
21919
- return `Popup window with URL "${url}" was opened.`;
21920
- }
21921
- return spaceTrim$1(`
21922
- Popup window with URL "${url}" was requested.
21923
-
21924
- Note: The agent is currently running on the server, so the popup cannot be opened automatically.
21925
- The user can open it manually from the tool call details in the chat.
21926
- `);
23131
+ async [SEND_EMAIL_TOOL_NAME](args) {
23132
+ return sendEmailViaBrowser(args);
21927
23133
  },
21928
23134
  };
21929
23135
  }
21930
23136
  }
21931
23137
  /**
21932
- * Note: [💞] Ignore a discrepancy between file name and entity name
21933
- */
21934
-
21935
- /**
21936
- * Tool name used by the USE PRIVACY commitment.
23138
+ * Adds USE EMAIL tool definition while keeping already registered tools untouched.
21937
23139
  *
21938
- * @private internal USE PRIVACY constant
23140
+ * @private utility of USE EMAIL commitment
21939
23141
  */
21940
- const TURN_PRIVACY_ON_TOOL_NAME = 'turn_privacy_on';
21941
- /**
21942
- * Status returned when the UI should ask the user to confirm private mode.
21943
- *
21944
- * @private internal USE PRIVACY constant
21945
- */
21946
- const PRIVACY_CONFIRMATION_REQUIRED_STATUS = 'confirmation-required';
21947
- /**
21948
- * Status returned when private mode is already enabled.
21949
- *
21950
- * @private internal USE PRIVACY constant
21951
- */
21952
- const PRIVACY_ALREADY_ENABLED_STATUS = 'already-enabled';
21953
- /**
21954
- * Checks whether private mode is already enabled in runtime context.
21955
- *
21956
- * @private utility of USE PRIVACY commitment
21957
- */
21958
- function isPrivateModeEnabledInRuntimeContext(args) {
21959
- var _a;
21960
- const runtimeContext = readToolRuntimeContextFromToolArgs(args);
21961
- return ((_a = runtimeContext === null || runtimeContext === void 0 ? void 0 : runtimeContext.memory) === null || _a === void 0 ? void 0 : _a.isPrivateMode) === true;
23142
+ function addUseEmailTools(existingTools) {
23143
+ if (existingTools.some((tool) => tool.name === SEND_EMAIL_TOOL_NAME)) {
23144
+ return [...existingTools];
23145
+ }
23146
+ return [
23147
+ ...existingTools,
23148
+ {
23149
+ name: SEND_EMAIL_TOOL_NAME,
23150
+ description: 'Send an outbound email through configured SMTP credentials. Prefer providing Message-like payload in `message`.',
23151
+ parameters: {
23152
+ type: 'object',
23153
+ properties: {
23154
+ message: {
23155
+ type: 'object',
23156
+ description: 'Preferred input payload compatible with Promptbook Message type. Use metadata.subject for subject line.',
23157
+ },
23158
+ to: {
23159
+ type: 'string',
23160
+ description: 'Legacy alias for recipients (use comma-separated emails or JSON array encoded as string).',
23161
+ },
23162
+ cc: {
23163
+ type: 'string',
23164
+ description: 'Optional CC recipients (use comma-separated emails or JSON array encoded as string).',
23165
+ },
23166
+ subject: {
23167
+ type: 'string',
23168
+ description: 'Legacy alias for subject.',
23169
+ },
23170
+ body: {
23171
+ type: 'string',
23172
+ description: 'Legacy alias for markdown body content.',
23173
+ },
23174
+ },
23175
+ required: [],
23176
+ },
23177
+ },
23178
+ ];
21962
23179
  }
21963
23180
  /**
21964
- * Creates a standard "confirmation required" result payload.
21965
- *
21966
- * @private utility of USE PRIVACY commitment
23181
+ * Note: [💞] Ignore a discrepancy between file name and entity name
21967
23182
  */
21968
- function createPrivacyConfirmationRequiredResult() {
21969
- return {
21970
- status: PRIVACY_CONFIRMATION_REQUIRED_STATUS,
21971
- message: 'Private mode requires explicit user confirmation in the UI. Ask the user to confirm the privacy prompt.',
21972
- };
21973
- }
23183
+
21974
23184
  /**
21975
- * Creates a standard "already enabled" result payload.
23185
+ * USE IMAGE GENERATOR commitment definition
21976
23186
  *
21977
- * @private utility of USE PRIVACY commitment
21978
- */
21979
- function createPrivacyAlreadyEnabledResult() {
21980
- return {
21981
- status: PRIVACY_ALREADY_ENABLED_STATUS,
21982
- message: 'Private mode is already enabled for this chat session.',
21983
- };
21984
- }
21985
- /**
21986
- * USE PRIVACY commitment definition.
23187
+ * The `USE IMAGE GENERATOR` commitment indicates that the agent can output
23188
+ * markdown placeholders for UI-driven image generation.
21987
23189
  *
21988
- * The `USE PRIVACY` commitment enables an agent to request enabling private mode.
23190
+ * Example usage in agent source:
23191
+ *
23192
+ * ```book
23193
+ * USE IMAGE GENERATOR
23194
+ * USE IMAGE GENERATOR Create realistic images of nature
23195
+ * ```
21989
23196
  *
21990
23197
  * @private [🪔] Maybe export the commitments through some package
21991
23198
  */
21992
- class UsePrivacyCommitmentDefinition extends BaseCommitmentDefinition {
21993
- constructor() {
21994
- super('USE PRIVACY', ['PRIVACY']);
23199
+ class UseImageGeneratorCommitmentDefinition extends BaseCommitmentDefinition {
23200
+ constructor(type = 'USE IMAGE GENERATOR') {
23201
+ super(type);
21995
23202
  }
21996
23203
  get requiresContent() {
21997
23204
  return false;
21998
23205
  }
21999
23206
  /**
22000
- * Short one-line description of USE PRIVACY.
23207
+ * Short one-line description of USE IMAGE GENERATOR.
22001
23208
  */
22002
23209
  get description() {
22003
- return 'Enable the agent to request turning private mode on for sensitive conversations.';
23210
+ return 'Enable the agent to output markdown image placeholders that the UI turns into generated images.';
22004
23211
  }
22005
23212
  /**
22006
23213
  * Icon for this commitment.
22007
23214
  */
22008
23215
  get icon() {
22009
- return '🔒';
23216
+ return '🖼️';
22010
23217
  }
22011
23218
  /**
22012
- * Markdown documentation for USE PRIVACY commitment.
23219
+ * Markdown documentation for USE IMAGE GENERATOR commitment.
22013
23220
  */
22014
23221
  get documentation() {
22015
23222
  return spaceTrim$1(`
22016
- # USE PRIVACY
23223
+ # USE IMAGE GENERATOR
22017
23224
 
22018
- Enables the agent to request turning on private mode in chat.
23225
+ Enables the agent to output markdown image placeholders that trigger image generation in the user interface.
22019
23226
 
22020
23227
  ## Key aspects
22021
23228
 
22022
- - The tool \`turn_privacy_on\` asks the UI to show a confirmation dialog to the user.
22023
- - Private mode is enabled only after explicit user confirmation in the UI.
22024
- - In the current implementation, this reuses existing private mode behavior in chat.
22025
- - While private mode is active, chat persistence, memory persistence, and self-learning are disabled.
22026
- - Proper encryption is planned for future updates, but not implemented by this commitment yet.
22027
- - Optional content after \`USE PRIVACY\` can provide additional privacy instructions.
23229
+ - The content following \`USE IMAGE GENERATOR\` is an arbitrary text that the agent should know (e.g. style instructions or safety guidelines).
23230
+ - The agent does **not** call an image-generation tool directly.
23231
+ - The agent inserts markdown notation: \`![alt](?image-prompt=...)\`.
23232
+ - The user interface detects the notation and generates the image asynchronously.
22028
23233
 
22029
23234
  ## Examples
22030
23235
 
22031
23236
  \`\`\`book
22032
- Sensitive Assistant
23237
+ Visual Artist
22033
23238
 
22034
- PERSONA You help with sensitive topics where privacy is important.
22035
- USE PRIVACY
23239
+ PERSONA You are a creative visual artist.
23240
+ USE IMAGE GENERATOR
23241
+ RULE Always describe the generated image to the user.
22036
23242
  \`\`\`
22037
23243
 
22038
23244
  \`\`\`book
22039
- Compliance Assistant
23245
+ Interior Designer
22040
23246
 
22041
- PERSONA You assist with legal and HR conversations.
22042
- USE PRIVACY Offer private mode when user asks to avoid storing data.
23247
+ PERSONA You are an interior designer who helps users visualize their space.
23248
+ USE IMAGE GENERATOR Professional interior design renders.
23249
+ ACTION Add one generated image placeholder whenever a user asks for a visual.
22043
23250
  \`\`\`
22044
23251
  `);
22045
23252
  }
22046
23253
  applyToAgentModelRequirements(requirements, content) {
22047
- const extraInstructions = formatOptionalInstructionBlock('Privacy instructions', content);
22048
- const existingTools = requirements.tools || [];
22049
- const tools = existingTools.some((tool) => tool.name === TURN_PRIVACY_ON_TOOL_NAME)
22050
- ? existingTools
22051
- : [
22052
- ...existingTools,
22053
- {
22054
- name: TURN_PRIVACY_ON_TOOL_NAME,
22055
- description: spaceTrim$1(`
22056
- Requests turning private mode on in the chat UI.
22057
- The user must explicitly confirm the action in a dialog before private mode is enabled.
22058
- Use this for sensitive topics or when the user asks not to store conversation data.
22059
- `),
22060
- parameters: {
22061
- type: 'object',
22062
- properties: {},
22063
- required: [],
22064
- },
22065
- },
22066
- ];
23254
+ const extraInstructions = formatOptionalInstructionBlock('Image instructions', content);
22067
23255
  return this.appendToSystemMessage({
22068
23256
  ...requirements,
22069
- tools,
22070
23257
  _metadata: {
22071
23258
  ...requirements._metadata,
22072
- usePrivacy: content || true,
23259
+ useImageGenerator: content || true,
22073
23260
  },
22074
23261
  }, spaceTrim$1((block) => `
22075
- Privacy mode:
22076
- - Use "${TURN_PRIVACY_ON_TOOL_NAME}" when the user asks for a private/sensitive conversation.
22077
- - This tool requests a UI confirmation dialog. Private mode is enabled only after user confirms.
22078
- - Current implementation uses the existing chat private mode (no chat persistence, memory persistence, or self-learning while active).
22079
- - Do not claim that end-to-end encryption is implemented yet.
23262
+ Image generation:
23263
+ - You do not generate images directly and you do not call any image tool.
23264
+ - When the user asks for an image, include markdown notation in your message:
23265
+ \`![<alt text>](?image-prompt=<prompt>)\`
23266
+ - Keep \`<alt text>\` short and descriptive.
23267
+ - Keep \`<prompt>\` detailed so the generated image matches the request.
23268
+ - You can include normal explanatory text before and after the notation.
22080
23269
  ${block(extraInstructions)}
22081
23270
  `));
22082
23271
  }
23272
+ }
23273
+ /**
23274
+ * Note: [💞] Ignore a discrepancy between file name and entity name
23275
+ */
23276
+
23277
+ /**
23278
+ * USE MCP commitment definition
23279
+ *
23280
+ * The `USE MCP` commitment allows to specify an MCP server URL which the agent will connect to
23281
+ * for retrieving additional instructions and actions.
23282
+ *
23283
+ * The content following `USE MCP` is the URL of the MCP server.
23284
+ *
23285
+ * Example usage in agent source:
23286
+ *
23287
+ * ```book
23288
+ * USE MCP http://mcp-server-url.com
23289
+ * ```
23290
+ *
23291
+ * @private [🪔] Maybe export the commitments through some package
23292
+ */
23293
+ class UseMcpCommitmentDefinition extends BaseCommitmentDefinition {
23294
+ constructor() {
23295
+ super('USE MCP', ['MCP']);
23296
+ }
22083
23297
  /**
22084
- * Gets human-readable titles for tool functions provided by this commitment.
23298
+ * Short one-line description of USE MCP.
22085
23299
  */
22086
- getToolTitles() {
22087
- return {
22088
- [TURN_PRIVACY_ON_TOOL_NAME]: 'Turn privacy mode on',
22089
- };
23300
+ get description() {
23301
+ return 'Connects the agent to an external MCP server for additional capabilities.';
22090
23302
  }
22091
23303
  /**
22092
- * Gets the `turn_privacy_on` tool function implementation.
23304
+ * Icon for this commitment.
22093
23305
  */
22094
- getToolFunctions() {
23306
+ get icon() {
23307
+ return '🔌';
23308
+ }
23309
+ /**
23310
+ * Markdown documentation for USE MCP commitment.
23311
+ */
23312
+ get documentation() {
23313
+ return spaceTrim$1(`
23314
+ # USE MCP
23315
+
23316
+ Connects the agent to an external Model Context Protocol (MCP) server.
23317
+
23318
+ ## Key aspects
23319
+
23320
+ - The content following \`USE MCP\` must be a valid URL
23321
+ - Multiple MCP servers can be connected by using multiple \`USE MCP\` commitments
23322
+ - The agent will have access to tools and resources provided by the MCP server
23323
+
23324
+ ## Example
23325
+
23326
+ \`\`\`book
23327
+ Company Lawyer
23328
+
23329
+ PERSONA You are a company lawyer.
23330
+ USE MCP http://legal-db.example.com
23331
+ \`\`\`
23332
+ `);
23333
+ }
23334
+ applyToAgentModelRequirements(requirements, content) {
23335
+ const mcpServerUrl = content.trim();
23336
+ if (!mcpServerUrl) {
23337
+ return requirements;
23338
+ }
23339
+ const existingMcpServers = requirements.mcpServers || [];
23340
+ // Avoid duplicates
23341
+ if (existingMcpServers.includes(mcpServerUrl)) {
23342
+ return requirements;
23343
+ }
22095
23344
  return {
22096
- async [TURN_PRIVACY_ON_TOOL_NAME](args) {
22097
- const result = isPrivateModeEnabledInRuntimeContext(args)
22098
- ? createPrivacyAlreadyEnabledResult()
22099
- : createPrivacyConfirmationRequiredResult();
22100
- return JSON.stringify(result);
22101
- },
23345
+ ...requirements,
23346
+ mcpServers: [...existingMcpServers, mcpServerUrl],
22102
23347
  };
22103
23348
  }
22104
23349
  }
@@ -22106,135 +23351,447 @@ class UsePrivacyCommitmentDefinition extends BaseCommitmentDefinition {
22106
23351
  * Note: [💞] Ignore a discrepancy between file name and entity name
22107
23352
  */
22108
23353
 
22109
- /* eslint-disable no-magic-numbers */
22110
23354
  /**
22111
- * Default byte limit for one best-effort text decode.
23355
+ * USE POPUP commitment definition
22112
23356
  *
22113
- * @private constant of decodeAttachmentAsText
22114
- */
22115
- const DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES = 512 * 1024;
22116
- /**
22117
- * Marker appended when only a prefix of the payload is decoded.
23357
+ * The `USE POPUP` commitment indicates that the agent can open a popup window with a specific website.
23358
+ * This is useful, for example, when the agent writes a post on Facebook but wants the user to post it on Facebook.
22118
23359
  *
22119
- * @private constant of decodeAttachmentAsText
22120
- */
22121
- const TRUNCATED_MARKER = '…[TRUNCATED]…';
22122
- /**
22123
- * MIME types that are trustworthy indicators of textual content.
23360
+ * Example usage in agent source:
22124
23361
  *
22125
- * @private constant of decodeAttachmentAsText
23362
+ * ```book
23363
+ * USE POPUP Allow to open Facebook and Linkedin
23364
+ * ```
23365
+ *
23366
+ * @private [🪔] Maybe export the commitments through some package
22126
23367
  */
22127
- const TRUSTED_TEXT_MIME_TYPES = new Set([
22128
- 'application/json',
22129
- 'application/ld+json',
22130
- 'application/javascript',
22131
- 'application/x-javascript',
22132
- 'application/xml',
22133
- 'application/xhtml+xml',
22134
- 'application/x-www-form-urlencoded',
22135
- 'application/yaml',
22136
- 'application/x-yaml',
22137
- 'application/toml',
22138
- 'application/sql',
22139
- 'application/rtf',
22140
- 'application/x-subrip',
22141
- ]);
23368
+ class UsePopupCommitmentDefinition extends BaseCommitmentDefinition {
23369
+ constructor() {
23370
+ super('USE POPUP', ['POPUP']);
23371
+ }
23372
+ /**
23373
+ * The `USE POPUP` commitment is standalone or with instructions.
23374
+ */
23375
+ get requiresContent() {
23376
+ return false;
23377
+ }
23378
+ /**
23379
+ * Short one-line description of USE POPUP.
23380
+ */
23381
+ get description() {
23382
+ return 'Enable the agent to open a popup window with a specific website.';
23383
+ }
23384
+ /**
23385
+ * Icon for this commitment.
23386
+ */
23387
+ get icon() {
23388
+ return '🪟';
23389
+ }
23390
+ /**
23391
+ * Markdown documentation for USE POPUP commitment.
23392
+ */
23393
+ get documentation() {
23394
+ return spaceTrim$1(`
23395
+ # USE POPUP
23396
+
23397
+ Enables the agent to open a popup window with a specific website.
23398
+
23399
+ ## Key aspects
23400
+
23401
+ - The content following \`USE POPUP\` is an arbitrary text that the agent should know (e.g. constraints or instructions).
23402
+ - The actual popup opening is handled by the agent runtime (usually in the browser)
23403
+ - Allows the agent to open websites for the user to interact with (e.g. social media posts)
23404
+
23405
+ ## Examples
23406
+
23407
+ \`\`\`book
23408
+ John the Copywriter
23409
+
23410
+ PERSONA You are a professional copywriter writing about CNC machines.
23411
+ USE POPUP Allow to open Facebook and Linkedin
23412
+ \`\`\`
23413
+ `);
23414
+ }
23415
+ applyToAgentModelRequirements(requirements, content) {
23416
+ const extraInstructions = formatOptionalInstructionBlock('Popup instructions', content);
23417
+ // Get existing tools array or create new one
23418
+ const existingTools = requirements.tools || [];
23419
+ // Add 'open_popup' to tools if not already present
23420
+ const updatedTools = existingTools.some((tool) => tool.name === 'open_popup')
23421
+ ? existingTools
23422
+ : [
23423
+ ...existingTools,
23424
+ {
23425
+ name: 'open_popup',
23426
+ description: spaceTrim$1(`
23427
+ Opens a popup window with a specific URL.
23428
+ Use this when you want to show a specific website to the user in a new window.
23429
+ ${!content ? '' : `Constraints / instructions: ${content}`}
23430
+ `),
23431
+ parameters: {
23432
+ type: 'object',
23433
+ properties: {
23434
+ url: {
23435
+ type: 'string',
23436
+ description: 'The URL to open in the popup window',
23437
+ },
23438
+ },
23439
+ required: ['url'],
23440
+ },
23441
+ },
23442
+ ];
23443
+ // Return requirements with updated tools and metadata
23444
+ return this.appendToSystemMessage({
23445
+ ...requirements,
23446
+ tools: updatedTools,
23447
+ _metadata: {
23448
+ ...requirements._metadata,
23449
+ usePopup: content || true,
23450
+ },
23451
+ }, spaceTrim$1((block) => `
23452
+ Tool:
23453
+ - You can open a popup window with a specific URL using the tool "open_popup".
23454
+ - Use this when you want the user to see or interact with a specific website.
23455
+ ${block(extraInstructions)}
23456
+ `));
23457
+ }
23458
+ /**
23459
+ * Gets human-readable titles for tool functions provided by this commitment.
23460
+ */
23461
+ getToolTitles() {
23462
+ return {
23463
+ open_popup: 'Open popup',
23464
+ };
23465
+ }
23466
+ /**
23467
+ * Gets the `open_popup` tool function implementation.
23468
+ */
23469
+ getToolFunctions() {
23470
+ return {
23471
+ async open_popup(args) {
23472
+ console.log('!!!! [Tool] open_popup called', { args });
23473
+ const { url } = args;
23474
+ if (typeof window !== 'undefined') {
23475
+ window.open(url, '_blank');
23476
+ return `Popup window with URL "${url}" was opened.`;
23477
+ }
23478
+ return spaceTrim$1(`
23479
+ Popup window with URL "${url}" was requested.
23480
+
23481
+ Note: The agent is currently running on the server, so the popup cannot be opened automatically.
23482
+ The user can open it manually from the tool call details in the chat.
23483
+ `);
23484
+ },
23485
+ };
23486
+ }
23487
+ }
22142
23488
  /**
22143
- * MIME types that are trustworthy indicators of binary content.
22144
- *
22145
- * @private constant of decodeAttachmentAsText
23489
+ * Note: [💞] Ignore a discrepancy between file name and entity name
22146
23490
  */
22147
- const TRUSTED_BINARY_MIME_TYPES = new Set([
22148
- 'application/pdf',
22149
- 'application/zip',
22150
- 'application/gzip',
22151
- 'application/x-gzip',
22152
- 'application/x-7z-compressed',
22153
- 'application/vnd.rar',
22154
- 'application/x-rar-compressed',
22155
- ]);
23491
+
22156
23492
  /**
22157
- * MIME prefixes that strongly indicate binary content.
23493
+ * Tool name used by the USE PRIVACY commitment.
22158
23494
  *
22159
- * @private constant of decodeAttachmentAsText
23495
+ * @private internal USE PRIVACY constant
22160
23496
  */
22161
- const TRUSTED_BINARY_MIME_PREFIXES = ['image/', 'audio/', 'video/', 'font/', 'model/'];
23497
+ const TURN_PRIVACY_ON_TOOL_NAME = 'turn_privacy_on';
22162
23498
  /**
22163
- * Suspicious characters that often appear when `windows-1250` text is decoded as `windows-1252`.
23499
+ * Status returned when the UI should ask the user to confirm private mode.
22164
23500
  *
22165
- * @private constant of decodeAttachmentAsText
23501
+ * @private internal USE PRIVACY constant
22166
23502
  */
22167
- const SUSPICIOUS_SINGLE_BYTE_ARTIFACTS = new Set([
22168
- 'ˇ',
22169
- '˘',
22170
- '˙',
22171
- '˛',
22172
- '˝',
22173
- '¸',
22174
- '¤',
22175
- '¦',
22176
- '¨',
22177
- '¯',
22178
- '²',
22179
- '³',
22180
- '¹',
22181
- ]);
23503
+ const PRIVACY_CONFIRMATION_REQUIRED_STATUS = 'confirmation-required';
22182
23504
  /**
22183
- * Supported fallback encodings tried when UTF-8 is not convincing.
23505
+ * Status returned when private mode is already enabled.
22184
23506
  *
22185
- * @private constant of decodeAttachmentAsText
23507
+ * @private internal USE PRIVACY constant
22186
23508
  */
22187
- const FALLBACK_ENCODINGS = ['windows-1250', 'windows-1252', 'iso-8859-1'];
23509
+ const PRIVACY_ALREADY_ENABLED_STATUS = 'already-enabled';
22188
23510
  /**
22189
- * Converts unknown byte containers into `Uint8Array`.
23511
+ * Checks whether private mode is already enabled in runtime context.
22190
23512
  *
22191
- * @private function of decodeAttachmentAsText
23513
+ * @private utility of USE PRIVACY commitment
22192
23514
  */
22193
- function toUint8Array(bytes) {
22194
- if (bytes instanceof Uint8Array) {
22195
- return bytes;
22196
- }
22197
- if (ArrayBuffer.isView(bytes)) {
22198
- return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
22199
- }
22200
- return new Uint8Array(bytes);
23515
+ function isPrivateModeEnabledInRuntimeContext(args) {
23516
+ var _a;
23517
+ const runtimeContext = readToolRuntimeContextFromToolArgs(args);
23518
+ return ((_a = runtimeContext === null || runtimeContext === void 0 ? void 0 : runtimeContext.memory) === null || _a === void 0 ? void 0 : _a.isPrivateMode) === true;
22201
23519
  }
22202
23520
  /**
22203
- * Removes MIME parameters and normalizes casing.
23521
+ * Creates a standard "confirmation required" result payload.
22204
23522
  *
22205
- * @private function of decodeAttachmentAsText
23523
+ * @private utility of USE PRIVACY commitment
22206
23524
  */
22207
- function normalizeMimeType(mimeType) {
22208
- if (!mimeType) {
22209
- return null;
22210
- }
22211
- const normalized = mimeType.split(';')[0].trim().toLowerCase();
22212
- return normalized || null;
23525
+ function createPrivacyConfirmationRequiredResult() {
23526
+ return {
23527
+ status: PRIVACY_CONFIRMATION_REQUIRED_STATUS,
23528
+ message: 'Private mode requires explicit user confirmation in the UI. Ask the user to confirm the privacy prompt.',
23529
+ };
22213
23530
  }
22214
23531
  /**
22215
- * Extracts optional `charset=...` information from a MIME type value.
23532
+ * Creates a standard "already enabled" result payload.
22216
23533
  *
22217
- * @private function of decodeAttachmentAsText
23534
+ * @private utility of USE PRIVACY commitment
22218
23535
  */
22219
- function extractCharset(mimeType) {
22220
- if (!mimeType) {
22221
- return null;
22222
- }
22223
- const match = mimeType.match(/charset\s*=\s*("?)([^";\s]+)\1/i);
22224
- if (!match) {
22225
- return null;
22226
- }
22227
- return match[2].trim().toLowerCase();
23536
+ function createPrivacyAlreadyEnabledResult() {
23537
+ return {
23538
+ status: PRIVACY_ALREADY_ENABLED_STATUS,
23539
+ message: 'Private mode is already enabled for this chat session.',
23540
+ };
22228
23541
  }
22229
23542
  /**
22230
- * Returns true when the MIME type is a strong textual hint.
23543
+ * USE PRIVACY commitment definition.
22231
23544
  *
22232
- * @private function of decodeAttachmentAsText
23545
+ * The `USE PRIVACY` commitment enables an agent to request enabling private mode.
23546
+ *
23547
+ * @private [🪔] Maybe export the commitments through some package
22233
23548
  */
22234
- function isTrustedTextMimeType(mimeType) {
22235
- if (!mimeType) {
22236
- return false;
22237
- }
23549
+ class UsePrivacyCommitmentDefinition extends BaseCommitmentDefinition {
23550
+ constructor() {
23551
+ super('USE PRIVACY', ['PRIVACY']);
23552
+ }
23553
+ get requiresContent() {
23554
+ return false;
23555
+ }
23556
+ /**
23557
+ * Short one-line description of USE PRIVACY.
23558
+ */
23559
+ get description() {
23560
+ return 'Enable the agent to request turning private mode on for sensitive conversations.';
23561
+ }
23562
+ /**
23563
+ * Icon for this commitment.
23564
+ */
23565
+ get icon() {
23566
+ return '🔒';
23567
+ }
23568
+ /**
23569
+ * Markdown documentation for USE PRIVACY commitment.
23570
+ */
23571
+ get documentation() {
23572
+ return spaceTrim$1(`
23573
+ # USE PRIVACY
23574
+
23575
+ Enables the agent to request turning on private mode in chat.
23576
+
23577
+ ## Key aspects
23578
+
23579
+ - The tool \`turn_privacy_on\` asks the UI to show a confirmation dialog to the user.
23580
+ - Private mode is enabled only after explicit user confirmation in the UI.
23581
+ - In the current implementation, this reuses existing private mode behavior in chat.
23582
+ - While private mode is active, chat persistence, memory persistence, and self-learning are disabled.
23583
+ - Proper encryption is planned for future updates, but not implemented by this commitment yet.
23584
+ - Optional content after \`USE PRIVACY\` can provide additional privacy instructions.
23585
+
23586
+ ## Examples
23587
+
23588
+ \`\`\`book
23589
+ Sensitive Assistant
23590
+
23591
+ PERSONA You help with sensitive topics where privacy is important.
23592
+ USE PRIVACY
23593
+ \`\`\`
23594
+
23595
+ \`\`\`book
23596
+ Compliance Assistant
23597
+
23598
+ PERSONA You assist with legal and HR conversations.
23599
+ USE PRIVACY Offer private mode when user asks to avoid storing data.
23600
+ \`\`\`
23601
+ `);
23602
+ }
23603
+ applyToAgentModelRequirements(requirements, content) {
23604
+ const extraInstructions = formatOptionalInstructionBlock('Privacy instructions', content);
23605
+ const existingTools = requirements.tools || [];
23606
+ const tools = existingTools.some((tool) => tool.name === TURN_PRIVACY_ON_TOOL_NAME)
23607
+ ? existingTools
23608
+ : [
23609
+ ...existingTools,
23610
+ {
23611
+ name: TURN_PRIVACY_ON_TOOL_NAME,
23612
+ description: spaceTrim$1(`
23613
+ Requests turning private mode on in the chat UI.
23614
+ The user must explicitly confirm the action in a dialog before private mode is enabled.
23615
+ Use this for sensitive topics or when the user asks not to store conversation data.
23616
+ `),
23617
+ parameters: {
23618
+ type: 'object',
23619
+ properties: {},
23620
+ required: [],
23621
+ },
23622
+ },
23623
+ ];
23624
+ return this.appendToSystemMessage({
23625
+ ...requirements,
23626
+ tools,
23627
+ _metadata: {
23628
+ ...requirements._metadata,
23629
+ usePrivacy: content || true,
23630
+ },
23631
+ }, spaceTrim$1((block) => `
23632
+ Privacy mode:
23633
+ - Use "${TURN_PRIVACY_ON_TOOL_NAME}" when the user asks for a private/sensitive conversation.
23634
+ - This tool requests a UI confirmation dialog. Private mode is enabled only after user confirms.
23635
+ - Current implementation uses the existing chat private mode (no chat persistence, memory persistence, or self-learning while active).
23636
+ - Do not claim that end-to-end encryption is implemented yet.
23637
+ ${block(extraInstructions)}
23638
+ `));
23639
+ }
23640
+ /**
23641
+ * Gets human-readable titles for tool functions provided by this commitment.
23642
+ */
23643
+ getToolTitles() {
23644
+ return {
23645
+ [TURN_PRIVACY_ON_TOOL_NAME]: 'Turn privacy mode on',
23646
+ };
23647
+ }
23648
+ /**
23649
+ * Gets the `turn_privacy_on` tool function implementation.
23650
+ */
23651
+ getToolFunctions() {
23652
+ return {
23653
+ async [TURN_PRIVACY_ON_TOOL_NAME](args) {
23654
+ const result = isPrivateModeEnabledInRuntimeContext(args)
23655
+ ? createPrivacyAlreadyEnabledResult()
23656
+ : createPrivacyConfirmationRequiredResult();
23657
+ return JSON.stringify(result);
23658
+ },
23659
+ };
23660
+ }
23661
+ }
23662
+ /**
23663
+ * Note: [💞] Ignore a discrepancy between file name and entity name
23664
+ */
23665
+
23666
+ /* eslint-disable no-magic-numbers */
23667
+ /**
23668
+ * Default byte limit for one best-effort text decode.
23669
+ *
23670
+ * @private constant of decodeAttachmentAsText
23671
+ */
23672
+ const DEFAULT_ATTACHMENT_TEXT_DECODE_BYTES = 512 * 1024;
23673
+ /**
23674
+ * Marker appended when only a prefix of the payload is decoded.
23675
+ *
23676
+ * @private constant of decodeAttachmentAsText
23677
+ */
23678
+ const TRUNCATED_MARKER = '…[TRUNCATED]…';
23679
+ /**
23680
+ * MIME types that are trustworthy indicators of textual content.
23681
+ *
23682
+ * @private constant of decodeAttachmentAsText
23683
+ */
23684
+ const TRUSTED_TEXT_MIME_TYPES = new Set([
23685
+ 'application/json',
23686
+ 'application/ld+json',
23687
+ 'application/javascript',
23688
+ 'application/x-javascript',
23689
+ 'application/xml',
23690
+ 'application/xhtml+xml',
23691
+ 'application/x-www-form-urlencoded',
23692
+ 'application/yaml',
23693
+ 'application/x-yaml',
23694
+ 'application/toml',
23695
+ 'application/sql',
23696
+ 'application/rtf',
23697
+ 'application/x-subrip',
23698
+ ]);
23699
+ /**
23700
+ * MIME types that are trustworthy indicators of binary content.
23701
+ *
23702
+ * @private constant of decodeAttachmentAsText
23703
+ */
23704
+ const TRUSTED_BINARY_MIME_TYPES = new Set([
23705
+ 'application/pdf',
23706
+ 'application/zip',
23707
+ 'application/gzip',
23708
+ 'application/x-gzip',
23709
+ 'application/x-7z-compressed',
23710
+ 'application/vnd.rar',
23711
+ 'application/x-rar-compressed',
23712
+ ]);
23713
+ /**
23714
+ * MIME prefixes that strongly indicate binary content.
23715
+ *
23716
+ * @private constant of decodeAttachmentAsText
23717
+ */
23718
+ const TRUSTED_BINARY_MIME_PREFIXES = ['image/', 'audio/', 'video/', 'font/', 'model/'];
23719
+ /**
23720
+ * Suspicious characters that often appear when `windows-1250` text is decoded as `windows-1252`.
23721
+ *
23722
+ * @private constant of decodeAttachmentAsText
23723
+ */
23724
+ const SUSPICIOUS_SINGLE_BYTE_ARTIFACTS = new Set([
23725
+ 'ˇ',
23726
+ '˘',
23727
+ '˙',
23728
+ '˛',
23729
+ '˝',
23730
+ '¸',
23731
+ '¤',
23732
+ '¦',
23733
+ '¨',
23734
+ '¯',
23735
+ '²',
23736
+ '³',
23737
+ '¹',
23738
+ ]);
23739
+ /**
23740
+ * Supported fallback encodings tried when UTF-8 is not convincing.
23741
+ *
23742
+ * @private constant of decodeAttachmentAsText
23743
+ */
23744
+ const FALLBACK_ENCODINGS = ['windows-1250', 'windows-1252', 'iso-8859-1'];
23745
+ /**
23746
+ * Converts unknown byte containers into `Uint8Array`.
23747
+ *
23748
+ * @private function of decodeAttachmentAsText
23749
+ */
23750
+ function toUint8Array(bytes) {
23751
+ if (bytes instanceof Uint8Array) {
23752
+ return bytes;
23753
+ }
23754
+ if (ArrayBuffer.isView(bytes)) {
23755
+ return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
23756
+ }
23757
+ return new Uint8Array(bytes);
23758
+ }
23759
+ /**
23760
+ * Removes MIME parameters and normalizes casing.
23761
+ *
23762
+ * @private function of decodeAttachmentAsText
23763
+ */
23764
+ function normalizeMimeType(mimeType) {
23765
+ if (!mimeType) {
23766
+ return null;
23767
+ }
23768
+ const normalized = mimeType.split(';')[0].trim().toLowerCase();
23769
+ return normalized || null;
23770
+ }
23771
+ /**
23772
+ * Extracts optional `charset=...` information from a MIME type value.
23773
+ *
23774
+ * @private function of decodeAttachmentAsText
23775
+ */
23776
+ function extractCharset(mimeType) {
23777
+ if (!mimeType) {
23778
+ return null;
23779
+ }
23780
+ const match = mimeType.match(/charset\s*=\s*("?)([^";\s]+)\1/i);
23781
+ if (!match) {
23782
+ return null;
23783
+ }
23784
+ return match[2].trim().toLowerCase();
23785
+ }
23786
+ /**
23787
+ * Returns true when the MIME type is a strong textual hint.
23788
+ *
23789
+ * @private function of decodeAttachmentAsText
23790
+ */
23791
+ function isTrustedTextMimeType(mimeType) {
23792
+ if (!mimeType) {
23793
+ return false;
23794
+ }
22238
23795
  return (mimeType.startsWith('text/') ||
22239
23796
  mimeType.endsWith('+json') ||
22240
23797
  mimeType.endsWith('+xml') ||
@@ -25198,503 +26755,186 @@ const COMMITMENT_REGISTRY = [
25198
26755
  new NoteCommitmentDefinition('COMMENT'),
25199
26756
  new NoteCommitmentDefinition('NONCE'),
25200
26757
  new NoteCommitmentDefinition('TODO'),
25201
- new GoalCommitmentDefinition('GOAL'),
25202
- new GoalCommitmentDefinition('GOALS'),
25203
- new InitialMessageCommitmentDefinition(),
25204
- new UserMessageCommitmentDefinition(),
25205
- new InternalMessageCommitmentDefinition(),
25206
- new AgentMessageCommitmentDefinition(),
25207
- new MessageSuffixCommitmentDefinition(),
25208
- new MessageCommitmentDefinition('MESSAGE'),
25209
- new MessageCommitmentDefinition('MESSAGES'),
25210
- new ScenarioCommitmentDefinition('SCENARIO'),
25211
- new ScenarioCommitmentDefinition('SCENARIOS'),
25212
- new DeleteCommitmentDefinition('DELETE'),
25213
- new DeleteCommitmentDefinition('CANCEL'),
25214
- new DeleteCommitmentDefinition('DISCARD'),
25215
- new DeleteCommitmentDefinition('REMOVE'),
25216
- new DictionaryCommitmentDefinition(),
25217
- new OpenCommitmentDefinition(),
25218
- new ClosedCommitmentDefinition(),
25219
- new TeamCommitmentDefinition(),
25220
- new UseBrowserCommitmentDefinition(),
25221
- new UseSearchEngineCommitmentDefinition(),
25222
- new UseSpawnCommitmentDefinition(),
25223
- new UseTimeoutCommitmentDefinition(),
25224
- new UseTimeCommitmentDefinition(),
25225
- new UseUserLocationCommitmentDefinition(),
25226
- new UseEmailCommitmentDefinition(),
25227
- new UsePopupCommitmentDefinition(),
25228
- new UseImageGeneratorCommitmentDefinition('USE IMAGE GENERATOR'),
25229
- new UseMcpCommitmentDefinition(),
25230
- new UsePrivacyCommitmentDefinition(),
25231
- new UseProjectCommitmentDefinition(),
25232
- new UseCommitmentDefinition(),
25233
- // Not yet implemented commitments (using placeholder)
25234
- new NotYetImplementedCommitmentDefinition('EXPECT'),
25235
- new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
25236
- new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
25237
- new NotYetImplementedCommitmentDefinition('AVOID'),
25238
- new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
25239
- new NotYetImplementedCommitmentDefinition('CONTEXT'),
25240
- // <- TODO: Prompt: Leverage aliases instead of duplicating commitment definitions
25241
- ];
25242
- /**
25243
- * TODO: [🧠] Maybe create through standardized $register
25244
- * Note: [💞] Ignore a discrepancy between file name and entity name
25245
- */
25246
-
25247
- /**
25248
- * Gets all available commitment definitions
25249
- * @returns Array of all commitment definitions
25250
- *
25251
- * @public exported from `@promptbook/core`
25252
- */
25253
- function getAllCommitmentDefinitions() {
25254
- return $deepFreeze([...COMMITMENT_REGISTRY]);
25255
- }
25256
-
25257
- /**
25258
- * Collects tool functions from all commitment definitions.
25259
- *
25260
- * @returns Map of tool function implementations.
25261
- * @private internal helper for commitment tool registry
25262
- */
25263
- function collectCommitmentToolFunctions() {
25264
- const allToolFunctions = {};
25265
- for (const commitmentDefinition of getAllCommitmentDefinitions()) {
25266
- const toolFunctions = commitmentDefinition.getToolFunctions();
25267
- for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
25268
- if (allToolFunctions[funcName] !== undefined &&
25269
- just(false) /* <- Note: [??] How to deal with commitment aliases */) {
25270
- throw new UnexpectedError(`Duplicate tool function name detected: \`${funcName}\` provided by commitment \`${commitmentDefinition.type}\``);
25271
- }
25272
- allToolFunctions[funcName] = funcImpl;
25273
- }
25274
- }
25275
- return allToolFunctions;
25276
- }
25277
- /**
25278
- * Creates a proxy that resolves tool functions on demand.
25279
- *
25280
- * @param getFunctions - Provider of current tool functions.
25281
- * @returns Proxy exposing tool functions as properties.
25282
- * @private internal helper for commitment tool registry
25283
- */
25284
- function createToolFunctionsProxy(getFunctions) {
25285
- const resolveFunctions = () => getFunctions();
25286
- return new Proxy({}, {
25287
- get(_target, prop) {
25288
- if (typeof prop !== 'string') {
25289
- return undefined;
25290
- }
25291
- return resolveFunctions()[prop];
25292
- },
25293
- has(_target, prop) {
25294
- if (typeof prop !== 'string') {
25295
- return false;
25296
- }
25297
- return prop in resolveFunctions();
25298
- },
25299
- ownKeys() {
25300
- return Object.keys(resolveFunctions());
25301
- },
25302
- getOwnPropertyDescriptor(_target, prop) {
25303
- if (typeof prop !== 'string') {
25304
- return undefined;
25305
- }
25306
- const value = resolveFunctions()[prop];
25307
- if (value === undefined) {
25308
- return undefined;
25309
- }
25310
- return {
25311
- enumerable: true,
25312
- configurable: true,
25313
- writable: false,
25314
- value,
25315
- };
25316
- },
25317
- });
25318
- }
25319
- /**
25320
- * Note: [💞] Ignore a discrepancy between file name and entity name
25321
- */
25322
-
25323
- const nodeToolFunctions = {
25324
- /**
25325
- * @@@
25326
- *
25327
- * Note: [??] This function has implementation both for browser and node, this is the full one for node
25328
- */
25329
- async fetch_url_content(args) {
25330
- console.log('!!!! [Tool] fetch_url_content called', { args });
25331
- const { url } = args;
25332
- return await fetchUrlContent(url);
25333
- },
25334
- /**
25335
- * @@@
25336
- *
25337
- * Note: [??] This function has implementation both for browser and node, this is the server one for node
25338
- */
25339
- run_browser: resolveRunBrowserToolForNode(),
25340
- /**
25341
- * @@@
25342
- *
25343
- * Note: [??] This function has implementation both for browser and node, this is the server one for node
25344
- */
25345
- send_email: resolveSendEmailToolForNode(),
25346
- /**
25347
- * @@@
25348
- *
25349
- * Note: [??] This function has implementation both for browser and node, this is the server one for node
25350
- */
25351
- spawn_agent: resolveSpawnAgentToolForNode(),
25352
- // TODO: !!!! Unhardcode, make proper server function register from definitions
25353
- };
25354
- const nodeToolFunctionsProxy = createToolFunctionsProxy(() => ({
25355
- ...collectCommitmentToolFunctions(),
25356
- ...nodeToolFunctions,
25357
- }));
25358
- /**
25359
- * Gets all function implementations provided by all commitments
25360
- *
25361
- * Note: This function is intended for server use, there is also equivalent `getAllCommitmentsToolFunctionsForBrowser` for browser use
25362
- *
25363
- * @public exported from `@promptbook/node`
25364
- */
25365
- function getAllCommitmentsToolFunctionsForNode() {
25366
- if (!$isRunningInNode()) {
25367
- throw new EnvironmentMismatchError(spaceTrim(`
25368
- Function getAllCommitmentsToolFunctionsForNode should be run in Node.js environment.
25369
-
25370
- - In browser use getAllCommitmentsToolFunctionsForBrowser instead.
25371
- - This function can include server-only tools which cannot run in browser environment.
25372
-
25373
- `));
25374
- }
25375
- return nodeToolFunctionsProxy;
25376
- }
25377
- /**
25378
- * Note: [??] Code in this file should never be never released in packages that could be imported into browser environment
25379
- * TODO: [??] Unite `xxxForServer` and `xxxForNode` naming
25380
- */
25381
-
25382
- /**
25383
- * Normalize options for `execCommand` and `execCommands`
25384
- *
25385
- * Note: `$` is used to indicate that this function behaves differently according to `process.platform`
25386
- *
25387
- * @private internal utility of `execCommand` and `execCommands`
25388
- */
25389
- function $execCommandNormalizeOptions(options) {
25390
- var _a, _b, _c, _d;
25391
- let command;
25392
- let cwd;
25393
- let crashOnError;
25394
- let args = [];
25395
- let timeout;
25396
- let isVerbose;
25397
- let env;
25398
- if (typeof options === 'string') {
25399
- // TODO: [1] DRY default values
25400
- command = options;
25401
- cwd = process.cwd();
25402
- crashOnError = true;
25403
- timeout = Infinity; // <- TODO: [⏳]
25404
- isVerbose = DEFAULT_IS_VERBOSE;
25405
- env = undefined;
25406
- }
25407
- else {
25408
- /*
25409
- TODO:
25410
- if ((options as any).commands !== undefined) {
25411
- commands = (options as any).commands;
25412
- } else {
25413
- commands = [(options as any).command];
25414
- }
25415
- */
25416
- // TODO: [1] DRY default values
25417
- command = options.command;
25418
- cwd = (_a = options.cwd) !== null && _a !== void 0 ? _a : process.cwd();
25419
- crashOnError = (_b = options.crashOnError) !== null && _b !== void 0 ? _b : true;
25420
- timeout = (_c = options.timeout) !== null && _c !== void 0 ? _c : Infinity;
25421
- isVerbose = (_d = options.isVerbose) !== null && _d !== void 0 ? _d : DEFAULT_IS_VERBOSE;
25422
- env = options.env;
25423
- }
25424
- // TODO: /(-[a-zA-Z0-9-]+\s+[^\s]*)|[^\s]*/g
25425
- const _ = Array.from(command.matchAll(/(".*")|([^\s]*)/g))
25426
- .map(([match]) => match)
25427
- .filter((arg) => arg !== '');
25428
- if (_.length > 1) {
25429
- [command, ...args] = _;
25430
- }
25431
- if (options.args) {
25432
- args = [...args, ...options.args];
25433
- }
25434
- let humanReadableCommand = !['npx', 'npm'].includes(command) ? command : args[0];
25435
- if (['ts-node'].includes(humanReadableCommand)) {
25436
- humanReadableCommand += ` ${args[1]}`;
25437
- }
25438
- if (/^win/.test(process.platform) && ['npm', 'npx'].includes(command)) {
25439
- command = `${command}.cmd`;
25440
- }
25441
- return { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose, env };
25442
- }
25443
- // TODO: This should show type error> execCommandNormalizeOptions({ command: '', commands: [''] });
25444
-
25445
- /**
25446
- * Run one command in a shell
25447
- *
25448
- *
25449
- * Note: There are 2 similar functions in the codebase:
25450
- * - `$execCommand` which runs a single command
25451
- * - `$execCommands` which runs multiple commands
25452
- * Note: `$` is used to indicate that this function is not a pure function - it runs a command in a shell
25453
- *
25454
- * @public exported from `@promptbook/node`
25455
- */
25456
- function $execCommand(options) {
25457
- if (!$isRunningInNode()) {
25458
- throw new EnvironmentMismatchError('Function `$execCommand` can run only in Node environment.js');
25459
- }
25460
- return new Promise((resolve, reject) => {
25461
- // eslint-disable-next-line prefer-const
25462
- const { command, humanReadableCommand, args, cwd, crashOnError, timeout, isVerbose = DEFAULT_IS_VERBOSE, env, } = $execCommandNormalizeOptions(options);
25463
- if (timeout !== Infinity) {
25464
- // TODO: In waitasecond forTime(Infinity) should be equivalent to forEver()
25465
- forTime(timeout).then(() => {
25466
- if (crashOnError) {
25467
- reject(new Error(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms`));
25468
- }
25469
- else {
25470
- console.warn(`Command "${humanReadableCommand}" exceeded time limit of ${timeout}ms but continues running`);
25471
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
25472
- resolve('Command exceeded time limit');
25473
- }
25474
- });
25475
- }
25476
- if (isVerbose) {
25477
- console.info(colors.yellow(cwd) + ' ' + colors.green(command) + ' ' + colors.blue(args.join(' ')));
25478
- }
25479
- try {
25480
- const commandProcess = spawn(command, args, {
25481
- cwd,
25482
- shell: true,
25483
- env: env ? { ...process.env, ...env } : process.env,
25484
- });
25485
- if (isVerbose) {
25486
- commandProcess.on('message', (message) => {
25487
- console.info({ message });
25488
- });
25489
- }
25490
- const output = [];
25491
- commandProcess.stdout.on('data', (stdout) => {
25492
- output.push(stdout.toString());
25493
- if (isVerbose) {
25494
- console.info(stdout.toString());
25495
- }
25496
- });
25497
- commandProcess.stderr.on('data', (stderr) => {
25498
- output.push(stderr.toString());
25499
- if (isVerbose && stderr.toString().trim()) {
25500
- console.warn(stderr.toString());
25501
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
25502
- }
25503
- });
25504
- const finishWithCode = (code) => {
25505
- if (code !== 0) {
25506
- if (crashOnError) {
25507
- reject(new Error(output.join('\n').trim() ||
25508
- `Command "${humanReadableCommand}" exited with code ${code}`));
25509
- }
25510
- else {
25511
- if (isVerbose) {
25512
- console.warn(`Command "${humanReadableCommand}" exited with code ${code}`);
25513
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
25514
- }
25515
- resolve(spaceTrim$1(output.join('\n')));
25516
- }
25517
- }
25518
- else {
25519
- resolve(spaceTrim$1(output.join('\n')));
25520
- }
25521
- };
25522
- commandProcess.on('close', finishWithCode);
25523
- commandProcess.on('exit', finishWithCode);
25524
- commandProcess.on('disconnect', () => {
25525
- // Note: Unexpected disconnection should always result in rejection
25526
- reject(new Error(`Command "${humanReadableCommand}" disconnected`));
25527
- });
25528
- commandProcess.on('error', (error) => {
25529
- if (crashOnError) {
25530
- reject(new Error(`Command "${humanReadableCommand}" failed: \n${error.message}`));
25531
- }
25532
- else {
25533
- if (isVerbose) {
25534
- console.warn(error);
25535
- // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
25536
- }
25537
- resolve(spaceTrim$1(output.join('\n')));
25538
- }
25539
- });
25540
- }
25541
- catch (error) {
25542
- // Note: Unexpected error in sync code should always result in rejection
25543
- reject(error);
25544
- }
25545
- });
25546
- }
25547
- /**
25548
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
25549
- */
25550
-
25551
- /**
25552
- * Attempts to locate the specified application on a Linux system using the 'which' command.
25553
- * Returns the path to the executable if found, or null otherwise.
25554
- *
25555
- * @private within the repository
25556
- */
25557
- async function locateAppOnLinux({ linuxWhich, }) {
25558
- try {
25559
- const result = await $execCommand({ crashOnError: true, command: `which ${linuxWhich}` });
25560
- return result.trim();
25561
- }
25562
- catch (error) {
25563
- assertsError(error);
25564
- return null;
25565
- }
25566
- }
26758
+ new GoalCommitmentDefinition('GOAL'),
26759
+ new GoalCommitmentDefinition('GOALS'),
26760
+ new InitialMessageCommitmentDefinition(),
26761
+ new UserMessageCommitmentDefinition(),
26762
+ new InternalMessageCommitmentDefinition(),
26763
+ new AgentMessageCommitmentDefinition(),
26764
+ new MessageSuffixCommitmentDefinition(),
26765
+ new MessageCommitmentDefinition('MESSAGE'),
26766
+ new MessageCommitmentDefinition('MESSAGES'),
26767
+ new ScenarioCommitmentDefinition('SCENARIO'),
26768
+ new ScenarioCommitmentDefinition('SCENARIOS'),
26769
+ new DeleteCommitmentDefinition('DELETE'),
26770
+ new DeleteCommitmentDefinition('CANCEL'),
26771
+ new DeleteCommitmentDefinition('DISCARD'),
26772
+ new DeleteCommitmentDefinition('REMOVE'),
26773
+ new DictionaryCommitmentDefinition(),
26774
+ new OpenCommitmentDefinition(),
26775
+ new ClosedCommitmentDefinition(),
26776
+ new TeamCommitmentDefinition(),
26777
+ new UseBrowserCommitmentDefinition(),
26778
+ new UseSearchEngineCommitmentDefinition(),
26779
+ new UseSpawnCommitmentDefinition(),
26780
+ new UseTimeoutCommitmentDefinition(),
26781
+ new UseTimeCommitmentDefinition(),
26782
+ new UseUserLocationCommitmentDefinition(),
26783
+ new UseCalendarCommitmentDefinition(),
26784
+ new UseEmailCommitmentDefinition(),
26785
+ new UsePopupCommitmentDefinition(),
26786
+ new UseImageGeneratorCommitmentDefinition('USE IMAGE GENERATOR'),
26787
+ new UseMcpCommitmentDefinition(),
26788
+ new UsePrivacyCommitmentDefinition(),
26789
+ new UseProjectCommitmentDefinition(),
26790
+ new UseCommitmentDefinition(),
26791
+ // Not yet implemented commitments (using placeholder)
26792
+ new NotYetImplementedCommitmentDefinition('EXPECT'),
26793
+ new NotYetImplementedCommitmentDefinition('BEHAVIOUR'),
26794
+ new NotYetImplementedCommitmentDefinition('BEHAVIOURS'),
26795
+ new NotYetImplementedCommitmentDefinition('AVOID'),
26796
+ new NotYetImplementedCommitmentDefinition('AVOIDANCE'),
26797
+ new NotYetImplementedCommitmentDefinition('CONTEXT'),
26798
+ // <- TODO: Prompt: Leverage aliases instead of duplicating commitment definitions
26799
+ ];
25567
26800
  /**
25568
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
25569
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
26801
+ * TODO: [🧠] Maybe create through standardized $register
26802
+ * Note: [💞] Ignore a discrepancy between file name and entity name
25570
26803
  */
25571
26804
 
25572
26805
  /**
25573
- * Checks if the file is executable
26806
+ * Gets all available commitment definitions
26807
+ * @returns Array of all commitment definitions
25574
26808
  *
25575
- * @private within the repository
26809
+ * @public exported from `@promptbook/core`
25576
26810
  */
25577
- async function isExecutable(path, fs) {
25578
- try {
25579
- await fs.access(path, fs.constants.X_OK);
25580
- return true;
25581
- }
25582
- catch (error) {
25583
- return false;
25584
- }
26811
+ function getAllCommitmentDefinitions() {
26812
+ return $deepFreeze([...COMMITMENT_REGISTRY]);
25585
26813
  }
25586
- /**
25587
- * Note: Not [~🟢~] because it is not directly dependent on `fs
25588
- * TODO: [🖇] What about symlinks?
25589
- */
25590
26814
 
25591
- // Note: Module `userhome` has no types available, so it is imported using `require`
25592
- // @see https://stackoverflow.com/questions/37000981/how-to-import-node-module-in-typescript-without-type-definitions
25593
- // eslint-disable-next-line @typescript-eslint/no-var-requires
25594
- const userhome = require('userhome');
25595
26815
  /**
25596
- * Attempts to locate the specified application on a macOS system by checking standard application paths and using mdfind.
25597
- * Returns the path to the executable if found, or null otherwise.
26816
+ * Collects tool functions from all commitment definitions.
25598
26817
  *
25599
- * @private within the repository
26818
+ * @returns Map of tool function implementations.
26819
+ * @private internal helper for commitment tool registry
25600
26820
  */
25601
- async function locateAppOnMacOs({ macOsName, }) {
25602
- try {
25603
- const toExec = `/Contents/MacOS/${macOsName}`;
25604
- const regPath = `/Applications/${macOsName}.app` + toExec;
25605
- const altPath = userhome(regPath.slice(1));
25606
- if (await isExecutable(regPath, $provideFilesystemForNode())) {
25607
- return regPath;
25608
- }
25609
- else if (await isExecutable(altPath, $provideFilesystemForNode())) {
25610
- return altPath;
26821
+ function collectCommitmentToolFunctions() {
26822
+ const allToolFunctions = {};
26823
+ for (const commitmentDefinition of getAllCommitmentDefinitions()) {
26824
+ const toolFunctions = commitmentDefinition.getToolFunctions();
26825
+ for (const [funcName, funcImpl] of Object.entries(toolFunctions)) {
26826
+ if (allToolFunctions[funcName] !== undefined &&
26827
+ just(false) /* <- Note: [??] How to deal with commitment aliases */) {
26828
+ throw new UnexpectedError(`Duplicate tool function name detected: \`${funcName}\` provided by commitment \`${commitmentDefinition.type}\``);
26829
+ }
26830
+ allToolFunctions[funcName] = funcImpl;
25611
26831
  }
25612
- const result = await $execCommand({
25613
- crashOnError: true,
25614
- command: `mdfind 'kMDItemDisplayName == "${macOsName}" && kMDItemKind == Application'`,
25615
- });
25616
- return result.trim() + toExec;
25617
- }
25618
- catch (error) {
25619
- assertsError(error);
25620
- return null;
25621
26832
  }
26833
+ return allToolFunctions;
25622
26834
  }
25623
26835
  /**
25624
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
25625
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
25626
- */
25627
-
25628
- /**
25629
- * Attempts to locate the specified application on a Windows system by searching common installation directories.
25630
- * Returns the path to the executable if found, or null otherwise.
26836
+ * Creates a proxy that resolves tool functions on demand.
25631
26837
  *
25632
- * @private within the repository
26838
+ * @param getFunctions - Provider of current tool functions.
26839
+ * @returns Proxy exposing tool functions as properties.
26840
+ * @private internal helper for commitment tool registry
25633
26841
  */
25634
- async function locateAppOnWindows({ appName, windowsSuffix, }) {
25635
- try {
25636
- const prefixes = [
25637
- process.env.LOCALAPPDATA,
25638
- join(process.env.LOCALAPPDATA || '', 'Programs'),
25639
- process.env.PROGRAMFILES,
25640
- process.env['PROGRAMFILES(X86)'],
25641
- ];
25642
- for (const prefix of prefixes) {
25643
- const path = prefix + windowsSuffix;
25644
- if (await isExecutable(path, $provideFilesystemForNode())) {
25645
- return path;
26842
+ function createToolFunctionsProxy(getFunctions) {
26843
+ const resolveFunctions = () => getFunctions();
26844
+ return new Proxy({}, {
26845
+ get(_target, prop) {
26846
+ if (typeof prop !== 'string') {
26847
+ return undefined;
25646
26848
  }
25647
- }
25648
- throw new Error(`Can not locate app ${appName} on Windows.`);
25649
- }
25650
- catch (error) {
25651
- assertsError(error);
25652
- return null;
25653
- }
26849
+ return resolveFunctions()[prop];
26850
+ },
26851
+ has(_target, prop) {
26852
+ if (typeof prop !== 'string') {
26853
+ return false;
26854
+ }
26855
+ return prop in resolveFunctions();
26856
+ },
26857
+ ownKeys() {
26858
+ return Object.keys(resolveFunctions());
26859
+ },
26860
+ getOwnPropertyDescriptor(_target, prop) {
26861
+ if (typeof prop !== 'string') {
26862
+ return undefined;
26863
+ }
26864
+ const value = resolveFunctions()[prop];
26865
+ if (value === undefined) {
26866
+ return undefined;
26867
+ }
26868
+ return {
26869
+ enumerable: true,
26870
+ configurable: true,
26871
+ writable: false,
26872
+ value,
26873
+ };
26874
+ },
26875
+ });
25654
26876
  }
25655
26877
  /**
25656
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
25657
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
26878
+ * Note: [💞] Ignore a discrepancy between file name and entity name
25658
26879
  */
25659
26880
 
26881
+ const nodeToolFunctions = {
26882
+ /**
26883
+ * @@@
26884
+ *
26885
+ * Note: [??] This function has implementation both for browser and node, this is the full one for node
26886
+ */
26887
+ async fetch_url_content(args) {
26888
+ console.log('!!!! [Tool] fetch_url_content called', { args });
26889
+ const { url } = args;
26890
+ return await fetchUrlContent(url);
26891
+ },
26892
+ /**
26893
+ * @@@
26894
+ *
26895
+ * Note: [??] This function has implementation both for browser and node, this is the server one for node
26896
+ */
26897
+ run_browser: resolveRunBrowserToolForNode(),
26898
+ /**
26899
+ * @@@
26900
+ *
26901
+ * Note: [??] This function has implementation both for browser and node, this is the server one for node
26902
+ */
26903
+ send_email: resolveSendEmailToolForNode(),
26904
+ /**
26905
+ * @@@
26906
+ *
26907
+ * Note: [??] This function has implementation both for browser and node, this is the server one for node
26908
+ */
26909
+ spawn_agent: resolveSpawnAgentToolForNode(),
26910
+ // TODO: !!!! Unhardcode, make proper server function register from definitions
26911
+ };
26912
+ const nodeToolFunctionsProxy = createToolFunctionsProxy(() => ({
26913
+ ...collectCommitmentToolFunctions(),
26914
+ ...nodeToolFunctions,
26915
+ }));
25660
26916
  /**
25661
- * Locates an application on the system
26917
+ * Gets all function implementations provided by all commitments
25662
26918
  *
25663
- * @private within the repository
26919
+ * Note: This function is intended for server use, there is also equivalent `getAllCommitmentsToolFunctionsForBrowser` for browser use
26920
+ *
26921
+ * @public exported from `@promptbook/node`
25664
26922
  */
25665
- function locateApp(options) {
26923
+ function getAllCommitmentsToolFunctionsForNode() {
25666
26924
  if (!$isRunningInNode()) {
25667
- throw new EnvironmentMismatchError('Locating apps works only in Node.js environment');
25668
- }
25669
- const { appName, linuxWhich, windowsSuffix, macOsName } = options;
25670
- if (process.platform === 'win32') {
25671
- if (windowsSuffix) {
25672
- return locateAppOnWindows({ appName, windowsSuffix });
25673
- }
25674
- else {
25675
- throw new Error(`${appName} is not available on Windows.`);
25676
- }
25677
- }
25678
- else if (process.platform === 'darwin') {
25679
- if (macOsName) {
25680
- return locateAppOnMacOs({ macOsName });
25681
- }
25682
- else {
25683
- throw new Error(`${appName} is not available on macOS.`);
25684
- }
25685
- }
25686
- else {
25687
- if (linuxWhich) {
25688
- return locateAppOnLinux({ linuxWhich });
25689
- }
25690
- else {
25691
- throw new Error(`${appName} is not available on Linux.`);
25692
- }
26925
+ throw new EnvironmentMismatchError(spaceTrim(`
26926
+ Function getAllCommitmentsToolFunctionsForNode should be run in Node.js environment.
26927
+
26928
+ - In browser use getAllCommitmentsToolFunctionsForBrowser instead.
26929
+ - This function can include server-only tools which cannot run in browser environment.
26930
+
26931
+ `));
25693
26932
  }
26933
+ return nodeToolFunctionsProxy;
25694
26934
  }
25695
26935
  /**
25696
- * TODO: [🧠][♿] Maybe export through `@promptbook/node`
25697
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
26936
+ * Note: [??] Code in this file should never be never released in packages that could be imported into browser environment
26937
+ * TODO: [??] Unite `xxxForServer` and `xxxForNode` naming
25698
26938
  */
25699
26939
 
25700
26940
  /**
@@ -27759,6 +28999,14 @@ function parseAgentSource(agentSource) {
27759
28999
  });
27760
29000
  continue;
27761
29001
  }
29002
+ if (commitment.type === 'USE CALENDAR') {
29003
+ capabilities.push({
29004
+ type: 'calendar',
29005
+ label: 'Calendar',
29006
+ iconName: 'Calendar',
29007
+ });
29008
+ continue;
29009
+ }
27762
29010
  if (commitment.type === 'FROM') {
27763
29011
  const content = spaceTrim$2(commitment.content).split(/\r?\n/)[0] || '';
27764
29012
  if (content === 'Adam' || content === '' /* <- Note: Adam is implicit */) {