@promptbook/cli 0.89.0-9 → 0.92.0-10

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 (56) hide show
  1. package/README.md +9 -7
  2. package/esm/index.es.js +1832 -673
  3. package/esm/index.es.js.map +1 -1
  4. package/esm/typings/servers.d.ts +40 -0
  5. package/esm/typings/src/_packages/core.index.d.ts +14 -4
  6. package/esm/typings/src/_packages/deepseek.index.d.ts +2 -0
  7. package/esm/typings/src/_packages/google.index.d.ts +2 -0
  8. package/esm/typings/src/_packages/types.index.d.ts +18 -0
  9. package/esm/typings/src/_packages/utils.index.d.ts +6 -0
  10. package/esm/typings/src/cli/cli-commands/login.d.ts +0 -1
  11. package/esm/typings/src/cli/common/$provideLlmToolsForCli.d.ts +16 -3
  12. package/esm/typings/src/cli/test/ptbk.d.ts +1 -1
  13. package/esm/typings/src/commands/EXPECT/expectCommandParser.d.ts +2 -0
  14. package/esm/typings/src/config.d.ts +10 -19
  15. package/esm/typings/src/conversion/archive/loadArchive.d.ts +2 -2
  16. package/esm/typings/src/errors/0-index.d.ts +7 -4
  17. package/esm/typings/src/errors/PipelineExecutionError.d.ts +1 -1
  18. package/esm/typings/src/errors/WrappedError.d.ts +10 -0
  19. package/esm/typings/src/errors/assertsError.d.ts +11 -0
  20. package/esm/typings/src/execution/CommonToolsOptions.d.ts +4 -0
  21. package/esm/typings/src/execution/PromptbookFetch.d.ts +1 -1
  22. package/esm/typings/src/execution/createPipelineExecutor/getKnowledgeForTask.d.ts +12 -0
  23. package/esm/typings/src/execution/createPipelineExecutor/getReservedParametersForTask.d.ts +5 -0
  24. package/esm/typings/src/formats/csv/utils/csvParse.d.ts +12 -0
  25. package/esm/typings/src/formats/csv/utils/isValidCsvString.d.ts +9 -0
  26. package/esm/typings/src/formats/csv/utils/isValidCsvString.test.d.ts +1 -0
  27. package/esm/typings/src/formats/json/utils/isValidJsonString.d.ts +3 -0
  28. package/esm/typings/src/formats/json/utils/jsonParse.d.ts +11 -0
  29. package/esm/typings/src/formats/xml/utils/isValidXmlString.d.ts +9 -0
  30. package/esm/typings/src/formats/xml/utils/isValidXmlString.test.d.ts +1 -0
  31. package/esm/typings/src/llm-providers/_common/filterModels.d.ts +15 -0
  32. package/esm/typings/src/llm-providers/_common/register/{$provideEnvFilepath.d.ts → $provideEnvFilename.d.ts} +2 -2
  33. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsConfigurationFromEnv.d.ts +1 -1
  34. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForTestingAndScriptsAndPlayground.d.ts +1 -1
  35. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsForWizzardOrCli.d.ts +11 -2
  36. package/esm/typings/src/llm-providers/_common/register/$provideLlmToolsFromEnv.d.ts +1 -1
  37. package/esm/typings/src/llm-providers/_common/register/LlmToolsMetadata.d.ts +43 -0
  38. package/esm/typings/src/llm-providers/azure-openai/AzureOpenAiExecutionTools.d.ts +4 -0
  39. package/esm/typings/src/llm-providers/deepseek/deepseek-models.d.ts +23 -0
  40. package/esm/typings/src/llm-providers/google/google-models.d.ts +23 -0
  41. package/esm/typings/src/llm-providers/openai/OpenAiExecutionTools.d.ts +4 -0
  42. package/esm/typings/src/personas/preparePersona.d.ts +1 -1
  43. package/esm/typings/src/pipeline/PipelineJson/PersonaJson.d.ts +4 -2
  44. package/esm/typings/src/remote-server/openapi-types.d.ts +626 -0
  45. package/esm/typings/src/remote-server/openapi.d.ts +581 -0
  46. package/esm/typings/src/remote-server/socket-types/_subtypes/Identification.d.ts +7 -1
  47. package/esm/typings/src/remote-server/socket-types/_subtypes/identificationToPromptbookToken.d.ts +11 -0
  48. package/esm/typings/src/remote-server/socket-types/_subtypes/promptbookTokenToIdentification.d.ts +10 -0
  49. package/esm/typings/src/remote-server/startRemoteServer.d.ts +1 -2
  50. package/esm/typings/src/remote-server/types/RemoteServerOptions.d.ts +15 -9
  51. package/esm/typings/src/storage/env-storage/$EnvStorage.d.ts +40 -0
  52. package/esm/typings/src/types/typeAliases.d.ts +26 -0
  53. package/package.json +16 -11
  54. package/umd/index.umd.js +1835 -676
  55. package/umd/index.umd.js.map +1 -1
  56. package/esm/typings/src/cli/test/ptbk2.d.ts +0 -5
package/umd/index.umd.js CHANGED
@@ -1,8 +1,8 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('waitasecond'), require('prompts'), require('path'), require('fs/promises'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), require('socket.io-client'), require('rxjs'), require('dotenv'), require('child_process'), require('jszip'), require('prettier'), require('prettier/parser-html'), require('papaparse'), require('crypto-js'), require('mime-types'), require('glob-promise'), require('moment'), require('express'), require('http'), require('socket.io'), require('swagger-jsdoc'), require('swagger-ui-express'), require('@anthropic-ai/sdk'), require('@azure/openai'), require('openai'), require('@mozilla/readability'), require('jsdom'), require('showdown')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'waitasecond', 'prompts', 'path', 'fs/promises', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', 'socket.io-client', 'rxjs', 'dotenv', 'child_process', 'jszip', 'prettier', 'prettier/parser-html', 'papaparse', 'crypto-js', 'mime-types', 'glob-promise', 'moment', 'express', 'http', 'socket.io', 'swagger-jsdoc', 'swagger-ui-express', '@anthropic-ai/sdk', '@azure/openai', 'openai', '@mozilla/readability', 'jsdom', 'showdown'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global.spaceTrim, global.waitasecond, global.prompts, global.path, global.promises, global.hexEncoder, global.sha256, global.crypto, global.socket_ioClient, global.rxjs, global.dotenv, global.child_process, global.JSZip, global.prettier, global.parserHtml, global.papaparse, global.cryptoJs, global.mimeTypes, global.glob, global.moment, global.express, global.http, global.socket_io, global.swaggerJsdoc, global.swaggerUi, global.Anthropic, global.openai, global.OpenAI, global.readability, global.jsdom, global.showdown));
5
- })(this, (function (exports, colors, commander, spaceTrim, waitasecond, prompts, path, promises, hexEncoder, sha256, crypto, socket_ioClient, rxjs, dotenv, child_process, JSZip, prettier, parserHtml, papaparse, cryptoJs, mimeTypes, glob, moment, express, http, socket_io, swaggerJsdoc, swaggerUi, Anthropic, openai, OpenAI, readability, jsdom, showdown) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('colors'), require('commander'), require('spacetrim'), require('waitasecond'), require('prompts'), require('path'), require('fs/promises'), require('dotenv'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), require('socket.io-client'), require('rxjs'), require('child_process'), require('jszip'), require('prettier'), require('prettier/parser-html'), require('papaparse'), require('crypto-js'), require('mime-types'), require('glob-promise'), require('moment'), require('express'), require('http'), require('socket.io'), require('express-openapi-validator'), require('swagger-ui-express'), require('@anthropic-ai/sdk'), require('@azure/openai'), require('bottleneck'), require('openai'), require('@mozilla/readability'), require('jsdom'), require('showdown')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'colors', 'commander', 'spacetrim', 'waitasecond', 'prompts', 'path', 'fs/promises', 'dotenv', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', 'socket.io-client', 'rxjs', 'child_process', 'jszip', 'prettier', 'prettier/parser-html', 'papaparse', 'crypto-js', 'mime-types', 'glob-promise', 'moment', 'express', 'http', 'socket.io', 'express-openapi-validator', 'swagger-ui-express', '@anthropic-ai/sdk', '@azure/openai', 'bottleneck', 'openai', '@mozilla/readability', 'jsdom', 'showdown'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["promptbook-cli"] = {}, global.colors, global.commander, global.spaceTrim, global.waitasecond, global.prompts, global.path, global.promises, global.dotenv, global.hexEncoder, global.sha256, global.crypto, global.socket_ioClient, global.rxjs, global.child_process, global.JSZip, global.prettier, global.parserHtml, global.papaparse, global.cryptoJs, global.mimeTypes, global.glob, global.moment, global.express, global.http, global.socket_io, global.OpenApiValidator, global.swaggerUi, global.Anthropic, global.openai, global.Bottleneck, global.OpenAI, global.readability, global.jsdom, global.showdown));
5
+ })(this, (function (exports, colors, commander, spaceTrim, waitasecond, prompts, path, promises, dotenv, hexEncoder, sha256, crypto, socket_ioClient, rxjs, child_process, JSZip, prettier, parserHtml, papaparse, cryptoJs, mimeTypes, glob, moment, express, http, socket_io, OpenApiValidator, swaggerUi, Anthropic, openai, Bottleneck, OpenAI, readability, jsdom, showdown) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -28,18 +28,19 @@
28
28
  var commander__default = /*#__PURE__*/_interopDefaultLegacy(commander);
29
29
  var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim);
30
30
  var prompts__default = /*#__PURE__*/_interopDefaultLegacy(prompts);
31
+ var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
31
32
  var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
32
33
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
33
- var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
34
34
  var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
35
35
  var parserHtml__default = /*#__PURE__*/_interopDefaultLegacy(parserHtml);
36
36
  var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob);
37
37
  var moment__default = /*#__PURE__*/_interopDefaultLegacy(moment);
38
38
  var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
39
39
  var http__default = /*#__PURE__*/_interopDefaultLegacy(http);
40
- var swaggerJsdoc__default = /*#__PURE__*/_interopDefaultLegacy(swaggerJsdoc);
40
+ var OpenApiValidator__namespace = /*#__PURE__*/_interopNamespace(OpenApiValidator);
41
41
  var swaggerUi__default = /*#__PURE__*/_interopDefaultLegacy(swaggerUi);
42
42
  var Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
43
+ var Bottleneck__default = /*#__PURE__*/_interopDefaultLegacy(Bottleneck);
43
44
  var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
44
45
 
45
46
  // ⚠️ WARNING: This code has been generated so that any manual changes will be overwritten
@@ -56,12 +57,43 @@
56
57
  * @generated
57
58
  * @see https://github.com/webgptorg/promptbook
58
59
  */
59
- const PROMPTBOOK_ENGINE_VERSION = '0.89.0-9';
60
+ const PROMPTBOOK_ENGINE_VERSION = '0.92.0-10';
60
61
  /**
61
62
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
62
63
  * Note: [💞] Ignore a discrepancy between file name and entity name
63
64
  */
64
65
 
66
+ /**
67
+ * Available remote servers for the Promptbook
68
+ *
69
+ * @public exported from `@promptbook/core`
70
+ */
71
+ const REMOTE_SERVER_URLS = [
72
+ {
73
+ title: 'Promptbook',
74
+ description: `Servers of Promptbook.studio`,
75
+ owner: 'AI Web, LLC <legal@ptbk.io> (https://www.ptbk.io/)',
76
+ isAnonymousModeAllowed: true,
77
+ urls: [
78
+ 'https://promptbook.s5.ptbk.io/',
79
+ // Note: Servers 1-4 are not running
80
+ ],
81
+ },
82
+ /*
83
+ Note: Working on older version of Promptbook and not supported anymore
84
+ {
85
+ title: 'Pavol Promptbook Server',
86
+ description: `Personal server of Pavol Hejný with simple testing server, DO NOT USE IT FOR PRODUCTION`,
87
+ owner: 'Pavol Hejný <pavol@ptbk.io> (https://www.pavolhejny.com/)',
88
+ isAnonymousModeAllowed: true,
89
+ urls: ['https://api.pavolhejny.com/promptbook'],
90
+ },
91
+ */
92
+ ];
93
+ /**
94
+ * Note: [💞] Ignore a discrepancy between file name and entity name
95
+ */
96
+
65
97
  /**
66
98
  * Returns the same value that is passed as argument.
67
99
  * No side effects.
@@ -116,6 +148,7 @@
116
148
  * @public exported from `@promptbook/core`
117
149
  */
118
150
  const CLAIM = `It's time for a paradigm shift. The future of software in plain English, French or Latin`;
151
+ // <- TODO: [🐊] Pick the best claim
119
152
  /**
120
153
  * When the title is not provided, the default title is used
121
154
  *
@@ -146,6 +179,12 @@
146
179
  * @private within the repository
147
180
  */
148
181
  const GENERATOR_WARNING_BY_PROMPTBOOK_CLI = `⚠️ WARNING: This code has been generated by \`@promptbook/cli\` so that any manual changes will be overwritten`;
182
+ /**
183
+ * Warning message for the automatically generated sections of `.env` files
184
+ *
185
+ * @private within the repository
186
+ */
187
+ const GENERATOR_WARNING_IN_ENV = `Note: Added by Promptbook`;
149
188
  // <- TODO: [🧠] Better system for generator warnings - not always "code" and "by `@promptbook/cli`"
150
189
  /**
151
190
  * The maximum number of iterations for a loops
@@ -166,6 +205,7 @@
166
205
  infinity: '(infinity; ∞)',
167
206
  negativeInfinity: '(negative infinity; -∞)',
168
207
  unserializable: '(unserializable value)',
208
+ circular: '(circular JSON)',
169
209
  };
170
210
  /**
171
211
  * Small number limit
@@ -225,7 +265,7 @@
225
265
  */
226
266
  const DEFAULT_BOOKS_DIRNAME = './books';
227
267
  // <- TODO: [🕝] Make also `BOOKS_DIRNAME_ALTERNATIVES`
228
- // TODO: !!!!!! Just .promptbook dir, hardocode others
268
+ // TODO: Just `.promptbook` in config, hardcode subfolders like `download-cache` or `execution-cache`
229
269
  /**
230
270
  * Where to store the temporary downloads
231
271
  *
@@ -281,11 +321,11 @@
281
321
  ss: 3, // <- least number of seconds to be counted in seconds, minus 1. Must be set after setting the `s` unit or without setting the `s` unit.
282
322
  };
283
323
  /**
284
- * @@@
324
+ * Default remote server URL for the Promptbook
285
325
  *
286
326
  * @public exported from `@promptbook/core`
287
327
  */
288
- const DEFAULT_REMOTE_SERVER_URL = 'https://api.pavolhejny.com/promptbook';
328
+ const DEFAULT_REMOTE_SERVER_URL = REMOTE_SERVER_URLS[0].urls[0];
289
329
  // <- TODO: [🧜‍♂️]
290
330
  /**
291
331
  * @@@
@@ -419,6 +459,122 @@
419
459
  * TODO: [🎺]
420
460
  */
421
461
 
462
+ /**
463
+ * Make error report URL for the given error
464
+ *
465
+ * @private private within the repository
466
+ */
467
+ function getErrorReportUrl(error) {
468
+ const report = {
469
+ title: `🐜 Error report from ${NAME}`,
470
+ body: spaceTrim__default["default"]((block) => `
471
+
472
+
473
+ \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
474
+
475
+ \`\`\`
476
+ ${block(error.message || '(no error message)')}
477
+ \`\`\`
478
+
479
+
480
+ ## More info:
481
+
482
+ - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
483
+ - **Book language version:** ${BOOK_LANGUAGE_VERSION}
484
+ - **Time:** ${new Date().toISOString()}
485
+
486
+ <details>
487
+ <summary>Stack trace:</summary>
488
+
489
+ ## Stack trace:
490
+
491
+ \`\`\`stacktrace
492
+ ${block(error.stack || '(empty)')}
493
+ \`\`\`
494
+ </details>
495
+
496
+ `),
497
+ };
498
+ const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
499
+ reportUrl.searchParams.set('labels', 'bug');
500
+ reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
501
+ reportUrl.searchParams.set('title', report.title);
502
+ reportUrl.searchParams.set('body', report.body);
503
+ return reportUrl;
504
+ }
505
+
506
+ /**
507
+ * This error type indicates that the error should not happen and its last check before crashing with some other error
508
+ *
509
+ * @public exported from `@promptbook/core`
510
+ */
511
+ class UnexpectedError extends Error {
512
+ constructor(message) {
513
+ super(spaceTrim.spaceTrim((block) => `
514
+ ${block(message)}
515
+
516
+ Note: This error should not happen.
517
+ It's probbably a bug in the pipeline collection
518
+
519
+ Please report issue:
520
+ ${block(getErrorReportUrl(new Error(message)).href)}
521
+
522
+ Or contact us on ${ADMIN_EMAIL}
523
+
524
+ `));
525
+ this.name = 'UnexpectedError';
526
+ Object.setPrototypeOf(this, UnexpectedError.prototype);
527
+ }
528
+ }
529
+
530
+ /**
531
+ * This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
532
+ *
533
+ * @public exported from `@promptbook/core`
534
+ */
535
+ class WrappedError extends Error {
536
+ constructor(whatWasThrown) {
537
+ const tag = `[🤮]`;
538
+ console.error(tag, whatWasThrown);
539
+ super(spaceTrim.spaceTrim(`
540
+ Non-Error object was thrown
541
+
542
+ Note: Look for ${tag} in the console for more details
543
+ Please report issue on ${ADMIN_EMAIL}
544
+ `));
545
+ this.name = 'WrappedError';
546
+ Object.setPrototypeOf(this, WrappedError.prototype);
547
+ }
548
+ }
549
+
550
+ /**
551
+ * Helper used in catch blocks to assert that the error is an instance of `Error`
552
+ *
553
+ * @param whatWasThrown Any object that was thrown
554
+ * @returns Nothing if the error is an instance of `Error`
555
+ * @throws `WrappedError` or `UnexpectedError` if the error is not standard
556
+ *
557
+ * @private within the repository
558
+ */
559
+ function assertsError(whatWasThrown) {
560
+ // Case 1: Handle error which was rethrown as `WrappedError`
561
+ if (whatWasThrown instanceof WrappedError) {
562
+ const wrappedError = whatWasThrown;
563
+ throw wrappedError;
564
+ }
565
+ // Case 2: Handle unexpected errors
566
+ if (whatWasThrown instanceof UnexpectedError) {
567
+ const unexpectedError = whatWasThrown;
568
+ throw unexpectedError;
569
+ }
570
+ // Case 3: Handle standard errors - keep them up to consumer
571
+ if (whatWasThrown instanceof Error) {
572
+ return;
573
+ }
574
+ // Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
575
+ throw new WrappedError(whatWasThrown);
576
+ }
577
+
422
578
  /**
423
579
  * Wraps action to handle error console logging and exit process with error code
424
580
  *
@@ -433,9 +589,7 @@
433
589
  return process.exit(0);
434
590
  }
435
591
  catch (error) {
436
- if (!(error instanceof Error)) {
437
- throw error;
438
- }
592
+ assertsError(error);
439
593
  // console.error(colors.bgRed(error.name));
440
594
  console.error(colors__default["default"].red(/* error.stack || */ error.message));
441
595
  return process.exit(1);
@@ -542,74 +696,6 @@
542
696
  }
543
697
  }
544
698
 
545
- /**
546
- * Make error report URL for the given error
547
- *
548
- * @private private within the repository
549
- */
550
- function getErrorReportUrl(error) {
551
- const report = {
552
- title: `🐜 Error report from ${NAME}`,
553
- body: spaceTrim__default["default"]((block) => `
554
-
555
-
556
- \`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
557
-
558
- \`\`\`
559
- ${block(error.message || '(no error message)')}
560
- \`\`\`
561
-
562
-
563
- ## More info:
564
-
565
- - **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
566
- - **Book language version:** ${BOOK_LANGUAGE_VERSION}
567
- - **Time:** ${new Date().toISOString()}
568
-
569
- <details>
570
- <summary>Stack trace:</summary>
571
-
572
- ## Stack trace:
573
-
574
- \`\`\`stacktrace
575
- ${block(error.stack || '(empty)')}
576
- \`\`\`
577
- </details>
578
-
579
- `),
580
- };
581
- const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
582
- reportUrl.searchParams.set('labels', 'bug');
583
- reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
584
- reportUrl.searchParams.set('title', report.title);
585
- reportUrl.searchParams.set('body', report.body);
586
- return reportUrl;
587
- }
588
-
589
- /**
590
- * This error type indicates that the error should not happen and its last check before crashing with some other error
591
- *
592
- * @public exported from `@promptbook/core`
593
- */
594
- class UnexpectedError extends Error {
595
- constructor(message) {
596
- super(spaceTrim.spaceTrim((block) => `
597
- ${block(message)}
598
-
599
- Note: This error should not happen.
600
- It's probbably a bug in the pipeline collection
601
-
602
- Please report issue:
603
- ${block(getErrorReportUrl(new Error(message)).href)}
604
-
605
- Or contact us on ${ADMIN_EMAIL}
606
-
607
- `));
608
- this.name = 'UnexpectedError';
609
- Object.setPrototypeOf(this, UnexpectedError.prototype);
610
- }
611
- }
612
-
613
699
  /**
614
700
  * @@@
615
701
  *
@@ -929,6 +1015,79 @@
929
1015
  keepUnused(...sideEffectSubjects);
930
1016
  }
931
1017
 
1018
+ /**
1019
+ * Converts a JavaScript Object Notation (JSON) string into an object.
1020
+ *
1021
+ * Note: This is wrapper around `JSON.parse()` with better error and type handling
1022
+ *
1023
+ * @public exported from `@promptbook/utils`
1024
+ */
1025
+ function jsonParse(value) {
1026
+ if (value === undefined) {
1027
+ throw new Error(`Can not parse JSON from undefined value.`);
1028
+ }
1029
+ else if (typeof value !== 'string') {
1030
+ console.error('Can not parse JSON from non-string value.', { text: value });
1031
+ throw new Error(spaceTrim__default["default"](`
1032
+ Can not parse JSON from non-string value.
1033
+
1034
+ The value type: ${typeof value}
1035
+ See more in console.
1036
+ `));
1037
+ }
1038
+ try {
1039
+ return JSON.parse(value);
1040
+ }
1041
+ catch (error) {
1042
+ if (!(error instanceof Error)) {
1043
+ throw error;
1044
+ }
1045
+ throw new Error(spaceTrim__default["default"]((block) => `
1046
+ ${block(error.message)}
1047
+
1048
+ The JSON text:
1049
+ ${block(value)}
1050
+ `));
1051
+ }
1052
+ }
1053
+ /**
1054
+ * TODO: !!!! Use in Promptbook.studio
1055
+ */
1056
+
1057
+ /**
1058
+ * Convert identification to Promptbook token
1059
+ *
1060
+ * @param identification
1061
+ *
1062
+ * @public exported from `@promptbook/core`
1063
+ */
1064
+ function identificationToPromptbookToken(identification) {
1065
+ const { appId, userId, userToken } = identification;
1066
+ const promptbookToken = `${appId}-${userId}-${userToken}`;
1067
+ return promptbookToken;
1068
+ }
1069
+
1070
+ /**
1071
+ * Convert Promptbook token to identification
1072
+ *
1073
+ * @param promptbookToken
1074
+ *
1075
+ * @public exported from `@promptbook/core`
1076
+ */
1077
+ function promptbookTokenToIdentification(promptbookToken) {
1078
+ const [appId, userId, userToken] = promptbookToken.split('-');
1079
+ if (!appId || !userId || !userToken) {
1080
+ throw new Error(`Invalid promptbook token: ${promptbookToken}`);
1081
+ }
1082
+ const identification = {
1083
+ appId,
1084
+ userId,
1085
+ userToken,
1086
+ isAnonymous: false,
1087
+ };
1088
+ return identification;
1089
+ }
1090
+
932
1091
  /**
933
1092
  * Just marks a place of place where should be something implemented
934
1093
  * No side effects.
@@ -942,27 +1101,199 @@
942
1101
  function TODO_USE(...value) {
943
1102
  }
944
1103
 
945
- /**
946
- * @@@
947
- *
948
- * @public exported from `@promptbook/node`
949
- */
950
- function $provideFilesystemForNode(options) {
951
- if (!$isRunningInNode()) {
952
- throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
1104
+ /**
1105
+ * @@@
1106
+ *
1107
+ * @public exported from `@promptbook/node`
1108
+ */
1109
+ function $provideFilesystemForNode(options) {
1110
+ if (!$isRunningInNode()) {
1111
+ throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
1112
+ }
1113
+ return {
1114
+ stat: promises.stat,
1115
+ access: promises.access,
1116
+ constants: promises.constants,
1117
+ readFile: promises.readFile,
1118
+ writeFile: promises.writeFile,
1119
+ readdir: promises.readdir,
1120
+ mkdir: promises.mkdir,
1121
+ };
1122
+ }
1123
+ /**
1124
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
1125
+ */
1126
+
1127
+ /**
1128
+ * Checks if the file exists
1129
+ *
1130
+ * @private within the repository
1131
+ */
1132
+ async function isFileExisting(filename, fs) {
1133
+ const isReadAccessAllowed = await fs
1134
+ .access(filename, fs.constants.R_OK)
1135
+ .then(() => true)
1136
+ .catch(() => false);
1137
+ if (!isReadAccessAllowed) {
1138
+ return false;
1139
+ }
1140
+ const isFile = await fs
1141
+ .stat(filename)
1142
+ .then((fileStat) => fileStat.isFile())
1143
+ .catch(() => false);
1144
+ return isFile;
1145
+ }
1146
+ /**
1147
+ * Note: Not [~🟢~] because it is not directly dependent on `fs
1148
+ * TODO: [🐠] This can be a validator - with variants that return true/false and variants that throw errors with meaningless messages
1149
+ * TODO: [🖇] What about symlinks?
1150
+ */
1151
+
1152
+ /**
1153
+ * Determines if the given path is a root path.
1154
+ *
1155
+ * Note: This does not check if the file exists only if the path is valid
1156
+ * @public exported from `@promptbook/utils`
1157
+ */
1158
+ function isRootPath(value) {
1159
+ if (value === '/') {
1160
+ return true;
1161
+ }
1162
+ if (/^[A-Z]:\\$/i.test(value)) {
1163
+ return true;
1164
+ }
1165
+ return false;
1166
+ }
1167
+ /**
1168
+ * TODO: [🍏] Make for MacOS paths
1169
+ */
1170
+
1171
+ /**
1172
+ * Provides the path to the `.env` file
1173
+ *
1174
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
1175
+ *
1176
+ * @private within the repository - for CLI utils
1177
+ */
1178
+ async function $provideEnvFilename() {
1179
+ if (!$isRunningInNode()) {
1180
+ throw new EnvironmentMismatchError('Function `$provideEnvFilename` works only in Node.js environment');
1181
+ }
1182
+ const envFilePatterns = [
1183
+ '.env',
1184
+ '.env.test',
1185
+ '.env.local',
1186
+ '.env.development.local',
1187
+ '.env.development',
1188
+ '.env.production.local',
1189
+ '.env.production',
1190
+ '.env.prod.local',
1191
+ '.env.prod',
1192
+ // <- TODO: Maybe add more patterns
1193
+ ];
1194
+ let rootDirname = process.cwd();
1195
+ up_to_root: for (let i = 0; i < LOOP_LIMIT; i++) {
1196
+ for (const pattern of envFilePatterns) {
1197
+ const envFilename = path.join(rootDirname, pattern);
1198
+ if (await isFileExisting(envFilename, $provideFilesystemForNode())) {
1199
+ $setUsedEnvFilename(envFilename);
1200
+ return envFilename;
1201
+ }
1202
+ }
1203
+ if (isRootPath(rootDirname)) {
1204
+ break up_to_root;
1205
+ }
1206
+ // Note: If the directory does not exist, try the parent directory
1207
+ rootDirname = path.join(rootDirname, '..');
1208
+ }
1209
+ return null;
1210
+ }
1211
+ /**
1212
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
1213
+ */
1214
+
1215
+ /**
1216
+ * Stores data in .env variables
1217
+ *
1218
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file and also writes to `process.env`
1219
+ *
1220
+ * @private within the repository - for CLI utils
1221
+ */
1222
+ class $EnvStorage {
1223
+ constructor() {
1224
+ this.envFilename = null;
1225
+ }
1226
+ async $provideOrCreateEnvFile() {
1227
+ if (this.envFilename !== null) {
1228
+ return this.envFilename;
1229
+ }
1230
+ let envFilename = await $provideEnvFilename();
1231
+ if (envFilename !== null) {
1232
+ this.envFilename = envFilename;
1233
+ return envFilename;
1234
+ }
1235
+ envFilename = path.join(process.cwd(), '.env');
1236
+ await promises.writeFile(envFilename, '# This file was initialized by Promptbook', 'utf-8');
1237
+ this.envFilename = envFilename;
1238
+ return envFilename;
1239
+ }
1240
+ transformKey(key) {
1241
+ return normalizeTo_SCREAMING_CASE(key);
1242
+ }
1243
+ /**
1244
+ * Returns the number of key/value pairs currently present in the list associated with the object.
1245
+ */
1246
+ get length() {
1247
+ throw new NotYetImplementedError('Method `$EnvStorage.length` not implemented.');
1248
+ }
1249
+ /**
1250
+ * Empties the list associated with the object of all key/value pairs, if there are any.
1251
+ */
1252
+ clear() {
1253
+ throw new NotYetImplementedError('Method `$EnvStorage.clear` not implemented.');
1254
+ }
1255
+ /**
1256
+ * Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
1257
+ */
1258
+ async getItem(key) {
1259
+ dotenv__namespace.config({ path: await this.$provideOrCreateEnvFile() });
1260
+ return process.env[this.transformKey(key)] || null;
1261
+ }
1262
+ /**
1263
+ * Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object.
1264
+ */
1265
+ key(index) {
1266
+ throw new NotYetImplementedError('Method `$EnvStorage.key` not implemented.');
1267
+ }
1268
+ /**
1269
+ * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
1270
+ */
1271
+ async setItem(key, value) {
1272
+ const envFilename = await this.$provideOrCreateEnvFile();
1273
+ const envContent = await promises.readFile(envFilename, 'utf-8');
1274
+ const transformedKey = this.transformKey(key);
1275
+ const updatedEnvContent = envContent
1276
+ .split('\n')
1277
+ .filter((line) => !line.startsWith(`# ${GENERATOR_WARNING_IN_ENV}`)) // Remove GENERATOR_WARNING_IN_ENV
1278
+ .filter((line) => !line.startsWith(`${transformedKey}=`)) // Remove existing key if present
1279
+ .join('\n');
1280
+ const newEnvContent = spaceTrim__default["default"]((block) => `
1281
+ ${block(updatedEnvContent)}
1282
+
1283
+ # ${GENERATOR_WARNING_IN_ENV}
1284
+ ${transformedKey}=${JSON.stringify(value)}
1285
+ `);
1286
+ await promises.writeFile(envFilename, newEnvContent, 'utf-8');
1287
+ }
1288
+ /**
1289
+ * Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists.
1290
+ */
1291
+ removeItem(key) {
1292
+ throw new NotYetImplementedError('Method `$EnvStorage.removeItem` not implemented.');
953
1293
  }
954
- return {
955
- stat: promises.stat,
956
- access: promises.access,
957
- constants: promises.constants,
958
- readFile: promises.readFile,
959
- writeFile: promises.writeFile,
960
- readdir: promises.readdir,
961
- mkdir: promises.mkdir,
962
- };
963
1294
  }
964
1295
  /**
965
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
1296
+ * TODO: Write file more securely - ensure that there can be no accidental overwriting of existing variables and other content
966
1297
  */
967
1298
 
968
1299
  /**
@@ -1098,9 +1429,7 @@
1098
1429
  JSON.stringify(value); // <- TODO: [0]
1099
1430
  }
1100
1431
  catch (error) {
1101
- if (!(error instanceof Error)) {
1102
- throw error;
1103
- }
1432
+ assertsError(error);
1104
1433
  throw new UnexpectedError(spaceTrim__default["default"]((block) => `
1105
1434
  \`${name}\` is not serializable
1106
1435
 
@@ -1333,31 +1662,6 @@
1333
1662
  * TODO: [🍙] Make some standard order of json properties
1334
1663
  */
1335
1664
 
1336
- /**
1337
- * Checks if the file exists
1338
- *
1339
- * @private within the repository
1340
- */
1341
- async function isFileExisting(filename, fs) {
1342
- const isReadAccessAllowed = await fs
1343
- .access(filename, fs.constants.R_OK)
1344
- .then(() => true)
1345
- .catch(() => false);
1346
- if (!isReadAccessAllowed) {
1347
- return false;
1348
- }
1349
- const isFile = await fs
1350
- .stat(filename)
1351
- .then((fileStat) => fileStat.isFile())
1352
- .catch(() => false);
1353
- return isFile;
1354
- }
1355
- /**
1356
- * Note: Not [~🟢~] because it is not directly dependent on `fs
1357
- * TODO: [🐠] This can be a validator - with variants that return true/false and variants that throw errors with meaningless messages
1358
- * TODO: [🖇] What about symlinks?
1359
- */
1360
-
1361
1665
  /**
1362
1666
  * Removes emojis from a string and fix whitespaces
1363
1667
  *
@@ -1831,7 +2135,7 @@
1831
2135
  return null;
1832
2136
  }
1833
2137
  const fileContent = await promises.readFile(filename, 'utf-8');
1834
- const value = JSON.parse(fileContent);
2138
+ const value = jsonParse(fileContent);
1835
2139
  // TODO: [🌗]
1836
2140
  return value;
1837
2141
  }
@@ -1863,53 +2167,6 @@
1863
2167
  * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
1864
2168
  */
1865
2169
 
1866
- /**
1867
- * Stores data in memory (HEAP)
1868
- *
1869
- * @public exported from `@promptbook/core`
1870
- */
1871
- class MemoryStorage {
1872
- constructor() {
1873
- this.storage = {};
1874
- }
1875
- /**
1876
- * Returns the number of key/value pairs currently present in the list associated with the object.
1877
- */
1878
- get length() {
1879
- return Object.keys(this.storage).length;
1880
- }
1881
- /**
1882
- * Empties the list associated with the object of all key/value pairs, if there are any.
1883
- */
1884
- clear() {
1885
- this.storage = {};
1886
- }
1887
- /**
1888
- * Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
1889
- */
1890
- getItem(key) {
1891
- return this.storage[key] || null;
1892
- }
1893
- /**
1894
- * Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object.
1895
- */
1896
- key(index) {
1897
- return Object.keys(this.storage)[index] || null;
1898
- }
1899
- /**
1900
- * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
1901
- */
1902
- setItem(key, value) {
1903
- this.storage[key] = value;
1904
- }
1905
- /**
1906
- * Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists.
1907
- */
1908
- removeItem(key) {
1909
- delete this.storage[key];
1910
- }
1911
- }
1912
-
1913
2170
  /**
1914
2171
  * This error indicates problems parsing the format value
1915
2172
  *
@@ -2086,7 +2343,7 @@
2086
2343
  }
2087
2344
  }
2088
2345
  /**
2089
- * TODO: !!!!!! Add id to all errors
2346
+ * TODO: [🧠][🌂] Add id to all errors
2090
2347
  */
2091
2348
 
2092
2349
  /**
@@ -2148,7 +2405,10 @@
2148
2405
  PipelineExecutionError,
2149
2406
  PipelineLogicError,
2150
2407
  PipelineUrlError,
2408
+ AuthenticationError,
2409
+ PromptbookFetchError,
2151
2410
  UnexpectedError,
2411
+ WrappedError,
2152
2412
  // TODO: [🪑]> VersionMismatchError,
2153
2413
  };
2154
2414
  /**
@@ -2165,8 +2425,6 @@
2165
2425
  TypeError,
2166
2426
  URIError,
2167
2427
  AggregateError,
2168
- AuthenticationError,
2169
- PromptbookFetchError,
2170
2428
  /*
2171
2429
  Note: Not widely supported
2172
2430
  > InternalError,
@@ -2224,17 +2482,31 @@
2224
2482
  */
2225
2483
  async function createRemoteClient(options) {
2226
2484
  const { remoteServerUrl } = options;
2227
- let path = new URL(remoteServerUrl).pathname;
2228
- if (path.endsWith('/')) {
2229
- path = path.slice(0, -1);
2485
+ if (!isValidUrl(remoteServerUrl)) {
2486
+ throw new Error(`Invalid \`remoteServerUrl\`: "${remoteServerUrl}"`);
2487
+ }
2488
+ const remoteServerUrlParsed = new URL(remoteServerUrl);
2489
+ if (remoteServerUrlParsed.pathname !== '/' && remoteServerUrlParsed.pathname !== '') {
2490
+ remoteServerUrlParsed.pathname = '/';
2491
+ throw new Error(spaceTrim__default["default"]((block) => `
2492
+ Remote server requires root url \`/\`
2493
+
2494
+ You have provided \`remoteServerUrl\`:
2495
+ ${block(remoteServerUrl)}
2496
+
2497
+ But something like this is expected:
2498
+ ${block(remoteServerUrlParsed.href)}
2499
+
2500
+ Note: If you need to run multiple services on the same server, use 3rd or 4th degree subdomain
2501
+
2502
+ `));
2230
2503
  }
2231
- path = `${path}/socket.io`;
2232
2504
  return new Promise((resolve, reject) => {
2233
2505
  const socket = socket_ioClient.io(remoteServerUrl, {
2234
2506
  retries: CONNECTION_RETRIES_LIMIT,
2235
2507
  timeout: CONNECTION_TIMEOUT_MS,
2236
- path,
2237
- transports: [/*'websocket', <- TODO: [🌬] Make websocket transport work */ 'polling'],
2508
+ path: '/socket.io',
2509
+ transports: ['polling', 'websocket' /*, <- TODO: [🌬] Allow to pass `transports`, add 'webtransport' */],
2238
2510
  });
2239
2511
  // console.log('Connecting to', this.options.remoteServerUrl.href, { socket });
2240
2512
  socket.on('connect', () => {
@@ -2359,6 +2631,53 @@
2359
2631
  * TODO: [🧠] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
2360
2632
  */
2361
2633
 
2634
+ /**
2635
+ * Stores data in memory (HEAP)
2636
+ *
2637
+ * @public exported from `@promptbook/core`
2638
+ */
2639
+ class MemoryStorage {
2640
+ constructor() {
2641
+ this.storage = {};
2642
+ }
2643
+ /**
2644
+ * Returns the number of key/value pairs currently present in the list associated with the object.
2645
+ */
2646
+ get length() {
2647
+ return Object.keys(this.storage).length;
2648
+ }
2649
+ /**
2650
+ * Empties the list associated with the object of all key/value pairs, if there are any.
2651
+ */
2652
+ clear() {
2653
+ this.storage = {};
2654
+ }
2655
+ /**
2656
+ * Returns the current value associated with the given key, or null if the given key does not exist in the list associated with the object.
2657
+ */
2658
+ getItem(key) {
2659
+ return this.storage[key] || null;
2660
+ }
2661
+ /**
2662
+ * Returns the name of the nth key in the list, or null if n is greater than or equal to the number of key/value pairs in the object.
2663
+ */
2664
+ key(index) {
2665
+ return Object.keys(this.storage)[index] || null;
2666
+ }
2667
+ /**
2668
+ * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
2669
+ */
2670
+ setItem(key, value) {
2671
+ this.storage[key] = value;
2672
+ }
2673
+ /**
2674
+ * Removes the key/value pair with the given key from the list associated with the object, if a key/value pair with the given key exists.
2675
+ */
2676
+ removeItem(key) {
2677
+ delete this.storage[key];
2678
+ }
2679
+ }
2680
+
2362
2681
  /**
2363
2682
  * Simple wrapper `new Date().toISOString()`
2364
2683
  *
@@ -2645,74 +2964,11 @@
2645
2964
  * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
2646
2965
  */
2647
2966
 
2648
- /**
2649
- * Determines if the given path is a root path.
2650
- *
2651
- * Note: This does not check if the file exists only if the path is valid
2652
- * @public exported from `@promptbook/utils`
2653
- */
2654
- function isRootPath(value) {
2655
- if (value === '/') {
2656
- return true;
2657
- }
2658
- if (/^[A-Z]:\\$/i.test(value)) {
2659
- return true;
2660
- }
2661
- return false;
2662
- }
2663
- /**
2664
- * TODO: [🍏] Make for MacOS paths
2665
- */
2666
-
2667
- /**
2668
- * Provides the path to the `.env` file
2669
- *
2670
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access .env file
2671
- *
2672
- * @private within the repository - for CLI utils
2673
- */
2674
- async function $provideEnvFilepath() {
2675
- if (!$isRunningInNode()) {
2676
- throw new EnvironmentMismatchError('Function `$provideEnvFilepath` works only in Node.js environment');
2677
- }
2678
- const envFilePatterns = [
2679
- '.env',
2680
- '.env.test',
2681
- '.env.local',
2682
- '.env.development.local',
2683
- '.env.development',
2684
- '.env.production.local',
2685
- '.env.production',
2686
- '.env.prod.local',
2687
- '.env.prod',
2688
- // <- TODO: Maybe add more patterns
2689
- ];
2690
- let rootDirname = process.cwd();
2691
- up_to_root: for (let i = 0; i < LOOP_LIMIT; i++) {
2692
- for (const pattern of envFilePatterns) {
2693
- const envFilename = path.join(rootDirname, pattern);
2694
- if (await isFileExisting(envFilename, $provideFilesystemForNode())) {
2695
- $setUsedEnvFilename(envFilename);
2696
- return envFilename;
2697
- }
2698
- }
2699
- if (isRootPath(rootDirname)) {
2700
- break up_to_root;
2701
- }
2702
- // Note: If the directory does not exist, try the parent directory
2703
- rootDirname = path.join(rootDirname, '..');
2704
- }
2705
- return null;
2706
- }
2707
- /**
2708
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
2709
- */
2710
-
2711
2967
  /**
2712
2968
  * @@@
2713
2969
  *
2714
2970
  * @@@ .env
2715
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access .env file
2971
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
2716
2972
  *
2717
2973
  * It looks for environment variables:
2718
2974
  * - `process.env.OPENAI_API_KEY`
@@ -2726,7 +2982,7 @@
2726
2982
  if (!$isRunningInNode()) {
2727
2983
  throw new EnvironmentMismatchError('Function `$provideLlmToolsFromEnv` works only in Node.js environment');
2728
2984
  }
2729
- const envFilepath = await $provideEnvFilepath();
2985
+ const envFilepath = await $provideEnvFilename();
2730
2986
  if (envFilepath !== null) {
2731
2987
  dotenv__namespace.config({ path: envFilepath });
2732
2988
  }
@@ -2834,14 +3090,15 @@
2834
3090
  }
2835
3091
  }
2836
3092
  catch (error) {
2837
- if (!(error instanceof Error) || error instanceof UnexpectedError) {
3093
+ assertsError(error);
3094
+ if (error instanceof UnexpectedError) {
2838
3095
  throw error;
2839
3096
  }
2840
3097
  errors.push({ llmExecutionTools, error });
2841
3098
  }
2842
3099
  }
2843
3100
  if (errors.length === 1) {
2844
- throw errors[0];
3101
+ throw errors[0].error;
2845
3102
  }
2846
3103
  else if (errors.length > 1) {
2847
3104
  throw new PipelineExecutionError(
@@ -2988,7 +3245,7 @@
2988
3245
  * Note: This function is not cached, every call creates new instance of `MultipleLlmExecutionTools`
2989
3246
  *
2990
3247
  * @@@ .env
2991
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access .env file
3248
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file
2992
3249
  *
2993
3250
  * It looks for environment variables:
2994
3251
  * - `process.env.OPENAI_API_KEY`
@@ -3033,7 +3290,7 @@
3033
3290
  /**
3034
3291
  * Returns LLM tools for CLI
3035
3292
  *
3036
- * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access .env file and also writes this .env file
3293
+ * Note: `$` is used to indicate that this function is not a pure function - it uses filesystem to access `.env` file and also writes this .env file
3037
3294
  *
3038
3295
  * @private within the repository - for CLI utils
3039
3296
  */
@@ -3042,18 +3299,27 @@
3042
3299
  throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizzardOrCli` works only in Node.js environment');
3043
3300
  }
3044
3301
  options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
3045
- const { strategy, isCacheReloaded } = options;
3302
+ const { isLoginloaded, strategy, isCacheReloaded } = options;
3046
3303
  let llmExecutionTools;
3047
3304
  if (strategy === 'REMOTE_SERVER') {
3048
3305
  const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
3049
- // TODO: !!!
3050
- // const envFilepath = await $provideEnvFilepath();
3051
- const storage = new MemoryStorage(); // <- TODO: !!!!!! Save to `.promptbook` folder
3052
- const key = `${remoteServerUrl}-identification`;
3053
- let identification = await storage.getItem(key);
3054
- if (identification === null) {
3306
+ const storage = new $EnvStorage();
3307
+ let key = `PROMPTBOOK_TOKEN`;
3308
+ if (remoteServerUrl !== DEFAULT_REMOTE_SERVER_URL) {
3309
+ key = `${key}_${remoteServerUrl.replace(/^https?:\/\//i, '')}`;
3310
+ }
3311
+ let identification = null;
3312
+ let promptbookToken = await storage.getItem(key);
3313
+ if (promptbookToken === null || isLoginloaded) {
3055
3314
  identification = await loginPrompt();
3056
- await storage.setItem(key, identification);
3315
+ // Note: When login prompt fails, `process.exit(1)` is called so no need to check for null
3316
+ if (identification.isAnonymous === false) {
3317
+ promptbookToken = identificationToPromptbookToken(identification);
3318
+ await storage.setItem(key, promptbookToken);
3319
+ }
3320
+ }
3321
+ else {
3322
+ identification = promptbookTokenToIdentification(promptbookToken);
3057
3323
  }
3058
3324
  llmExecutionTools = new RemoteLlmExecutionTools({
3059
3325
  remoteServerUrl,
@@ -3094,9 +3360,7 @@
3094
3360
  return await fetch(urlOrRequest, init);
3095
3361
  }
3096
3362
  catch (error) {
3097
- if (!(error instanceof Error)) {
3098
- throw error;
3099
- }
3363
+ assertsError(error);
3100
3364
  let url;
3101
3365
  if (typeof urlOrRequest === 'string') {
3102
3366
  url = urlOrRequest;
@@ -3135,8 +3399,8 @@
3135
3399
  /**
3136
3400
  * @private utility of CLI
3137
3401
  */
3138
- function $provideLlmToolsForCli(options) {
3139
- const { cliOptions: {
3402
+ async function $provideLlmToolsForCli(options) {
3403
+ const { isLoginloaded, cliOptions: {
3140
3404
  /* TODO: Use verbose: isVerbose, */ interactive: isInteractive, provider, remoteServerUrl: remoteServerUrlRaw, }, } = options;
3141
3405
  let strategy;
3142
3406
  if (/^b/i.test(provider)) {
@@ -3150,7 +3414,11 @@
3150
3414
  process.exit(1);
3151
3415
  }
3152
3416
  if (strategy === 'BRING_YOUR_OWN_KEYS') {
3153
- return /* not await */ $provideLlmToolsForWizzardOrCli({ strategy, ...options });
3417
+ if (isLoginloaded) {
3418
+ throw new UnexpectedError(`\`$provideLlmToolsForCli\` isLoginloaded is not supported for strategy "BRING_YOUR_OWN_KEYS"`);
3419
+ }
3420
+ const llm = await $provideLlmToolsForWizzardOrCli({ strategy, ...options });
3421
+ return { strategy, llm };
3154
3422
  }
3155
3423
  else if (strategy === 'REMOTE_SERVER') {
3156
3424
  if (!isValidUrl(remoteServerUrlRaw)) {
@@ -3158,7 +3426,8 @@
3158
3426
  process.exit(1);
3159
3427
  }
3160
3428
  const remoteServerUrl = remoteServerUrlRaw.endsWith('/') ? remoteServerUrlRaw.slice(0, -1) : remoteServerUrlRaw;
3161
- return /* not await */ $provideLlmToolsForWizzardOrCli({
3429
+ const llm = await $provideLlmToolsForWizzardOrCli({
3430
+ isLoginloaded,
3162
3431
  strategy,
3163
3432
  appId: CLI_APP_ID,
3164
3433
  remoteServerUrl,
@@ -3168,6 +3437,10 @@
3168
3437
  console.log(colors__default["default"].red(`You can not login to remote server in non-interactive mode`));
3169
3438
  process.exit(1);
3170
3439
  }
3440
+ console.info(colors__default["default"].cyan(spaceTrim__default["default"](`
3441
+ You will be logged in to ${remoteServerUrl}
3442
+ If you don't have an account, it will be created automatically.
3443
+ `)));
3171
3444
  const { username, password } = await prompts__default["default"]([
3172
3445
  {
3173
3446
  type: 'text',
@@ -3185,7 +3458,6 @@
3185
3458
  },
3186
3459
  ]);
3187
3460
  const loginUrl = `${remoteServerUrl}/login`;
3188
- console.log('!!!', { loginUrl });
3189
3461
  // TODO: [🧠] Should we use normal `fetch` or `scraperFetch`
3190
3462
  const response = await promptbookFetch(loginUrl, {
3191
3463
  method: 'POST',
@@ -3198,20 +3470,7 @@
3198
3470
  password,
3199
3471
  }),
3200
3472
  });
3201
- console.log('!!!', {
3202
- loginUrl,
3203
- username,
3204
- password,
3205
- // type: response.type,
3206
- // text: await response.text(),
3207
- });
3208
- const { isSuccess, message, error, identification } = (await response.json());
3209
- console.log('!!!', {
3210
- isSuccess,
3211
- message,
3212
- error,
3213
- identification,
3214
- });
3473
+ const { isSuccess, message, error, identification } = jsonParse(await response.text());
3215
3474
  if (message) {
3216
3475
  if (isSuccess) {
3217
3476
  console.log(colors__default["default"].green(message));
@@ -3232,6 +3491,7 @@
3232
3491
  return identification;
3233
3492
  },
3234
3493
  });
3494
+ return { strategy, llm };
3235
3495
  }
3236
3496
  else {
3237
3497
  throw new UnexpectedError(`\`$provideLlmToolsForCli\` wrong strategy "${strategy}"`);
@@ -3253,11 +3513,12 @@
3253
3513
  listModelsCommand.alias('models');
3254
3514
  listModelsCommand.alias('llm');
3255
3515
  listModelsCommand.action(handleActionErrors(async (cliOptions) => {
3256
- console.log('!!!', cliOptions);
3257
- // TODO: !!!!!! Not relevant for remote server and also for `about` command
3258
- const llm = await $provideLlmToolsForCli({ cliOptions });
3516
+ const { strategy, llm } = await $provideLlmToolsForCli({ cliOptions });
3259
3517
  $sideEffect(llm);
3260
3518
  // <- Note: Providing LLM tools will make a side effect of registering all available LLM tools to show the message
3519
+ if (strategy !== 'BRING_YOUR_OWN_KEYS') {
3520
+ console.warn(colors__default["default"].yellow(`You are using --strategy ${strategy} but models listed below are relevant for --strategy BRING_YOUR_OWN_KEYS`));
3521
+ }
3261
3522
  console.info($registeredLlmToolsMessage());
3262
3523
  return process.exit(0);
3263
3524
  }));
@@ -3440,9 +3701,7 @@
3440
3701
  return result.trim();
3441
3702
  }
3442
3703
  catch (error) {
3443
- if (!(error instanceof Error)) {
3444
- throw error;
3445
- }
3704
+ assertsError(error);
3446
3705
  return null;
3447
3706
  }
3448
3707
  }
@@ -3497,9 +3756,7 @@
3497
3756
  return result.trim() + toExec;
3498
3757
  }
3499
3758
  catch (error) {
3500
- if (!(error instanceof Error)) {
3501
- throw error;
3502
- }
3759
+ assertsError(error);
3503
3760
  return null;
3504
3761
  }
3505
3762
  }
@@ -3530,9 +3787,7 @@
3530
3787
  throw new Error(`Can not locate app ${appName} on Windows.`);
3531
3788
  }
3532
3789
  catch (error) {
3533
- if (!(error instanceof Error)) {
3534
- throw error;
3535
- }
3790
+ assertsError(error);
3536
3791
  return null;
3537
3792
  }
3538
3793
  }
@@ -3791,6 +4046,7 @@
3791
4046
  `));
3792
4047
  listModelsCommand.alias('scrapers');
3793
4048
  listModelsCommand.action(handleActionErrors(async () => {
4049
+ // TODO: [🌞] Do not allow on REMOTE_SERVER strategy
3794
4050
  const scrapers = await $provideScrapersForNode({});
3795
4051
  const executables = await $provideExecutablesForNode();
3796
4052
  console.info(spaceTrim__default["default"]((block) => `
@@ -3826,42 +4082,20 @@
3826
4082
  loginCommand.description(spaceTrim__default["default"](`
3827
4083
  Login to the remote Promptbook server
3828
4084
  `));
3829
- loginCommand.action(handleActionErrors(async () => {
3830
- // @@@
3831
- console.error(colors__default["default"].green(spaceTrim__default["default"](`
3832
- You will be logged in to https://promptbook.studio server.
3833
- If you don't have an account, it will be created automatically.
3834
- `)));
3835
- // !!!!!!!!! Remove from here and use $provideLlmToolsForCli
3836
- const { email, password } = await prompts__default["default"]([
3837
- {
3838
- type: 'text',
3839
- name: 'email',
3840
- message: 'Enter your email:',
3841
- validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
3842
- },
3843
- {
3844
- type: 'password',
3845
- name: 'password',
3846
- message: 'Enter your password:',
3847
- validate: (value) => value.length /* <- TODO: [🧠] Better password validation */ > 0 ? true : 'Password is required',
4085
+ loginCommand.action(handleActionErrors(async (cliOptions) => {
4086
+ // Note: Not interested in return value of this function but the side effect of logging in
4087
+ await $provideLlmToolsForCli({
4088
+ isLoginloaded: true,
4089
+ cliOptions: {
4090
+ ...cliOptions,
4091
+ strategy: 'REMOTE_SERVER', // <- Note: Overriding strategy to `REMOTE_SERVER`
4092
+ // TODO: Do not allow flag `--strategy` in `login` command at all
3848
4093
  },
3849
- ]);
3850
- TODO_USE(email, password);
3851
- await waitasecond.forTime(1000);
3852
- console.error(colors__default["default"].green(spaceTrim__default["default"](`
3853
- Your account ${email} was successfully created.
3854
-
3855
- Please verify your email:
3856
- https://brj.app/api/v1/customer/register-account?apiKey=PRODdh003eNKaec7PoO1AzU244tsL4WO
3857
-
3858
- After verification, you will receive 500 000 credits for free 🎉
3859
- `)));
4094
+ });
3860
4095
  return process.exit(0);
3861
4096
  }));
3862
4097
  }
3863
4098
  /**
3864
- * TODO: Pass remote server URL (and path)
3865
4099
  * TODO: Implement non-interactive login
3866
4100
  * Note: [💞] Ignore a discrepancy between file name and entity name
3867
4101
  * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
@@ -4267,7 +4501,7 @@
4267
4501
  if (!indexFile) {
4268
4502
  throw new UnexpectedError(`Archive does not contain 'index.book.json' file`);
4269
4503
  }
4270
- const collectionJson = JSON.parse(await indexFile.async('text'));
4504
+ const collectionJson = jsonParse(await indexFile.async('text'));
4271
4505
  for (const pipeline of collectionJson) {
4272
4506
  validatePipeline(pipeline);
4273
4507
  }
@@ -4277,11 +4511,14 @@
4277
4511
  * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
4278
4512
  */
4279
4513
 
4280
- var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModelNames",description:"List of available model names separated by comma (,)",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n```json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n```\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelRequirements",format:"JSON",dependentParameterNames:["availableModelNames","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModelNames}` List of available model names separated by comma (,)\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are experienced AI engineer, you need to create virtual assistant.\nWrite\n\n## Example\n\n\\`\\`\\`json\n{\n\"modelName\": \"gpt-4o\",\n\"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n\"temperature\": 0.7\n}\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON object\n- Write just the JSON object, no other text should be present\n- It contains the following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nPick from the following models:\n\n- {availableModelNames}\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
4514
+ var PipelineCollection = [{title:"Prepare Knowledge from Markdown",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book",formfactorName:"GENERIC",parameters:[{name:"knowledgeContent",description:"Markdown document content",isInput:true,isOutput:false},{name:"knowledgePieces",description:"The knowledge JSON object",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}",resultingParameterName:"knowledgePieces",dependentParameterNames:["knowledgeContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge from Markdown\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-from-markdown.book`\n- INPUT PARAMETER `{knowledgeContent}` Markdown document content\n- OUTPUT PARAMETER `{knowledgePieces}` The knowledge JSON object\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, extract the important knowledge from the document.\n\n# Rules\n\n- Make pieces of information concise, clear, and easy to understand\n- One piece of information should be approximately 1 paragraph\n- Divide the paragraphs by markdown horizontal lines ---\n- Omit irrelevant information\n- Group redundant information\n- Write just extracted information, nothing else\n\n# The document\n\nTake information from this document:\n\n> {knowledgeContent}\n```\n\n`-> {knowledgePieces}`\n"}],sourceFile:"./books/prepare-knowledge-from-markdown.book"},{title:"Prepare Keywords",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-keywords.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"keywords",description:"Keywords separated by comma",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}",resultingParameterName:"keywords",dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Keywords\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-keywords.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{keywords}` Keywords separated by comma\n\n## Knowledge\n\n<!-- TODO: [🍆] -FORMAT JSON -->\n\n```markdown\nYou are experienced data researcher, detect the important keywords in the document.\n\n# Rules\n\n- Write just keywords separated by comma\n\n# The document\n\nTake information from this document:\n\n> {knowledgePieceContent}\n```\n\n`-> {keywords}`\n"}],sourceFile:"./books/prepare-knowledge-keywords.book"},{title:"Prepare Knowledge-piece Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-knowledge-title.book",formfactorName:"GENERIC",parameters:[{name:"knowledgePieceContent",description:"The content",isInput:true,isOutput:false},{name:"title",description:"The title of the document",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"knowledge",title:"Knowledge",content:"You are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}",resultingParameterName:"title",expectations:{words:{min:1,max:8}},dependentParameterNames:["knowledgePieceContent"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Knowledge-piece Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-knowledge-title.book`\n- INPUT PARAMETER `{knowledgePieceContent}` The content\n- OUTPUT PARAMETER `{title}` The title of the document\n\n## Knowledge\n\n- EXPECT MIN 1 WORD\n- EXPECT MAX 8 WORDS\n\n```markdown\nYou are experienced content creator, write best title for the document.\n\n# Rules\n\n- Write just title, nothing else\n- Write maximum 5 words for the title\n\n# The document\n\n> {knowledgePieceContent}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-knowledge-title.book"},{title:"Prepare Persona",pipelineUrl:"https://promptbook.studio/promptbook/prepare-persona.book",formfactorName:"GENERIC",parameters:[{name:"availableModels",description:"List of available model names together with their descriptions as JSON",isInput:true,isOutput:false},{name:"personaDescription",description:"Description of the persona",isInput:true,isOutput:false},{name:"modelsRequirements",description:"Specific requirements for the model",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-model-requirements",title:"Make modelRequirements",content:"You are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n```json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n```\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n```json\n{availableModels}\n```\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}",resultingParameterName:"modelsRequirements",format:"JSON",dependentParameterNames:["availableModels","personaDescription"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Persona\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-persona.book`\n- INPUT PARAMETER `{availableModels}` List of available model names together with their descriptions as JSON\n- INPUT PARAMETER `{personaDescription}` Description of the persona\n- OUTPUT PARAMETER `{modelsRequirements}` Specific requirements for the model\n\n## Make modelRequirements\n\n- FORMAT JSON\n\n```markdown\nYou are an experienced AI engineer, you need to find the best models for virtual assistants:\n\n## Example\n\n\\`\\`\\`json\n[\n {\n \"modelName\": \"gpt-4o\",\n \"systemMessage\": \"You are experienced AI engineer and helpfull assistant.\",\n \"temperature\": 0.7\n },\n {\n \"modelName\": \"claude-3-5-sonnet\",\n \"systemMessage\": \"You are a friendly and knowledgeable chatbot.\",\n \"temperature\": 0.5\n }\n]\n\\`\\`\\`\n\n## Instructions\n\n- Your output format is JSON array\n- Sort best-fitting models first\n- Omit any models that are not suitable\n- Write just the JSON, no other text should be present\n- Array contain items with following keys:\n - `modelName`: The name of the model to use\n - `systemMessage`: The system message to provide context to the model\n - `temperature`: The sampling temperature to use\n\n### Key `modelName`\n\nHere are the available models:\n\n\\`\\`\\`json\n{availableModels}\n\\`\\`\\`\n\n### Key `systemMessage`\n\nThe system message is used to communicate instructions or provide context to the model at the beginning of a conversation. It is displayed in a different format compared to user messages, helping the model understand its role in the conversation. The system message typically guides the model's behavior, sets the tone, or specifies desired output from the model. By utilizing the system message effectively, users can steer the model towards generating more accurate and relevant responses.\n\nFor example:\n\n> You are an experienced AI engineer and helpful assistant.\n\n> You are a friendly and knowledgeable chatbot.\n\n### Key `temperature`\n\nThe sampling temperature, between 0 and 1. Higher values like 0.8 will make the output more random, while lower values like 0.2 will make it more focused and deterministic. If set to 0, the model will use log probability to automatically increase the temperature until certain thresholds are hit.\n\nYou can pick a value between 0 and 2. For example:\n\n- `0.1`: Low temperature, extremely conservative and deterministic\n- `0.5`: Medium temperature, balanced between conservative and creative\n- `1.0`: High temperature, creative and bit random\n- `1.5`: Very high temperature, extremely creative and often chaotic and unpredictable\n- `2.0`: Maximum temperature, completely random and unpredictable, for some extreme creative use cases\n\n# The assistant\n\nTake this description of the persona:\n\n> {personaDescription}\n```\n\n`-> {modelsRequirements}`\n"}],sourceFile:"./books/prepare-persona.book"},{title:"Prepare Title",pipelineUrl:"https://promptbook.studio/promptbook/prepare-title.book",formfactorName:"GENERIC",parameters:[{name:"book",description:"The book to prepare the title for",isInput:true,isOutput:false},{name:"title",description:"Best title for the book",isInput:false,isOutput:true}],tasks:[{taskType:"PROMPT_TASK",name:"make-title",title:"Make title",content:"Make best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}",resultingParameterName:"title",expectations:{words:{min:1,max:8},lines:{min:1,max:1}},dependentParameterNames:["book"]}],personas:[],preparations:[],knowledgeSources:[],knowledgePieces:[],sources:[{type:"BOOK",path:null,content:"# Prepare Title\n\n- PIPELINE URL `https://promptbook.studio/promptbook/prepare-title.book`\n- INPUT PARAMETER `{book}` The book to prepare the title for\n- OUTPUT PARAMETER `{title}` Best title for the book\n\n## Make title\n\n- EXPECT MIN 1 Word\n- EXPECT MAX 8 Words\n- EXPECT EXACTLY 1 Line\n\n```markdown\nMake best title for given text which describes the workflow:\n\n## Rules\n\n- Write just title, nothing else\n- Title should be concise and clear - Write maximum ideally 2 words, maximum 5 words\n- Title starts with emoticon\n- Title should not mention the input and output of the workflow but the main purpose of the workflow\n _For example, not \"✍ Convert Knowledge-piece to title\" but \"✍ Title\"_\n\n## The workflow\n\n> {book}\n```\n\n`-> {title}`\n"}],sourceFile:"./books/prepare-title.book"}];
4281
4515
 
4282
4516
  /**
4283
4517
  * Function isValidJsonString will tell you if the string is valid JSON or not
4284
4518
  *
4519
+ * @param value The string to check
4520
+ * @returns True if the string is a valid JSON string, false otherwise
4521
+ *
4285
4522
  * @public exported from `@promptbook/utils`
4286
4523
  */
4287
4524
  function isValidJsonString(value /* <- [👨‍⚖️] */) {
@@ -4290,9 +4527,7 @@
4290
4527
  return true;
4291
4528
  }
4292
4529
  catch (error) {
4293
- if (!(error instanceof Error)) {
4294
- throw error;
4295
- }
4530
+ assertsError(error);
4296
4531
  if (error.message.includes('Unexpected token')) {
4297
4532
  return false;
4298
4533
  }
@@ -4537,7 +4772,7 @@
4537
4772
  */
4538
4773
  function unpreparePipeline(pipeline) {
4539
4774
  let { personas, knowledgeSources, tasks } = pipeline;
4540
- personas = personas.map((persona) => ({ ...persona, modelRequirements: undefined, preparationIds: undefined }));
4775
+ personas = personas.map((persona) => ({ ...persona, modelsRequirements: undefined, preparationIds: undefined }));
4541
4776
  knowledgeSources = knowledgeSources.map((knowledgeSource) => ({ ...knowledgeSource, preparationIds: undefined }));
4542
4777
  tasks = tasks.map((task) => {
4543
4778
  let { dependentParameterNames } = task;
@@ -4695,7 +4930,7 @@
4695
4930
  if (pipeline.title === undefined || pipeline.title === '' || pipeline.title === DEFAULT_BOOK_TITLE) {
4696
4931
  return false;
4697
4932
  }
4698
- if (!pipeline.personas.every((persona) => persona.modelRequirements !== undefined)) {
4933
+ if (!pipeline.personas.every((persona) => persona.modelsRequirements !== undefined)) {
4699
4934
  return false;
4700
4935
  }
4701
4936
  if (!pipeline.knowledgeSources.every((knowledgeSource) => knowledgeSource.preparationIds !== undefined)) {
@@ -4737,7 +4972,7 @@
4737
4972
  const newObject = { ...object };
4738
4973
  for (const [key, value] of Object.entries(object)) {
4739
4974
  if (typeof value === 'string' && isValidJsonString(value)) {
4740
- newObject[key] = JSON.parse(value);
4975
+ newObject[key] = jsonParse(value);
4741
4976
  }
4742
4977
  else {
4743
4978
  newObject[key] = jsonStringsToJsons(value);
@@ -4823,8 +5058,8 @@
4823
5058
  updatedAt = new Date();
4824
5059
  errors.push(...executionResult.errors);
4825
5060
  warnings.push(...executionResult.warnings);
4826
- // <- TODO: !!! Only unique errors and warnings should be added (or filtered)
4827
- // TODO: [🧠] !!! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
5061
+ // <- TODO: [🌂] Only unique errors and warnings should be added (or filtered)
5062
+ // TODO: [🧠] !! errors, warning, isSuccessful are redundant both in `ExecutionTask` and `ExecutionTask.currentValue`
4828
5063
  // Also maybe move `ExecutionTask.currentValue.usage` -> `ExecutionTask.usage`
4829
5064
  // And delete `ExecutionTask.currentValue.preparedPipeline`
4830
5065
  assertsTaskSuccessful(executionResult);
@@ -4834,6 +5069,7 @@
4834
5069
  partialResultSubject.next(executionResult);
4835
5070
  }
4836
5071
  catch (error) {
5072
+ assertsError(error);
4837
5073
  status = 'ERROR';
4838
5074
  errors.push(error);
4839
5075
  partialResultSubject.error(error);
@@ -4979,13 +5215,19 @@
4979
5215
  return value.toISOString();
4980
5216
  }
4981
5217
  else {
4982
- return JSON.stringify(value);
5218
+ try {
5219
+ return JSON.stringify(value);
5220
+ }
5221
+ catch (error) {
5222
+ if (error instanceof TypeError && error.message.includes('circular structure')) {
5223
+ return VALUE_STRINGS.circular;
5224
+ }
5225
+ throw error;
5226
+ }
4983
5227
  }
4984
5228
  }
4985
5229
  catch (error) {
4986
- if (!(error instanceof Error)) {
4987
- throw error;
4988
- }
5230
+ assertsError(error);
4989
5231
  console.error(error);
4990
5232
  return VALUE_STRINGS.unserializable;
4991
5233
  }
@@ -5042,9 +5284,7 @@
5042
5284
  }
5043
5285
  }
5044
5286
  catch (error) {
5045
- if (!(error instanceof Error)) {
5046
- throw error;
5047
- }
5287
+ assertsError(error);
5048
5288
  throw new ParseError(spaceTrim.spaceTrim((block) => `
5049
5289
  Can not extract variables from the script
5050
5290
  ${block(error.stack || error.message)}
@@ -5163,6 +5403,46 @@
5163
5403
  // encoding: 'utf-8',
5164
5404
  });
5165
5405
 
5406
+ /**
5407
+ * Function to check if a string is valid CSV
5408
+ *
5409
+ * @param value The string to check
5410
+ * @returns True if the string is a valid CSV string, false otherwise
5411
+ *
5412
+ * @public exported from `@promptbook/utils`
5413
+ */
5414
+ function isValidCsvString(value) {
5415
+ try {
5416
+ // A simple check for CSV format: at least one comma and no invalid characters
5417
+ if (value.includes(',') && /^[\w\s,"']+$/.test(value)) {
5418
+ return true;
5419
+ }
5420
+ return false;
5421
+ }
5422
+ catch (error) {
5423
+ assertsError(error);
5424
+ return false;
5425
+ }
5426
+ }
5427
+
5428
+ /**
5429
+ * Converts a CSV string into an object
5430
+ *
5431
+ * Note: This is wrapper around `papaparse.parse()` with better autohealing
5432
+ *
5433
+ * @private - for now until `@promptbook/csv` is released
5434
+ */
5435
+ function csvParse(value /* <- TODO: string_csv */, settings, schema /* <- TODO: Make CSV Schemas */) {
5436
+ settings = { ...settings, ...MANDATORY_CSV_SETTINGS };
5437
+ // Note: Autoheal invalid '\n' characters
5438
+ if (settings.newline && !settings.newline.includes('\r') && value.includes('\r')) {
5439
+ console.warn('CSV string contains carriage return characters, but in the CSV settings the `newline` setting does not include them. Autohealing the CSV string.');
5440
+ value = value.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
5441
+ }
5442
+ const csv = papaparse.parse(value, settings);
5443
+ return csv;
5444
+ }
5445
+
5166
5446
  /**
5167
5447
  * Definition for CSV spreadsheet
5168
5448
  *
@@ -5173,7 +5453,7 @@
5173
5453
  formatName: 'CSV',
5174
5454
  aliases: ['SPREADSHEET', 'TABLE'],
5175
5455
  isValid(value, settings, schema) {
5176
- return true;
5456
+ return isValidCsvString(value);
5177
5457
  },
5178
5458
  canBeValid(partialValue, settings, schema) {
5179
5459
  return true;
@@ -5185,8 +5465,7 @@
5185
5465
  {
5186
5466
  subvalueName: 'ROW',
5187
5467
  async mapValues(value, outputParameterName, settings, mapCallback) {
5188
- // TODO: [👨🏾‍🤝‍👨🏼] DRY csv parsing
5189
- const csv = papaparse.parse(value, { ...settings, ...MANDATORY_CSV_SETTINGS });
5468
+ const csv = csvParse(value, settings);
5190
5469
  if (csv.errors.length !== 0) {
5191
5470
  throw new CsvFormatError(spaceTrim__default["default"]((block) => `
5192
5471
  CSV parsing error
@@ -5216,8 +5495,7 @@
5216
5495
  {
5217
5496
  subvalueName: 'CELL',
5218
5497
  async mapValues(value, outputParameterName, settings, mapCallback) {
5219
- // TODO: [👨🏾‍🤝‍👨🏼] DRY csv parsing
5220
- const csv = papaparse.parse(value, { ...settings, ...MANDATORY_CSV_SETTINGS });
5498
+ const csv = csvParse(value, settings);
5221
5499
  if (csv.errors.length !== 0) {
5222
5500
  throw new CsvFormatError(spaceTrim__default["default"]((block) => `
5223
5501
  CSV parsing error
@@ -5327,6 +5605,30 @@
5327
5605
  * TODO: [🏢] Allow to expect something inside each item of list and other formats
5328
5606
  */
5329
5607
 
5608
+ /**
5609
+ * Function to check if a string is valid XML
5610
+ *
5611
+ * @param value
5612
+ * @returns True if the string is a valid XML string, false otherwise
5613
+ *
5614
+ * @public exported from `@promptbook/utils`
5615
+ */
5616
+ function isValidXmlString(value) {
5617
+ try {
5618
+ const parser = new DOMParser();
5619
+ const parsedDocument = parser.parseFromString(value, 'application/xml');
5620
+ const parserError = parsedDocument.getElementsByTagName('parsererror');
5621
+ if (parserError.length > 0) {
5622
+ return false;
5623
+ }
5624
+ return true;
5625
+ }
5626
+ catch (error) {
5627
+ assertsError(error);
5628
+ return false;
5629
+ }
5630
+ }
5631
+
5330
5632
  /**
5331
5633
  * Definition for XML format
5332
5634
  *
@@ -5336,7 +5638,7 @@
5336
5638
  formatName: 'XML',
5337
5639
  mimeType: 'application/xml',
5338
5640
  isValid(value, settings, schema) {
5339
- return true;
5641
+ return isValidXmlString(value);
5340
5642
  },
5341
5643
  canBeValid(partialValue, settings, schema) {
5342
5644
  return true;
@@ -5928,9 +6230,7 @@
5928
6230
  break scripts;
5929
6231
  }
5930
6232
  catch (error) {
5931
- if (!(error instanceof Error)) {
5932
- throw error;
5933
- }
6233
+ assertsError(error);
5934
6234
  if (error instanceof UnexpectedError) {
5935
6235
  throw error;
5936
6236
  }
@@ -6000,9 +6300,7 @@
6000
6300
  break scripts;
6001
6301
  }
6002
6302
  catch (error) {
6003
- if (!(error instanceof Error)) {
6004
- throw error;
6005
- }
6303
+ assertsError(error);
6006
6304
  if (error instanceof UnexpectedError) {
6007
6305
  throw error;
6008
6306
  }
@@ -6245,13 +6543,79 @@
6245
6543
  /**
6246
6544
  * @@@
6247
6545
  *
6546
+ * Here is the place where RAG (retrieval-augmented generation) happens
6547
+ *
6248
6548
  * @private internal utility of `createPipelineExecutor`
6249
6549
  */
6250
6550
  async function getKnowledgeForTask(options) {
6251
- const { preparedPipeline, task } = options;
6252
- return preparedPipeline.knowledgePieces.map(({ content }) => `- ${content}`).join('\n');
6551
+ const { tools, preparedPipeline, task } = options;
6552
+ const firstKnowlegePiece = preparedPipeline.knowledgePieces[0];
6553
+ const firstKnowlegeIndex = firstKnowlegePiece === null || firstKnowlegePiece === void 0 ? void 0 : firstKnowlegePiece.index[0];
6554
+ // <- TODO: Do not use just first knowledge piece and first index to determine embedding model, use also keyword search
6555
+ if (firstKnowlegePiece === undefined || firstKnowlegeIndex === undefined) {
6556
+ return 'No knowledge pieces found';
6557
+ }
6558
+ // TODO: [🚐] Make arrayable LLMs -> single LLM DRY
6559
+ const _llms = arrayableToArray(tools.llm);
6560
+ const llmTools = _llms.length === 1 ? _llms[0] : joinLlmExecutionTools(..._llms);
6561
+ const taskEmbeddingPrompt = {
6562
+ title: 'Knowledge Search',
6563
+ modelRequirements: {
6564
+ modelVariant: 'EMBEDDING',
6565
+ modelName: firstKnowlegeIndex.modelName,
6566
+ },
6567
+ content: task.content,
6568
+ parameters: {
6569
+ /* !!!!!!!! */
6570
+ },
6571
+ };
6572
+ const taskEmbeddingResult = await llmTools.callEmbeddingModel(taskEmbeddingPrompt);
6573
+ const knowledgePiecesWithRelevance = preparedPipeline.knowledgePieces.map((knowledgePiece) => {
6574
+ const { index } = knowledgePiece;
6575
+ const knowledgePieceIndex = index.find((i) => i.modelName === firstKnowlegeIndex.modelName);
6576
+ // <- TODO: Do not use just first knowledge piece and first index to determine embedding model
6577
+ if (knowledgePieceIndex === undefined) {
6578
+ return {
6579
+ content: knowledgePiece.content,
6580
+ relevance: 0,
6581
+ };
6582
+ }
6583
+ const relevance = computeCosineSimilarity(knowledgePieceIndex.position, taskEmbeddingResult.content);
6584
+ return {
6585
+ content: knowledgePiece.content,
6586
+ relevance,
6587
+ };
6588
+ });
6589
+ const knowledgePiecesSorted = knowledgePiecesWithRelevance.sort((a, b) => a.relevance - b.relevance);
6590
+ const knowledgePiecesLimited = knowledgePiecesSorted.slice(0, 5);
6591
+ console.log('!!! Embedding', {
6592
+ task,
6593
+ taskEmbeddingPrompt,
6594
+ taskEmbeddingResult,
6595
+ firstKnowlegePiece,
6596
+ firstKnowlegeIndex,
6597
+ knowledgePiecesWithRelevance,
6598
+ knowledgePiecesSorted,
6599
+ knowledgePiecesLimited,
6600
+ });
6601
+ return knowledgePiecesLimited.map(({ content }) => `- ${content}`).join('\n');
6253
6602
  // <- TODO: [🧠] Some smart aggregation of knowledge pieces, single-line vs multi-line vs mixed
6254
6603
  }
6604
+ // TODO: !!!!!! Annotate + to new file
6605
+ function computeCosineSimilarity(embeddingVector1, embeddingVector2) {
6606
+ if (embeddingVector1.length !== embeddingVector2.length) {
6607
+ throw new TypeError('Embedding vectors must have the same length');
6608
+ }
6609
+ const dotProduct = embeddingVector1.reduce((sum, value, index) => sum + value * embeddingVector2[index], 0);
6610
+ const magnitude1 = Math.sqrt(embeddingVector1.reduce((sum, value) => sum + value * value, 0));
6611
+ const magnitude2 = Math.sqrt(embeddingVector2.reduce((sum, value) => sum + value * value, 0));
6612
+ return 1 - dotProduct / (magnitude1 * magnitude2);
6613
+ }
6614
+ /**
6615
+ * TODO: !!!! Verify if this is working
6616
+ * TODO: [♨] Implement Better - use keyword search
6617
+ * TODO: [♨] Examples of values
6618
+ */
6255
6619
 
6256
6620
  /**
6257
6621
  * @@@
@@ -6259,9 +6623,9 @@
6259
6623
  * @private internal utility of `createPipelineExecutor`
6260
6624
  */
6261
6625
  async function getReservedParametersForTask(options) {
6262
- const { preparedPipeline, task, pipelineIdentification } = options;
6626
+ const { tools, preparedPipeline, task, pipelineIdentification } = options;
6263
6627
  const context = await getContextForTask(); // <- [🏍]
6264
- const knowledge = await getKnowledgeForTask({ preparedPipeline, task });
6628
+ const knowledge = await getKnowledgeForTask({ tools, preparedPipeline, task });
6265
6629
  const examples = await getExamplesForTask();
6266
6630
  const currentDate = new Date().toISOString(); // <- TODO: [🧠][💩] Better
6267
6631
  const modelName = RESERVED_PARAMETER_MISSING_VALUE;
@@ -6323,6 +6687,7 @@
6323
6687
  }
6324
6688
  const definedParameters = Object.freeze({
6325
6689
  ...(await getReservedParametersForTask({
6690
+ tools,
6326
6691
  preparedPipeline,
6327
6692
  task: currentTask,
6328
6693
  pipelineIdentification,
@@ -6623,9 +6988,7 @@
6623
6988
  await Promise.all(resolving);
6624
6989
  }
6625
6990
  catch (error /* <- Note: [3] */) {
6626
- if (!(error instanceof Error)) {
6627
- throw error;
6628
- }
6991
+ assertsError(error);
6629
6992
  // Note: No need to rethrow UnexpectedError
6630
6993
  // if (error instanceof UnexpectedError) {
6631
6994
  // Note: Count usage, [🧠] Maybe put to separate function executionReportJsonToUsage + DRY [🤹‍♂️]
@@ -6810,27 +7173,48 @@
6810
7173
  pipeline: await collection.getPipelineByUrl('https://promptbook.studio/promptbook/prepare-persona.book'),
6811
7174
  tools,
6812
7175
  });
6813
- // TODO: [🚐] Make arrayable LLMs -> single LLM DRY
6814
7176
  const _llms = arrayableToArray(tools.llm);
6815
7177
  const llmTools = _llms.length === 1 ? _llms[0] : joinLlmExecutionTools(..._llms);
6816
- const availableModels = await llmTools.listModels();
6817
- const availableModelNames = availableModels
7178
+ const availableModels = (await llmTools.listModels())
6818
7179
  .filter(({ modelVariant }) => modelVariant === 'CHAT')
6819
- .map(({ modelName }) => modelName)
6820
- .join(',');
6821
- const result = await preparePersonaExecutor({ availableModelNames, personaDescription }).asPromise();
7180
+ .map(({ modelName, modelDescription }) => ({
7181
+ modelName,
7182
+ modelDescription,
7183
+ // <- Note: `modelTitle` and `modelVariant` is not relevant for this task
7184
+ }));
7185
+ const result = await preparePersonaExecutor({
7186
+ availableModels /* <- Note: Passing as JSON */,
7187
+ personaDescription,
7188
+ }).asPromise();
6822
7189
  const { outputParameters } = result;
6823
- const { modelRequirements: modelRequirementsRaw } = outputParameters;
6824
- const modelRequirements = JSON.parse(modelRequirementsRaw);
7190
+ const { modelsRequirements: modelsRequirementsJson } = outputParameters;
7191
+ let modelsRequirementsUnchecked = jsonParse(modelsRequirementsJson);
6825
7192
  if (isVerbose) {
6826
- console.info(`PERSONA ${personaDescription}`, modelRequirements);
7193
+ console.info(`PERSONA ${personaDescription}`, modelsRequirementsUnchecked);
6827
7194
  }
6828
- const { modelName, systemMessage, temperature } = modelRequirements;
6829
- return {
7195
+ if (!Array.isArray(modelsRequirementsUnchecked)) {
7196
+ // <- TODO: Book should have syntax and system to enforce shape of JSON
7197
+ modelsRequirementsUnchecked = [modelsRequirementsUnchecked];
7198
+ /*
7199
+ throw new UnexpectedError(
7200
+ spaceTrim(
7201
+ (block) => `
7202
+ Invalid \`modelsRequirements\`:
7203
+
7204
+ \`\`\`json
7205
+ ${block(JSON.stringify(modelsRequirementsUnchecked, null, 4))}
7206
+ \`\`\`
7207
+ `,
7208
+ ),
7209
+ );
7210
+ */
7211
+ }
7212
+ const modelsRequirements = modelsRequirementsUnchecked.map((modelRequirements) => ({
6830
7213
  modelVariant: 'CHAT',
6831
- modelName,
6832
- systemMessage,
6833
- temperature,
7214
+ ...modelRequirements,
7215
+ }));
7216
+ return {
7217
+ modelsRequirements,
6834
7218
  };
6835
7219
  }
6836
7220
  /**
@@ -6999,7 +7383,7 @@
6999
7383
  > },
7000
7384
  */
7001
7385
  async asJson() {
7002
- return JSON.parse(await tools.fs.readFile(filename, 'utf-8'));
7386
+ return jsonParse(await tools.fs.readFile(filename, 'utf-8'));
7003
7387
  },
7004
7388
  async asText() {
7005
7389
  return await tools.fs.readFile(filename, 'utf-8');
@@ -7101,9 +7485,7 @@
7101
7485
  knowledgePreparedUnflatten[index] = pieces;
7102
7486
  }
7103
7487
  catch (error) {
7104
- if (!(error instanceof Error)) {
7105
- throw error;
7106
- }
7488
+ assertsError(error);
7107
7489
  console.warn(error);
7108
7490
  // <- TODO: [🏮] Some standard way how to transform errors into warnings and how to handle non-critical fails during the tasks
7109
7491
  }
@@ -7259,14 +7641,14 @@
7259
7641
  // TODO: [🖌][🧠] Implement some `mapAsync` function
7260
7642
  const preparedPersonas = new Array(personas.length);
7261
7643
  await forEachAsync(personas, { maxParallelCount /* <- TODO: [🪂] When there are subtasks, this maximul limit can be broken */ }, async (persona, index) => {
7262
- const modelRequirements = await preparePersona(persona.description, { ...tools, llm: llmToolsWithUsage }, {
7644
+ const { modelsRequirements } = await preparePersona(persona.description, { ...tools, llm: llmToolsWithUsage }, {
7263
7645
  rootDirname,
7264
7646
  maxParallelCount /* <- TODO: [🪂] */,
7265
7647
  isVerbose,
7266
7648
  });
7267
7649
  const preparedPersona = {
7268
7650
  ...persona,
7269
- modelRequirements,
7651
+ modelsRequirements,
7270
7652
  preparationIds: [/* TODO: [🧊] -> */ currentPreparation.id],
7271
7653
  // <- TODO: [🍙] Make some standard order of json properties
7272
7654
  };
@@ -7901,6 +8283,8 @@
7901
8283
  */
7902
8284
 
7903
8285
  /**
8286
+ import { WrappedError } from '../../errors/WrappedError';
8287
+ import { assertsError } from '../../errors/assertsError';
7904
8288
  * Parses the expect command
7905
8289
  *
7906
8290
  * @see `documentationUrl` for more details
@@ -7992,9 +8376,7 @@
7992
8376
  };
7993
8377
  }
7994
8378
  catch (error) {
7995
- if (!(error instanceof Error)) {
7996
- throw error;
7997
- }
8379
+ assertsError(error);
7998
8380
  throw new ParseError(spaceTrim__default["default"]((block) => `
7999
8381
  Invalid FORMAT command
8000
8382
  ${block(error.message)}:
@@ -11242,9 +11624,7 @@
11242
11624
  }
11243
11625
  }
11244
11626
  catch (error) {
11245
- if (!(error instanceof Error)) {
11246
- throw error;
11247
- }
11627
+ assertsError(error);
11248
11628
  if (error instanceof ReferenceError) {
11249
11629
  const undefinedName = error.message.split(' ')[0];
11250
11630
  /*
@@ -11519,9 +11899,7 @@
11519
11899
  // ---
11520
11900
  }
11521
11901
  catch (error) {
11522
- if (!(error instanceof Error)) {
11523
- throw error;
11524
- }
11902
+ assertsError(error);
11525
11903
  // TODO: [7] DRY
11526
11904
  const wrappedErrorMessage = spaceTrim__default["default"]((block) => `
11527
11905
  ${error.name} in pipeline ${fileName.split('\\').join('/')}⁠:
@@ -11612,9 +11990,7 @@
11612
11990
  }
11613
11991
  }
11614
11992
  catch (error) {
11615
- if (!(error instanceof Error)) {
11616
- throw error;
11617
- }
11993
+ assertsError(error);
11618
11994
  // TODO: [7] DRY
11619
11995
  const wrappedErrorMessage = spaceTrim__default["default"]((block) => `
11620
11996
  ${error.name} in pipeline ${fileName.split('\\').join('/')}⁠:
@@ -11830,7 +12206,7 @@
11830
12206
  isCacheReloaded,
11831
12207
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
11832
12208
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
11833
- const llm = await $provideLlmToolsForCli({
12209
+ const { llm } = await $provideLlmToolsForCli({
11834
12210
  cliOptions,
11835
12211
  ...prepareAndScrapeOptions,
11836
12212
  });
@@ -12123,9 +12499,7 @@
12123
12499
  }
12124
12500
  }
12125
12501
  catch (error) {
12126
- if (!(error instanceof Error)) {
12127
- throw error;
12128
- }
12502
+ assertsError(error);
12129
12503
  console.info(colors__default["default"].red(`Prettify ${error.name} ${filename}`));
12130
12504
  console.error(colors__default["default"].bgRed(`${error.name} in ${path.basename(__filename)}`));
12131
12505
  console.error(colors__default["default"].red(error.stack || error.message));
@@ -12445,9 +12819,7 @@
12445
12819
  return true;
12446
12820
  }
12447
12821
  catch (error) {
12448
- if (!(error instanceof Error)) {
12449
- throw error;
12450
- }
12822
+ assertsError(error);
12451
12823
  return false;
12452
12824
  }
12453
12825
  }
@@ -12672,9 +13044,7 @@
12672
13044
  ongoingParameters = result.outputParameters;
12673
13045
  }
12674
13046
  catch (error) {
12675
- if (!(error instanceof Error)) {
12676
- throw error;
12677
- }
13047
+ assertsError(error);
12678
13048
  // TODO: Allow to ressurect the chatbot after an error - prompt the user to continue
12679
13049
  console.error(colors__default["default"].red(error.stack || error.message));
12680
13050
  return process.exit(1);
@@ -12707,7 +13077,6 @@
12707
13077
  runCommand.option('-j, --json <json>', `Pass all or some input parameters as JSON record, if used the output is also returned as JSON`);
12708
13078
  runCommand.option('-s, --save-report <path>', `Save report to file`);
12709
13079
  runCommand.action(handleActionErrors(async (pipelineSource, cliOptions) => {
12710
- console.log('!!!', cliOptions);
12711
13080
  const { reload: isCacheReloaded, interactive: isInteractive, formfactor: isFormfactorUsed, json, verbose: isVerbose, saveReport, } = cliOptions;
12712
13081
  if (pipelineSource.includes('-') && normalizeToKebabCase(pipelineSource) === pipelineSource) {
12713
13082
  console.error(colors__default["default"].red(`""${pipelineSource}" is not a valid command or book. See 'ptbk --help'.`));
@@ -12719,7 +13088,7 @@
12719
13088
  }
12720
13089
  let inputParameters = {};
12721
13090
  if (json) {
12722
- inputParameters = JSON.parse(json);
13091
+ inputParameters = jsonParse(json);
12723
13092
  // <- TODO: Maybe check shape of passed JSON and if its valid parameters Record
12724
13093
  }
12725
13094
  // TODO: DRY [◽]
@@ -12733,12 +13102,10 @@
12733
13102
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
12734
13103
  let llm;
12735
13104
  try {
12736
- llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13105
+ llm = (await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions })).llm;
12737
13106
  }
12738
13107
  catch (error) {
12739
- if (!(error instanceof Error)) {
12740
- throw error;
12741
- }
13108
+ assertsError(error);
12742
13109
  if (!error.message.includes('No LLM tools')) {
12743
13110
  throw error;
12744
13111
  }
@@ -12936,12 +13303,610 @@
12936
13303
  }));
12937
13304
  }
12938
13305
  /**
12939
- * TODO: !!5 Catch and wrap all errors from CLI
12940
- * TODO: [🧠] Pass `maxExecutionAttempts`, `csvSettings`
12941
- * TODO: [🥃][main] !!3 Allow `ptbk run` without configuring any llm tools
13306
+ * TODO: !!5 Catch and wrap all errors from CLI
13307
+ * TODO: [🧠] Pass `maxExecutionAttempts`, `csvSettings`
13308
+ * TODO: [🥃][main] !!3 Allow `ptbk run` without configuring any llm tools
13309
+ * Note: [💞] Ignore a discrepancy between file name and entity name
13310
+ * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
13311
+ * TODO: [🖇] What about symlinks? Maybe flag --follow-symlinks
13312
+ */
13313
+
13314
+ // TODO: !!!! List running services from REMOTE_SERVER_URLS
13315
+ // TODO: !!!! Import directly from YML
13316
+ /**
13317
+ * @private !!!! Decide how to expose this
13318
+ */
13319
+ const openapiJson = {
13320
+ openapi: '3.0.0',
13321
+ info: {
13322
+ title: 'Promptbook Remote Server API (!!!! From YML)',
13323
+ version: '1.0.0',
13324
+ description: 'API documentation for the Promptbook Remote Server',
13325
+ },
13326
+ paths: {
13327
+ '/': {
13328
+ get: {
13329
+ summary: 'Get server details',
13330
+ description: 'Returns details about the Promptbook server.',
13331
+ responses: {
13332
+ '200': {
13333
+ description: 'Server details in markdown format.',
13334
+ content: {
13335
+ 'text/markdown': {
13336
+ schema: {
13337
+ type: 'string',
13338
+ },
13339
+ },
13340
+ },
13341
+ },
13342
+ },
13343
+ },
13344
+ },
13345
+ '/login': {
13346
+ post: {
13347
+ summary: 'Login to the server',
13348
+ description: 'Login to the server and get identification.',
13349
+ requestBody: {
13350
+ required: true,
13351
+ content: {
13352
+ 'application/json': {
13353
+ schema: {
13354
+ type: 'object',
13355
+ properties: {
13356
+ username: {
13357
+ type: 'string',
13358
+ },
13359
+ password: {
13360
+ type: 'string',
13361
+ },
13362
+ appId: {
13363
+ type: 'string',
13364
+ },
13365
+ },
13366
+ },
13367
+ },
13368
+ },
13369
+ },
13370
+ responses: {
13371
+ '201': {
13372
+ description: 'Successful login',
13373
+ content: {
13374
+ 'application/json': {
13375
+ schema: {
13376
+ type: 'object',
13377
+ properties: {
13378
+ isSuccess: {
13379
+ type: 'boolean',
13380
+ },
13381
+ message: {
13382
+ type: 'string',
13383
+ },
13384
+ error: {
13385
+ type: 'object',
13386
+ },
13387
+ identification: {
13388
+ type: 'object',
13389
+ },
13390
+ },
13391
+ },
13392
+ },
13393
+ },
13394
+ },
13395
+ '400': {
13396
+ description: 'Bad request or login failed',
13397
+ content: {
13398
+ 'application/json': {
13399
+ schema: {
13400
+ type: 'object',
13401
+ properties: {
13402
+ error: {
13403
+ type: 'object',
13404
+ },
13405
+ },
13406
+ },
13407
+ },
13408
+ },
13409
+ },
13410
+ '401': {
13411
+ description: 'Authentication error',
13412
+ content: {
13413
+ 'application/json': {
13414
+ schema: {
13415
+ type: 'object',
13416
+ properties: {
13417
+ isSuccess: {
13418
+ type: 'boolean',
13419
+ enum: [false],
13420
+ },
13421
+ message: {
13422
+ type: 'string',
13423
+ },
13424
+ error: {
13425
+ type: 'object',
13426
+ },
13427
+ },
13428
+ },
13429
+ },
13430
+ },
13431
+ },
13432
+ },
13433
+ },
13434
+ },
13435
+ '/books': {
13436
+ get: {
13437
+ summary: 'List all books',
13438
+ description: 'Returns a list of all available books in the collection.',
13439
+ responses: {
13440
+ '200': {
13441
+ description: 'A list of books.',
13442
+ content: {
13443
+ 'application/json': {
13444
+ schema: {
13445
+ type: 'array',
13446
+ items: {
13447
+ type: 'string',
13448
+ },
13449
+ },
13450
+ },
13451
+ },
13452
+ },
13453
+ '500': {
13454
+ description: 'No collection available',
13455
+ content: {
13456
+ 'text/plain': {
13457
+ schema: {
13458
+ type: 'string',
13459
+ },
13460
+ },
13461
+ },
13462
+ },
13463
+ },
13464
+ },
13465
+ },
13466
+ '/books/{bookId}': {
13467
+ get: {
13468
+ summary: 'Get book content',
13469
+ description: 'Returns the content of a specific book.',
13470
+ parameters: [
13471
+ {
13472
+ in: 'path',
13473
+ name: 'bookId',
13474
+ required: true,
13475
+ schema: {
13476
+ type: 'string',
13477
+ },
13478
+ description: 'The ID of the book to retrieve.',
13479
+ },
13480
+ ],
13481
+ responses: {
13482
+ '200': {
13483
+ description: 'The content of the book.',
13484
+ content: {
13485
+ 'text/markdown': {
13486
+ schema: {
13487
+ type: 'string',
13488
+ },
13489
+ },
13490
+ },
13491
+ },
13492
+ '404': {
13493
+ description: 'Book not found.',
13494
+ content: {
13495
+ 'application/json': {
13496
+ schema: {
13497
+ type: 'object',
13498
+ properties: {
13499
+ error: {
13500
+ type: 'object',
13501
+ },
13502
+ },
13503
+ },
13504
+ },
13505
+ },
13506
+ },
13507
+ '500': {
13508
+ description: 'No collection available',
13509
+ content: {
13510
+ 'text/plain': {
13511
+ schema: {
13512
+ type: 'string',
13513
+ },
13514
+ },
13515
+ },
13516
+ },
13517
+ },
13518
+ },
13519
+ },
13520
+ '/executions': {
13521
+ get: {
13522
+ summary: 'List all executions',
13523
+ description: 'Returns a list of all running execution tasks.',
13524
+ responses: {
13525
+ '200': {
13526
+ description: 'A list of execution tasks.',
13527
+ content: {
13528
+ 'application/json': {
13529
+ schema: {
13530
+ type: 'array',
13531
+ items: {
13532
+ type: 'object',
13533
+ properties: {
13534
+ nonce: {
13535
+ type: 'string',
13536
+ },
13537
+ taskId: {
13538
+ type: 'string',
13539
+ },
13540
+ taskType: {
13541
+ type: 'string',
13542
+ },
13543
+ status: {
13544
+ type: 'string',
13545
+ },
13546
+ createdAt: {
13547
+ type: 'string',
13548
+ format: 'date-time',
13549
+ },
13550
+ updatedAt: {
13551
+ type: 'string',
13552
+ format: 'date-time',
13553
+ },
13554
+ },
13555
+ },
13556
+ },
13557
+ },
13558
+ },
13559
+ },
13560
+ },
13561
+ },
13562
+ },
13563
+ '/executions/last': {
13564
+ get: {
13565
+ summary: 'Get the last execution',
13566
+ description: 'Returns details of the last execution task.',
13567
+ responses: {
13568
+ '200': {
13569
+ description: 'The last execution task with full details.',
13570
+ content: {
13571
+ 'application/json': {
13572
+ schema: {
13573
+ type: 'object',
13574
+ properties: {
13575
+ nonce: {
13576
+ type: 'string',
13577
+ },
13578
+ taskId: {
13579
+ type: 'string',
13580
+ },
13581
+ taskType: {
13582
+ type: 'string',
13583
+ },
13584
+ status: {
13585
+ type: 'string',
13586
+ },
13587
+ errors: {
13588
+ type: 'array',
13589
+ items: {
13590
+ type: 'object',
13591
+ },
13592
+ },
13593
+ warnings: {
13594
+ type: 'array',
13595
+ items: {
13596
+ type: 'object',
13597
+ },
13598
+ },
13599
+ createdAt: {
13600
+ type: 'string',
13601
+ format: 'date-time',
13602
+ },
13603
+ updatedAt: {
13604
+ type: 'string',
13605
+ format: 'date-time',
13606
+ },
13607
+ currentValue: {
13608
+ type: 'object',
13609
+ },
13610
+ },
13611
+ },
13612
+ },
13613
+ },
13614
+ },
13615
+ '404': {
13616
+ description: 'No execution tasks found.',
13617
+ content: {
13618
+ 'text/plain': {
13619
+ schema: {
13620
+ type: 'string',
13621
+ },
13622
+ },
13623
+ },
13624
+ },
13625
+ },
13626
+ },
13627
+ },
13628
+ '/executions/{taskId}': {
13629
+ get: {
13630
+ summary: 'Get specific execution',
13631
+ description: 'Returns details of a specific execution task.',
13632
+ parameters: [
13633
+ {
13634
+ in: 'path',
13635
+ name: 'taskId',
13636
+ required: true,
13637
+ schema: {
13638
+ type: 'string',
13639
+ },
13640
+ description: 'The ID of the execution task to retrieve.',
13641
+ },
13642
+ ],
13643
+ responses: {
13644
+ '200': {
13645
+ description: 'The execution task with full details.',
13646
+ content: {
13647
+ 'application/json': {
13648
+ schema: {
13649
+ type: 'object',
13650
+ properties: {
13651
+ nonce: {
13652
+ type: 'string',
13653
+ },
13654
+ taskId: {
13655
+ type: 'string',
13656
+ },
13657
+ taskType: {
13658
+ type: 'string',
13659
+ },
13660
+ status: {
13661
+ type: 'string',
13662
+ },
13663
+ errors: {
13664
+ type: 'array',
13665
+ items: {
13666
+ type: 'object',
13667
+ },
13668
+ },
13669
+ warnings: {
13670
+ type: 'array',
13671
+ items: {
13672
+ type: 'object',
13673
+ },
13674
+ },
13675
+ createdAt: {
13676
+ type: 'string',
13677
+ format: 'date-time',
13678
+ },
13679
+ updatedAt: {
13680
+ type: 'string',
13681
+ format: 'date-time',
13682
+ },
13683
+ currentValue: {
13684
+ type: 'object',
13685
+ },
13686
+ },
13687
+ },
13688
+ },
13689
+ },
13690
+ },
13691
+ '404': {
13692
+ description: 'Execution task not found.',
13693
+ content: {
13694
+ 'text/plain': {
13695
+ schema: {
13696
+ type: 'string',
13697
+ },
13698
+ },
13699
+ },
13700
+ },
13701
+ },
13702
+ },
13703
+ },
13704
+ '/executions/new': {
13705
+ post: {
13706
+ summary: 'Start a new execution',
13707
+ description: 'Starts a new execution task for a given pipeline.',
13708
+ requestBody: {
13709
+ required: true,
13710
+ content: {
13711
+ 'application/json': {
13712
+ schema: {
13713
+ type: 'object',
13714
+ properties: {
13715
+ pipelineUrl: {
13716
+ type: 'string',
13717
+ description: 'URL of the pipeline to execute',
13718
+ },
13719
+ book: {
13720
+ type: 'string',
13721
+ description: 'Alternative field for pipelineUrl',
13722
+ },
13723
+ inputParameters: {
13724
+ type: 'object',
13725
+ description: 'Parameters for pipeline execution',
13726
+ },
13727
+ identification: {
13728
+ type: 'object',
13729
+ description: 'User identification data',
13730
+ },
13731
+ },
13732
+ },
13733
+ },
13734
+ },
13735
+ },
13736
+ responses: {
13737
+ '200': {
13738
+ description: 'The newly created execution task.',
13739
+ content: {
13740
+ 'application/json': {
13741
+ schema: {
13742
+ type: 'object',
13743
+ },
13744
+ },
13745
+ },
13746
+ },
13747
+ '400': {
13748
+ description: 'Invalid input.',
13749
+ content: {
13750
+ 'application/json': {
13751
+ schema: {
13752
+ type: 'object',
13753
+ properties: {
13754
+ error: {
13755
+ type: 'object',
13756
+ },
13757
+ },
13758
+ },
13759
+ },
13760
+ },
13761
+ },
13762
+ '404': {
13763
+ description: 'Pipeline not found.',
13764
+ content: {
13765
+ 'text/plain': {
13766
+ schema: {
13767
+ type: 'string',
13768
+ },
13769
+ },
13770
+ },
13771
+ },
13772
+ },
13773
+ },
13774
+ },
13775
+ '/api-docs': {
13776
+ get: {
13777
+ summary: 'API documentation UI',
13778
+ description: 'Swagger UI for API documentation',
13779
+ responses: {
13780
+ '200': {
13781
+ description: 'HTML Swagger UI',
13782
+ },
13783
+ },
13784
+ },
13785
+ },
13786
+ '/swagger': {
13787
+ get: {
13788
+ summary: 'API documentation UI (alternative path)',
13789
+ description: 'Swagger UI for API documentation',
13790
+ responses: {
13791
+ '200': {
13792
+ description: 'HTML Swagger UI',
13793
+ },
13794
+ },
13795
+ },
13796
+ },
13797
+ '/openapi': {
13798
+ get: {
13799
+ summary: 'OpenAPI specification',
13800
+ description: 'Returns the OpenAPI JSON specification',
13801
+ responses: {
13802
+ '200': {
13803
+ description: 'OpenAPI specification',
13804
+ content: {
13805
+ 'application/json': {
13806
+ schema: {
13807
+ type: 'object',
13808
+ },
13809
+ },
13810
+ },
13811
+ },
13812
+ },
13813
+ },
13814
+ },
13815
+ },
13816
+ components: {
13817
+ schemas: {
13818
+ Error: {
13819
+ type: 'object',
13820
+ properties: {
13821
+ error: {
13822
+ type: 'object',
13823
+ },
13824
+ },
13825
+ },
13826
+ ExecutionTaskSummary: {
13827
+ type: 'object',
13828
+ properties: {
13829
+ nonce: {
13830
+ type: 'string',
13831
+ },
13832
+ taskId: {
13833
+ type: 'string',
13834
+ },
13835
+ taskType: {
13836
+ type: 'string',
13837
+ },
13838
+ status: {
13839
+ type: 'string',
13840
+ },
13841
+ createdAt: {
13842
+ type: 'string',
13843
+ format: 'date-time',
13844
+ },
13845
+ updatedAt: {
13846
+ type: 'string',
13847
+ format: 'date-time',
13848
+ },
13849
+ },
13850
+ },
13851
+ ExecutionTaskFull: {
13852
+ type: 'object',
13853
+ properties: {
13854
+ nonce: {
13855
+ type: 'string',
13856
+ },
13857
+ taskId: {
13858
+ type: 'string',
13859
+ },
13860
+ taskType: {
13861
+ type: 'string',
13862
+ },
13863
+ status: {
13864
+ type: 'string',
13865
+ },
13866
+ errors: {
13867
+ type: 'array',
13868
+ items: {
13869
+ type: 'object',
13870
+ },
13871
+ },
13872
+ warnings: {
13873
+ type: 'array',
13874
+ items: {
13875
+ type: 'object',
13876
+ },
13877
+ },
13878
+ createdAt: {
13879
+ type: 'string',
13880
+ format: 'date-time',
13881
+ },
13882
+ updatedAt: {
13883
+ type: 'string',
13884
+ format: 'date-time',
13885
+ },
13886
+ currentValue: {
13887
+ type: 'object',
13888
+ },
13889
+ },
13890
+ },
13891
+ },
13892
+ },
13893
+ tags: [
13894
+ {
13895
+ name: 'Books',
13896
+ description: 'Operations related to books and pipelines',
13897
+ },
13898
+ {
13899
+ name: 'Executions',
13900
+ description: 'Operations related to execution tasks',
13901
+ },
13902
+ {
13903
+ name: 'Authentication',
13904
+ description: 'Authentication operations',
13905
+ },
13906
+ ],
13907
+ };
13908
+ /**
12942
13909
  * Note: [💞] Ignore a discrepancy between file name and entity name
12943
- * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
12944
- * TODO: [🖇] What about symlinks? Maybe flag --follow-symlinks
12945
13910
  */
12946
13911
 
12947
13912
  /**
@@ -12954,7 +13919,7 @@
12954
13919
  * @public exported from `@promptbook/remote-server`
12955
13920
  */
12956
13921
  function startRemoteServer(options) {
12957
- const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, login, } = {
13922
+ const { port, collection, createLlmExecutionTools, createExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, login, } = {
12958
13923
  isAnonymousModeAllowed: false,
12959
13924
  isApplicationModeAllowed: false,
12960
13925
  collection: null,
@@ -12962,22 +13927,6 @@
12962
13927
  login: null,
12963
13928
  ...options,
12964
13929
  };
12965
- // <- TODO: [🦪] Some helper type to be able to use discriminant union types with destructuring
12966
- let { rootPath = '/' } = options;
12967
- if (!rootPath.startsWith('/')) {
12968
- rootPath = `/${rootPath}`;
12969
- } /* not else */
12970
- if (rootPath.endsWith('/')) {
12971
- rootPath = rootPath.slice(0, -1);
12972
- } /* not else */
12973
- if (rootPath === '/') {
12974
- rootPath = '';
12975
- }
12976
- const socketioPath = '/' +
12977
- `${rootPath}/socket.io`
12978
- .split('/')
12979
- .filter((part) => part !== '')
12980
- .join('/');
12981
13930
  const startupDate = new Date();
12982
13931
  async function getExecutionToolsFromIdentification(identification) {
12983
13932
  if (identification === null || identification === undefined) {
@@ -13000,23 +13949,25 @@
13000
13949
  }
13001
13950
  else if (isAnonymous === false && createLlmExecutionTools !== null) {
13002
13951
  // Note: Application mode
13003
- const { appId, userId, customOptions } = identification;
13004
- llm = await createLlmExecutionTools({
13005
- appId,
13006
- userId,
13007
- customOptions,
13008
- });
13952
+ llm = await createLlmExecutionTools(identification);
13009
13953
  }
13010
13954
  else {
13011
13955
  throw new PipelineExecutionError(`You must provide either llmToolsConfiguration or non-anonymous mode must be propperly configured`);
13012
13956
  }
13013
- const fs = $provideFilesystemForNode();
13014
- const executables = await $provideExecutablesForNode();
13957
+ const customExecutionTools = createExecutionTools ? await createExecutionTools(identification) : {};
13958
+ const fs = customExecutionTools.fs || $provideFilesystemForNode();
13959
+ const executables = customExecutionTools.executables || (await $provideExecutablesForNode());
13960
+ const scrapers = customExecutionTools.scrapers || (await $provideScrapersForNode({ fs, llm, executables }));
13961
+ const script = customExecutionTools.script || (await $provideScriptingForNode({}));
13962
+ const fetch = customExecutionTools.fetch || promptbookFetch;
13963
+ const userInterface = customExecutionTools.userInterface || undefined;
13015
13964
  const tools = {
13016
13965
  llm,
13017
13966
  fs,
13018
- scrapers: await $provideScrapersForNode({ fs, llm, executables }),
13019
- script: await $provideScriptingForNode({}),
13967
+ scrapers,
13968
+ script,
13969
+ fetch,
13970
+ userInterface,
13020
13971
  };
13021
13972
  return tools;
13022
13973
  }
@@ -13026,39 +13977,27 @@
13026
13977
  response.setHeader('X-Powered-By', 'Promptbook engine');
13027
13978
  next();
13028
13979
  });
13029
- const swaggerOptions = {
13030
- definition: {
13031
- openapi: '3.0.0',
13032
- info: {
13033
- title: 'Promptbook Remote Server API',
13034
- version: '1.0.0',
13035
- description: 'API documentation for the Promptbook Remote Server',
13036
- },
13037
- servers: [
13038
- {
13039
- url: `http://localhost:${port}${rootPath}`,
13040
- // <- TODO: !!!!! Probbably: Pass `remoteServerUrl` instead of `port` and `rootPath`
13041
- },
13042
- ],
13980
+ // TODO: !!!! Expose openapiJson to consumer and also allow to add new routes
13981
+ app.use(OpenApiValidator__namespace.middleware({
13982
+ apiSpec: openapiJson,
13983
+ ignorePaths(path) {
13984
+ return path.startsWith('/api-docs') || path.startsWith('/swagger') || path.startsWith('/openapi');
13043
13985
  },
13044
- apis: ['./src/remote-server/**/*.ts'], // Adjust path as needed
13045
- };
13046
- const swaggerSpec = swaggerJsdoc__default["default"](swaggerOptions);
13047
- app.use([`/api-docs`, `${rootPath}/api-docs`], swaggerUi__default["default"].serve, swaggerUi__default["default"].setup(swaggerSpec));
13986
+ validateRequests: true,
13987
+ validateResponses: true,
13988
+ }));
13989
+ app.use([`/api-docs`, `/swagger`], swaggerUi__default["default"].serve, swaggerUi__default["default"].setup(openapiJson, {
13990
+ // customCss: '.swagger-ui .topbar { display: none }',
13991
+ // customSiteTitle: 'BRJ API',
13992
+ // customfavIcon: 'https://brj.app/favicon.ico',
13993
+ }));
13994
+ app.get(`/openapi`, (request, response) => {
13995
+ response.json(openapiJson);
13996
+ });
13048
13997
  const runningExecutionTasks = [];
13049
13998
  // <- TODO: [🤬] Identify the users
13050
13999
  // TODO: [🧠] Do here some garbage collection of finished tasks
13051
- /**
13052
- * @swagger
13053
- * /:
13054
- * get:
13055
- * summary: Get server details
13056
- * description: Returns details about the Promptbook server.
13057
- * responses:
13058
- * 200:
13059
- * description: Server details in markdown format.
13060
- */
13061
- app.get(['/', rootPath], async (request, response) => {
14000
+ app.get('/', async (request, response) => {
13062
14001
  var _a;
13063
14002
  if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.includes('socket.io')) {
13064
14003
  return;
@@ -13077,8 +14016,6 @@
13077
14016
  ## Details
13078
14017
 
13079
14018
  **Server port:** ${port}
13080
- **Server root path:** ${rootPath}
13081
- **Socket.io path:** ${socketioPath}
13082
14019
  **Startup date:** ${startupDate.toISOString()}
13083
14020
  **Anonymouse mode:** ${isAnonymousModeAllowed ? 'enabled' : 'disabled'}
13084
14021
  **Application mode:** ${isApplicationModeAllowed ? 'enabled' : 'disabled'}
@@ -13117,38 +14054,7 @@
13117
14054
  https://github.com/webgptorg/promptbook
13118
14055
  `));
13119
14056
  });
13120
- /**
13121
- * @swagger
13122
- *
13123
- * /login:
13124
- * post:
13125
- * summary: Login to the server
13126
- * description: Login to the server and get identification.
13127
- * requestBody:
13128
- * required: true
13129
- * content:
13130
- * application/json:
13131
- * schema:
13132
- * type: object
13133
- * properties:
13134
- * username:
13135
- * type: string
13136
- * password:
13137
- * type: string
13138
- * appId:
13139
- * type: string
13140
- * responses:
13141
- * 200:
13142
- * description: Successful login
13143
- * content:
13144
- * application/json:
13145
- * schema:
13146
- * type: object
13147
- * properties:
13148
- * identification:
13149
- * type: object
13150
- */
13151
- app.post([`/login`, `${rootPath}/login`], async (request, response) => {
14057
+ app.post(`/login`, async (request, response) => {
13152
14058
  if (!isApplicationModeAllowed || login === null) {
13153
14059
  response.status(400).send('Application mode is not allowed');
13154
14060
  return;
@@ -13173,9 +14079,7 @@
13173
14079
  return;
13174
14080
  }
13175
14081
  catch (error) {
13176
- if (!(error instanceof Error)) {
13177
- throw error;
13178
- }
14082
+ assertsError(error);
13179
14083
  if (error instanceof AuthenticationError) {
13180
14084
  response.status(401).send({
13181
14085
  isSuccess: false,
@@ -13190,23 +14094,7 @@
13190
14094
  response.status(400).send({ error: serializeError(error) });
13191
14095
  }
13192
14096
  });
13193
- /**
13194
- * @swagger
13195
- * /books:
13196
- * get:
13197
- * summary: List all books
13198
- * description: Returns a list of all available books in the collection.
13199
- * responses:
13200
- * 200:
13201
- * description: A list of books.
13202
- * content:
13203
- * application/json:
13204
- * schema:
13205
- * type: array
13206
- * items:
13207
- * type: string
13208
- */
13209
- app.get([`/books`, `${rootPath}/books`], async (request, response) => {
14097
+ app.get(`/books`, async (request, response) => {
13210
14098
  if (collection === null) {
13211
14099
  response.status(500).send('No collection available');
13212
14100
  return;
@@ -13216,30 +14104,7 @@
13216
14104
  response.send(pipelines);
13217
14105
  });
13218
14106
  // TODO: [🧠] Is it secure / good idea to expose source codes of hosted books
13219
- /**
13220
- * @swagger
13221
- * /books/{bookId}:
13222
- * get:
13223
- * summary: Get book content
13224
- * description: Returns the content of a specific book.
13225
- * parameters:
13226
- * - in: path
13227
- * name: bookId
13228
- * required: true
13229
- * schema:
13230
- * type: string
13231
- * description: The ID of the book to retrieve.
13232
- * responses:
13233
- * 200:
13234
- * description: The content of the book.
13235
- * content:
13236
- * text/markdown:
13237
- * schema:
13238
- * type: string
13239
- * 404:
13240
- * description: Book not found.
13241
- */
13242
- app.get([`/books/*`, `${rootPath}/books/*`], async (request, response) => {
14107
+ app.get(`/books/*`, async (request, response) => {
13243
14108
  try {
13244
14109
  if (collection === null) {
13245
14110
  response.status(500).send('No collection nor books available');
@@ -13258,9 +14123,7 @@
13258
14123
  .send(source.content);
13259
14124
  }
13260
14125
  catch (error) {
13261
- if (!(error instanceof Error)) {
13262
- throw error;
13263
- }
14126
+ assertsError(error);
13264
14127
  response
13265
14128
  .status(404)
13266
14129
  .send({ error: serializeError(error) });
@@ -13293,26 +14156,10 @@
13293
14156
  };
13294
14157
  }
13295
14158
  }
13296
- /**
13297
- * @swagger
13298
- * /executions:
13299
- * get:
13300
- * summary: List all executions
13301
- * description: Returns a list of all running execution tasks.
13302
- * responses:
13303
- * 200:
13304
- * description: A list of execution tasks.
13305
- * content:
13306
- * application/json:
13307
- * schema:
13308
- * type: array
13309
- * items:
13310
- * type: object
13311
- */
13312
- app.get([`/executions`, `${rootPath}/executions`], async (request, response) => {
13313
- response.send(runningExecutionTasks.map((runningExecutionTask) => exportExecutionTask(runningExecutionTask, false)));
14159
+ app.get(`/executions`, async (request, response) => {
14160
+ response.send(runningExecutionTasks.map((runningExecutionTask) => exportExecutionTask(runningExecutionTask, false)) /* <- TODO: satisfies paths['/executions']['get']['responses']['200']['content']['application/json'] */);
13314
14161
  });
13315
- app.get([`/executions/last`, `${rootPath}/executions/last`], async (request, response) => {
14162
+ app.get(`/executions/last`, async (request, response) => {
13316
14163
  // TODO: [🤬] Filter only for user
13317
14164
  if (runningExecutionTasks.length === 0) {
13318
14165
  response.status(404).send('No execution tasks found');
@@ -13321,7 +14168,7 @@
13321
14168
  const lastExecutionTask = runningExecutionTasks[runningExecutionTasks.length - 1];
13322
14169
  response.send(exportExecutionTask(lastExecutionTask, true));
13323
14170
  });
13324
- app.get([`/executions/:taskId`, `${rootPath}/executions/:taskId`], async (request, response) => {
14171
+ app.get(`/executions/:taskId`, async (request, response) => {
13325
14172
  const { taskId } = request.params;
13326
14173
  // TODO: [🤬] Filter only for user
13327
14174
  const executionTask = runningExecutionTasks.find((executionTask) => executionTask.taskId === taskId);
@@ -13333,39 +14180,12 @@
13333
14180
  }
13334
14181
  response.send(exportExecutionTask(executionTask, true));
13335
14182
  });
13336
- /**
13337
- * @swagger
13338
- * /executions/new:
13339
- * post:
13340
- * summary: Start a new execution
13341
- * description: Starts a new execution task for a given pipeline.
13342
- * requestBody:
13343
- * required: true
13344
- * content:
13345
- * application/json:
13346
- * schema:
13347
- * type: object
13348
- * properties:
13349
- * pipelineUrl:
13350
- * type: string
13351
- * inputParameters:
13352
- * type: object
13353
- * identification:
13354
- * type: object
13355
- * responses:
13356
- * 200:
13357
- * description: The newly created execution task.
13358
- * content:
13359
- * application/json:
13360
- * schema:
13361
- * type: object
13362
- * 400:
13363
- * description: Invalid input.
13364
- */
13365
- app.post([`/executions/new`, `${rootPath}/executions/new`], async (request, response) => {
14183
+ app.post(`/executions/new`, async (request, response) => {
13366
14184
  try {
13367
14185
  const { inputParameters, identification /* <- [🤬] */ } = request.body;
13368
- const pipelineUrl = request.body.pipelineUrl || request.body.book;
14186
+ const pipelineUrl = request.body
14187
+ .pipelineUrl /* <- TODO: as paths['/executions/new']['post']['requestBody']['content']['application/json'] */ ||
14188
+ request.body.book;
13369
14189
  // TODO: [🧠] Check `pipelineUrl` and `inputParameters` here or it should be responsibility of `collection.getPipelineByUrl` and `pipelineExecutor`
13370
14190
  const pipeline = await (collection === null || collection === void 0 ? void 0 : collection.getPipelineByUrl(pipelineUrl));
13371
14191
  if (pipeline === undefined) {
@@ -13379,7 +14199,7 @@
13379
14199
  await waitasecond.forTime(10);
13380
14200
  // <- Note: Wait for a while to wait for quick responses or sudden but asynchronous errors
13381
14201
  // <- TODO: Put this into configuration
13382
- response.send(executionTask);
14202
+ response.send(executionTask /* <- TODO: satisfies paths['/executions/new']['post']['responses']['200']['content']['application/json'] */);
13383
14203
  /*/
13384
14204
  executionTask.asObservable().subscribe({
13385
14205
  next(partialResult) {
@@ -13399,19 +14219,24 @@
13399
14219
  */
13400
14220
  }
13401
14221
  catch (error) {
13402
- if (!(error instanceof Error)) {
13403
- throw error;
13404
- }
14222
+ assertsError(error);
13405
14223
  response.status(400).send({ error: serializeError(error) });
13406
14224
  }
13407
14225
  });
14226
+ /**
14227
+ * Catch-all handler for unmatched routes
14228
+ */
14229
+ app.use((request, response) => {
14230
+ response.status(404).send(`URL "${request.originalUrl}" was not found on Promptbook server.`);
14231
+ });
13408
14232
  const httpServer = http__default["default"].createServer(app);
13409
14233
  const server = new socket_io.Server(httpServer, {
13410
- path: socketioPath,
13411
- transports: [/*'websocket', <- TODO: [🌬] Make websocket transport work */ 'polling'],
14234
+ path: '/socket.io',
14235
+ transports: ['polling', 'websocket' /*, <- TODO: [🌬] Allow to pass `transports`, add 'webtransport' */],
13412
14236
  cors: {
13413
14237
  origin: '*',
13414
14238
  methods: ['GET', 'POST'],
14239
+ // <- TODO: [🌡] Allow to pass
13415
14240
  },
13416
14241
  });
13417
14242
  server.on('connection', (socket) => {
@@ -13465,9 +14290,7 @@
13465
14290
  socket.emit('prompt-response', { promptResult } /* <- Note: [🤛] */);
13466
14291
  }
13467
14292
  catch (error) {
13468
- if (!(error instanceof Error)) {
13469
- throw error;
13470
- }
14293
+ assertsError(error);
13471
14294
  socket.emit('error', serializeError(error) /* <- Note: [🤛] */);
13472
14295
  }
13473
14296
  finally {
@@ -13489,9 +14312,7 @@
13489
14312
  socket.emit('listModels-response', { models } /* <- Note: [🤛] */);
13490
14313
  }
13491
14314
  catch (error) {
13492
- if (!(error instanceof Error)) {
13493
- throw error;
13494
- }
14315
+ assertsError(error);
13495
14316
  socket.emit('error', serializeError(error));
13496
14317
  }
13497
14318
  finally {
@@ -13512,9 +14333,7 @@
13512
14333
  socket.emit('preparePipeline-response', { preparedPipeline } /* <- Note: [🤛] */);
13513
14334
  }
13514
14335
  catch (error) {
13515
- if (!(error instanceof Error)) {
13516
- throw error;
13517
- }
14336
+ assertsError(error);
13518
14337
  socket.emit('error', serializeError(error));
13519
14338
  // <- TODO: [🚋] There is a problem with the remote server handling errors and sending them back to the client
13520
14339
  }
@@ -13562,8 +14381,7 @@
13562
14381
  };
13563
14382
  }
13564
14383
  /**
13565
- * TODO: !! Add CORS and security - probbably via `helmet`
13566
- * TODO: [👩🏾‍🤝‍🧑🏾] Allow to pass custom fetch function here - PromptbookFetch
14384
+ * TODO: [🌡] Add CORS and security - probbably via `helmet`
13567
14385
  * TODO: Split this file into multiple functions - handler for each request
13568
14386
  * TODO: Maybe use `$exportJson`
13569
14387
  * TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
@@ -13622,9 +14440,9 @@
13622
14440
  if (url !== null) {
13623
14441
  rootUrl = suffixUrl(url, '/books');
13624
14442
  }
13625
- let rootPath = '/';
13626
- if (url !== null) {
13627
- rootPath = url.pathname;
14443
+ if (url !== null && url.pathname !== '/' && url.pathname !== '') {
14444
+ console.error(colors__default["default"].red(`URL of the server can not have path, but got "${url.pathname}"`));
14445
+ process.exit(1);
13628
14446
  }
13629
14447
  // TODO: DRY [◽]
13630
14448
  const prepareAndScrapeOptions = {
@@ -13632,7 +14450,7 @@
13632
14450
  isCacheReloaded,
13633
14451
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
13634
14452
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
13635
- const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
14453
+ const { /* [0] strategy,*/ llm } = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13636
14454
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
13637
14455
  const tools = {
13638
14456
  llm,
@@ -13651,7 +14469,6 @@
13651
14469
  });
13652
14470
  // console.log(path, await collection.listPipelines());
13653
14471
  const server = startRemoteServer({
13654
- rootPath,
13655
14472
  port,
13656
14473
  isAnonymousModeAllowed,
13657
14474
  isApplicationModeAllowed: true,
@@ -13664,6 +14481,7 @@
13664
14481
  TODO_USE({ appId, userId });
13665
14482
  return llm;
13666
14483
  },
14484
+ // <- TODO: [🧠][0] Maybe pass here strategy
13667
14485
  });
13668
14486
  keepUnused(server);
13669
14487
  // Note: Already logged by `startRemoteServer`
@@ -13705,7 +14523,7 @@
13705
14523
  isCacheReloaded,
13706
14524
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
13707
14525
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
13708
- const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
14526
+ const { llm } = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13709
14527
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
13710
14528
  tools = {
13711
14529
  llm,
@@ -13730,7 +14548,7 @@
13730
14548
  }
13731
14549
  }
13732
14550
  if (filename.endsWith('.bookc')) {
13733
- pipeline = JSON.parse(await promises.readFile(filename, 'utf-8'));
14551
+ pipeline = jsonParse(await promises.readFile(filename, 'utf-8'));
13734
14552
  }
13735
14553
  else {
13736
14554
  if (isVerbose) {
@@ -13744,9 +14562,7 @@
13744
14562
  }
13745
14563
  }
13746
14564
  catch (error) {
13747
- if (!(error instanceof Error)) {
13748
- throw error;
13749
- }
14565
+ assertsError(error);
13750
14566
  console.info(colors__default["default"].red(`Pipeline is not valid ${filename}`));
13751
14567
  console.error(colors__default["default"].bgRed(`${error.name} in ${path.basename(__filename)}`));
13752
14568
  console.error(colors__default["default"].red(error.stack || error.message));
@@ -13841,6 +14657,37 @@
13841
14657
  * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
13842
14658
  */
13843
14659
 
14660
+ /**
14661
+ * How is the model provider trusted?
14662
+ *
14663
+ * @public exported from `@promptbook/core`
14664
+ */
14665
+ // <- TODO: Maybe do better levels of trust
14666
+ /**
14667
+ * How is the model provider important?
14668
+ *
14669
+ * @public exported from `@promptbook/core`
14670
+ */
14671
+ const MODEL_ORDER = {
14672
+ /**
14673
+ * Top-tier models, e.g. OpenAI, Anthropic,...
14674
+ */
14675
+ TOP_TIER: 333,
14676
+ /**
14677
+ * Mid-tier models, e.g. Llama, Mistral, etc.
14678
+ */
14679
+ NORMAL: 100,
14680
+ /**
14681
+ * Low-tier models, e.g. Phi, Tiny, etc.
14682
+ */
14683
+ LOW_TIER: 0,
14684
+ };
14685
+ /**
14686
+ * TODO: Add configuration schema and maybe some documentation link
14687
+ * TODO: Maybe constrain LlmToolsConfiguration[number] by generic to ensure that `createConfigurationFromEnv` and `getBoilerplateConfiguration` always create same `packageName` and `className`
14688
+ * TODO: [®] DRY Register logic
14689
+ */
14690
+
13844
14691
  /**
13845
14692
  * Registration of LLM provider metadata
13846
14693
  *
@@ -13855,9 +14702,11 @@
13855
14702
  packageName: '@promptbook/anthropic-claude',
13856
14703
  className: 'AnthropicClaudeExecutionTools',
13857
14704
  envVariables: ['ANTHROPIC_CLAUDE_API_KEY'],
14705
+ trustLevel: 'CLOSED',
14706
+ order: MODEL_ORDER.TOP_TIER,
13858
14707
  getBoilerplateConfiguration() {
13859
14708
  return {
13860
- title: 'Anthropic Claude (boilerplate)',
14709
+ title: 'Anthropic Claude',
13861
14710
  packageName: '@promptbook/anthropic-claude',
13862
14711
  className: 'AnthropicClaudeExecutionTools',
13863
14712
  options: {
@@ -13911,6 +14760,7 @@
13911
14760
  modelVariant: 'CHAT',
13912
14761
  modelTitle: 'Claude 3.5 Sonnet',
13913
14762
  modelName: 'claude-3-5-sonnet-20240620',
14763
+ modelDescription: 'Latest Claude model with great reasoning, coding, and language understanding capabilities. 200K context window. Optimized balance of intelligence and speed.',
13914
14764
  pricing: {
13915
14765
  prompt: computeUsage(`$3.00 / 1M tokens`),
13916
14766
  output: computeUsage(`$15.00 / 1M tokens`),
@@ -13920,6 +14770,7 @@
13920
14770
  modelVariant: 'CHAT',
13921
14771
  modelTitle: 'Claude 3 Opus',
13922
14772
  modelName: 'claude-3-opus-20240229',
14773
+ modelDescription: 'Most capable Claude model excelling at complex reasoning, coding, and detailed instruction following. 200K context window. Best for sophisticated tasks requiring nuanced understanding.',
13923
14774
  pricing: {
13924
14775
  prompt: computeUsage(`$15.00 / 1M tokens`),
13925
14776
  output: computeUsage(`$75.00 / 1M tokens`),
@@ -13929,6 +14780,7 @@
13929
14780
  modelVariant: 'CHAT',
13930
14781
  modelTitle: 'Claude 3 Sonnet',
13931
14782
  modelName: 'claude-3-sonnet-20240229',
14783
+ modelDescription: 'Strong general-purpose model with excellent performance across reasoning, conversation, and coding tasks. 200K context window. Good balance of intelligence and cost-efficiency.',
13932
14784
  pricing: {
13933
14785
  prompt: computeUsage(`$3.00 / 1M tokens`),
13934
14786
  output: computeUsage(`$15.00 / 1M tokens`),
@@ -13938,6 +14790,7 @@
13938
14790
  modelVariant: 'CHAT',
13939
14791
  modelTitle: 'Claude 3 Haiku',
13940
14792
  modelName: ' claude-3-haiku-20240307',
14793
+ modelDescription: 'Fastest and most compact Claude model optimized for responsiveness in interactive applications. 200K context window. Excellent for quick responses and lightweight applications.',
13941
14794
  pricing: {
13942
14795
  prompt: computeUsage(`$0.25 / 1M tokens`),
13943
14796
  output: computeUsage(`$1.25 / 1M tokens`),
@@ -13947,6 +14800,7 @@
13947
14800
  modelVariant: 'CHAT',
13948
14801
  modelTitle: 'Claude 2.1',
13949
14802
  modelName: 'claude-2.1',
14803
+ modelDescription: 'Improved version of Claude 2 with better performance across reasoning and truthfulness. 100K context window. Legacy model with strong reliability.',
13950
14804
  pricing: {
13951
14805
  prompt: computeUsage(`$8.00 / 1M tokens`),
13952
14806
  output: computeUsage(`$24.00 / 1M tokens`),
@@ -13956,6 +14810,7 @@
13956
14810
  modelVariant: 'CHAT',
13957
14811
  modelTitle: 'Claude 2',
13958
14812
  modelName: 'claude-2.0',
14813
+ modelDescription: 'Legacy model with strong general reasoning and language capabilities. 100K context window. Superseded by newer Claude 3 models.',
13959
14814
  pricing: {
13960
14815
  prompt: computeUsage(`$8.00 / 1M tokens`),
13961
14816
  output: computeUsage(`$24.00 / 1M tokens`),
@@ -13963,14 +14818,35 @@
13963
14818
  },
13964
14819
  {
13965
14820
  modelVariant: 'CHAT',
13966
- modelTitle: ' Claude Instant 1.2',
14821
+ modelTitle: 'Claude Instant 1.2',
13967
14822
  modelName: 'claude-instant-1.2',
14823
+ modelDescription: 'Older, faster Claude model optimized for high throughput applications. Lower cost but less capable than newer models. 100K context window.',
13968
14824
  pricing: {
13969
14825
  prompt: computeUsage(`$0.80 / 1M tokens`),
13970
14826
  output: computeUsage(`$2.40 / 1M tokens`),
13971
14827
  },
13972
14828
  },
13973
- // TODO: [main] !!3 Claude 1 and 2 has also completion versions - ask Hoagy
14829
+ {
14830
+ modelVariant: 'CHAT',
14831
+ modelTitle: 'Claude 3.7 Sonnet',
14832
+ modelName: 'claude-3-7-sonnet-20250219',
14833
+ modelDescription: 'Latest generation Claude model with advanced reasoning and language understanding. Enhanced capabilities over 3.5 with improved domain knowledge. 200K context window.',
14834
+ pricing: {
14835
+ prompt: computeUsage(`$3.00 / 1M tokens`),
14836
+ output: computeUsage(`$15.00 / 1M tokens`),
14837
+ },
14838
+ },
14839
+ {
14840
+ modelVariant: 'CHAT',
14841
+ modelTitle: 'Claude 3.5 Haiku',
14842
+ modelName: 'claude-3-5-haiku-20241022',
14843
+ modelDescription: 'Fast and efficient Claude 3.5 variant optimized for speed and cost-effectiveness. Great for interactive applications requiring quick responses. 200K context window.',
14844
+ pricing: {
14845
+ prompt: computeUsage(`$0.25 / 1M tokens`),
14846
+ output: computeUsage(`$1.25 / 1M tokens`),
14847
+ },
14848
+ },
14849
+ // <- [🕕]
13974
14850
  ],
13975
14851
  });
13976
14852
  /**
@@ -14366,9 +15242,11 @@
14366
15242
  packageName: '@promptbook/azure-openai',
14367
15243
  className: 'AzureOpenAiExecutionTools',
14368
15244
  envVariables: ['AZUREOPENAI_RESOURCE_NAME', 'AZUREOPENAI_DEPLOYMENT_NAME', 'AZUREOPENAI_API_KEY'],
15245
+ trustLevel: 'CLOSED_BUSINESS',
15246
+ order: MODEL_ORDER.NORMAL,
14369
15247
  getBoilerplateConfiguration() {
14370
15248
  return {
14371
- title: 'Azure Open AI (boilerplate)',
15249
+ title: 'Azure Open AI',
14372
15250
  packageName: '@promptbook/azure-openai',
14373
15251
  className: 'AzureOpenAiExecutionTools',
14374
15252
  options: {
@@ -14449,9 +15327,10 @@
14449
15327
  modelVariant: 'COMPLETION',
14450
15328
  modelTitle: 'davinci-002',
14451
15329
  modelName: 'davinci-002',
15330
+ modelDescription: 'Legacy completion model with strong performance on text generation tasks. Optimized for complex instructions and longer outputs.',
14452
15331
  pricing: {
14453
15332
  prompt: computeUsage(`$2.00 / 1M tokens`),
14454
- output: computeUsage(`$2.00 / 1M tokens`), // <- not sure
15333
+ output: computeUsage(`$2.00 / 1M tokens`),
14455
15334
  },
14456
15335
  },
14457
15336
  /**/
@@ -14466,6 +15345,7 @@
14466
15345
  modelVariant: 'CHAT',
14467
15346
  modelTitle: 'gpt-3.5-turbo-16k',
14468
15347
  modelName: 'gpt-3.5-turbo-16k',
15348
+ modelDescription: 'GPT-3.5 Turbo with extended 16k token context length for handling longer conversations and documents.',
14469
15349
  pricing: {
14470
15350
  prompt: computeUsage(`$3.00 / 1M tokens`),
14471
15351
  output: computeUsage(`$4.00 / 1M tokens`),
@@ -14489,6 +15369,7 @@
14489
15369
  modelVariant: 'CHAT',
14490
15370
  modelTitle: 'gpt-4',
14491
15371
  modelName: 'gpt-4',
15372
+ modelDescription: 'GPT-4 is a powerful language model with enhanced reasoning, instruction-following capabilities, and 8K context window. Optimized for complex tasks requiring deep understanding.',
14492
15373
  pricing: {
14493
15374
  prompt: computeUsage(`$30.00 / 1M tokens`),
14494
15375
  output: computeUsage(`$60.00 / 1M tokens`),
@@ -14500,6 +15381,7 @@
14500
15381
  modelVariant: 'CHAT',
14501
15382
  modelTitle: 'gpt-4-32k',
14502
15383
  modelName: 'gpt-4-32k',
15384
+ modelDescription: 'Extended context version of GPT-4 with a 32K token window for processing very long inputs and generating comprehensive responses for complex tasks.',
14503
15385
  pricing: {
14504
15386
  prompt: computeUsage(`$60.00 / 1M tokens`),
14505
15387
  output: computeUsage(`$120.00 / 1M tokens`),
@@ -14522,6 +15404,7 @@
14522
15404
  modelVariant: 'CHAT',
14523
15405
  modelTitle: 'gpt-4-turbo-2024-04-09',
14524
15406
  modelName: 'gpt-4-turbo-2024-04-09',
15407
+ modelDescription: 'Latest stable GPT-4 Turbo model from April 2024 with enhanced reasoning and context handling capabilities. Offers 128K context window and improved performance.',
14525
15408
  pricing: {
14526
15409
  prompt: computeUsage(`$10.00 / 1M tokens`),
14527
15410
  output: computeUsage(`$30.00 / 1M tokens`),
@@ -14533,6 +15416,7 @@
14533
15416
  modelVariant: 'CHAT',
14534
15417
  modelTitle: 'gpt-3.5-turbo-1106',
14535
15418
  modelName: 'gpt-3.5-turbo-1106',
15419
+ modelDescription: 'November 2023 version of GPT-3.5 Turbo with improved instruction following and a 16K token context window.',
14536
15420
  pricing: {
14537
15421
  prompt: computeUsage(`$1.00 / 1M tokens`),
14538
15422
  output: computeUsage(`$2.00 / 1M tokens`),
@@ -14544,6 +15428,7 @@
14544
15428
  modelVariant: 'CHAT',
14545
15429
  modelTitle: 'gpt-4-turbo',
14546
15430
  modelName: 'gpt-4-turbo',
15431
+ modelDescription: 'More capable model than GPT-4 with improved instruction following, function calling and a 128K token context window for handling very large documents.',
14547
15432
  pricing: {
14548
15433
  prompt: computeUsage(`$10.00 / 1M tokens`),
14549
15434
  output: computeUsage(`$30.00 / 1M tokens`),
@@ -14555,6 +15440,7 @@
14555
15440
  modelVariant: 'COMPLETION',
14556
15441
  modelTitle: 'gpt-3.5-turbo-instruct-0914',
14557
15442
  modelName: 'gpt-3.5-turbo-instruct-0914',
15443
+ modelDescription: 'September 2023 version of GPT-3.5 Turbo optimized for completion-style instruction following with a 4K context window.',
14558
15444
  pricing: {
14559
15445
  prompt: computeUsage(`$1.50 / 1M tokens`),
14560
15446
  output: computeUsage(`$2.00 / 1M tokens`), // <- For gpt-3.5-turbo-instruct
@@ -14566,6 +15452,7 @@
14566
15452
  modelVariant: 'COMPLETION',
14567
15453
  modelTitle: 'gpt-3.5-turbo-instruct',
14568
15454
  modelName: 'gpt-3.5-turbo-instruct',
15455
+ modelDescription: 'Optimized version of GPT-3.5 for completion-style API with good instruction following and a 4K token context window.',
14569
15456
  pricing: {
14570
15457
  prompt: computeUsage(`$1.50 / 1M tokens`),
14571
15458
  output: computeUsage(`$2.00 / 1M tokens`),
@@ -14583,9 +15470,10 @@
14583
15470
  modelVariant: 'CHAT',
14584
15471
  modelTitle: 'gpt-3.5-turbo',
14585
15472
  modelName: 'gpt-3.5-turbo',
15473
+ modelDescription: 'Latest version of GPT-3.5 Turbo with improved performance and instruction following capabilities. Default 4K context window with options for 16K.',
14586
15474
  pricing: {
14587
- prompt: computeUsage(`$3.00 / 1M tokens`),
14588
- output: computeUsage(`$6.00 / 1M tokens`), // <- Not sure, refer to gpt-3.5-turbo in Fine-tuning models
15475
+ prompt: computeUsage(`$0.50 / 1M tokens`),
15476
+ output: computeUsage(`$1.50 / 1M tokens`),
14589
15477
  },
14590
15478
  },
14591
15479
  /**/
@@ -14594,6 +15482,7 @@
14594
15482
  modelVariant: 'CHAT',
14595
15483
  modelTitle: 'gpt-3.5-turbo-0301',
14596
15484
  modelName: 'gpt-3.5-turbo-0301',
15485
+ modelDescription: 'March 2023 version of GPT-3.5 Turbo with a 4K token context window. Legacy model maintained for backward compatibility.',
14597
15486
  pricing: {
14598
15487
  prompt: computeUsage(`$1.50 / 1M tokens`),
14599
15488
  output: computeUsage(`$2.00 / 1M tokens`),
@@ -14605,9 +15494,10 @@
14605
15494
  modelVariant: 'COMPLETION',
14606
15495
  modelTitle: 'babbage-002',
14607
15496
  modelName: 'babbage-002',
15497
+ modelDescription: 'Efficient legacy completion model with a good balance of performance and speed. Suitable for straightforward text generation tasks.',
14608
15498
  pricing: {
14609
15499
  prompt: computeUsage(`$0.40 / 1M tokens`),
14610
- output: computeUsage(`$0.40 / 1M tokens`), // <- Not sure
15500
+ output: computeUsage(`$0.40 / 1M tokens`),
14611
15501
  },
14612
15502
  },
14613
15503
  /**/
@@ -14616,6 +15506,7 @@
14616
15506
  modelVariant: 'CHAT',
14617
15507
  modelTitle: 'gpt-4-1106-preview',
14618
15508
  modelName: 'gpt-4-1106-preview',
15509
+ modelDescription: 'November 2023 preview version of GPT-4 Turbo with improved instruction following and a 128K token context window.',
14619
15510
  pricing: {
14620
15511
  prompt: computeUsage(`$10.00 / 1M tokens`),
14621
15512
  output: computeUsage(`$30.00 / 1M tokens`),
@@ -14627,6 +15518,7 @@
14627
15518
  modelVariant: 'CHAT',
14628
15519
  modelTitle: 'gpt-4-0125-preview',
14629
15520
  modelName: 'gpt-4-0125-preview',
15521
+ modelDescription: 'January 2024 preview version of GPT-4 Turbo with improved reasoning capabilities and a 128K token context window.',
14630
15522
  pricing: {
14631
15523
  prompt: computeUsage(`$10.00 / 1M tokens`),
14632
15524
  output: computeUsage(`$30.00 / 1M tokens`),
@@ -14644,6 +15536,7 @@
14644
15536
  modelVariant: 'CHAT',
14645
15537
  modelTitle: 'gpt-3.5-turbo-0125',
14646
15538
  modelName: 'gpt-3.5-turbo-0125',
15539
+ modelDescription: 'January 2024 version of GPT-3.5 Turbo with improved reasoning capabilities and a 16K token context window.',
14647
15540
  pricing: {
14648
15541
  prompt: computeUsage(`$0.50 / 1M tokens`),
14649
15542
  output: computeUsage(`$1.50 / 1M tokens`),
@@ -14655,9 +15548,10 @@
14655
15548
  modelVariant: 'CHAT',
14656
15549
  modelTitle: 'gpt-4-turbo-preview',
14657
15550
  modelName: 'gpt-4-turbo-preview',
15551
+ modelDescription: 'Preview version of GPT-4 Turbo that points to the latest model version. Features improved instruction following, 128K token context window and lower latency.',
14658
15552
  pricing: {
14659
15553
  prompt: computeUsage(`$10.00 / 1M tokens`),
14660
- output: computeUsage(`$30.00 / 1M tokens`), // <- Not sure, just for gpt-4-turbo
15554
+ output: computeUsage(`$30.00 / 1M tokens`),
14661
15555
  },
14662
15556
  },
14663
15557
  /**/
@@ -14666,6 +15560,7 @@
14666
15560
  modelVariant: 'EMBEDDING',
14667
15561
  modelTitle: 'text-embedding-3-large',
14668
15562
  modelName: 'text-embedding-3-large',
15563
+ modelDescription: "OpenAI's most capable text embedding model designed for high-quality embeddings for complex similarity tasks and information retrieval.",
14669
15564
  pricing: {
14670
15565
  prompt: computeUsage(`$0.13 / 1M tokens`),
14671
15566
  // TODO: [🏏] Leverage the batch API @see https://platform.openai.com/docs/guides/batch
@@ -14678,6 +15573,7 @@
14678
15573
  modelVariant: 'EMBEDDING',
14679
15574
  modelTitle: 'text-embedding-3-small',
14680
15575
  modelName: 'text-embedding-3-small',
15576
+ modelDescription: 'Cost-effective embedding model with good performance for simpler tasks like text similarity and retrieval. Good balance of quality and efficiency.',
14681
15577
  pricing: {
14682
15578
  prompt: computeUsage(`$0.02 / 1M tokens`),
14683
15579
  // TODO: [🏏] Leverage the batch API @see https://platform.openai.com/docs/guides/batch
@@ -14690,6 +15586,7 @@
14690
15586
  modelVariant: 'CHAT',
14691
15587
  modelTitle: 'gpt-3.5-turbo-0613',
14692
15588
  modelName: 'gpt-3.5-turbo-0613',
15589
+ modelDescription: 'June 2023 version of GPT-3.5 Turbo with function calling capabilities and a 4K token context window.',
14693
15590
  pricing: {
14694
15591
  prompt: computeUsage(`$1.50 / 1M tokens`),
14695
15592
  output: computeUsage(`$2.00 / 1M tokens`),
@@ -14701,6 +15598,7 @@
14701
15598
  modelVariant: 'EMBEDDING',
14702
15599
  modelTitle: 'text-embedding-ada-002',
14703
15600
  modelName: 'text-embedding-ada-002',
15601
+ modelDescription: 'Legacy text embedding model suitable for text similarity and retrieval augmented generation use cases. Replaced by newer embedding-3 models.',
14704
15602
  pricing: {
14705
15603
  prompt: computeUsage(`$0.1 / 1M tokens`),
14706
15604
  // TODO: [🏏] Leverage the batch API @see https://platform.openai.com/docs/guides/batch
@@ -14731,11 +15629,11 @@
14731
15629
  modelVariant: 'CHAT',
14732
15630
  modelTitle: 'gpt-4o-2024-05-13',
14733
15631
  modelName: 'gpt-4o-2024-05-13',
15632
+ modelDescription: 'May 2024 version of GPT-4o with enhanced multimodal capabilities, improved reasoning, and optimized for vision, audio and chat at lower latencies.',
14734
15633
  pricing: {
14735
15634
  prompt: computeUsage(`$5.00 / 1M tokens`),
14736
15635
  output: computeUsage(`$15.00 / 1M tokens`),
14737
15636
  },
14738
- //TODO: [main] !!3 Add gpt-4o-mini-2024-07-18 and all others to be up to date
14739
15637
  },
14740
15638
  /**/
14741
15639
  /**/
@@ -14743,6 +15641,7 @@
14743
15641
  modelVariant: 'CHAT',
14744
15642
  modelTitle: 'gpt-4o',
14745
15643
  modelName: 'gpt-4o',
15644
+ modelDescription: "OpenAI's most advanced multimodal model optimized for performance, speed, and cost. Capable of vision, reasoning, and high quality text generation.",
14746
15645
  pricing: {
14747
15646
  prompt: computeUsage(`$5.00 / 1M tokens`),
14748
15647
  output: computeUsage(`$15.00 / 1M tokens`),
@@ -14750,10 +15649,23 @@
14750
15649
  },
14751
15650
  /**/
14752
15651
  /**/
15652
+ {
15653
+ modelVariant: 'CHAT',
15654
+ modelTitle: 'gpt-4o-mini',
15655
+ modelName: 'gpt-4o-mini',
15656
+ modelDescription: 'Smaller, more cost-effective version of GPT-4o with good performance across text, vision, and audio tasks at reduced complexity.',
15657
+ pricing: {
15658
+ prompt: computeUsage(`$3.00 / 1M tokens`),
15659
+ output: computeUsage(`$9.00 / 1M tokens`),
15660
+ },
15661
+ },
15662
+ /**/
15663
+ /**/
14753
15664
  {
14754
15665
  modelVariant: 'CHAT',
14755
15666
  modelTitle: 'o1-preview',
14756
15667
  modelName: 'o1-preview',
15668
+ modelDescription: 'Advanced reasoning model with exceptional performance on complex logical, mathematical, and analytical tasks. Built for deep reasoning and specialized professional tasks.',
14757
15669
  pricing: {
14758
15670
  prompt: computeUsage(`$15.00 / 1M tokens`),
14759
15671
  output: computeUsage(`$60.00 / 1M tokens`),
@@ -14765,6 +15677,7 @@
14765
15677
  modelVariant: 'CHAT',
14766
15678
  modelTitle: 'o1-preview-2024-09-12',
14767
15679
  modelName: 'o1-preview-2024-09-12',
15680
+ modelDescription: 'September 2024 version of O1 preview with specialized reasoning capabilities for complex tasks requiring precise analytical thinking.',
14768
15681
  // <- TODO: [💩] Some better system to organize theese date suffixes and versions
14769
15682
  pricing: {
14770
15683
  prompt: computeUsage(`$15.00 / 1M tokens`),
@@ -14777,6 +15690,7 @@
14777
15690
  modelVariant: 'CHAT',
14778
15691
  modelTitle: 'o1-mini',
14779
15692
  modelName: 'o1-mini',
15693
+ modelDescription: 'Smaller, cost-effective version of the O1 model with good performance on reasoning tasks while maintaining efficiency for everyday analytical use.',
14780
15694
  pricing: {
14781
15695
  prompt: computeUsage(`$3.00 / 1M tokens`),
14782
15696
  output: computeUsage(`$12.00 / 1M tokens`),
@@ -14788,10 +15702,10 @@
14788
15702
  modelVariant: 'CHAT',
14789
15703
  modelTitle: 'o1',
14790
15704
  modelName: 'o1',
15705
+ modelDescription: "OpenAI's advanced reasoning model focused on logic and problem-solving. Designed for complex analytical tasks with rigorous step-by-step reasoning. 128K context window.",
14791
15706
  pricing: {
14792
- prompt: computeUsage(`$3.00 / 1M tokens`),
14793
- output: computeUsage(`$12.00 / 1M tokens`),
14794
- // <- TODO: !! Unsure, check the pricing
15707
+ prompt: computeUsage(`$15.00 / 1M tokens`),
15708
+ output: computeUsage(`$60.00 / 1M tokens`),
14795
15709
  },
14796
15710
  },
14797
15711
  /**/
@@ -14800,6 +15714,7 @@
14800
15714
  modelVariant: 'CHAT',
14801
15715
  modelTitle: 'o3-mini',
14802
15716
  modelName: 'o3-mini',
15717
+ modelDescription: 'Cost-effective reasoning model optimized for academic and scientific problem-solving. Efficient performance on STEM tasks with deep mathematical and scientific knowledge. 128K context window.',
14803
15718
  pricing: {
14804
15719
  prompt: computeUsage(`$3.00 / 1M tokens`),
14805
15720
  output: computeUsage(`$12.00 / 1M tokens`),
@@ -14812,6 +15727,7 @@
14812
15727
  modelVariant: 'CHAT',
14813
15728
  modelTitle: 'o1-mini-2024-09-12',
14814
15729
  modelName: 'o1-mini-2024-09-12',
15730
+ modelDescription: "September 2024 version of O1-mini with balanced reasoning capabilities and cost-efficiency. Good for analytical tasks that don't require the full O1 model.",
14815
15731
  pricing: {
14816
15732
  prompt: computeUsage(`$3.00 / 1M tokens`),
14817
15733
  output: computeUsage(`$12.00 / 1M tokens`),
@@ -14823,12 +15739,14 @@
14823
15739
  modelVariant: 'CHAT',
14824
15740
  modelTitle: 'gpt-3.5-turbo-16k-0613',
14825
15741
  modelName: 'gpt-3.5-turbo-16k-0613',
15742
+ modelDescription: 'June 2023 version of GPT-3.5 Turbo with extended 16k token context window for processing longer conversations and documents.',
14826
15743
  pricing: {
14827
15744
  prompt: computeUsage(`$3.00 / 1M tokens`),
14828
15745
  output: computeUsage(`$4.00 / 1M tokens`),
14829
15746
  },
14830
15747
  },
14831
15748
  /**/
15749
+ // <- [🕕]
14832
15750
  ],
14833
15751
  });
14834
15752
  /**
@@ -14846,6 +15764,9 @@
14846
15764
  * Note: [💞] Ignore a discrepancy between file name and entity name
14847
15765
  */
14848
15766
 
15767
+ // Default rate limits (requests per minute) - adjust as needed based on Azure OpenAI tier
15768
+ const DEFAULT_RPM$1 = 60;
15769
+ // <- TODO: !!! Put in some better place
14849
15770
  /**
14850
15771
  * Execution Tools for calling Azure OpenAI API.
14851
15772
  *
@@ -14863,6 +15784,10 @@
14863
15784
  * OpenAI Azure API client.
14864
15785
  */
14865
15786
  this.client = null;
15787
+ // TODO: Allow configuring rate limits via options
15788
+ this.limiter = new Bottleneck__default["default"]({
15789
+ minTime: 60000 / (this.options.maxRequestsPerMinute || DEFAULT_RPM$1),
15790
+ });
14866
15791
  }
14867
15792
  get title() {
14868
15793
  return 'Azure OpenAI';
@@ -14940,7 +15865,9 @@
14940
15865
  console.info(colors__default["default"].bgWhite('messages'), JSON.stringify(messages, null, 4));
14941
15866
  }
14942
15867
  const rawRequest = [modelName, messages, modelSettings];
14943
- const rawResponse = await this.withTimeout(client.getChatCompletions(...rawRequest)).catch((error) => {
15868
+ const rawResponse = await this.limiter
15869
+ .schedule(() => this.withTimeout(client.getChatCompletions(...rawRequest)))
15870
+ .catch((error) => {
14944
15871
  if (this.options.isVerbose) {
14945
15872
  console.info(colors__default["default"].bgRed('error'), error);
14946
15873
  }
@@ -15036,7 +15963,9 @@
15036
15963
  [rawPromptContent],
15037
15964
  modelSettings,
15038
15965
  ];
15039
- const rawResponse = await this.withTimeout(client.getCompletions(...rawRequest)).catch((error) => {
15966
+ const rawResponse = await this.limiter
15967
+ .schedule(() => this.withTimeout(client.getCompletions(...rawRequest)))
15968
+ .catch((error) => {
15040
15969
  if (this.options.isVerbose) {
15041
15970
  console.info(colors__default["default"].bgRed('error'), error);
15042
15971
  }
@@ -15176,9 +16105,11 @@
15176
16105
  packageName: '@promptbook/deepseek',
15177
16106
  className: 'DeepseekExecutionTools',
15178
16107
  envVariables: ['DEEPSEEK_GENERATIVE_AI_API_KEY'],
16108
+ trustLevel: 'UNTRUSTED',
16109
+ order: MODEL_ORDER.NORMAL,
15179
16110
  getBoilerplateConfiguration() {
15180
16111
  return {
15181
- title: 'Deepseek (boilerplate)',
16112
+ title: 'Deepseek',
15182
16113
  packageName: '@promptbook/deepseek',
15183
16114
  className: 'DeepseekExecutionTools',
15184
16115
  options: {
@@ -15375,6 +16306,67 @@
15375
16306
  };
15376
16307
  }
15377
16308
 
16309
+ /**
16310
+ * List of available Deepseek models with descriptions
16311
+ *
16312
+ * Note: Done at 2025-04-22
16313
+ *
16314
+ * @see https://www.deepseek.com/models
16315
+ * @public exported from `@promptbook/deepseek`
16316
+ */
16317
+ const DEEPSEEK_MODELS = exportJson({
16318
+ name: 'DEEPSEEK_MODELS',
16319
+ value: [
16320
+ {
16321
+ modelVariant: 'CHAT',
16322
+ modelTitle: 'Deepseek Chat',
16323
+ modelName: 'deepseek-chat',
16324
+ modelDescription: 'General-purpose language model with strong performance across conversation, reasoning, and content generation. 128K context window with excellent instruction following capabilities.',
16325
+ pricing: {
16326
+ prompt: computeUsage(`$1.00 / 1M tokens`),
16327
+ output: computeUsage(`$2.00 / 1M tokens`),
16328
+ },
16329
+ },
16330
+ {
16331
+ modelVariant: 'CHAT',
16332
+ modelTitle: 'Deepseek Reasoner',
16333
+ modelName: 'deepseek-reasoner',
16334
+ modelDescription: 'Specialized model focused on complex reasoning tasks like mathematical problem-solving and logical analysis. Enhanced step-by-step reasoning with explicit chain-of-thought processes. 128K context window.',
16335
+ pricing: {
16336
+ prompt: computeUsage(`$4.00 / 1M tokens`),
16337
+ output: computeUsage(`$8.00 / 1M tokens`),
16338
+ },
16339
+ },
16340
+ {
16341
+ modelVariant: 'CHAT',
16342
+ modelTitle: 'DeepSeek V3',
16343
+ modelName: 'deepseek-v3-0324',
16344
+ modelDescription: 'Advanced general-purpose model with improved reasoning, coding abilities, and multimodal understanding. Built on the latest DeepSeek architecture with enhanced knowledge representation.',
16345
+ pricing: {
16346
+ prompt: computeUsage(`$1.50 / 1M tokens`),
16347
+ output: computeUsage(`$3.00 / 1M tokens`),
16348
+ },
16349
+ },
16350
+ {
16351
+ modelVariant: 'CHAT',
16352
+ modelTitle: 'DeepSeek R1',
16353
+ modelName: 'deepseek-r1',
16354
+ modelDescription: 'Research-focused model optimized for scientific problem-solving and analytical tasks. Excellent performance on tasks requiring domain-specific expertise and critical thinking.',
16355
+ pricing: {
16356
+ prompt: computeUsage(`$5.00 / 1M tokens`),
16357
+ output: computeUsage(`$10.00 / 1M tokens`),
16358
+ },
16359
+ },
16360
+ // <- [🕕]
16361
+ ],
16362
+ });
16363
+ /**
16364
+ * TODO: [🧠] Add information about context window sizes, capabilities, and relative performance characteristics
16365
+ * TODO: [🎰] Some mechanism to auto-update available models
16366
+ * TODO: [🧠] Verify pricing information is current with Deepseek's official documentation
16367
+ * Note: [💞] Ignore a discrepancy between file name and entity name
16368
+ */
16369
+
15378
16370
  /**
15379
16371
  * Execution Tools for calling Deepseek API.
15380
16372
  *
@@ -15396,12 +16388,7 @@
15396
16388
  title: 'Deepseek',
15397
16389
  description: 'Implementation of Deepseek models',
15398
16390
  vercelProvider: deepseekVercelProvider,
15399
- availableModels: [
15400
- // TODO: [🕘] Maybe list models in same way as in other providers - in separate file with metadata
15401
- 'deepseek-chat',
15402
- 'deepseek-reasoner',
15403
- // <- TODO: How picking of the default model looks like in `createExecutionToolsFromVercelProvider`
15404
- ].map((modelName) => ({ modelName, modelVariant: 'CHAT' })),
16391
+ availableModels: DEEPSEEK_MODELS,
15405
16392
  ...options,
15406
16393
  });
15407
16394
  }, {
@@ -15441,9 +16428,11 @@
15441
16428
  packageName: '@promptbook/google',
15442
16429
  className: 'GoogleExecutionTools',
15443
16430
  envVariables: ['GOOGLE_GENERATIVE_AI_API_KEY'],
16431
+ trustLevel: 'CLOSED',
16432
+ order: MODEL_ORDER.NORMAL,
15444
16433
  getBoilerplateConfiguration() {
15445
16434
  return {
15446
- title: 'Google Gemini (boilerplate)',
16435
+ title: 'Google Gemini',
15447
16436
  packageName: '@promptbook/google',
15448
16437
  className: 'GoogleExecutionTools',
15449
16438
  options: {
@@ -15476,6 +16465,173 @@
15476
16465
  * Note: [💞] Ignore a discrepancy between file name and entity name
15477
16466
  */
15478
16467
 
16468
+ /**
16469
+ * List of available Google models with descriptions
16470
+ *
16471
+ * Note: Done at 2025-04-22
16472
+ *
16473
+ * @see https://ai.google.dev/models/gemini
16474
+ * @public exported from `@promptbook/google`
16475
+ */
16476
+ const GOOGLE_MODELS = exportJson({
16477
+ name: 'GOOGLE_MODELS',
16478
+ value: [
16479
+ {
16480
+ modelVariant: 'CHAT',
16481
+ modelTitle: 'Gemini 2.5 Pro',
16482
+ modelName: 'gemini-2.5-pro-preview-03-25',
16483
+ modelDescription: 'Latest advanced multimodal model with exceptional reasoning, tool use, and instruction following. 1M token context window with improved vision capabilities for complex visual tasks.',
16484
+ pricing: {
16485
+ prompt: computeUsage(`$7.00 / 1M tokens`),
16486
+ output: computeUsage(`$21.00 / 1M tokens`),
16487
+ },
16488
+ },
16489
+ {
16490
+ modelVariant: 'CHAT',
16491
+ modelTitle: 'Gemini 2.0 Flash',
16492
+ modelName: 'gemini-2.0-flash',
16493
+ modelDescription: 'Fast, efficient model optimized for rapid response times. Good balance between performance and cost, with strong capabilities across text, code, and reasoning tasks. 128K context window.',
16494
+ pricing: {
16495
+ prompt: computeUsage(`$0.35 / 1M tokens`),
16496
+ output: computeUsage(`$1.05 / 1M tokens`),
16497
+ },
16498
+ },
16499
+ {
16500
+ modelVariant: 'CHAT',
16501
+ modelTitle: 'Gemini 2.0 Flash Lite',
16502
+ modelName: 'gemini-2.0-flash-lite',
16503
+ modelDescription: 'Streamlined version of Gemini 2.0 Flash, designed for extremely low-latency applications and edge deployments. Optimized for efficiency while maintaining core capabilities.',
16504
+ pricing: {
16505
+ prompt: computeUsage(`$0.20 / 1M tokens`),
16506
+ output: computeUsage(`$0.60 / 1M tokens`),
16507
+ },
16508
+ },
16509
+ {
16510
+ modelVariant: 'CHAT',
16511
+ modelTitle: 'Gemini 2.0 Flash Thinking',
16512
+ modelName: 'gemini-2.0-flash-thinking-exp-01-21',
16513
+ modelDescription: 'Experimental model focused on enhanced reasoning with explicit chain-of-thought processes. Designed for tasks requiring structured thinking and problem-solving approaches.',
16514
+ pricing: {
16515
+ prompt: computeUsage(`$0.35 / 1M tokens`),
16516
+ output: computeUsage(`$1.05 / 1M tokens`),
16517
+ },
16518
+ },
16519
+ {
16520
+ modelVariant: 'CHAT',
16521
+ modelTitle: 'Gemini 1.5 Flash',
16522
+ modelName: 'gemini-1.5-flash',
16523
+ modelDescription: 'Efficient model balancing speed and quality for general-purpose applications. 1M token context window with good multimodal capabilities and quick response times.',
16524
+ pricing: {
16525
+ prompt: computeUsage(`$0.35 / 1M tokens`),
16526
+ output: computeUsage(`$1.05 / 1M tokens`),
16527
+ },
16528
+ },
16529
+ {
16530
+ modelVariant: 'CHAT',
16531
+ modelTitle: 'Gemini 1.5 Flash Latest',
16532
+ modelName: 'gemini-1.5-flash-latest',
16533
+ modelDescription: 'Points to the latest version of Gemini 1.5 Flash, ensuring access to the most recent improvements and bug fixes while maintaining stable interfaces.',
16534
+ },
16535
+ {
16536
+ modelVariant: 'CHAT',
16537
+ modelTitle: 'Gemini 1.5 Flash 001',
16538
+ modelName: 'gemini-1.5-flash-001',
16539
+ modelDescription: 'First stable release of Gemini 1.5 Flash model with reliable performance characteristics for production applications. 1M token context window.',
16540
+ },
16541
+ {
16542
+ modelVariant: 'CHAT',
16543
+ modelTitle: 'Gemini 1.5 Flash 002',
16544
+ modelName: 'gemini-1.5-flash-002',
16545
+ modelDescription: 'Improved version of Gemini 1.5 Flash with enhanced instruction following and more consistent outputs. Refined for better application integration.',
16546
+ },
16547
+ {
16548
+ modelVariant: 'CHAT',
16549
+ modelTitle: 'Gemini 1.5 Flash Exp',
16550
+ modelName: 'gemini-1.5-flash-exp-0827',
16551
+ modelDescription: 'Experimental version of Gemini 1.5 Flash with new capabilities being tested. May offer improved performance but with potential behavior differences from stable releases.',
16552
+ },
16553
+ {
16554
+ modelVariant: 'CHAT',
16555
+ modelTitle: 'Gemini 1.5 Flash 8B',
16556
+ modelName: 'gemini-1.5-flash-8b',
16557
+ modelDescription: 'Compact 8B parameter model optimized for efficiency and deployment in resource-constrained environments. Good performance despite smaller size.',
16558
+ },
16559
+ {
16560
+ modelVariant: 'CHAT',
16561
+ modelTitle: 'Gemini 1.5 Flash 8B Latest',
16562
+ modelName: 'gemini-1.5-flash-8b-latest',
16563
+ modelDescription: 'Points to the most recent version of the compact 8B parameter model, providing latest improvements while maintaining a small footprint.',
16564
+ },
16565
+ {
16566
+ modelVariant: 'CHAT',
16567
+ modelTitle: 'Gemini 1.5 Flash 8B Exp',
16568
+ modelName: 'gemini-1.5-flash-8b-exp-0924',
16569
+ modelDescription: 'Experimental version of the 8B parameter model with new capabilities and optimizations being evaluated for future stable releases.',
16570
+ },
16571
+ {
16572
+ modelVariant: 'CHAT',
16573
+ modelTitle: 'Gemini 1.5 Flash 8B Exp',
16574
+ modelName: 'gemini-1.5-flash-8b-exp-0827',
16575
+ modelDescription: 'August experimental release of the efficient 8B parameter model with specific improvements to reasoning capabilities and response quality.',
16576
+ },
16577
+ {
16578
+ modelVariant: 'CHAT',
16579
+ modelTitle: 'Gemini 1.5 Pro Latest',
16580
+ modelName: 'gemini-1.5-pro-latest',
16581
+ modelDescription: 'Points to the most recent version of the flagship Gemini 1.5 Pro model, ensuring access to the latest capabilities and improvements.',
16582
+ pricing: {
16583
+ prompt: computeUsage(`$7.00 / 1M tokens`),
16584
+ output: computeUsage(`$21.00 / 1M tokens`),
16585
+ },
16586
+ },
16587
+ {
16588
+ modelVariant: 'CHAT',
16589
+ modelTitle: 'Gemini 1.5 Pro',
16590
+ modelName: 'gemini-1.5-pro',
16591
+ modelDescription: 'Flagship multimodal model with strong performance across text, code, vision, and audio tasks. 1M token context window with excellent reasoning capabilities.',
16592
+ pricing: {
16593
+ prompt: computeUsage(`$7.00 / 1M tokens`),
16594
+ output: computeUsage(`$21.00 / 1M tokens`),
16595
+ },
16596
+ },
16597
+ {
16598
+ modelVariant: 'CHAT',
16599
+ modelTitle: 'Gemini 1.5 Pro 001',
16600
+ modelName: 'gemini-1.5-pro-001',
16601
+ modelDescription: 'First stable release of Gemini 1.5 Pro with consistent performance characteristics and reliable behavior for production applications.',
16602
+ },
16603
+ {
16604
+ modelVariant: 'CHAT',
16605
+ modelTitle: 'Gemini 1.5 Pro 002',
16606
+ modelName: 'gemini-1.5-pro-002',
16607
+ modelDescription: 'Refined version of Gemini 1.5 Pro with improved instruction following, better multimodal understanding, and more consistent outputs.',
16608
+ },
16609
+ {
16610
+ modelVariant: 'CHAT',
16611
+ modelTitle: 'Gemini 1.5 Pro Exp',
16612
+ modelName: 'gemini-1.5-pro-exp-0827',
16613
+ modelDescription: 'Experimental version of Gemini 1.5 Pro with new capabilities and optimizations being tested before wider release. May offer improved performance.',
16614
+ },
16615
+ {
16616
+ modelVariant: 'CHAT',
16617
+ modelTitle: 'Gemini 1.0 Pro',
16618
+ modelName: 'gemini-1.0-pro',
16619
+ modelDescription: 'Original Gemini series foundation model with solid multimodal capabilities. 32K context window with good performance on text, code, and basic vision tasks.',
16620
+ pricing: {
16621
+ prompt: computeUsage(`$0.35 / 1M tokens`),
16622
+ output: computeUsage(`$1.05 / 1M tokens`),
16623
+ },
16624
+ },
16625
+ // <- [🕕]
16626
+ ],
16627
+ });
16628
+ /**
16629
+ * TODO: [🧠] Add information about context window sizes, capabilities, and relative performance characteristics
16630
+ * TODO: [🎰] Some mechanism to auto-update available models
16631
+ * TODO: [🧠] Verify pricing information is current with Google's official documentation
16632
+ * Note: [💞] Ignore a discrepancy between file name and entity name
16633
+ */
16634
+
15479
16635
  /**
15480
16636
  * Execution Tools for calling Google Gemini API.
15481
16637
  *
@@ -15497,24 +16653,7 @@
15497
16653
  title: 'Google',
15498
16654
  description: 'Implementation of Google models',
15499
16655
  vercelProvider: googleGeminiVercelProvider,
15500
- availableModels: [
15501
- // TODO: [🕘] Maybe list models in same way as in other providers - in separate file with metadata
15502
- 'gemini-1.5-flash',
15503
- 'gemini-1.5-flash-latest',
15504
- 'gemini-1.5-flash-001',
15505
- 'gemini-1.5-flash-002',
15506
- 'gemini-1.5-flash-exp-0827',
15507
- 'gemini-1.5-flash-8b',
15508
- 'gemini-1.5-flash-8b-latest',
15509
- 'gemini-1.5-flash-8b-exp-0924',
15510
- 'gemini-1.5-flash-8b-exp-0827',
15511
- 'gemini-1.5-pro-latest',
15512
- 'gemini-1.5-pro',
15513
- 'gemini-1.5-pro-001',
15514
- 'gemini-1.5-pro-002',
15515
- 'gemini-1.5-pro-exp-0827',
15516
- 'gemini-1.0-pro',
15517
- ].map((modelName) => ({ modelName, modelVariant: 'CHAT' })),
16656
+ availableModels: GOOGLE_MODELS,
15518
16657
  ...options,
15519
16658
  });
15520
16659
  }, {
@@ -15554,9 +16693,11 @@
15554
16693
  packageName: '@promptbook/openai',
15555
16694
  className: 'OpenAiExecutionTools',
15556
16695
  envVariables: ['OPENAI_API_KEY'],
16696
+ trustLevel: 'CLOSED',
16697
+ order: MODEL_ORDER.TOP_TIER,
15557
16698
  getBoilerplateConfiguration() {
15558
16699
  return {
15559
- title: 'Open AI (boilerplate)',
16700
+ title: 'Open AI',
15560
16701
  packageName: '@promptbook/openai',
15561
16702
  className: 'OpenAiExecutionTools',
15562
16703
  options: {
@@ -15594,9 +16735,11 @@
15594
16735
  className: 'OpenAiAssistantExecutionTools',
15595
16736
  envVariables: null,
15596
16737
  // <- TODO: ['OPENAI_API_KEY', 'OPENAI_ASSISTANT_ID']
16738
+ trustLevel: 'CLOSED',
16739
+ order: MODEL_ORDER.NORMAL,
15597
16740
  getBoilerplateConfiguration() {
15598
16741
  return {
15599
- title: 'Open AI Assistant (boilerplate)',
16742
+ title: 'Open AI Assistant',
15600
16743
  packageName: '@promptbook/openai',
15601
16744
  className: 'OpenAiAssistantExecutionTools',
15602
16745
  options: {
@@ -15672,6 +16815,9 @@
15672
16815
  * TODO: [🤝] DRY Maybe some common abstraction between `computeOpenAiUsage` and `computeAnthropicClaudeUsage`
15673
16816
  */
15674
16817
 
16818
+ // Default rate limits (requests per minute) - adjust as needed based on OpenAI tier
16819
+ const DEFAULT_RPM = 60;
16820
+ // <- TODO: !!! Put in some better place
15675
16821
  /**
15676
16822
  * Execution Tools for calling OpenAI API
15677
16823
  *
@@ -15689,6 +16835,10 @@
15689
16835
  * OpenAI API client.
15690
16836
  */
15691
16837
  this.client = null;
16838
+ // TODO: Allow configuring rate limits via options
16839
+ this.limiter = new Bottleneck__default["default"]({
16840
+ minTime: 60000 / (this.options.maxRequestsPerMinute || DEFAULT_RPM),
16841
+ });
15692
16842
  }
15693
16843
  get title() {
15694
16844
  return 'OpenAI';
@@ -15792,7 +16942,10 @@
15792
16942
  if (this.options.isVerbose) {
15793
16943
  console.info(colors__default["default"].bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
15794
16944
  }
15795
- const rawResponse = await client.chat.completions.create(rawRequest).catch((error) => {
16945
+ const rawResponse = await this.limiter
16946
+ .schedule(() => client.chat.completions.create(rawRequest))
16947
+ .catch((error) => {
16948
+ assertsError(error);
15796
16949
  if (this.options.isVerbose) {
15797
16950
  console.info(colors__default["default"].bgRed('error'), error);
15798
16951
  }
@@ -15868,7 +17021,10 @@
15868
17021
  if (this.options.isVerbose) {
15869
17022
  console.info(colors__default["default"].bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
15870
17023
  }
15871
- const rawResponse = await client.completions.create(rawRequest).catch((error) => {
17024
+ const rawResponse = await this.limiter
17025
+ .schedule(() => client.completions.create(rawRequest))
17026
+ .catch((error) => {
17027
+ assertsError(error);
15872
17028
  if (this.options.isVerbose) {
15873
17029
  console.info(colors__default["default"].bgRed('error'), error);
15874
17030
  }
@@ -15931,7 +17087,10 @@
15931
17087
  if (this.options.isVerbose) {
15932
17088
  console.info(colors__default["default"].bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
15933
17089
  }
15934
- const rawResponse = await client.embeddings.create(rawRequest).catch((error) => {
17090
+ const rawResponse = await this.limiter
17091
+ .schedule(() => client.embeddings.create(rawRequest))
17092
+ .catch((error) => {
17093
+ assertsError(error);
15935
17094
  if (this.options.isVerbose) {
15936
17095
  console.info(colors__default["default"].bgRed('error'), error);
15937
17096
  }