@promptbook/cli 0.89.0-5 → 0.89.0-7

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.
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('path'), require('fs/promises'), require('crypto-js/enc-hex'), require('crypto-js/sha256'), require('crypto'), 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('prompts'), require('moment'), require('express'), require('http'), require('socket.io'), require('socket.io-client'), 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', 'path', 'fs/promises', 'crypto-js/enc-hex', 'crypto-js/sha256', 'crypto', 'rxjs', 'dotenv', 'child_process', 'jszip', 'prettier', 'prettier/parser-html', 'papaparse', 'crypto-js', 'mime-types', 'glob-promise', 'prompts', 'moment', 'express', 'http', 'socket.io', 'socket.io-client', '@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.path, global.promises, global.hexEncoder, global.sha256, global.crypto, global.rxjs, global.dotenv, global.child_process, global.JSZip, global.prettier, global.parserHtml, global.papaparse, global.cryptoJs, global.mimeTypes, global.glob, global.prompts, global.moment, global.express, global.http, global.socket_io, global.socket_ioClient, global.Anthropic, global.openai, global.OpenAI, global.readability, global.jsdom, global.showdown));
5
- })(this, (function (exports, colors, commander, spaceTrim, waitasecond, path, promises, hexEncoder, sha256, crypto, rxjs, dotenv, child_process, JSZip, prettier, parserHtml, papaparse, cryptoJs, mimeTypes, glob, prompts, moment, express, http, socket_io, socket_ioClient, 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('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';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
@@ -27,16 +27,18 @@
27
27
  var colors__default = /*#__PURE__*/_interopDefaultLegacy(colors);
28
28
  var commander__default = /*#__PURE__*/_interopDefaultLegacy(commander);
29
29
  var spaceTrim__default = /*#__PURE__*/_interopDefaultLegacy(spaceTrim);
30
+ var prompts__default = /*#__PURE__*/_interopDefaultLegacy(prompts);
30
31
  var hexEncoder__default = /*#__PURE__*/_interopDefaultLegacy(hexEncoder);
31
32
  var sha256__default = /*#__PURE__*/_interopDefaultLegacy(sha256);
32
33
  var dotenv__namespace = /*#__PURE__*/_interopNamespace(dotenv);
33
34
  var JSZip__default = /*#__PURE__*/_interopDefaultLegacy(JSZip);
34
35
  var parserHtml__default = /*#__PURE__*/_interopDefaultLegacy(parserHtml);
35
36
  var glob__default = /*#__PURE__*/_interopDefaultLegacy(glob);
36
- var prompts__default = /*#__PURE__*/_interopDefaultLegacy(prompts);
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);
41
+ var swaggerUi__default = /*#__PURE__*/_interopDefaultLegacy(swaggerUi);
40
42
  var Anthropic__default = /*#__PURE__*/_interopDefaultLegacy(Anthropic);
41
43
  var OpenAI__default = /*#__PURE__*/_interopDefaultLegacy(OpenAI);
42
44
 
@@ -54,7 +56,7 @@
54
56
  * @generated
55
57
  * @see https://github.com/webgptorg/promptbook
56
58
  */
57
- const PROMPTBOOK_ENGINE_VERSION = '0.89.0-5';
59
+ const PROMPTBOOK_ENGINE_VERSION = '0.89.0-7';
58
60
  /**
59
61
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
60
62
  * Note: [💞] Ignore a discrepancy between file name and entity name
@@ -223,6 +225,7 @@
223
225
  */
224
226
  const DEFAULT_BOOKS_DIRNAME = './books';
225
227
  // <- TODO: [🕝] Make also `BOOKS_DIRNAME_ALTERNATIVES`
228
+ // TODO: !!!!!! Just .promptbook dir, hardocode others
226
229
  /**
227
230
  * Where to store the temporary downloads
228
231
  *
@@ -247,6 +250,21 @@
247
250
  * @public exported from `@promptbook/core`
248
251
  */
249
252
  const DEFAULT_SCRAPE_CACHE_DIRNAME = './.promptbook/scrape-cache';
253
+ /**
254
+ * Id of application for the CLI when using remote server
255
+ *
256
+ * @public exported from `@promptbook/core`
257
+ */
258
+ const CLI_APP_ID = 'cli';
259
+ /*
260
+ TODO: [🌃]
261
+ /**
262
+ * Id of application for the wizzard when using remote server
263
+ *
264
+ * @public exported from `@promptbook/core`
265
+ * /
266
+ ex-port const WIZZARD_APP_ID: string_app_id = 'wizzard';
267
+ */
250
268
  /**
251
269
  * The name of the builded pipeline collection made by CLI `ptbk make` and for lookup in `createCollectionFromDirectory`
252
270
  *
@@ -267,13 +285,7 @@
267
285
  *
268
286
  * @public exported from `@promptbook/core`
269
287
  */
270
- const DEFAULT_REMOTE_URL = 'https://api.pavolhejny.com/';
271
- /**
272
- * @@@
273
- *
274
- * @public exported from `@promptbook/core`
275
- */
276
- const DEFAULT_REMOTE_URL_PATH = '/promptbook/socket.io';
288
+ const DEFAULT_REMOTE_SERVER_URL = 'https://api.pavolhejny.com/promptbook';
277
289
  // <- TODO: [🧜‍♂️]
278
290
  /**
279
291
  * @@@
@@ -315,7 +327,7 @@
315
327
  true);
316
328
  /**
317
329
  * Note: [💞] Ignore a discrepancy between file name and entity name
318
- * TODO: [🧠][🧜‍♂️] Maybe join remoteUrl and path into single value
330
+ * TODO: [🧠][🧜‍♂️] Maybe join remoteServerUrl and path into single value
319
331
  */
320
332
 
321
333
  /**
@@ -493,7 +505,8 @@
493
505
  helloCommand.alias('hi');
494
506
  helloCommand.argument('[name]', 'Your name', 'Paul');
495
507
  helloCommand.option('-g, --greeting <greeting>', `Greeting`, 'Hello');
496
- helloCommand.action(handleActionErrors(async (name, { greeting }) => {
508
+ helloCommand.action(handleActionErrors(async (name, cliOptions) => {
509
+ const { greeting } = cliOptions;
497
510
  console.info(colors__default["default"].cyan(`${greeting} ${name}`));
498
511
  await waitasecond.forTime(1000);
499
512
  console.info(colors__default["default"].rainbow(`Nice to meet you!`));
@@ -507,40 +520,27 @@
507
520
  */
508
521
 
509
522
  /**
510
- * Just marks a place of place where should be something implemented
511
- * No side effects.
512
- *
513
- * Note: It can be usefull suppressing eslint errors of unused variables
523
+ * This error type indicates that some part of the code is not implemented yet
514
524
  *
515
- * @param value any values
516
- * @returns void
517
- * @private within the repository
525
+ * @public exported from `@promptbook/core`
518
526
  */
519
- function TODO_USE(...value) {
520
- }
527
+ class NotYetImplementedError extends Error {
528
+ constructor(message) {
529
+ super(spaceTrim.spaceTrim((block) => `
530
+ ${block(message)}
521
531
 
522
- /**
523
- * @@@
524
- *
525
- * @public exported from `@promptbook/node`
526
- */
527
- function $provideFilesystemForNode(options) {
528
- if (!$isRunningInNode()) {
529
- throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
532
+ Note: This feature is not implemented yet but it will be soon.
533
+
534
+ If you want speed up the implementation or just read more, look here:
535
+ https://github.com/webgptorg/promptbook
536
+
537
+ Or contact us on pavol@ptbk.io
538
+
539
+ `));
540
+ this.name = 'NotYetImplementedError';
541
+ Object.setPrototypeOf(this, NotYetImplementedError.prototype);
530
542
  }
531
- return {
532
- stat: promises.stat,
533
- access: promises.access,
534
- constants: promises.constants,
535
- readFile: promises.readFile,
536
- writeFile: promises.writeFile,
537
- readdir: promises.readdir,
538
- mkdir: promises.mkdir,
539
- };
540
543
  }
541
- /**
542
- * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
543
- */
544
544
 
545
545
  /**
546
546
  * Make error report URL for the given error
@@ -611,123 +611,569 @@
611
611
  }
612
612
 
613
613
  /**
614
- * Orders JSON object by keys
614
+ * @@@
615
615
  *
616
- * @returns The same type of object as the input re-ordered
617
- * @public exported from `@promptbook/utils`
616
+ * Note: `$` is used to indicate that this function is not a pure function - it access global scope
617
+ *
618
+ * @private internal function of `$Register`
618
619
  */
619
- function orderJson(options) {
620
- const { value, order } = options;
621
- const orderedValue = {
622
- ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
623
- ...value,
624
- };
625
- return orderedValue;
620
+ function $getGlobalScope() {
621
+ return Function('return this')();
626
622
  }
627
623
 
628
624
  /**
629
- * Freezes the given object and all its nested objects recursively
630
- *
631
- * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
632
- * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
625
+ * @@@
633
626
  *
634
- * @returns The same object as the input, but deeply frozen
627
+ * @param text @@@
628
+ * @returns @@@
629
+ * @example 'HELLO_WORLD'
630
+ * @example 'I_LOVE_PROMPTBOOK'
635
631
  * @public exported from `@promptbook/utils`
636
632
  */
637
- function $deepFreeze(objectValue) {
638
- if (Array.isArray(objectValue)) {
639
- return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
640
- }
641
- const propertyNames = Object.getOwnPropertyNames(objectValue);
642
- for (const propertyName of propertyNames) {
643
- const value = objectValue[propertyName];
644
- if (value && typeof value === 'object') {
645
- $deepFreeze(value);
633
+ function normalizeTo_SCREAMING_CASE(text) {
634
+ let charType;
635
+ let lastCharType = 'OTHER';
636
+ let normalizedName = '';
637
+ for (const char of text) {
638
+ let normalizedChar;
639
+ if (/^[a-z]$/.test(char)) {
640
+ charType = 'LOWERCASE';
641
+ normalizedChar = char.toUpperCase();
642
+ }
643
+ else if (/^[A-Z]$/.test(char)) {
644
+ charType = 'UPPERCASE';
645
+ normalizedChar = char;
646
+ }
647
+ else if (/^[0-9]$/.test(char)) {
648
+ charType = 'NUMBER';
649
+ normalizedChar = char;
650
+ }
651
+ else {
652
+ charType = 'OTHER';
653
+ normalizedChar = '_';
654
+ }
655
+ if (charType !== lastCharType &&
656
+ !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
657
+ !(lastCharType === 'NUMBER') &&
658
+ !(charType === 'NUMBER')) {
659
+ normalizedName += '_';
646
660
  }
661
+ normalizedName += normalizedChar;
662
+ lastCharType = charType;
647
663
  }
648
- Object.freeze(objectValue);
649
- return objectValue;
664
+ normalizedName = normalizedName.replace(/_+/g, '_');
665
+ normalizedName = normalizedName.replace(/_?\/_?/g, '/');
666
+ normalizedName = normalizedName.replace(/^_/, '');
667
+ normalizedName = normalizedName.replace(/_$/, '');
668
+ return normalizedName;
650
669
  }
651
670
  /**
652
- * TODO: [🧠] Is there a way how to meaningfully test this utility
671
+ * TODO: Tests
672
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
673
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
674
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
675
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
676
+ * TODO: [🌺] Use some intermediate util splitWords
653
677
  */
654
678
 
655
679
  /**
656
- * Checks if the value is [🚉] serializable as JSON
657
- * If not, throws an UnexpectedError with a rich error message and tracking
658
- *
659
- * - Almost all primitives are serializable BUT:
660
- * - `undefined` is not serializable
661
- * - `NaN` is not serializable
662
- * - Objects and arrays are serializable if all their properties are serializable
663
- * - Functions are not serializable
664
- * - Circular references are not serializable
665
- * - `Date` objects are not serializable
666
- * - `Map` and `Set` objects are not serializable
667
- * - `RegExp` objects are not serializable
668
- * - `Error` objects are not serializable
669
- * - `Symbol` objects are not serializable
670
- * - And much more...
680
+ * @@@
671
681
  *
672
- * @throws UnexpectedError if the value is not serializable as JSON
682
+ * @param text @@@
683
+ * @returns @@@
684
+ * @example 'hello_world'
685
+ * @example 'i_love_promptbook'
673
686
  * @public exported from `@promptbook/utils`
674
687
  */
675
- function checkSerializableAsJson(options) {
676
- const { value, name, message } = options;
677
- if (value === undefined) {
678
- throw new UnexpectedError(`${name} is undefined`);
679
- }
680
- else if (value === null) {
681
- return;
682
- }
683
- else if (typeof value === 'boolean') {
684
- return;
685
- }
686
- else if (typeof value === 'number' && !isNaN(value)) {
687
- return;
688
- }
689
- else if (typeof value === 'string') {
690
- return;
691
- }
692
- else if (typeof value === 'symbol') {
693
- throw new UnexpectedError(`${name} is symbol`);
694
- }
695
- else if (typeof value === 'function') {
696
- throw new UnexpectedError(`${name} is function`);
697
- }
698
- else if (typeof value === 'object' && Array.isArray(value)) {
699
- for (let i = 0; i < value.length; i++) {
700
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
701
- }
702
- }
703
- else if (typeof value === 'object') {
704
- if (value instanceof Date) {
705
- throw new UnexpectedError(spaceTrim__default["default"]((block) => `
706
- \`${name}\` is Date
707
-
708
- Use \`string_date_iso8601\` instead
688
+ function normalizeTo_snake_case(text) {
689
+ return normalizeTo_SCREAMING_CASE(text).toLowerCase();
690
+ }
709
691
 
710
- Additional message for \`${name}\`:
711
- ${block(message || '(nothing)')}
712
- `));
692
+ /**
693
+ * Register is @@@
694
+ *
695
+ * Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
696
+ *
697
+ * @private internal utility, exported are only signleton instances of this class
698
+ */
699
+ class $Register {
700
+ constructor(registerName) {
701
+ this.registerName = registerName;
702
+ const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
703
+ const globalScope = $getGlobalScope();
704
+ if (globalScope[storageName] === undefined) {
705
+ globalScope[storageName] = [];
713
706
  }
714
- else if (value instanceof Map) {
715
- throw new UnexpectedError(`${name} is Map`);
707
+ else if (!Array.isArray(globalScope[storageName])) {
708
+ throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
716
709
  }
717
- else if (value instanceof Set) {
718
- throw new UnexpectedError(`${name} is Set`);
710
+ this.storage = globalScope[storageName];
711
+ }
712
+ list() {
713
+ // <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>>
714
+ return this.storage;
715
+ }
716
+ register(registered) {
717
+ const { packageName, className } = registered;
718
+ const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className);
719
+ const existingRegistration = this.storage[existingRegistrationIndex];
720
+ if (!existingRegistration) {
721
+ this.storage.push(registered);
719
722
  }
720
- else if (value instanceof RegExp) {
721
- throw new UnexpectedError(`${name} is RegExp`);
723
+ else {
724
+ this.storage[existingRegistrationIndex] = registered;
722
725
  }
723
- else if (value instanceof Error) {
724
- throw new UnexpectedError(spaceTrim__default["default"]((block) => `
725
- \`${name}\` is unserialized Error
726
-
727
- Use function \`serializeError\`
726
+ return {
727
+ registerName: this.registerName,
728
+ packageName,
729
+ className,
730
+ get isDestroyed() {
731
+ return false;
732
+ },
733
+ destroy() {
734
+ throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`);
735
+ },
736
+ };
737
+ }
738
+ }
728
739
 
729
- Additional message for \`${name}\`:
730
- ${block(message || '(nothing)')}
740
+ /**
741
+ * @@@
742
+ *
743
+ * Note: `$` is used to indicate that this interacts with the global scope
744
+ * @singleton Only one instance of each register is created per build, but thare can be more @@@
745
+ * @public exported from `@promptbook/core`
746
+ */
747
+ const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
748
+ /**
749
+ * TODO: [®] DRY Register logic
750
+ */
751
+
752
+ /**
753
+ * @@@
754
+ *
755
+ * Note: `$` is used to indicate that this interacts with the global scope
756
+ * @singleton Only one instance of each register is created per build, but thare can be more @@@
757
+ * @public exported from `@promptbook/core`
758
+ */
759
+ const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
760
+ /**
761
+ * TODO: [®] DRY Register logic
762
+ */
763
+
764
+ /**
765
+ * Path to the `.env` file which was used to configure LLM tools
766
+ *
767
+ * Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
768
+ */
769
+ let $usedEnvFilename = null;
770
+ /**
771
+ * Pass the `.env` file which was used to configure LLM tools
772
+ *
773
+ * Note: `$` is used to indicate that this variable is making side effect
774
+ *
775
+ * @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
776
+ */
777
+ function $setUsedEnvFilename(filepath) {
778
+ $usedEnvFilename = filepath;
779
+ }
780
+ /**
781
+ * Creates a message with all registered LLM tools
782
+ *
783
+ * Note: This function is used to create a (error) message when there is no constructor for some LLM provider
784
+ *
785
+ * @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
786
+ */
787
+ function $registeredLlmToolsMessage() {
788
+ let env;
789
+ if ($isRunningInNode()) {
790
+ env = process.env;
791
+ // <- TODO: [⚛] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
792
+ }
793
+ else {
794
+ env = {};
795
+ }
796
+ /**
797
+ * Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
798
+ */
799
+ const all = [];
800
+ for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
801
+ if (all.some((item) => item.packageName === packageName && item.className === className)) {
802
+ continue;
803
+ }
804
+ all.push({ title, packageName, className, envVariables });
805
+ }
806
+ for (const { packageName, className } of $llmToolsRegister.list()) {
807
+ if (all.some((item) => item.packageName === packageName && item.className === className)) {
808
+ continue;
809
+ }
810
+ all.push({ packageName, className });
811
+ }
812
+ const metadata = all.map((metadata) => {
813
+ var _a, _b;
814
+ const isMetadataAviailable = $llmToolsMetadataRegister
815
+ .list()
816
+ .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
817
+ const isInstalled = $llmToolsRegister
818
+ .list()
819
+ .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
820
+ const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
821
+ const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
822
+ // <- Note: [🗨]
823
+ return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
824
+ });
825
+ const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
826
+ if (metadata.length === 0) {
827
+ return spaceTrim__default["default"]((block) => `
828
+ No LLM providers are available.
829
+
830
+ ${block(usedEnvMessage)}
831
+ `);
832
+ }
833
+ return spaceTrim__default["default"]((block) => `
834
+
835
+ ${block(usedEnvMessage)}
836
+
837
+ Relevant environment variables:
838
+ ${block(Object.keys(env)
839
+ .filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
840
+ .map((envVariableName) => `- \`${envVariableName}\``)
841
+ .join('\n'))}
842
+
843
+ Available LLM providers are:
844
+ ${block(metadata
845
+ .map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
846
+ const morePieces = [];
847
+ if (just(false)) ;
848
+ else if (!isMetadataAviailable && !isInstalled) {
849
+ // TODO: [�][�] Maybe do allow to do auto-install if package not registered and not found
850
+ morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
851
+ }
852
+ else if (isMetadataAviailable && !isInstalled) {
853
+ // TODO: [�][�]
854
+ morePieces.push(`Not installed`);
855
+ }
856
+ else if (!isMetadataAviailable && isInstalled) {
857
+ morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
858
+ }
859
+ else if (isMetadataAviailable && isInstalled) {
860
+ morePieces.push(`Installed`);
861
+ }
862
+ else {
863
+ morePieces.push(`unknown state, looks like a unexpected behavior`);
864
+ } /* not else */
865
+ if (isFullyConfigured) {
866
+ morePieces.push(`Configured`);
867
+ }
868
+ else if (isPartiallyConfigured) {
869
+ morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
870
+ }
871
+ else {
872
+ if (envVariables !== null) {
873
+ morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
874
+ }
875
+ else {
876
+ morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
877
+ }
878
+ }
879
+ let providerMessage = spaceTrim__default["default"](`
880
+ ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
881
+ ${morePieces.join('; ')}
882
+ `);
883
+ if ($isRunningInNode) {
884
+ if (isInstalled && isFullyConfigured) {
885
+ providerMessage = colors__default["default"].green(providerMessage);
886
+ }
887
+ else if (isInstalled && isPartiallyConfigured) {
888
+ providerMessage = colors__default["default"].yellow(providerMessage);
889
+ }
890
+ else {
891
+ providerMessage = colors__default["default"].gray(providerMessage);
892
+ }
893
+ }
894
+ return providerMessage;
895
+ })
896
+ .join('\n'))}
897
+ `);
898
+ }
899
+ /**
900
+ * TODO: [®] DRY Register logic
901
+ * TODO: [🧠][⚛] Maybe pass env as argument
902
+ */
903
+
904
+ /**
905
+ * Just says that the variable is not used but should be kept
906
+ * No side effects.
907
+ *
908
+ * Note: It can be usefull for:
909
+ *
910
+ * 1) Suppressing eager optimization of unused imports
911
+ * 2) Suppressing eslint errors of unused variables in the tests
912
+ * 3) Keeping the type of the variable for type testing
913
+ *
914
+ * @param value any values
915
+ * @returns void
916
+ * @private within the repository
917
+ */
918
+ function keepUnused(...valuesToKeep) {
919
+ }
920
+
921
+ /**
922
+ * Just says that the variable is not used directlys but should be kept because the existence of the variable is important
923
+ *
924
+ * @param value any values
925
+ * @returns void
926
+ * @private within the repository
927
+ */
928
+ function $sideEffect(...sideEffectSubjects) {
929
+ keepUnused(...sideEffectSubjects);
930
+ }
931
+
932
+ /**
933
+ * Checks if value is valid email
934
+ *
935
+ * @public exported from `@promptbook/utils`
936
+ */
937
+ function isValidEmail(email) {
938
+ if (typeof email !== 'string') {
939
+ return false;
940
+ }
941
+ if (email.split('\n').length > 1) {
942
+ return false;
943
+ }
944
+ return /^.+@.+\..+$/.test(email);
945
+ }
946
+
947
+ /**
948
+ * Tests if given string is valid URL.
949
+ *
950
+ * Note: Dataurl are considered perfectly valid.
951
+ * Note: There are two simmilar functions:
952
+ * - `isValidUrl` which tests any URL
953
+ * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
954
+ *
955
+ * @public exported from `@promptbook/utils`
956
+ */
957
+ function isValidUrl(url) {
958
+ if (typeof url !== 'string') {
959
+ return false;
960
+ }
961
+ try {
962
+ if (url.startsWith('blob:')) {
963
+ url = url.replace(/^blob:/, '');
964
+ }
965
+ const urlObject = new URL(url /* because fail is handled */);
966
+ if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
967
+ return false;
968
+ }
969
+ return true;
970
+ }
971
+ catch (error) {
972
+ return false;
973
+ }
974
+ }
975
+
976
+ /**
977
+ * Stores data in memory (HEAP)
978
+ *
979
+ * @public exported from `@promptbook/core`
980
+ */
981
+ class MemoryStorage {
982
+ constructor() {
983
+ this.storage = {};
984
+ }
985
+ /**
986
+ * Returns the number of key/value pairs currently present in the list associated with the object.
987
+ */
988
+ get length() {
989
+ return Object.keys(this.storage).length;
990
+ }
991
+ /**
992
+ * Empties the list associated with the object of all key/value pairs, if there are any.
993
+ */
994
+ clear() {
995
+ this.storage = {};
996
+ }
997
+ /**
998
+ * 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.
999
+ */
1000
+ getItem(key) {
1001
+ return this.storage[key] || null;
1002
+ }
1003
+ /**
1004
+ * 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.
1005
+ */
1006
+ key(index) {
1007
+ return Object.keys(this.storage)[index] || null;
1008
+ }
1009
+ /**
1010
+ * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
1011
+ */
1012
+ setItem(key, value) {
1013
+ this.storage[key] = value;
1014
+ }
1015
+ /**
1016
+ * 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.
1017
+ */
1018
+ removeItem(key) {
1019
+ delete this.storage[key];
1020
+ }
1021
+ }
1022
+
1023
+ /**
1024
+ * Just marks a place of place where should be something implemented
1025
+ * No side effects.
1026
+ *
1027
+ * Note: It can be usefull suppressing eslint errors of unused variables
1028
+ *
1029
+ * @param value any values
1030
+ * @returns void
1031
+ * @private within the repository
1032
+ */
1033
+ function TODO_USE(...value) {
1034
+ }
1035
+
1036
+ /**
1037
+ * @@@
1038
+ *
1039
+ * @public exported from `@promptbook/node`
1040
+ */
1041
+ function $provideFilesystemForNode(options) {
1042
+ if (!$isRunningInNode()) {
1043
+ throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
1044
+ }
1045
+ return {
1046
+ stat: promises.stat,
1047
+ access: promises.access,
1048
+ constants: promises.constants,
1049
+ readFile: promises.readFile,
1050
+ writeFile: promises.writeFile,
1051
+ readdir: promises.readdir,
1052
+ mkdir: promises.mkdir,
1053
+ };
1054
+ }
1055
+ /**
1056
+ * Note: [🟢] Code in this file should never be never released in packages that could be imported into browser environment
1057
+ */
1058
+
1059
+ /**
1060
+ * Orders JSON object by keys
1061
+ *
1062
+ * @returns The same type of object as the input re-ordered
1063
+ * @public exported from `@promptbook/utils`
1064
+ */
1065
+ function orderJson(options) {
1066
+ const { value, order } = options;
1067
+ const orderedValue = {
1068
+ ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
1069
+ ...value,
1070
+ };
1071
+ return orderedValue;
1072
+ }
1073
+
1074
+ /**
1075
+ * Freezes the given object and all its nested objects recursively
1076
+ *
1077
+ * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
1078
+ * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
1079
+ *
1080
+ * @returns The same object as the input, but deeply frozen
1081
+ * @public exported from `@promptbook/utils`
1082
+ */
1083
+ function $deepFreeze(objectValue) {
1084
+ if (Array.isArray(objectValue)) {
1085
+ return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
1086
+ }
1087
+ const propertyNames = Object.getOwnPropertyNames(objectValue);
1088
+ for (const propertyName of propertyNames) {
1089
+ const value = objectValue[propertyName];
1090
+ if (value && typeof value === 'object') {
1091
+ $deepFreeze(value);
1092
+ }
1093
+ }
1094
+ Object.freeze(objectValue);
1095
+ return objectValue;
1096
+ }
1097
+ /**
1098
+ * TODO: [🧠] Is there a way how to meaningfully test this utility
1099
+ */
1100
+
1101
+ /**
1102
+ * Checks if the value is [🚉] serializable as JSON
1103
+ * If not, throws an UnexpectedError with a rich error message and tracking
1104
+ *
1105
+ * - Almost all primitives are serializable BUT:
1106
+ * - `undefined` is not serializable
1107
+ * - `NaN` is not serializable
1108
+ * - Objects and arrays are serializable if all their properties are serializable
1109
+ * - Functions are not serializable
1110
+ * - Circular references are not serializable
1111
+ * - `Date` objects are not serializable
1112
+ * - `Map` and `Set` objects are not serializable
1113
+ * - `RegExp` objects are not serializable
1114
+ * - `Error` objects are not serializable
1115
+ * - `Symbol` objects are not serializable
1116
+ * - And much more...
1117
+ *
1118
+ * @throws UnexpectedError if the value is not serializable as JSON
1119
+ * @public exported from `@promptbook/utils`
1120
+ */
1121
+ function checkSerializableAsJson(options) {
1122
+ const { value, name, message } = options;
1123
+ if (value === undefined) {
1124
+ throw new UnexpectedError(`${name} is undefined`);
1125
+ }
1126
+ else if (value === null) {
1127
+ return;
1128
+ }
1129
+ else if (typeof value === 'boolean') {
1130
+ return;
1131
+ }
1132
+ else if (typeof value === 'number' && !isNaN(value)) {
1133
+ return;
1134
+ }
1135
+ else if (typeof value === 'string') {
1136
+ return;
1137
+ }
1138
+ else if (typeof value === 'symbol') {
1139
+ throw new UnexpectedError(`${name} is symbol`);
1140
+ }
1141
+ else if (typeof value === 'function') {
1142
+ throw new UnexpectedError(`${name} is function`);
1143
+ }
1144
+ else if (typeof value === 'object' && Array.isArray(value)) {
1145
+ for (let i = 0; i < value.length; i++) {
1146
+ checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1147
+ }
1148
+ }
1149
+ else if (typeof value === 'object') {
1150
+ if (value instanceof Date) {
1151
+ throw new UnexpectedError(spaceTrim__default["default"]((block) => `
1152
+ \`${name}\` is Date
1153
+
1154
+ Use \`string_date_iso8601\` instead
1155
+
1156
+ Additional message for \`${name}\`:
1157
+ ${block(message || '(nothing)')}
1158
+ `));
1159
+ }
1160
+ else if (value instanceof Map) {
1161
+ throw new UnexpectedError(`${name} is Map`);
1162
+ }
1163
+ else if (value instanceof Set) {
1164
+ throw new UnexpectedError(`${name} is Set`);
1165
+ }
1166
+ else if (value instanceof RegExp) {
1167
+ throw new UnexpectedError(`${name} is RegExp`);
1168
+ }
1169
+ else if (value instanceof Error) {
1170
+ throw new UnexpectedError(spaceTrim__default["default"]((block) => `
1171
+ \`${name}\` is unserialized Error
1172
+
1173
+ Use function \`serializeError\`
1174
+
1175
+ Additional message for \`${name}\`:
1176
+ ${block(message || '(nothing)')}
731
1177
 
732
1178
  `));
733
1179
  }
@@ -1051,51 +1497,22 @@
1051
1497
  if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
1052
1498
  // console.log(filename, 'Relative path: ./hello.txt');
1053
1499
  return true;
1054
- }
1055
- // Allow paths like foo/hello
1056
- if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
1057
- // console.log(filename, 'Allow paths like foo/hello');
1058
- return true;
1059
- }
1060
- // Allow paths like hello.book
1061
- if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
1062
- // console.log(filename, 'Allow paths like hello.book');
1063
- return true;
1064
- }
1065
- return false;
1066
- }
1067
- /**
1068
- * TODO: [🍏] Implement for MacOs
1069
- */
1070
-
1071
- /**
1072
- * Tests if given string is valid URL.
1073
- *
1074
- * Note: Dataurl are considered perfectly valid.
1075
- * Note: There are two simmilar functions:
1076
- * - `isValidUrl` which tests any URL
1077
- * - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
1078
- *
1079
- * @public exported from `@promptbook/utils`
1080
- */
1081
- function isValidUrl(url) {
1082
- if (typeof url !== 'string') {
1083
- return false;
1084
- }
1085
- try {
1086
- if (url.startsWith('blob:')) {
1087
- url = url.replace(/^blob:/, '');
1088
- }
1089
- const urlObject = new URL(url /* because fail is handled */);
1090
- if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
1091
- return false;
1092
- }
1500
+ }
1501
+ // Allow paths like foo/hello
1502
+ if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
1503
+ // console.log(filename, 'Allow paths like foo/hello');
1093
1504
  return true;
1094
1505
  }
1095
- catch (error) {
1096
- return false;
1506
+ // Allow paths like hello.book
1507
+ if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
1508
+ // console.log(filename, 'Allow paths like hello.book');
1509
+ return true;
1097
1510
  }
1511
+ return false;
1098
1512
  }
1513
+ /**
1514
+ * TODO: [🍏] Implement for MacOs
1515
+ */
1099
1516
 
1100
1517
  const defaultDiacriticsRemovalMap = [
1101
1518
  {
@@ -1509,703 +1926,743 @@
1509
1926
  */
1510
1927
 
1511
1928
  /**
1512
- * Generates random token
1929
+ * This error indicates problems parsing the format value
1513
1930
  *
1514
- * Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
1931
+ * For example, when the format value is not a valid JSON or CSV
1932
+ * This is not thrown directly but in extended classes
1515
1933
  *
1516
- * @private internal helper function
1517
- * @returns secure random token
1934
+ * @public exported from `@promptbook/core`
1518
1935
  */
1519
- function $randomToken(randomness) {
1520
- return crypto.randomBytes(randomness).toString('hex');
1936
+ class AbstractFormatError extends Error {
1937
+ // Note: To allow instanceof do not put here error `name`
1938
+ // public readonly name = 'AbstractFormatError';
1939
+ constructor(message) {
1940
+ super(message);
1941
+ Object.setPrototypeOf(this, AbstractFormatError.prototype);
1942
+ }
1521
1943
  }
1522
- /**
1523
- * TODO: Maybe use nanoid instead https://github.com/ai/nanoid
1524
- */
1525
1944
 
1526
1945
  /**
1527
- * This error indicates errors during the execution of the pipeline
1946
+ * This error indicates problem with parsing of CSV
1528
1947
  *
1529
1948
  * @public exported from `@promptbook/core`
1530
1949
  */
1531
- class PipelineExecutionError extends Error {
1950
+ class CsvFormatError extends AbstractFormatError {
1532
1951
  constructor(message) {
1533
- // Added id parameter
1534
1952
  super(message);
1535
- this.name = 'PipelineExecutionError';
1536
- // TODO: [🐙] DRY - Maybe $randomId
1537
- this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
1538
- Object.setPrototypeOf(this, PipelineExecutionError.prototype);
1953
+ this.name = 'CsvFormatError';
1954
+ Object.setPrototypeOf(this, CsvFormatError.prototype);
1539
1955
  }
1540
1956
  }
1541
- /**
1542
- * TODO: !!!!!! Add id to all errors
1543
- */
1544
1957
 
1545
1958
  /**
1546
- * Stores data in memory (HEAP)
1959
+ * AuthenticationError is thrown from login function which is dependency of remote server
1547
1960
  *
1548
1961
  * @public exported from `@promptbook/core`
1549
1962
  */
1550
- class MemoryStorage {
1551
- constructor() {
1552
- this.storage = {};
1553
- }
1554
- /**
1555
- * Returns the number of key/value pairs currently present in the list associated with the object.
1556
- */
1557
- get length() {
1558
- return Object.keys(this.storage).length;
1559
- }
1560
- /**
1561
- * Empties the list associated with the object of all key/value pairs, if there are any.
1562
- */
1563
- clear() {
1564
- this.storage = {};
1565
- }
1566
- /**
1567
- * 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.
1568
- */
1569
- getItem(key) {
1570
- return this.storage[key] || null;
1571
- }
1572
- /**
1573
- * 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.
1574
- */
1575
- key(index) {
1576
- return Object.keys(this.storage)[index] || null;
1577
- }
1578
- /**
1579
- * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
1580
- */
1581
- setItem(key, value) {
1582
- this.storage[key] = value;
1583
- }
1584
- /**
1585
- * 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.
1586
- */
1587
- removeItem(key) {
1588
- delete this.storage[key];
1963
+ class AuthenticationError extends Error {
1964
+ constructor(message) {
1965
+ super(message);
1966
+ this.name = 'AuthenticationError';
1967
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
1589
1968
  }
1590
1969
  }
1591
1970
 
1592
1971
  /**
1593
- * Simple wrapper `new Date().toISOString()`
1594
- *
1595
- * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
1972
+ * This error indicates that the pipeline collection cannot be propperly loaded
1596
1973
  *
1597
- * @returns string_date branded type
1598
- * @public exported from `@promptbook/utils`
1974
+ * @public exported from `@promptbook/core`
1599
1975
  */
1600
- function $getCurrentDate() {
1601
- return new Date().toISOString();
1976
+ class CollectionError extends Error {
1977
+ constructor(message) {
1978
+ super(message);
1979
+ this.name = 'CollectionError';
1980
+ Object.setPrototypeOf(this, CollectionError.prototype);
1981
+ }
1602
1982
  }
1603
1983
 
1604
1984
  /**
1605
- * Intercepts LLM tools and counts total usage of the tools
1606
- *
1607
- * Note: It can take extended `LlmExecutionTools` and cache the
1985
+ * This error occurs when some expectation is not met in the execution of the pipeline
1608
1986
  *
1609
- * @param llmTools LLM tools to be intercepted with usage counting, it can contain extra methods like `totalUsage`
1610
- * @returns LLM tools with same functionality with added total cost counting
1611
1987
  * @public exported from `@promptbook/core`
1988
+ * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
1989
+ * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
1990
+ * Note: This is a kindof subtype of PipelineExecutionError
1612
1991
  */
1613
- function cacheLlmTools(llmTools, options = {}) {
1614
- const { storage = new MemoryStorage(), isCacheReloaded = false } = options;
1615
- const proxyTools = {
1616
- ...llmTools,
1617
- // <- Note: [🥫]
1618
- get title() {
1619
- // TODO: [🧠] Maybe put here some suffix
1620
- return llmTools.title;
1621
- },
1622
- get description() {
1623
- // TODO: [🧠] Maybe put here some suffix
1624
- return llmTools.description;
1625
- },
1626
- listModels() {
1627
- // TODO: [🧠] Should be model listing also cached?
1628
- return /* not await */ llmTools.listModels();
1629
- },
1630
- };
1631
- const callCommonModel = async (prompt) => {
1632
- const { parameters, content, modelRequirements } = prompt;
1633
- // <- Note: These are relevant things from the prompt that the cache key should depend on.
1634
- const key = titleToName(prompt.title.substring(0, MAX_FILENAME_LENGTH - 10) +
1635
- '-' +
1636
- sha256__default["default"](hexEncoder__default["default"].parse(JSON.stringify({ parameters, content, modelRequirements }))).toString( /* hex */));
1637
- const cacheItem = !isCacheReloaded ? await storage.getItem(key) : null;
1638
- if (cacheItem) {
1639
- return cacheItem.promptResult;
1640
- }
1641
- let promptResult;
1642
- variant: switch (prompt.modelRequirements.modelVariant) {
1643
- case 'CHAT':
1644
- promptResult = await llmTools.callChatModel(prompt);
1645
- break variant;
1646
- case 'COMPLETION':
1647
- promptResult = await llmTools.callCompletionModel(prompt);
1648
- break variant;
1649
- case 'EMBEDDING':
1650
- promptResult = await llmTools.callEmbeddingModel(prompt);
1651
- break variant;
1652
- // <- case [🤖]:
1653
- default:
1654
- throw new PipelineExecutionError(`Unknown model variant "${prompt.modelRequirements.modelVariant}"`);
1655
- }
1656
- // TODO: [🧠] !!5 How to do timing in mixed cache / non-cache situation
1657
- // promptResult.timing: FromtoItems
1658
- await storage.setItem(key, {
1659
- date: $getCurrentDate(),
1660
- promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
1661
- prompt,
1662
- promptResult,
1663
- });
1664
- return promptResult;
1665
- };
1666
- if (llmTools.callChatModel !== undefined) {
1667
- proxyTools.callChatModel = async (prompt) => {
1668
- return /* not await */ callCommonModel(prompt);
1669
- };
1670
- }
1671
- if (llmTools.callCompletionModel !== undefined) {
1672
- proxyTools.callCompletionModel = async (prompt) => {
1673
- return /* not await */ callCommonModel(prompt);
1674
- };
1675
- }
1676
- if (llmTools.callEmbeddingModel !== undefined) {
1677
- proxyTools.callEmbeddingModel = async (prompt) => {
1678
- return /* not await */ callCommonModel(prompt);
1679
- };
1992
+ class ExpectError extends Error {
1993
+ constructor(message) {
1994
+ super(message);
1995
+ this.name = 'ExpectError';
1996
+ Object.setPrototypeOf(this, ExpectError.prototype);
1680
1997
  }
1681
- // <- Note: [🤖]
1682
- return proxyTools;
1683
1998
  }
1684
- /**
1685
- * TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
1686
- * TODO: [🧠] Is there some meaningfull way how to test this util
1687
- * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
1688
- * @@@ write discussion about this and storages
1689
- * @@@ write how to combine multiple interceptors
1690
- */
1691
1999
 
1692
2000
  /**
1693
- * Represents the uncertain value
2001
+ * This error indicates that the promptbook can not retrieve knowledge from external sources
1694
2002
  *
1695
2003
  * @public exported from `@promptbook/core`
1696
2004
  */
1697
- const ZERO_VALUE = $deepFreeze({ value: 0 });
2005
+ class KnowledgeScrapeError extends Error {
2006
+ constructor(message) {
2007
+ super(message);
2008
+ this.name = 'KnowledgeScrapeError';
2009
+ Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
2010
+ }
2011
+ }
2012
+
1698
2013
  /**
1699
- * Represents the uncertain value
2014
+ * This error type indicates that some limit was reached
1700
2015
  *
1701
2016
  * @public exported from `@promptbook/core`
1702
2017
  */
1703
- const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
2018
+ class LimitReachedError extends Error {
2019
+ constructor(message) {
2020
+ super(message);
2021
+ this.name = 'LimitReachedError';
2022
+ Object.setPrototypeOf(this, LimitReachedError.prototype);
2023
+ }
2024
+ }
2025
+
1704
2026
  /**
1705
- * Represents the usage with no resources consumed
2027
+ * This error type indicates that some tools are missing for pipeline execution or preparation
1706
2028
  *
1707
2029
  * @public exported from `@promptbook/core`
1708
2030
  */
1709
- const ZERO_USAGE = $deepFreeze({
1710
- price: ZERO_VALUE,
1711
- input: {
1712
- tokensCount: ZERO_VALUE,
1713
- charactersCount: ZERO_VALUE,
1714
- wordsCount: ZERO_VALUE,
1715
- sentencesCount: ZERO_VALUE,
1716
- linesCount: ZERO_VALUE,
1717
- paragraphsCount: ZERO_VALUE,
1718
- pagesCount: ZERO_VALUE,
1719
- },
1720
- output: {
1721
- tokensCount: ZERO_VALUE,
1722
- charactersCount: ZERO_VALUE,
1723
- wordsCount: ZERO_VALUE,
1724
- sentencesCount: ZERO_VALUE,
1725
- linesCount: ZERO_VALUE,
1726
- paragraphsCount: ZERO_VALUE,
1727
- pagesCount: ZERO_VALUE,
1728
- },
1729
- });
2031
+ class MissingToolsError extends Error {
2032
+ constructor(message) {
2033
+ super(spaceTrim.spaceTrim((block) => `
2034
+ ${block(message)}
2035
+
2036
+ Note: You have probbably forgot to provide some tools for pipeline execution or preparation
2037
+
2038
+ `));
2039
+ this.name = 'MissingToolsError';
2040
+ Object.setPrototypeOf(this, MissingToolsError.prototype);
2041
+ }
2042
+ }
2043
+
2044
+ /**
2045
+ * This error indicates that promptbook not found in the collection
2046
+ *
2047
+ * @public exported from `@promptbook/core`
2048
+ */
2049
+ class NotFoundError extends Error {
2050
+ constructor(message) {
2051
+ super(message);
2052
+ this.name = 'NotFoundError';
2053
+ Object.setPrototypeOf(this, NotFoundError.prototype);
2054
+ }
2055
+ }
2056
+
1730
2057
  /**
1731
- * Represents the usage with unknown resources consumed
2058
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
1732
2059
  *
1733
2060
  * @public exported from `@promptbook/core`
1734
2061
  */
1735
- const UNCERTAIN_USAGE = $deepFreeze({
1736
- price: UNCERTAIN_ZERO_VALUE,
1737
- input: {
1738
- tokensCount: UNCERTAIN_ZERO_VALUE,
1739
- charactersCount: UNCERTAIN_ZERO_VALUE,
1740
- wordsCount: UNCERTAIN_ZERO_VALUE,
1741
- sentencesCount: UNCERTAIN_ZERO_VALUE,
1742
- linesCount: UNCERTAIN_ZERO_VALUE,
1743
- paragraphsCount: UNCERTAIN_ZERO_VALUE,
1744
- pagesCount: UNCERTAIN_ZERO_VALUE,
1745
- },
1746
- output: {
1747
- tokensCount: UNCERTAIN_ZERO_VALUE,
1748
- charactersCount: UNCERTAIN_ZERO_VALUE,
1749
- wordsCount: UNCERTAIN_ZERO_VALUE,
1750
- sentencesCount: UNCERTAIN_ZERO_VALUE,
1751
- linesCount: UNCERTAIN_ZERO_VALUE,
1752
- paragraphsCount: UNCERTAIN_ZERO_VALUE,
1753
- pagesCount: UNCERTAIN_ZERO_VALUE,
1754
- },
1755
- });
2062
+ class ParseError extends Error {
2063
+ constructor(message) {
2064
+ super(message);
2065
+ this.name = 'ParseError';
2066
+ Object.setPrototypeOf(this, ParseError.prototype);
2067
+ }
2068
+ }
1756
2069
  /**
1757
- * Note: [💞] Ignore a discrepancy between file name and entity name
2070
+ * TODO: Maybe split `ParseError` and `ApplyError`
1758
2071
  */
1759
2072
 
1760
2073
  /**
1761
- * Function `addUsage` will add multiple usages into one
2074
+ * Generates random token
1762
2075
  *
1763
- * Note: If you provide 0 values, it returns ZERO_USAGE
2076
+ * Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
1764
2077
  *
1765
- * @public exported from `@promptbook/core`
2078
+ * @private internal helper function
2079
+ * @returns secure random token
1766
2080
  */
1767
- function addUsage(...usageItems) {
1768
- return usageItems.reduce((acc, item) => {
1769
- var _a;
1770
- acc.price.value += ((_a = item.price) === null || _a === void 0 ? void 0 : _a.value) || 0;
1771
- for (const key of Object.keys(acc.input)) {
1772
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1773
- //@ts-ignore
1774
- if (item.input[key]) {
1775
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1776
- //@ts-ignore
1777
- acc.input[key].value += item.input[key].value || 0;
1778
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1779
- //@ts-ignore
1780
- if (item.input[key].isUncertain) {
1781
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1782
- //@ts-ignore
1783
- acc.input[key].isUncertain = true;
1784
- }
1785
- }
1786
- }
1787
- for (const key of Object.keys(acc.output)) {
1788
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1789
- //@ts-ignore
1790
- if (item.output[key]) {
1791
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1792
- //@ts-ignore
1793
- acc.output[key].value += item.output[key].value || 0;
1794
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1795
- //@ts-ignore
1796
- if (item.output[key].isUncertain) {
1797
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
1798
- //@ts-ignore
1799
- acc.output[key].isUncertain = true;
1800
- }
1801
- }
1802
- }
1803
- return acc;
1804
- }, deepClone(ZERO_USAGE));
2081
+ function $randomToken(randomness) {
2082
+ return crypto.randomBytes(randomness).toString('hex');
1805
2083
  }
2084
+ /**
2085
+ * TODO: Maybe use nanoid instead https://github.com/ai/nanoid
2086
+ */
1806
2087
 
1807
2088
  /**
1808
- * Intercepts LLM tools and counts total usage of the tools
2089
+ * This error indicates errors during the execution of the pipeline
1809
2090
  *
1810
- * @param llmTools LLM tools to be intercepted with usage counting
1811
- * @returns LLM tools with same functionality with added total cost counting
1812
2091
  * @public exported from `@promptbook/core`
1813
2092
  */
1814
- function countUsage(llmTools) {
1815
- let totalUsage = ZERO_USAGE;
1816
- const spending = new rxjs.Subject();
1817
- const proxyTools = {
1818
- get title() {
1819
- // TODO: [🧠] Maybe put here some suffix
1820
- return llmTools.title;
1821
- },
1822
- get description() {
1823
- // TODO: [🧠] Maybe put here some suffix
1824
- return llmTools.description;
1825
- },
1826
- checkConfiguration() {
1827
- return /* not await */ llmTools.checkConfiguration();
1828
- },
1829
- listModels() {
1830
- return /* not await */ llmTools.listModels();
1831
- },
1832
- spending() {
1833
- return spending.asObservable();
1834
- },
1835
- getTotalUsage() {
1836
- // <- Note: [🥫] Not using getter `get totalUsage` but `getTotalUsage` to allow this object to be proxied
1837
- return totalUsage;
1838
- },
1839
- };
1840
- if (llmTools.callChatModel !== undefined) {
1841
- proxyTools.callChatModel = async (prompt) => {
1842
- // console.info('[🚕] callChatModel through countTotalUsage');
1843
- const promptResult = await llmTools.callChatModel(prompt);
1844
- totalUsage = addUsage(totalUsage, promptResult.usage);
1845
- spending.next(promptResult.usage);
1846
- return promptResult;
1847
- };
1848
- }
1849
- if (llmTools.callCompletionModel !== undefined) {
1850
- proxyTools.callCompletionModel = async (prompt) => {
1851
- // console.info('[🚕] callCompletionModel through countTotalUsage');
1852
- const promptResult = await llmTools.callCompletionModel(prompt);
1853
- totalUsage = addUsage(totalUsage, promptResult.usage);
1854
- spending.next(promptResult.usage);
1855
- return promptResult;
1856
- };
1857
- }
1858
- if (llmTools.callEmbeddingModel !== undefined) {
1859
- proxyTools.callEmbeddingModel = async (prompt) => {
1860
- // console.info('[🚕] callEmbeddingModel through countTotalUsage');
1861
- const promptResult = await llmTools.callEmbeddingModel(prompt);
1862
- totalUsage = addUsage(totalUsage, promptResult.usage);
1863
- spending.next(promptResult.usage);
1864
- return promptResult;
1865
- };
2093
+ class PipelineExecutionError extends Error {
2094
+ constructor(message) {
2095
+ // Added id parameter
2096
+ super(message);
2097
+ this.name = 'PipelineExecutionError';
2098
+ // TODO: [🐙] DRY - Maybe $randomId
2099
+ this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid simmilar char conflicts */)}`;
2100
+ Object.setPrototypeOf(this, PipelineExecutionError.prototype);
1866
2101
  }
1867
- // <- Note: [🤖]
1868
- return proxyTools;
1869
2102
  }
1870
2103
  /**
1871
- * TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
1872
- * TODO: [🧠] Is there some meaningfull way how to test this util
1873
- * TODO: [🧠][🌯] Maybe a way how to hide ability to `get totalUsage`
1874
- * > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
1875
- * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
2104
+ * TODO: !!!!!! Add id to all errors
1876
2105
  */
1877
2106
 
1878
2107
  /**
1879
- * This error type indicates that some part of the code is not implemented yet
2108
+ * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
1880
2109
  *
1881
2110
  * @public exported from `@promptbook/core`
1882
2111
  */
1883
- class NotYetImplementedError extends Error {
2112
+ class PipelineLogicError extends Error {
1884
2113
  constructor(message) {
1885
- super(spaceTrim.spaceTrim((block) => `
1886
- ${block(message)}
2114
+ super(message);
2115
+ this.name = 'PipelineLogicError';
2116
+ Object.setPrototypeOf(this, PipelineLogicError.prototype);
2117
+ }
2118
+ }
1887
2119
 
1888
- Note: This feature is not implemented yet but it will be soon.
2120
+ /**
2121
+ * This error indicates errors in referencing promptbooks between each other
2122
+ *
2123
+ * @public exported from `@promptbook/core`
2124
+ */
2125
+ class PipelineUrlError extends Error {
2126
+ constructor(message) {
2127
+ super(message);
2128
+ this.name = 'PipelineUrlError';
2129
+ Object.setPrototypeOf(this, PipelineUrlError.prototype);
2130
+ }
2131
+ }
1889
2132
 
1890
- If you want speed up the implementation or just read more, look here:
1891
- https://github.com/webgptorg/promptbook
2133
+ /**
2134
+ * Index of all custom errors
2135
+ *
2136
+ * @public exported from `@promptbook/core`
2137
+ */
2138
+ const PROMPTBOOK_ERRORS = {
2139
+ AbstractFormatError,
2140
+ CsvFormatError,
2141
+ CollectionError,
2142
+ EnvironmentMismatchError,
2143
+ ExpectError,
2144
+ KnowledgeScrapeError,
2145
+ LimitReachedError,
2146
+ MissingToolsError,
2147
+ NotFoundError,
2148
+ NotYetImplementedError,
2149
+ ParseError,
2150
+ PipelineExecutionError,
2151
+ PipelineLogicError,
2152
+ PipelineUrlError,
2153
+ UnexpectedError,
2154
+ // TODO: [🪑]> VersionMismatchError,
2155
+ };
2156
+ /**
2157
+ * Index of all javascript errors
2158
+ *
2159
+ * @private for internal usage
2160
+ */
2161
+ const COMMON_JAVASCRIPT_ERRORS = {
2162
+ Error,
2163
+ EvalError,
2164
+ RangeError,
2165
+ ReferenceError,
2166
+ SyntaxError,
2167
+ TypeError,
2168
+ URIError,
2169
+ AggregateError,
2170
+ AuthenticationError,
2171
+ /*
2172
+ Note: Not widely supported
2173
+ > InternalError,
2174
+ > ModuleError,
2175
+ > HeapError,
2176
+ > WebAssemblyCompileError,
2177
+ > WebAssemblyRuntimeError,
2178
+ */
2179
+ };
2180
+ /**
2181
+ * Index of all errors
2182
+ *
2183
+ * @private for internal usage
2184
+ */
2185
+ const ALL_ERRORS = {
2186
+ ...PROMPTBOOK_ERRORS,
2187
+ ...COMMON_JAVASCRIPT_ERRORS,
2188
+ };
2189
+ /**
2190
+ * Note: [💞] Ignore a discrepancy between file name and entity name
2191
+ */
1892
2192
 
1893
- Or contact us on pavol@ptbk.io
2193
+ /**
2194
+ * Deserializes the error object
2195
+ *
2196
+ * @public exported from `@promptbook/utils`
2197
+ */
2198
+ function deserializeError(error) {
2199
+ const { name, stack, id } = error; // Added id
2200
+ let { message } = error;
2201
+ let ErrorClass = ALL_ERRORS[error.name];
2202
+ if (ErrorClass === undefined) {
2203
+ ErrorClass = Error;
2204
+ message = `${name}: ${message}`;
2205
+ }
2206
+ if (stack !== undefined && stack !== '') {
2207
+ message = spaceTrim__default["default"]((block) => `
2208
+ ${block(message)}
1894
2209
 
1895
- `));
1896
- this.name = 'NotYetImplementedError';
1897
- Object.setPrototypeOf(this, NotYetImplementedError.prototype);
2210
+ Original stack trace:
2211
+ ${block(stack || '')}
2212
+ `);
1898
2213
  }
2214
+ const deserializedError = new ErrorClass(message);
2215
+ deserializedError.id = id; // Assign id to the error object
2216
+ return deserializedError;
1899
2217
  }
1900
2218
 
1901
2219
  /**
1902
- * @@@
2220
+ * Creates a connection to the remote proxy server.
1903
2221
  *
1904
- * Note: `$` is used to indicate that this function is not a pure function - it access global scope
2222
+ * Note: This function creates a connection to the remote server and returns a socket but responsibility of closing the connection is on the caller
1905
2223
  *
1906
- * @private internal function of `$Register`
2224
+ * @private internal utility function
1907
2225
  */
1908
- function $getGlobalScope() {
1909
- return Function('return this')();
2226
+ async function createRemoteClient(options) {
2227
+ const { remoteServerUrl } = options;
2228
+ let path = new URL(remoteServerUrl).pathname;
2229
+ if (path.endsWith('/')) {
2230
+ path = path.slice(0, -1);
2231
+ }
2232
+ path = `${path}/socket.io`;
2233
+ return new Promise((resolve, reject) => {
2234
+ const socket = socket_ioClient.io(remoteServerUrl, {
2235
+ retries: CONNECTION_RETRIES_LIMIT,
2236
+ timeout: CONNECTION_TIMEOUT_MS,
2237
+ path,
2238
+ transports: [/*'websocket', <- TODO: [🌬] Make websocket transport work */ 'polling'],
2239
+ });
2240
+ // console.log('Connecting to', this.options.remoteServerUrl.href, { socket });
2241
+ socket.on('connect', () => {
2242
+ resolve(socket);
2243
+ });
2244
+ // TODO: [💩] Better timeout handling
2245
+ setTimeout(() => {
2246
+ reject(new Error(`Timeout while connecting to ${remoteServerUrl}`));
2247
+ }, CONNECTION_TIMEOUT_MS);
2248
+ });
1910
2249
  }
1911
2250
 
1912
2251
  /**
1913
- * @@@
2252
+ * Remote server is a proxy server that uses its execution tools internally and exposes the executor interface externally.
1914
2253
  *
1915
- * @param text @@@
1916
- * @returns @@@
1917
- * @example 'HELLO_WORLD'
1918
- * @example 'I_LOVE_PROMPTBOOK'
1919
- * @public exported from `@promptbook/utils`
2254
+ * You can simply use `RemoteExecutionTools` on client-side javascript and connect to your remote server.
2255
+ * This is useful to make all logic on browser side but not expose your API keys or no need to use customer's GPU.
2256
+ *
2257
+ * @see https://github.com/webgptorg/promptbook#remote-server
2258
+ * @public exported from `@promptbook/remote-client`
1920
2259
  */
1921
- function normalizeTo_SCREAMING_CASE(text) {
1922
- let charType;
1923
- let lastCharType = 'OTHER';
1924
- let normalizedName = '';
1925
- for (const char of text) {
1926
- let normalizedChar;
1927
- if (/^[a-z]$/.test(char)) {
1928
- charType = 'LOWERCASE';
1929
- normalizedChar = char.toUpperCase();
1930
- }
1931
- else if (/^[A-Z]$/.test(char)) {
1932
- charType = 'UPPERCASE';
1933
- normalizedChar = char;
1934
- }
1935
- else if (/^[0-9]$/.test(char)) {
1936
- charType = 'NUMBER';
1937
- normalizedChar = char;
2260
+ class RemoteLlmExecutionTools {
2261
+ /* <- TODO: [🍚] `, Destroyable` */
2262
+ constructor(options) {
2263
+ this.options = options;
2264
+ }
2265
+ get title() {
2266
+ // TODO: [🧠] Maybe fetch title+description from the remote server (as well as if model methods are defined)
2267
+ return 'Remote server';
2268
+ }
2269
+ get description() {
2270
+ return 'Use all models by your remote server';
2271
+ }
2272
+ /**
2273
+ * Check the configuration of all execution tools
2274
+ */
2275
+ async checkConfiguration() {
2276
+ const socket = await createRemoteClient(this.options);
2277
+ socket.disconnect();
2278
+ // TODO: [main] !!3 Check version of the remote server and compatibility
2279
+ // TODO: [🎍] Send checkConfiguration
2280
+ }
2281
+ /**
2282
+ * List all available models that can be used
2283
+ */
2284
+ async listModels() {
2285
+ // TODO: [👒] Listing models (and checking configuration) probbably should go through REST API not Socket.io
2286
+ const socket = await createRemoteClient(this.options);
2287
+ socket.emit('listModels-request', {
2288
+ identification: this.options.identification,
2289
+ } /* <- Note: [🤛] */);
2290
+ const promptResult = await new Promise((resolve, reject) => {
2291
+ socket.on('listModels-response', (response) => {
2292
+ resolve(response.models);
2293
+ socket.disconnect();
2294
+ });
2295
+ socket.on('error', (error) => {
2296
+ reject(deserializeError(error));
2297
+ socket.disconnect();
2298
+ });
2299
+ });
2300
+ socket.disconnect();
2301
+ return promptResult;
2302
+ }
2303
+ /**
2304
+ * Calls remote proxy server to use a chat model
2305
+ */
2306
+ callChatModel(prompt) {
2307
+ if (this.options.isVerbose) {
2308
+ console.info(`🖋 Remote callChatModel call`);
1938
2309
  }
1939
- else {
1940
- charType = 'OTHER';
1941
- normalizedChar = '_';
2310
+ return /* not await */ this.callCommonModel(prompt);
2311
+ }
2312
+ /**
2313
+ * Calls remote proxy server to use a completion model
2314
+ */
2315
+ callCompletionModel(prompt) {
2316
+ if (this.options.isVerbose) {
2317
+ console.info(`💬 Remote callCompletionModel call`);
1942
2318
  }
1943
- if (charType !== lastCharType &&
1944
- !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
1945
- !(lastCharType === 'NUMBER') &&
1946
- !(charType === 'NUMBER')) {
1947
- normalizedName += '_';
2319
+ return /* not await */ this.callCommonModel(prompt);
2320
+ }
2321
+ /**
2322
+ * Calls remote proxy server to use a embedding model
2323
+ */
2324
+ callEmbeddingModel(prompt) {
2325
+ if (this.options.isVerbose) {
2326
+ console.info(`💬 Remote callEmbeddingModel call`);
1948
2327
  }
1949
- normalizedName += normalizedChar;
1950
- lastCharType = charType;
2328
+ return /* not await */ this.callCommonModel(prompt);
2329
+ }
2330
+ // <- Note: [🤖] callXxxModel
2331
+ /**
2332
+ * Calls remote proxy server to use both completion or chat model
2333
+ */
2334
+ async callCommonModel(prompt) {
2335
+ const socket = await createRemoteClient(this.options);
2336
+ socket.emit('prompt-request', {
2337
+ identification: this.options.identification,
2338
+ prompt,
2339
+ } /* <- Note: [🤛] */);
2340
+ const promptResult = await new Promise((resolve, reject) => {
2341
+ socket.on('prompt-response', (response) => {
2342
+ resolve(response.promptResult);
2343
+ socket.disconnect();
2344
+ });
2345
+ socket.on('error', (error) => {
2346
+ reject(deserializeError(error));
2347
+ socket.disconnect();
2348
+ });
2349
+ });
2350
+ socket.disconnect();
2351
+ return promptResult;
1951
2352
  }
1952
- normalizedName = normalizedName.replace(/_+/g, '_');
1953
- normalizedName = normalizedName.replace(/_?\/_?/g, '/');
1954
- normalizedName = normalizedName.replace(/^_/, '');
1955
- normalizedName = normalizedName.replace(/_$/, '');
1956
- return normalizedName;
1957
2353
  }
1958
2354
  /**
1959
- * TODO: Tests
1960
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
1961
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ěščřžžýáíúů' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
1962
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
1963
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
1964
- * TODO: [🌺] Use some intermediate util splitWords
2355
+ * TODO: Maybe use `$exportJson`
2356
+ * TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
2357
+ * TODO: [🍓] Allow to list compatible models with each variant
2358
+ * TODO: [🗯] RemoteLlmExecutionTools should extend Destroyable and implement IDestroyable
2359
+ * TODO: [🧠][🌰] Allow to pass `title` for tracking purposes
2360
+ * TODO: [🧠] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
1965
2361
  */
1966
2362
 
1967
2363
  /**
1968
- * @@@
2364
+ * Simple wrapper `new Date().toISOString()`
1969
2365
  *
1970
- * @param text @@@
1971
- * @returns @@@
1972
- * @example 'hello_world'
1973
- * @example 'i_love_promptbook'
2366
+ * Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
2367
+ *
2368
+ * @returns string_date branded type
1974
2369
  * @public exported from `@promptbook/utils`
1975
2370
  */
1976
- function normalizeTo_snake_case(text) {
1977
- return normalizeTo_SCREAMING_CASE(text).toLowerCase();
2371
+ function $getCurrentDate() {
2372
+ return new Date().toISOString();
1978
2373
  }
1979
2374
 
1980
2375
  /**
1981
- * Register is @@@
2376
+ * Intercepts LLM tools and counts total usage of the tools
1982
2377
  *
1983
- * Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
2378
+ * Note: It can take extended `LlmExecutionTools` and cache the
1984
2379
  *
1985
- * @private internal utility, exported are only signleton instances of this class
2380
+ * @param llmTools LLM tools to be intercepted with usage counting, it can contain extra methods like `totalUsage`
2381
+ * @returns LLM tools with same functionality with added total cost counting
2382
+ * @public exported from `@promptbook/core`
1986
2383
  */
1987
- class $Register {
1988
- constructor(registerName) {
1989
- this.registerName = registerName;
1990
- const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
1991
- const globalScope = $getGlobalScope();
1992
- if (globalScope[storageName] === undefined) {
1993
- globalScope[storageName] = [];
2384
+ function cacheLlmTools(llmTools, options = {}) {
2385
+ const { storage = new MemoryStorage(), isCacheReloaded = false } = options;
2386
+ const proxyTools = {
2387
+ ...llmTools,
2388
+ // <- Note: [🥫]
2389
+ get title() {
2390
+ // TODO: [🧠] Maybe put here some suffix
2391
+ return llmTools.title;
2392
+ },
2393
+ get description() {
2394
+ // TODO: [🧠] Maybe put here some suffix
2395
+ return llmTools.description;
2396
+ },
2397
+ listModels() {
2398
+ // TODO: [🧠] Should be model listing also cached?
2399
+ return /* not await */ llmTools.listModels();
2400
+ },
2401
+ };
2402
+ const callCommonModel = async (prompt) => {
2403
+ const { parameters, content, modelRequirements } = prompt;
2404
+ // <- Note: These are relevant things from the prompt that the cache key should depend on.
2405
+ const key = titleToName(prompt.title.substring(0, MAX_FILENAME_LENGTH - 10) +
2406
+ '-' +
2407
+ sha256__default["default"](hexEncoder__default["default"].parse(JSON.stringify({ parameters, content, modelRequirements }))).toString( /* hex */));
2408
+ const cacheItem = !isCacheReloaded ? await storage.getItem(key) : null;
2409
+ if (cacheItem) {
2410
+ return cacheItem.promptResult;
1994
2411
  }
1995
- else if (!Array.isArray(globalScope[storageName])) {
1996
- throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
2412
+ let promptResult;
2413
+ variant: switch (prompt.modelRequirements.modelVariant) {
2414
+ case 'CHAT':
2415
+ promptResult = await llmTools.callChatModel(prompt);
2416
+ break variant;
2417
+ case 'COMPLETION':
2418
+ promptResult = await llmTools.callCompletionModel(prompt);
2419
+ break variant;
2420
+ case 'EMBEDDING':
2421
+ promptResult = await llmTools.callEmbeddingModel(prompt);
2422
+ break variant;
2423
+ // <- case [🤖]:
2424
+ default:
2425
+ throw new PipelineExecutionError(`Unknown model variant "${prompt.modelRequirements.modelVariant}"`);
1997
2426
  }
1998
- this.storage = globalScope[storageName];
2427
+ // TODO: [🧠] !!5 How to do timing in mixed cache / non-cache situation
2428
+ // promptResult.timing: FromtoItems
2429
+ await storage.setItem(key, {
2430
+ date: $getCurrentDate(),
2431
+ promptbookVersion: PROMPTBOOK_ENGINE_VERSION,
2432
+ prompt,
2433
+ promptResult,
2434
+ });
2435
+ return promptResult;
2436
+ };
2437
+ if (llmTools.callChatModel !== undefined) {
2438
+ proxyTools.callChatModel = async (prompt) => {
2439
+ return /* not await */ callCommonModel(prompt);
2440
+ };
1999
2441
  }
2000
- list() {
2001
- // <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>>
2002
- return this.storage;
2442
+ if (llmTools.callCompletionModel !== undefined) {
2443
+ proxyTools.callCompletionModel = async (prompt) => {
2444
+ return /* not await */ callCommonModel(prompt);
2445
+ };
2003
2446
  }
2004
- register(registered) {
2005
- const { packageName, className } = registered;
2006
- const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className);
2007
- const existingRegistration = this.storage[existingRegistrationIndex];
2008
- if (!existingRegistration) {
2009
- this.storage.push(registered);
2010
- }
2011
- else {
2012
- this.storage[existingRegistrationIndex] = registered;
2013
- }
2014
- return {
2015
- registerName: this.registerName,
2016
- packageName,
2017
- className,
2018
- get isDestroyed() {
2019
- return false;
2020
- },
2021
- destroy() {
2022
- throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`);
2023
- },
2447
+ if (llmTools.callEmbeddingModel !== undefined) {
2448
+ proxyTools.callEmbeddingModel = async (prompt) => {
2449
+ return /* not await */ callCommonModel(prompt);
2024
2450
  };
2025
2451
  }
2452
+ // <- Note: [🤖]
2453
+ return proxyTools;
2026
2454
  }
2027
-
2028
- /**
2029
- * @@@
2030
- *
2031
- * Note: `$` is used to indicate that this interacts with the global scope
2032
- * @singleton Only one instance of each register is created per build, but thare can be more @@@
2033
- * @public exported from `@promptbook/core`
2034
- */
2035
- const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
2036
2455
  /**
2037
- * TODO: [®] DRY Register logic
2456
+ * TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
2457
+ * TODO: [🧠] Is there some meaningfull way how to test this util
2458
+ * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
2459
+ * @@@ write discussion about this and storages
2460
+ * @@@ write how to combine multiple interceptors
2038
2461
  */
2039
2462
 
2040
2463
  /**
2041
- * Determines if the given path is a root path.
2464
+ * Represents the uncertain value
2042
2465
  *
2043
- * Note: This does not check if the file exists only if the path is valid
2044
- * @public exported from `@promptbook/utils`
2045
- */
2046
- function isRootPath(value) {
2047
- if (value === '/') {
2048
- return true;
2049
- }
2050
- if (/^[A-Z]:\\$/i.test(value)) {
2051
- return true;
2052
- }
2053
- return false;
2054
- }
2055
- /**
2056
- * TODO: [🍏] Make for MacOS paths
2466
+ * @public exported from `@promptbook/core`
2057
2467
  */
2058
-
2468
+ const ZERO_VALUE = $deepFreeze({ value: 0 });
2059
2469
  /**
2060
- * @@@
2470
+ * Represents the uncertain value
2061
2471
  *
2062
- * Note: `$` is used to indicate that this interacts with the global scope
2063
- * @singleton Only one instance of each register is created per build, but thare can be more @@@
2064
2472
  * @public exported from `@promptbook/core`
2065
2473
  */
2066
- const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
2474
+ const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
2067
2475
  /**
2068
- * TODO: [®] DRY Register logic
2476
+ * Represents the usage with no resources consumed
2477
+ *
2478
+ * @public exported from `@promptbook/core`
2069
2479
  */
2070
-
2480
+ const ZERO_USAGE = $deepFreeze({
2481
+ price: ZERO_VALUE,
2482
+ input: {
2483
+ tokensCount: ZERO_VALUE,
2484
+ charactersCount: ZERO_VALUE,
2485
+ wordsCount: ZERO_VALUE,
2486
+ sentencesCount: ZERO_VALUE,
2487
+ linesCount: ZERO_VALUE,
2488
+ paragraphsCount: ZERO_VALUE,
2489
+ pagesCount: ZERO_VALUE,
2490
+ },
2491
+ output: {
2492
+ tokensCount: ZERO_VALUE,
2493
+ charactersCount: ZERO_VALUE,
2494
+ wordsCount: ZERO_VALUE,
2495
+ sentencesCount: ZERO_VALUE,
2496
+ linesCount: ZERO_VALUE,
2497
+ paragraphsCount: ZERO_VALUE,
2498
+ pagesCount: ZERO_VALUE,
2499
+ },
2500
+ });
2071
2501
  /**
2072
- * Path to the `.env` file which was used to configure LLM tools
2502
+ * Represents the usage with unknown resources consumed
2073
2503
  *
2074
- * Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
2504
+ * @public exported from `@promptbook/core`
2075
2505
  */
2076
- let $usedEnvFilename = null;
2506
+ const UNCERTAIN_USAGE = $deepFreeze({
2507
+ price: UNCERTAIN_ZERO_VALUE,
2508
+ input: {
2509
+ tokensCount: UNCERTAIN_ZERO_VALUE,
2510
+ charactersCount: UNCERTAIN_ZERO_VALUE,
2511
+ wordsCount: UNCERTAIN_ZERO_VALUE,
2512
+ sentencesCount: UNCERTAIN_ZERO_VALUE,
2513
+ linesCount: UNCERTAIN_ZERO_VALUE,
2514
+ paragraphsCount: UNCERTAIN_ZERO_VALUE,
2515
+ pagesCount: UNCERTAIN_ZERO_VALUE,
2516
+ },
2517
+ output: {
2518
+ tokensCount: UNCERTAIN_ZERO_VALUE,
2519
+ charactersCount: UNCERTAIN_ZERO_VALUE,
2520
+ wordsCount: UNCERTAIN_ZERO_VALUE,
2521
+ sentencesCount: UNCERTAIN_ZERO_VALUE,
2522
+ linesCount: UNCERTAIN_ZERO_VALUE,
2523
+ paragraphsCount: UNCERTAIN_ZERO_VALUE,
2524
+ pagesCount: UNCERTAIN_ZERO_VALUE,
2525
+ },
2526
+ });
2077
2527
  /**
2078
- * Pass the `.env` file which was used to configure LLM tools
2079
- *
2080
- * Note: `$` is used to indicate that this variable is making side effect
2081
- *
2082
- * @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
2528
+ * Note: [💞] Ignore a discrepancy between file name and entity name
2083
2529
  */
2084
- function $setUsedEnvFilename(filepath) {
2085
- $usedEnvFilename = filepath;
2086
- }
2530
+
2087
2531
  /**
2088
- * Creates a message with all registered LLM tools
2532
+ * Function `addUsage` will add multiple usages into one
2089
2533
  *
2090
- * Note: This function is used to create a (error) message when there is no constructor for some LLM provider
2534
+ * Note: If you provide 0 values, it returns ZERO_USAGE
2091
2535
  *
2092
- * @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
2536
+ * @public exported from `@promptbook/core`
2093
2537
  */
2094
- function $registeredLlmToolsMessage() {
2095
- let env;
2096
- if ($isRunningInNode()) {
2097
- env = process.env;
2098
- // <- TODO: [⚛] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
2099
- }
2100
- else {
2101
- env = {};
2102
- }
2103
- /**
2104
- * Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
2105
- */
2106
- const all = [];
2107
- for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
2108
- if (all.some((item) => item.packageName === packageName && item.className === className)) {
2109
- continue;
2110
- }
2111
- all.push({ title, packageName, className, envVariables });
2112
- }
2113
- for (const { packageName, className } of $llmToolsRegister.list()) {
2114
- if (all.some((item) => item.packageName === packageName && item.className === className)) {
2115
- continue;
2538
+ function addUsage(...usageItems) {
2539
+ return usageItems.reduce((acc, item) => {
2540
+ var _a;
2541
+ acc.price.value += ((_a = item.price) === null || _a === void 0 ? void 0 : _a.value) || 0;
2542
+ for (const key of Object.keys(acc.input)) {
2543
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2544
+ //@ts-ignore
2545
+ if (item.input[key]) {
2546
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2547
+ //@ts-ignore
2548
+ acc.input[key].value += item.input[key].value || 0;
2549
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2550
+ //@ts-ignore
2551
+ if (item.input[key].isUncertain) {
2552
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2553
+ //@ts-ignore
2554
+ acc.input[key].isUncertain = true;
2555
+ }
2556
+ }
2116
2557
  }
2117
- all.push({ packageName, className });
2118
- }
2119
- const metadata = all.map((metadata) => {
2120
- var _a, _b;
2121
- const isMetadataAviailable = $llmToolsMetadataRegister
2122
- .list()
2123
- .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
2124
- const isInstalled = $llmToolsRegister
2125
- .list()
2126
- .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
2127
- const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
2128
- const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
2129
- // <- Note: [🗨]
2130
- return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
2131
- });
2132
- const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
2133
- if (metadata.length === 0) {
2134
- return spaceTrim__default["default"]((block) => `
2135
- No LLM providers are available.
2136
-
2137
- ${block(usedEnvMessage)}
2138
- `);
2139
- }
2140
- return spaceTrim__default["default"]((block) => `
2141
-
2142
- ${block(usedEnvMessage)}
2143
-
2144
- Relevant environment variables:
2145
- ${block(Object.keys(env)
2146
- .filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
2147
- .map((envVariableName) => `- \`${envVariableName}\``)
2148
- .join('\n'))}
2149
-
2150
- Available LLM providers are:
2151
- ${block(metadata
2152
- .map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
2153
- const morePieces = [];
2154
- if (just(false)) ;
2155
- else if (!isMetadataAviailable && !isInstalled) {
2156
- // TODO: [�][�] Maybe do allow to do auto-install if package not registered and not found
2157
- morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
2158
- }
2159
- else if (isMetadataAviailable && !isInstalled) {
2160
- // TODO: [�][�]
2161
- morePieces.push(`Not installed`);
2162
- }
2163
- else if (!isMetadataAviailable && isInstalled) {
2164
- morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
2165
- }
2166
- else if (isMetadataAviailable && isInstalled) {
2167
- morePieces.push(`Installed`);
2558
+ for (const key of Object.keys(acc.output)) {
2559
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2560
+ //@ts-ignore
2561
+ if (item.output[key]) {
2562
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2563
+ //@ts-ignore
2564
+ acc.output[key].value += item.output[key].value || 0;
2565
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2566
+ //@ts-ignore
2567
+ if (item.output[key].isUncertain) {
2568
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
2569
+ //@ts-ignore
2570
+ acc.output[key].isUncertain = true;
2571
+ }
2572
+ }
2573
+ }
2574
+ return acc;
2575
+ }, deepClone(ZERO_USAGE));
2576
+ }
2577
+
2578
+ /**
2579
+ * Intercepts LLM tools and counts total usage of the tools
2580
+ *
2581
+ * @param llmTools LLM tools to be intercepted with usage counting
2582
+ * @returns LLM tools with same functionality with added total cost counting
2583
+ * @public exported from `@promptbook/core`
2584
+ */
2585
+ function countUsage(llmTools) {
2586
+ let totalUsage = ZERO_USAGE;
2587
+ const spending = new rxjs.Subject();
2588
+ const proxyTools = {
2589
+ get title() {
2590
+ // TODO: [🧠] Maybe put here some suffix
2591
+ return llmTools.title;
2592
+ },
2593
+ get description() {
2594
+ // TODO: [🧠] Maybe put here some suffix
2595
+ return llmTools.description;
2596
+ },
2597
+ checkConfiguration() {
2598
+ return /* not await */ llmTools.checkConfiguration();
2599
+ },
2600
+ listModels() {
2601
+ return /* not await */ llmTools.listModels();
2602
+ },
2603
+ spending() {
2604
+ return spending.asObservable();
2605
+ },
2606
+ getTotalUsage() {
2607
+ // <- Note: [🥫] Not using getter `get totalUsage` but `getTotalUsage` to allow this object to be proxied
2608
+ return totalUsage;
2609
+ },
2610
+ };
2611
+ if (llmTools.callChatModel !== undefined) {
2612
+ proxyTools.callChatModel = async (prompt) => {
2613
+ // console.info('[🚕] callChatModel through countTotalUsage');
2614
+ const promptResult = await llmTools.callChatModel(prompt);
2615
+ totalUsage = addUsage(totalUsage, promptResult.usage);
2616
+ spending.next(promptResult.usage);
2617
+ return promptResult;
2618
+ };
2168
2619
  }
2169
- else {
2170
- morePieces.push(`unknown state, looks like a unexpected behavior`);
2171
- } /* not else */
2172
- if (isFullyConfigured) {
2173
- morePieces.push(`Configured`);
2620
+ if (llmTools.callCompletionModel !== undefined) {
2621
+ proxyTools.callCompletionModel = async (prompt) => {
2622
+ // console.info('[🚕] callCompletionModel through countTotalUsage');
2623
+ const promptResult = await llmTools.callCompletionModel(prompt);
2624
+ totalUsage = addUsage(totalUsage, promptResult.usage);
2625
+ spending.next(promptResult.usage);
2626
+ return promptResult;
2627
+ };
2174
2628
  }
2175
- else if (isPartiallyConfigured) {
2176
- morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
2629
+ if (llmTools.callEmbeddingModel !== undefined) {
2630
+ proxyTools.callEmbeddingModel = async (prompt) => {
2631
+ // console.info('[🚕] callEmbeddingModel through countTotalUsage');
2632
+ const promptResult = await llmTools.callEmbeddingModel(prompt);
2633
+ totalUsage = addUsage(totalUsage, promptResult.usage);
2634
+ spending.next(promptResult.usage);
2635
+ return promptResult;
2636
+ };
2177
2637
  }
2178
- else {
2179
- if (envVariables !== null) {
2180
- morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
2181
- }
2182
- else {
2183
- morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
2184
- }
2638
+ // <- Note: [🤖]
2639
+ return proxyTools;
2640
+ }
2641
+ /**
2642
+ * TODO: [🧠][💸] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
2643
+ * TODO: [🧠] Is there some meaningfull way how to test this util
2644
+ * TODO: [🧠][🌯] Maybe a way how to hide ability to `get totalUsage`
2645
+ * > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
2646
+ * TODO: [👷‍♂️] @@@ Manual about construction of llmTools
2647
+ */
2648
+
2649
+ /**
2650
+ * Determines if the given path is a root path.
2651
+ *
2652
+ * Note: This does not check if the file exists only if the path is valid
2653
+ * @public exported from `@promptbook/utils`
2654
+ */
2655
+ function isRootPath(value) {
2656
+ if (value === '/') {
2657
+ return true;
2185
2658
  }
2186
- let providerMessage = spaceTrim__default["default"](`
2187
- ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
2188
- ${morePieces.join('; ')}
2189
- `);
2190
- if ($isRunningInNode) {
2191
- if (isInstalled && isFullyConfigured) {
2192
- providerMessage = colors__default["default"].green(providerMessage);
2193
- }
2194
- else if (isInstalled && isPartiallyConfigured) {
2195
- providerMessage = colors__default["default"].yellow(providerMessage);
2196
- }
2197
- else {
2198
- providerMessage = colors__default["default"].gray(providerMessage);
2199
- }
2659
+ if (/^[A-Z]:\\$/i.test(value)) {
2660
+ return true;
2200
2661
  }
2201
- return providerMessage;
2202
- })
2203
- .join('\n'))}
2204
- `);
2662
+ return false;
2205
2663
  }
2206
2664
  /**
2207
- * TODO: [®] DRY Register logic
2208
- * TODO: [🧠][⚛] Maybe pass env as argument
2665
+ * TODO: [🍏] Make for MacOS paths
2209
2666
  */
2210
2667
 
2211
2668
  /**
@@ -2568,10 +3025,33 @@
2568
3025
  if (!$isRunningInNode()) {
2569
3026
  throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizzardOrCli` works only in Node.js environment');
2570
3027
  }
2571
- const { isCacheReloaded } = options !== null && options !== void 0 ? options : {};
3028
+ options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
3029
+ const { strategy, isCacheReloaded } = options;
3030
+ let llmExecutionTools;
3031
+ if (strategy === 'REMOTE_SERVER') {
3032
+ const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
3033
+ const storage = new MemoryStorage(); // <- TODO: !!!!!! Save to `.promptbook` folder
3034
+ const key = `${remoteServerUrl}-identification`;
3035
+ let identification = await storage.getItem(key);
3036
+ if (identification === null) {
3037
+ identification = await loginPrompt();
3038
+ await storage.setItem(key, identification);
3039
+ }
3040
+ llmExecutionTools = new RemoteLlmExecutionTools({
3041
+ remoteServerUrl,
3042
+ identification,
3043
+ });
3044
+ }
3045
+ else if (strategy === 'BRING_YOUR_OWN_KEYS') {
3046
+ llmExecutionTools = await $provideLlmToolsFromEnv();
3047
+ }
3048
+ else {
3049
+ throw new UnexpectedError(`\`$provideLlmToolsForWizzardOrCli\` wrong strategy "${strategy}"`);
3050
+ }
2572
3051
  return cacheLlmTools(countUsage(
3052
+ // <- TODO: [🌯] We dont use countUsage at all, maybe just unwrap it
2573
3053
  // <- Note: for example here we don`t want the [🌯]
2574
- await $provideLlmToolsFromEnv()), {
3054
+ llmExecutionTools), {
2575
3055
  storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
2576
3056
  rootFolderPath: path.join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
2577
3057
  }),
@@ -2587,31 +3067,88 @@
2587
3067
  */
2588
3068
 
2589
3069
  /**
2590
- * Just says that the variable is not used but should be kept
2591
- * No side effects.
2592
- *
2593
- * Note: It can be usefull for:
2594
- *
2595
- * 1) Suppressing eager optimization of unused imports
2596
- * 2) Suppressing eslint errors of unused variables in the tests
2597
- * 3) Keeping the type of the variable for type testing
2598
- *
2599
- * @param value any values
2600
- * @returns void
2601
- * @private within the repository
2602
- */
2603
- function keepUnused(...valuesToKeep) {
2604
- }
2605
-
2606
- /**
2607
- * Just says that the variable is not used directlys but should be kept because the existence of the variable is important
2608
- *
2609
- * @param value any values
2610
- * @returns void
2611
- * @private within the repository
3070
+ * @private utility of CLI
2612
3071
  */
2613
- function $sideEffect(...sideEffectSubjects) {
2614
- keepUnused(...sideEffectSubjects);
3072
+ function $provideLlmToolsForCli(options) {
3073
+ const { cliOptions: {
3074
+ /* TODO: Use verbose: isVerbose, */ interactive: isInteractive, provider, remoteServerUrl: remoteServerUrlRaw, }, } = options;
3075
+ let strategy;
3076
+ if (/^b/i.test(provider)) {
3077
+ strategy = 'BRING_YOUR_OWN_KEYS';
3078
+ }
3079
+ else if (/^r/i.test(provider)) {
3080
+ strategy = 'REMOTE_SERVER';
3081
+ }
3082
+ else {
3083
+ console.log(colors__default["default"].red(`Unknown provider: "${provider}", please use "BRING_YOUR_OWN_KEYS" or "REMOTE_SERVER"`));
3084
+ process.exit(1);
3085
+ }
3086
+ if (strategy === 'BRING_YOUR_OWN_KEYS') {
3087
+ return /* not await */ $provideLlmToolsForWizzardOrCli({ strategy, ...options });
3088
+ }
3089
+ else if (strategy === 'REMOTE_SERVER') {
3090
+ if (!isValidUrl(remoteServerUrlRaw)) {
3091
+ console.log(colors__default["default"].red(`Invalid URL of remote server: "${remoteServerUrlRaw}"`));
3092
+ process.exit(1);
3093
+ }
3094
+ const remoteServerUrl = remoteServerUrlRaw.endsWith('/') ? remoteServerUrlRaw.slice(0, -1) : remoteServerUrlRaw;
3095
+ return /* not await */ $provideLlmToolsForWizzardOrCli({
3096
+ strategy,
3097
+ appId: CLI_APP_ID,
3098
+ remoteServerUrl,
3099
+ ...options,
3100
+ async loginPrompt() {
3101
+ if (!isInteractive) {
3102
+ console.log(colors__default["default"].red(`You can not login to remote server in non-interactive mode`));
3103
+ process.exit(1);
3104
+ }
3105
+ const { username, password } = await prompts__default["default"]([
3106
+ {
3107
+ type: 'text',
3108
+ name: 'username',
3109
+ message: 'Enter your email:',
3110
+ validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
3111
+ },
3112
+ {
3113
+ type: 'password',
3114
+ name: 'password',
3115
+ message: 'Enter your password:',
3116
+ validate: (value) => value.length /* <- TODO: [🧠] Better password validation */ > 0
3117
+ ? true
3118
+ : 'Password is required',
3119
+ },
3120
+ ]);
3121
+ const loginUrl = `${remoteServerUrl}/login`;
3122
+ const response = await fetch(loginUrl, {
3123
+ method: 'POST',
3124
+ headers: {
3125
+ 'Content-Type': 'application/json',
3126
+ },
3127
+ body: JSON.stringify({
3128
+ appId: CLI_APP_ID,
3129
+ username,
3130
+ password,
3131
+ }),
3132
+ });
3133
+ console.log('!!!', {
3134
+ loginUrl,
3135
+ username,
3136
+ password,
3137
+ // type: response.type,
3138
+ // text: await response.text(),
3139
+ });
3140
+ const body = (await response.json());
3141
+ if ('error' in body) {
3142
+ console.log(colors__default["default"].red(body.error.message));
3143
+ process.exit(1);
3144
+ }
3145
+ return body.identification;
3146
+ },
3147
+ });
3148
+ }
3149
+ else {
3150
+ throw new UnexpectedError(`\`$provideLlmToolsForCli\` wrong strategy "${strategy}"`);
3151
+ }
2615
3152
  }
2616
3153
 
2617
3154
  /**
@@ -2628,8 +3165,10 @@
2628
3165
  `));
2629
3166
  listModelsCommand.alias('models');
2630
3167
  listModelsCommand.alias('llm');
2631
- listModelsCommand.action(handleActionErrors(async () => {
2632
- const llm = await $provideLlmToolsForWizzardOrCli({});
3168
+ listModelsCommand.action(handleActionErrors(async (cliOptions) => {
3169
+ console.log('!!!', cliOptions);
3170
+ // TODO: !!!!!! Not relevant for remote server and also for `about` command
3171
+ const llm = await $provideLlmToolsForCli({ cliOptions });
2633
3172
  $sideEffect(llm);
2634
3173
  // <- Note: Providing LLM tools will make a side effect of registering all available LLM tools to show the message
2635
3174
  console.info($registeredLlmToolsMessage());
@@ -3189,49 +3728,73 @@
3189
3728
  */
3190
3729
 
3191
3730
  /**
3192
- * Converts PipelineCollection to serialized JSON
3731
+ * Initializes `login` command for Promptbook CLI utilities
3193
3732
  *
3194
- * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
3733
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
3195
3734
  *
3196
- * @public exported from `@promptbook/core`
3197
- */
3198
- async function collectionToJson(collection) {
3199
- const pipelineUrls = await collection.listPipelines();
3200
- const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
3201
- return promptbooks;
3202
- }
3203
- /**
3204
- * TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
3735
+ * @private internal function of `promptbookCli`
3205
3736
  */
3737
+ function $initializeLoginCommand(program) {
3738
+ const loginCommand = program.command('login');
3739
+ loginCommand.description(spaceTrim__default["default"](`
3740
+ Login to the remote Promptbook server
3741
+ `));
3742
+ loginCommand.action(handleActionErrors(async () => {
3743
+ // @@@
3744
+ console.error(colors__default["default"].green(spaceTrim__default["default"](`
3745
+ You will be logged in to https://promptbook.studio server.
3746
+ If you don't have an account, it will be created automatically.
3747
+ `)));
3748
+ // !!!!!!!!! Remove from here and use $provideLlmToolsForCli
3749
+ const { email, password } = await prompts__default["default"]([
3750
+ {
3751
+ type: 'text',
3752
+ name: 'email',
3753
+ message: 'Enter your email:',
3754
+ validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
3755
+ },
3756
+ {
3757
+ type: 'password',
3758
+ name: 'password',
3759
+ message: 'Enter your password:',
3760
+ validate: (value) => value.length /* <- TODO: [🧠] Better password validation */ > 0 ? true : 'Password is required',
3761
+ },
3762
+ ]);
3763
+ TODO_USE(email, password);
3764
+ await waitasecond.forTime(1000);
3765
+ console.error(colors__default["default"].green(spaceTrim__default["default"](`
3766
+ Your account ${email} was successfully created.
3206
3767
 
3207
- /**
3208
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
3209
- *
3210
- * @public exported from `@promptbook/core`
3211
- */
3212
- class ParseError extends Error {
3213
- constructor(message) {
3214
- super(message);
3215
- this.name = 'ParseError';
3216
- Object.setPrototypeOf(this, ParseError.prototype);
3217
- }
3768
+ Please verify your email:
3769
+ https://brj.app/api/v1/customer/register-account?apiKey=PRODdh003eNKaec7PoO1AzU244tsL4WO
3770
+
3771
+ After verification, you will receive 500 000 credits for free 🎉
3772
+ `)));
3773
+ return process.exit(0);
3774
+ }));
3218
3775
  }
3219
3776
  /**
3220
- * TODO: Maybe split `ParseError` and `ApplyError`
3777
+ * TODO: Pass remote server URL (and path)
3778
+ * TODO: Implement non-interactive login
3779
+ * Note: [💞] Ignore a discrepancy between file name and entity name
3780
+ * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
3221
3781
  */
3222
3782
 
3223
3783
  /**
3224
- * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
3784
+ * Converts PipelineCollection to serialized JSON
3785
+ *
3786
+ * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
3225
3787
  *
3226
3788
  * @public exported from `@promptbook/core`
3227
3789
  */
3228
- class PipelineLogicError extends Error {
3229
- constructor(message) {
3230
- super(message);
3231
- this.name = 'PipelineLogicError';
3232
- Object.setPrototypeOf(this, PipelineLogicError.prototype);
3233
- }
3790
+ async function collectionToJson(collection) {
3791
+ const pipelineUrls = await collection.listPipelines();
3792
+ const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
3793
+ return promptbooks;
3234
3794
  }
3795
+ /**
3796
+ * TODO: [🧠] Maybe clear `sourceFile` or clear when exposing through API or remote server
3797
+ */
3235
3798
 
3236
3799
  /**
3237
3800
  * Tests if given string is valid semantic version
@@ -3629,21 +4192,6 @@
3629
4192
 
3630
4193
  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"}];
3631
4194
 
3632
- /**
3633
- * Checks if value is valid email
3634
- *
3635
- * @public exported from `@promptbook/utils`
3636
- */
3637
- function isValidEmail(email) {
3638
- if (typeof email !== 'string') {
3639
- return false;
3640
- }
3641
- if (email.split('\n').length > 1) {
3642
- return false;
3643
- }
3644
- return /^.+@.+\..+$/.test(email);
3645
- }
3646
-
3647
4195
  /**
3648
4196
  * Function isValidJsonString will tell you if the string is valid JSON or not
3649
4197
  *
@@ -3877,32 +4425,6 @@
3877
4425
  * TODO: [🧠] Should be in generated .book.md file GENERATOR_WARNING
3878
4426
  */
3879
4427
 
3880
- /**
3881
- * This error indicates that promptbook not found in the collection
3882
- *
3883
- * @public exported from `@promptbook/core`
3884
- */
3885
- class NotFoundError extends Error {
3886
- constructor(message) {
3887
- super(message);
3888
- this.name = 'NotFoundError';
3889
- Object.setPrototypeOf(this, NotFoundError.prototype);
3890
- }
3891
- }
3892
-
3893
- /**
3894
- * This error indicates errors in referencing promptbooks between each other
3895
- *
3896
- * @public exported from `@promptbook/core`
3897
- */
3898
- class PipelineUrlError extends Error {
3899
- constructor(message) {
3900
- super(message);
3901
- this.name = 'PipelineUrlError';
3902
- Object.setPrototypeOf(this, PipelineUrlError.prototype);
3903
- }
3904
- }
3905
-
3906
4428
  /**
3907
4429
  * Parses the task and returns the list of all parameter names
3908
4430
  *
@@ -4073,24 +4595,6 @@
4073
4595
  return new SimplePipelineCollection(...promptbooks);
4074
4596
  }
4075
4597
 
4076
- /**
4077
- * This error type indicates that some tools are missing for pipeline execution or preparation
4078
- *
4079
- * @public exported from `@promptbook/core`
4080
- */
4081
- class MissingToolsError extends Error {
4082
- constructor(message) {
4083
- super(spaceTrim.spaceTrim((block) => `
4084
- ${block(message)}
4085
-
4086
- Note: You have probbably forgot to provide some tools for pipeline execution or preparation
4087
-
4088
- `));
4089
- this.name = 'MissingToolsError';
4090
- Object.setPrototypeOf(this, MissingToolsError.prototype);
4091
- }
4092
- }
4093
-
4094
4598
  /**
4095
4599
  * Determine if the pipeline is fully prepared
4096
4600
  *
@@ -4123,210 +4627,40 @@
4123
4627
  * TODO: [🐠] Maybe base this on `makeValidator`
4124
4628
  * TODO: [🧊] Pipeline can be partially prepared, this should return true ONLY if fully prepared
4125
4629
  * TODO: [🧿] Maybe do same process with same granularity and subfinctions as `preparePipeline`
4126
- * - [🏍] ? Is context in each task
4127
- * - [♨] Are examples prepared
4128
- * - [♨] Are tasks prepared
4129
- */
4130
-
4131
- /**
4132
- * Recursively converts JSON strings to JSON objects
4133
-
4134
- * @public exported from `@promptbook/utils`
4135
- */
4136
- function jsonStringsToJsons(object) {
4137
- if (object === null) {
4138
- return object;
4139
- }
4140
- if (Array.isArray(object)) {
4141
- return object.map(jsonStringsToJsons);
4142
- }
4143
- if (typeof object !== 'object') {
4144
- return object;
4145
- }
4146
- const newObject = { ...object };
4147
- for (const [key, value] of Object.entries(object)) {
4148
- if (typeof value === 'string' && isValidJsonString(value)) {
4149
- newObject[key] = JSON.parse(value);
4150
- }
4151
- else {
4152
- newObject[key] = jsonStringsToJsons(value);
4153
- }
4154
- }
4155
- return newObject;
4156
- }
4157
- /**
4158
- * TODO: Type the return type correctly
4159
- */
4160
-
4161
- /**
4162
- * This error indicates problems parsing the format value
4163
- *
4164
- * For example, when the format value is not a valid JSON or CSV
4165
- * This is not thrown directly but in extended classes
4166
- *
4167
- * @public exported from `@promptbook/core`
4168
- */
4169
- class AbstractFormatError extends Error {
4170
- // Note: To allow instanceof do not put here error `name`
4171
- // public readonly name = 'AbstractFormatError';
4172
- constructor(message) {
4173
- super(message);
4174
- Object.setPrototypeOf(this, AbstractFormatError.prototype);
4175
- }
4176
- }
4177
-
4178
- /**
4179
- * This error indicates problem with parsing of CSV
4180
- *
4181
- * @public exported from `@promptbook/core`
4182
- */
4183
- class CsvFormatError extends AbstractFormatError {
4184
- constructor(message) {
4185
- super(message);
4186
- this.name = 'CsvFormatError';
4187
- Object.setPrototypeOf(this, CsvFormatError.prototype);
4188
- }
4189
- }
4190
-
4191
- /**
4192
- * This error indicates that the pipeline collection cannot be propperly loaded
4193
- *
4194
- * @public exported from `@promptbook/core`
4195
- */
4196
- class CollectionError extends Error {
4197
- constructor(message) {
4198
- super(message);
4199
- this.name = 'CollectionError';
4200
- Object.setPrototypeOf(this, CollectionError.prototype);
4201
- }
4202
- }
4203
-
4204
- /**
4205
- * This error occurs when some expectation is not met in the execution of the pipeline
4206
- *
4207
- * @public exported from `@promptbook/core`
4208
- * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
4209
- * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
4210
- * Note: This is a kindof subtype of PipelineExecutionError
4211
- */
4212
- class ExpectError extends Error {
4213
- constructor(message) {
4214
- super(message);
4215
- this.name = 'ExpectError';
4216
- Object.setPrototypeOf(this, ExpectError.prototype);
4217
- }
4218
- }
4219
-
4220
- /**
4221
- * This error indicates that the promptbook can not retrieve knowledge from external sources
4222
- *
4223
- * @public exported from `@promptbook/core`
4224
- */
4225
- class KnowledgeScrapeError extends Error {
4226
- constructor(message) {
4227
- super(message);
4228
- this.name = 'KnowledgeScrapeError';
4229
- Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
4230
- }
4231
- }
4232
-
4233
- /**
4234
- * This error type indicates that some limit was reached
4235
- *
4236
- * @public exported from `@promptbook/core`
4237
- */
4238
- class LimitReachedError extends Error {
4239
- constructor(message) {
4240
- super(message);
4241
- this.name = 'LimitReachedError';
4242
- Object.setPrototypeOf(this, LimitReachedError.prototype);
4243
- }
4244
- }
4245
-
4246
- /**
4247
- * Index of all custom errors
4248
- *
4249
- * @public exported from `@promptbook/core`
4250
- */
4251
- const PROMPTBOOK_ERRORS = {
4252
- AbstractFormatError,
4253
- CsvFormatError,
4254
- CollectionError,
4255
- EnvironmentMismatchError,
4256
- ExpectError,
4257
- KnowledgeScrapeError,
4258
- LimitReachedError,
4259
- MissingToolsError,
4260
- NotFoundError,
4261
- NotYetImplementedError,
4262
- ParseError,
4263
- PipelineExecutionError,
4264
- PipelineLogicError,
4265
- PipelineUrlError,
4266
- UnexpectedError,
4267
- // TODO: [🪑]> VersionMismatchError,
4268
- };
4269
- /**
4270
- * Index of all javascript errors
4271
- *
4272
- * @private for internal usage
4273
- */
4274
- const COMMON_JAVASCRIPT_ERRORS = {
4275
- Error,
4276
- EvalError,
4277
- RangeError,
4278
- ReferenceError,
4279
- SyntaxError,
4280
- TypeError,
4281
- URIError,
4282
- AggregateError,
4283
- /*
4284
- Note: Not widely supported
4285
- > InternalError,
4286
- > ModuleError,
4287
- > HeapError,
4288
- > WebAssemblyCompileError,
4289
- > WebAssemblyRuntimeError,
4290
- */
4291
- };
4292
- /**
4293
- * Index of all errors
4294
- *
4295
- * @private for internal usage
4296
- */
4297
- const ALL_ERRORS = {
4298
- ...PROMPTBOOK_ERRORS,
4299
- ...COMMON_JAVASCRIPT_ERRORS,
4300
- };
4301
- /**
4302
- * Note: [💞] Ignore a discrepancy between file name and entity name
4303
- */
4304
-
4305
- /**
4306
- * Deserializes the error object
4307
- *
4308
- * @public exported from `@promptbook/utils`
4309
- */
4310
- function deserializeError(error) {
4311
- const { name, stack, id } = error; // Added id
4312
- let { message } = error;
4313
- let ErrorClass = ALL_ERRORS[error.name];
4314
- if (ErrorClass === undefined) {
4315
- ErrorClass = Error;
4316
- message = `${name}: ${message}`;
4317
- }
4318
- if (stack !== undefined && stack !== '') {
4319
- message = spaceTrim__default["default"]((block) => `
4320
- ${block(message)}
4630
+ * - [🏍] ? Is context in each task
4631
+ * - [♨] Are examples prepared
4632
+ * - [♨] Are tasks prepared
4633
+ */
4321
4634
 
4322
- Original stack trace:
4323
- ${block(stack || '')}
4324
- `);
4635
+ /**
4636
+ * Recursively converts JSON strings to JSON objects
4637
+
4638
+ * @public exported from `@promptbook/utils`
4639
+ */
4640
+ function jsonStringsToJsons(object) {
4641
+ if (object === null) {
4642
+ return object;
4325
4643
  }
4326
- const deserializedError = new ErrorClass(message);
4327
- deserializedError.id = id; // Assign id to the error object
4328
- return deserializedError;
4644
+ if (Array.isArray(object)) {
4645
+ return object.map(jsonStringsToJsons);
4646
+ }
4647
+ if (typeof object !== 'object') {
4648
+ return object;
4649
+ }
4650
+ const newObject = { ...object };
4651
+ for (const [key, value] of Object.entries(object)) {
4652
+ if (typeof value === 'string' && isValidJsonString(value)) {
4653
+ newObject[key] = JSON.parse(value);
4654
+ }
4655
+ else {
4656
+ newObject[key] = jsonStringsToJsons(value);
4657
+ }
4658
+ }
4659
+ return newObject;
4329
4660
  }
4661
+ /**
4662
+ * TODO: Type the return type correctly
4663
+ */
4330
4664
 
4331
4665
  /**
4332
4666
  * Asserts that the execution of a Promptbook is successful
@@ -4479,6 +4813,10 @@
4479
4813
 
4480
4814
  Cannot serialize error with name "${name}"
4481
4815
 
4816
+ Authors of Promptbook probably forgot to add this error into the list of errors:
4817
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
4818
+
4819
+
4482
4820
  ${block(stack || message)}
4483
4821
 
4484
4822
  `));
@@ -11389,7 +11727,6 @@
11389
11727
  makeCommand.option('--no-validation', `Do not validate logic of pipelines in collection`, true);
11390
11728
  makeCommand.option('--validation', `Types of validations separated by comma (options "logic","imports")`, 'logic,imports');
11391
11729
  makeCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
11392
- makeCommand.option('-v, --verbose', `Is output verbose`, false);
11393
11730
  makeCommand.option('-o, --output <path>', spaceTrim__default["default"](`
11394
11731
  Where to save the builded collection
11395
11732
 
@@ -11403,7 +11740,8 @@
11403
11740
  Note: This can be used only with "javascript" or "typescript" format
11404
11741
 
11405
11742
  `), DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME);
11406
- makeCommand.action(handleActionErrors(async (path$1, { projectName, rootUrl, format, functionName, validation, reload: isCacheReloaded, verbose: isVerbose, output, }) => {
11743
+ makeCommand.action(handleActionErrors(async (path$1, cliOptions) => {
11744
+ const { projectName, rootUrl, format, functionName, validation, reload: isCacheReloaded, verbose: isVerbose, output, } = cliOptions;
11407
11745
  if (!isValidJavascriptName(functionName)) {
11408
11746
  console.error(colors__default["default"].red(`Function name "${functionName}" is not valid javascript name`));
11409
11747
  return process.exit(1);
@@ -11431,7 +11769,10 @@
11431
11769
  isCacheReloaded,
11432
11770
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
11433
11771
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
11434
- const llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
11772
+ const llm = await $provideLlmToolsForCli({
11773
+ cliOptions,
11774
+ ...prepareAndScrapeOptions,
11775
+ });
11435
11776
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
11436
11777
  const tools = {
11437
11778
  llm,
@@ -11699,8 +12040,8 @@
11699
12040
  // <- TODO: [🧟‍♂️] Unite path to promptbook collection argument
11700
12041
  'Pipelines to prettify as glob pattern');
11701
12042
  prettifyCommand.option('-i, --ignore <glob>', `Ignore as glob pattern`);
11702
- prettifyCommand.option('-v, --verbose', `Is output verbose`, false);
11703
- prettifyCommand.action(handleActionErrors(async (filesGlob, { ignore, verbose: isVerbose }) => {
12043
+ prettifyCommand.action(handleActionErrors(async (filesGlob, cliOptions) => {
12044
+ const { ignore, verbose: isVerbose } = cliOptions;
11704
12045
  const filenames = await glob__default["default"](filesGlob, { ignore });
11705
12046
  // <- TODO: [😶]
11706
12047
  for (const filename of filenames) {
@@ -12301,13 +12642,12 @@
12301
12642
  // TODO: [🧅] DRY command arguments
12302
12643
  runCommand.argument('[pipelineSource]', 'Path to book file OR URL to book file, if not provided it will be asked');
12303
12644
  runCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
12304
- runCommand.option('-v, --verbose', `Is output verbose`, false);
12305
- runCommand.option('--no-interactive', `Input is not interactive, if true you need to pass all the input parameters through --json`);
12306
12645
  runCommand.option('--no-formfactor', `When set, behavior of the interactive mode is not changed by the formfactor of the pipeline`);
12307
12646
  runCommand.option('-j, --json <json>', `Pass all or some input parameters as JSON record, if used the output is also returned as JSON`);
12308
12647
  runCommand.option('-s, --save-report <path>', `Save report to file`);
12309
- runCommand.action(handleActionErrors(async (pipelineSource, options) => {
12310
- const { reload: isCacheReloaded, interactive: isInteractive, formfactor: isFormfactorUsed, json, verbose: isVerbose, saveReport, } = options;
12648
+ runCommand.action(handleActionErrors(async (pipelineSource, cliOptions) => {
12649
+ console.log('!!!', cliOptions);
12650
+ const { reload: isCacheReloaded, interactive: isInteractive, formfactor: isFormfactorUsed, json, verbose: isVerbose, saveReport, } = cliOptions;
12311
12651
  if (pipelineSource.includes('-') && normalizeToKebabCase(pipelineSource) === pipelineSource) {
12312
12652
  console.error(colors__default["default"].red(`""${pipelineSource}" is not a valid command or book. See 'ptbk --help'.`));
12313
12653
  return process.exit(1);
@@ -12332,7 +12672,7 @@
12332
12672
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
12333
12673
  let llm;
12334
12674
  try {
12335
- llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
12675
+ llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
12336
12676
  }
12337
12677
  catch (error) {
12338
12678
  if (!(error instanceof Error)) {
@@ -12389,7 +12729,7 @@
12389
12729
  fs,
12390
12730
  fetch: scraperFetch,
12391
12731
  scrapers: await $provideScrapersForNode({ fs, llm, executables }, prepareAndScrapeOptions),
12392
- script: [new JavascriptExecutionTools(options)],
12732
+ script: [new JavascriptExecutionTools(cliOptions)],
12393
12733
  };
12394
12734
  if (isVerbose) {
12395
12735
  console.info(colors__default["default"].gray('--- Getting the book ---'));
@@ -12553,11 +12893,12 @@
12553
12893
  * @public exported from `@promptbook/remote-server`
12554
12894
  */
12555
12895
  function startRemoteServer(options) {
12556
- const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, } = {
12896
+ const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, login, } = {
12557
12897
  isAnonymousModeAllowed: false,
12558
12898
  isApplicationModeAllowed: false,
12559
12899
  collection: null,
12560
12900
  createLlmExecutionTools: null,
12901
+ login: null,
12561
12902
  ...options,
12562
12903
  };
12563
12904
  // <- TODO: [🦪] Some helper type to be able to use discriminant union types with destructuring
@@ -12624,9 +12965,38 @@
12624
12965
  response.setHeader('X-Powered-By', 'Promptbook engine');
12625
12966
  next();
12626
12967
  });
12968
+ const swaggerOptions = {
12969
+ definition: {
12970
+ openapi: '3.0.0',
12971
+ info: {
12972
+ title: 'Promptbook Remote Server API',
12973
+ version: '1.0.0',
12974
+ description: 'API documentation for the Promptbook Remote Server',
12975
+ },
12976
+ servers: [
12977
+ {
12978
+ url: `http://localhost:${port}${rootPath}`,
12979
+ // <- TODO: !!!!! Probbably: Pass `remoteServerUrl` instead of `port` and `rootPath`
12980
+ },
12981
+ ],
12982
+ },
12983
+ apis: ['./src/remote-server/**/*.ts'], // Adjust path as needed
12984
+ };
12985
+ const swaggerSpec = swaggerJsdoc__default["default"](swaggerOptions);
12986
+ app.use([`/api-docs`, `${rootPath}/api-docs`], swaggerUi__default["default"].serve, swaggerUi__default["default"].setup(swaggerSpec));
12627
12987
  const runningExecutionTasks = [];
12628
12988
  // <- TODO: [🤬] Identify the users
12629
12989
  // TODO: [🧠] Do here some garbage collection of finished tasks
12990
+ /**
12991
+ * @swagger
12992
+ * /:
12993
+ * get:
12994
+ * summary: Get server details
12995
+ * description: Returns details about the Promptbook server.
12996
+ * responses:
12997
+ * 200:
12998
+ * description: Server details in markdown format.
12999
+ */
12630
13000
  app.get(['/', rootPath], async (request, response) => {
12631
13001
  var _a;
12632
13002
  if ((_a = request.url) === null || _a === void 0 ? void 0 : _a.includes('socket.io')) {
@@ -12663,9 +13033,12 @@
12663
13033
 
12664
13034
  ## Paths
12665
13035
 
12666
- ${block(app._router.stack
12667
- .map(({ route }) => (route === null || route === void 0 ? void 0 : route.path) || null)
12668
- .filter((path) => path !== null)
13036
+ ${block([
13037
+ ...app._router.stack
13038
+ .map(({ route }) => (route === null || route === void 0 ? void 0 : route.path) || null)
13039
+ .filter((path) => path !== null),
13040
+ '/api-docs',
13041
+ ]
12669
13042
  .map((path) => `- ${path}`)
12670
13043
  .join('\n'))}
12671
13044
 
@@ -12683,8 +13056,81 @@
12683
13056
  https://github.com/webgptorg/promptbook
12684
13057
  `));
12685
13058
  });
12686
- // TODO: !!!!!! Add login route
12687
- app.get(`${rootPath}/books`, async (request, response) => {
13059
+ /**
13060
+ * @swagger
13061
+ *
13062
+ * /login:
13063
+ * post:
13064
+ * summary: Login to the server
13065
+ * description: Login to the server and get identification.
13066
+ * requestBody:
13067
+ * required: true
13068
+ * content:
13069
+ * application/json:
13070
+ * schema:
13071
+ * type: object
13072
+ * properties:
13073
+ * username:
13074
+ * type: string
13075
+ * password:
13076
+ * type: string
13077
+ * appId:
13078
+ * type: string
13079
+ * responses:
13080
+ * 200:
13081
+ * description: Successful login
13082
+ * content:
13083
+ * application/json:
13084
+ * schema:
13085
+ * type: object
13086
+ * properties:
13087
+ * identification:
13088
+ * type: object
13089
+ */
13090
+ app.post([`/login`, `${rootPath}/login`], async (request, response) => {
13091
+ if (!isApplicationModeAllowed || login === null) {
13092
+ response.status(400).send('Application mode is not allowed');
13093
+ return;
13094
+ }
13095
+ try {
13096
+ const username = request.body.username;
13097
+ const password = request.body.password;
13098
+ const appId = request.body.appId;
13099
+ const identification = await login({ username, password, appId });
13100
+ response.status(201).send({ identification });
13101
+ return;
13102
+ }
13103
+ catch (error) {
13104
+ if (!(error instanceof Error)) {
13105
+ throw error;
13106
+ }
13107
+ if (error instanceof AuthenticationError) {
13108
+ response.status(401).send({ error: serializeError(error) });
13109
+ }
13110
+ console.warn(`Login function thrown different error than AuthenticationError`, {
13111
+ error,
13112
+ serializedError: serializeError(error),
13113
+ });
13114
+ response.status(400).send({ error: serializeError(error) });
13115
+ }
13116
+ });
13117
+ /**
13118
+ * @swagger
13119
+ * /books:
13120
+ * get:
13121
+ * summary: List all books
13122
+ * description: Returns a list of all available books in the collection.
13123
+ * responses:
13124
+ * 200:
13125
+ * description: A list of books.
13126
+ * content:
13127
+ * application/json:
13128
+ * schema:
13129
+ * type: array
13130
+ * items:
13131
+ * type: string
13132
+ */
13133
+ app.get([`/books`, `${rootPath}/books`], async (request, response) => {
12688
13134
  if (collection === null) {
12689
13135
  response.status(500).send('No collection available');
12690
13136
  return;
@@ -12694,7 +13140,30 @@
12694
13140
  response.send(pipelines);
12695
13141
  });
12696
13142
  // TODO: [🧠] Is it secure / good idea to expose source codes of hosted books
12697
- app.get(`${rootPath}/books/*`, async (request, response) => {
13143
+ /**
13144
+ * @swagger
13145
+ * /books/{bookId}:
13146
+ * get:
13147
+ * summary: Get book content
13148
+ * description: Returns the content of a specific book.
13149
+ * parameters:
13150
+ * - in: path
13151
+ * name: bookId
13152
+ * required: true
13153
+ * schema:
13154
+ * type: string
13155
+ * description: The ID of the book to retrieve.
13156
+ * responses:
13157
+ * 200:
13158
+ * description: The content of the book.
13159
+ * content:
13160
+ * text/markdown:
13161
+ * schema:
13162
+ * type: string
13163
+ * 404:
13164
+ * description: Book not found.
13165
+ */
13166
+ app.get([`/books/*`, `${rootPath}/books/*`], async (request, response) => {
12698
13167
  try {
12699
13168
  if (collection === null) {
12700
13169
  response.status(500).send('No collection nor books available');
@@ -12748,10 +13217,26 @@
12748
13217
  };
12749
13218
  }
12750
13219
  }
12751
- app.get(`${rootPath}/executions`, async (request, response) => {
13220
+ /**
13221
+ * @swagger
13222
+ * /executions:
13223
+ * get:
13224
+ * summary: List all executions
13225
+ * description: Returns a list of all running execution tasks.
13226
+ * responses:
13227
+ * 200:
13228
+ * description: A list of execution tasks.
13229
+ * content:
13230
+ * application/json:
13231
+ * schema:
13232
+ * type: array
13233
+ * items:
13234
+ * type: object
13235
+ */
13236
+ app.get([`/executions`, `${rootPath}/executions`], async (request, response) => {
12752
13237
  response.send(runningExecutionTasks.map((runningExecutionTask) => exportExecutionTask(runningExecutionTask, false)));
12753
13238
  });
12754
- app.get(`${rootPath}/executions/last`, async (request, response) => {
13239
+ app.get([`/executions/last`, `${rootPath}/executions/last`], async (request, response) => {
12755
13240
  // TODO: [🤬] Filter only for user
12756
13241
  if (runningExecutionTasks.length === 0) {
12757
13242
  response.status(404).send('No execution tasks found');
@@ -12760,7 +13245,7 @@
12760
13245
  const lastExecutionTask = runningExecutionTasks[runningExecutionTasks.length - 1];
12761
13246
  response.send(exportExecutionTask(lastExecutionTask, true));
12762
13247
  });
12763
- app.get(`${rootPath}/executions/:taskId`, async (request, response) => {
13248
+ app.get([`/executions/:taskId`, `${rootPath}/executions/:taskId`], async (request, response) => {
12764
13249
  const { taskId } = request.params;
12765
13250
  // TODO: [🤬] Filter only for user
12766
13251
  const executionTask = runningExecutionTasks.find((executionTask) => executionTask.taskId === taskId);
@@ -12772,7 +13257,36 @@
12772
13257
  }
12773
13258
  response.send(exportExecutionTask(executionTask, true));
12774
13259
  });
12775
- app.post(`${rootPath}/executions/new`, async (request, response) => {
13260
+ /**
13261
+ * @swagger
13262
+ * /executions/new:
13263
+ * post:
13264
+ * summary: Start a new execution
13265
+ * description: Starts a new execution task for a given pipeline.
13266
+ * requestBody:
13267
+ * required: true
13268
+ * content:
13269
+ * application/json:
13270
+ * schema:
13271
+ * type: object
13272
+ * properties:
13273
+ * pipelineUrl:
13274
+ * type: string
13275
+ * inputParameters:
13276
+ * type: object
13277
+ * identification:
13278
+ * type: object
13279
+ * responses:
13280
+ * 200:
13281
+ * description: The newly created execution task.
13282
+ * content:
13283
+ * application/json:
13284
+ * schema:
13285
+ * type: object
13286
+ * 400:
13287
+ * description: Invalid input.
13288
+ */
13289
+ app.post([`/executions/new`, `${rootPath}/executions/new`], async (request, response) => {
12776
13290
  try {
12777
13291
  const { inputParameters, identification /* <- [🤬] */ } = request.body;
12778
13292
  const pipelineUrl = request.body.pipelineUrl || request.body.book;
@@ -13008,12 +13522,12 @@
13008
13522
  `));
13009
13523
  startServerCommand.option('--allow-anonymous', `Is anonymous mode allowed`, false);
13010
13524
  startServerCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
13011
- startServerCommand.option('-v, --verbose', `Is output verbose`, false);
13012
13525
  startServerCommand.description(spaceTrim__default["default"](`
13013
13526
  Starts a remote server to execute books
13014
13527
  `));
13015
13528
  startServerCommand.alias('server');
13016
- startServerCommand.action(handleActionErrors(async (path, { port: portRaw, url: rawUrl, allowAnonymous: isAnonymousModeAllowed, reload: isCacheReloaded, verbose: isVerbose, }) => {
13529
+ startServerCommand.action(handleActionErrors(async (path, cliOptions) => {
13530
+ const { port: portRaw, url: rawUrl, allowAnonymous: isAnonymousModeAllowed, reload: isCacheReloaded, verbose: isVerbose, } = cliOptions;
13017
13531
  if (rawUrl && !isValidUrl(rawUrl)) {
13018
13532
  console.error(colors__default["default"].red(`Invalid URL: ${rawUrl}`));
13019
13533
  return process.exit(1);
@@ -13042,7 +13556,7 @@
13042
13556
  isCacheReloaded,
13043
13557
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
13044
13558
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
13045
- const llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
13559
+ const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13046
13560
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
13047
13561
  const tools = {
13048
13562
  llm,
@@ -13066,6 +13580,9 @@
13066
13580
  isAnonymousModeAllowed,
13067
13581
  isApplicationModeAllowed: true,
13068
13582
  collection,
13583
+ async login() {
13584
+ throw new AuthenticationError('You can not login to the server started by `ptbk start-server` in cli, use `startRemoteServer` function instead.');
13585
+ },
13069
13586
  createLlmExecutionTools(options) {
13070
13587
  const { appId, userId } = options;
13071
13588
  TODO_USE({ appId, userId });
@@ -13102,8 +13619,8 @@
13102
13619
  testCommand.option('--no-validation', `Do not validate logic of pipelines in collection`, true);
13103
13620
  testCommand.option('--no-prepare', `Do not prepare the pipelines, ideal when no LLM tools or scrapers available`, true);
13104
13621
  testCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache `, false);
13105
- testCommand.option('-v, --verbose', `Is output verbose`, false);
13106
- testCommand.action(handleActionErrors(async (filesGlob, { ignore: ignoreRaw = '', validation: isValidated, prepare: isPrepared, reload: isCacheReloaded, verbose: isVerbose, }) => {
13622
+ testCommand.action(handleActionErrors(async (filesGlob, cliOptions) => {
13623
+ const { ignore: ignoreRaw = '', validation: isValidated, prepare: isPrepared, reload: isCacheReloaded, verbose: isVerbose, } = cliOptions;
13107
13624
  let tools = undefined;
13108
13625
  if (isPrepared) {
13109
13626
  // TODO: DRY [◽]
@@ -13112,7 +13629,7 @@
13112
13629
  isCacheReloaded,
13113
13630
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
13114
13631
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
13115
- const llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
13632
+ const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13116
13633
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
13117
13634
  tools = {
13118
13635
  llm,
@@ -13173,56 +13690,16 @@
13173
13690
  */
13174
13691
 
13175
13692
  /**
13176
- * Initializes `login` command for Promptbook CLI utilities
13177
- *
13178
- * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
13693
+ * Note: `$` is used to indicate that this function is not a pure function - it registers an option in the CLI
13179
13694
  *
13180
- * @private internal function of `promptbookCli`
13695
+ * @private utility of CLI
13181
13696
  */
13182
- function $initializeLoginCommand(program) {
13183
- const loginCommand = program.command('login');
13184
- loginCommand.description(spaceTrim__default["default"](`
13185
- Login to the remote Promptbook server
13186
- `));
13187
- loginCommand.action(handleActionErrors(async () => {
13188
- // @@@
13189
- console.error(colors__default["default"].green(spaceTrim__default["default"](`
13190
- You will be logged in to https://promptbook.studio server.
13191
- If you don't have an account, it will be created automatically.
13192
- `)));
13193
- const { email, password } = await prompts__default["default"]([
13194
- {
13195
- type: 'text',
13196
- name: 'email',
13197
- message: 'Enter your email:',
13198
- validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
13199
- },
13200
- {
13201
- type: 'password',
13202
- name: 'password',
13203
- message: 'Enter your password:',
13204
- validate: (value) => value.length /* <- TODO: [🧠] Better password validation */ > 0 ? true : 'Password is required',
13205
- },
13206
- ]);
13207
- TODO_USE(email, password);
13208
- await waitasecond.forTime(1000);
13209
- console.error(colors__default["default"].green(spaceTrim__default["default"](`
13210
- Your account ${email} was successfully created.
13211
-
13212
- Please verify your email:
13213
- https://brj.app/api/v1/customer/register-account?apiKey=PRODdh003eNKaec7PoO1AzU244tsL4WO
13214
-
13215
- After verification, you will receive 500 000 credits for free 🎉
13216
- `)));
13217
- return process.exit(0);
13218
- }));
13697
+ function $addGlobalOptionsToCommand(command) {
13698
+ command.option('-v, --verbose', `Log more details`, false);
13699
+ command.option('--no-interactive', `Input is not interactive, if true, no CLI input is prompted if required, so either defaults are used or the command fails with exit code 1`);
13700
+ command.option('-p, --provider <provider>', `Which LLM provider to use: "BYOK" / "BRING_YOUR_OWN_KEYS" or "REMOTE_SERVER" / "RS"`, 'REMOTE_SERVER');
13701
+ command.option('--remote-server-url <url>', `URL of remote server to use when `, DEFAULT_REMOTE_SERVER_URL);
13219
13702
  }
13220
- /**
13221
- * TODO: Pass remote server URL (and path)
13222
- * TODO: Implement non-interactive login
13223
- * Note: [💞] Ignore a discrepancy between file name and entity name
13224
- * Note: [🟡] Code in this file should never be published outside of `@promptbook/cli`
13225
- */
13226
13703
 
13227
13704
  /**
13228
13705
  * Runs CLI utilities of Promptbook package
@@ -13248,6 +13725,7 @@
13248
13725
  program.alias('ptbk');
13249
13726
  program.version(PROMPTBOOK_ENGINE_VERSION);
13250
13727
  program.description(CLAIM);
13728
+ // Note: Theese options are valid for all commands
13251
13729
  $initializeAboutCommand(program);
13252
13730
  $initializeRunCommand(program);
13253
13731
  $initializeLoginCommand(program);
@@ -13258,6 +13736,8 @@
13258
13736
  $initializeListModelsCommand(program);
13259
13737
  $initializeListScrapersCommand(program);
13260
13738
  $initializeStartServerCommand(program);
13739
+ // TODO: [🧠] Should it be here or not> $addGlobalOptionsToCommand(program);
13740
+ program.commands.forEach($addGlobalOptionsToCommand);
13261
13741
  program.parse(process.argv);
13262
13742
  }
13263
13743
  /**
@@ -13307,8 +13787,7 @@
13307
13787
  options: {
13308
13788
  apiKey: 'sk-ant-api03-',
13309
13789
  isProxied: true,
13310
- remoteUrl: DEFAULT_REMOTE_URL,
13311
- path: DEFAULT_REMOTE_URL_PATH,
13790
+ remoteServerUrl: DEFAULT_REMOTE_SERVER_URL,
13312
13791
  },
13313
13792
  };
13314
13793
  },
@@ -13331,146 +13810,6 @@
13331
13810
  * Note: [💞] Ignore a discrepancy between file name and entity name
13332
13811
  */
13333
13812
 
13334
- /**
13335
- * Creates a connection to the remote proxy server.
13336
- *
13337
- * Note: This function creates a connection to the remote server and returns a socket but responsibility of closing the connection is on the caller
13338
- *
13339
- * @private internal utility function
13340
- */
13341
- async function createRemoteClient(options) {
13342
- const { remoteUrl, path } = options;
13343
- return new Promise((resolve, reject) => {
13344
- const socket = socket_ioClient.io(remoteUrl, {
13345
- retries: CONNECTION_RETRIES_LIMIT,
13346
- timeout: CONNECTION_TIMEOUT_MS,
13347
- path,
13348
- // path: `${this.remoteUrl.pathname}/socket.io`,
13349
- transports: [/*'websocket', <- TODO: [🌬] Make websocket transport work */ 'polling'],
13350
- });
13351
- // console.log('Connecting to', this.options.remoteUrl.href, { socket });
13352
- socket.on('connect', () => {
13353
- resolve(socket);
13354
- });
13355
- // TODO: [💩] Better timeout handling
13356
- setTimeout(() => {
13357
- reject(new Error(`Timeout while connecting to ${remoteUrl}`));
13358
- }, CONNECTION_TIMEOUT_MS);
13359
- });
13360
- }
13361
-
13362
- /**
13363
- * Remote server is a proxy server that uses its execution tools internally and exposes the executor interface externally.
13364
- *
13365
- * You can simply use `RemoteExecutionTools` on client-side javascript and connect to your remote server.
13366
- * This is useful to make all logic on browser side but not expose your API keys or no need to use customer's GPU.
13367
- *
13368
- * @see https://github.com/webgptorg/promptbook#remote-server
13369
- * @public exported from `@promptbook/remote-client`
13370
- */
13371
- class RemoteLlmExecutionTools {
13372
- /* <- TODO: [🍚] `, Destroyable` */
13373
- constructor(options) {
13374
- this.options = options;
13375
- }
13376
- get title() {
13377
- // TODO: [🧠] Maybe fetch title+description from the remote server (as well as if model methods are defined)
13378
- return 'Remote server';
13379
- }
13380
- get description() {
13381
- return 'Use all models by your remote server';
13382
- }
13383
- /**
13384
- * Check the configuration of all execution tools
13385
- */
13386
- async checkConfiguration() {
13387
- const socket = await createRemoteClient(this.options);
13388
- socket.disconnect();
13389
- // TODO: [main] !!3 Check version of the remote server and compatibility
13390
- // TODO: [🎍] Send checkConfiguration
13391
- }
13392
- /**
13393
- * List all available models that can be used
13394
- */
13395
- async listModels() {
13396
- // TODO: [👒] Listing models (and checking configuration) probbably should go through REST API not Socket.io
13397
- const socket = await createRemoteClient(this.options);
13398
- socket.emit('listModels-request', {
13399
- identification: this.options.identification,
13400
- } /* <- Note: [🤛] */);
13401
- const promptResult = await new Promise((resolve, reject) => {
13402
- socket.on('listModels-response', (response) => {
13403
- resolve(response.models);
13404
- socket.disconnect();
13405
- });
13406
- socket.on('error', (error) => {
13407
- reject(deserializeError(error));
13408
- socket.disconnect();
13409
- });
13410
- });
13411
- socket.disconnect();
13412
- return promptResult;
13413
- }
13414
- /**
13415
- * Calls remote proxy server to use a chat model
13416
- */
13417
- callChatModel(prompt) {
13418
- if (this.options.isVerbose) {
13419
- console.info(`🖋 Remote callChatModel call`);
13420
- }
13421
- return /* not await */ this.callCommonModel(prompt);
13422
- }
13423
- /**
13424
- * Calls remote proxy server to use a completion model
13425
- */
13426
- callCompletionModel(prompt) {
13427
- if (this.options.isVerbose) {
13428
- console.info(`💬 Remote callCompletionModel call`);
13429
- }
13430
- return /* not await */ this.callCommonModel(prompt);
13431
- }
13432
- /**
13433
- * Calls remote proxy server to use a embedding model
13434
- */
13435
- callEmbeddingModel(prompt) {
13436
- if (this.options.isVerbose) {
13437
- console.info(`💬 Remote callEmbeddingModel call`);
13438
- }
13439
- return /* not await */ this.callCommonModel(prompt);
13440
- }
13441
- // <- Note: [🤖] callXxxModel
13442
- /**
13443
- * Calls remote proxy server to use both completion or chat model
13444
- */
13445
- async callCommonModel(prompt) {
13446
- const socket = await createRemoteClient(this.options);
13447
- socket.emit('prompt-request', {
13448
- identification: this.options.identification,
13449
- prompt,
13450
- } /* <- Note: [🤛] */);
13451
- const promptResult = await new Promise((resolve, reject) => {
13452
- socket.on('prompt-response', (response) => {
13453
- resolve(response.promptResult);
13454
- socket.disconnect();
13455
- });
13456
- socket.on('error', (error) => {
13457
- reject(deserializeError(error));
13458
- socket.disconnect();
13459
- });
13460
- });
13461
- socket.disconnect();
13462
- return promptResult;
13463
- }
13464
- }
13465
- /**
13466
- * TODO: Maybe use `$exportJson`
13467
- * TODO: [🧠][🛍] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
13468
- * TODO: [🍓] Allow to list compatible models with each variant
13469
- * TODO: [🗯] RemoteLlmExecutionTools should extend Destroyable and implement IDestroyable
13470
- * TODO: [🧠][🌰] Allow to pass `title` for tracking purposes
13471
- * TODO: [🧠] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
13472
- */
13473
-
13474
13813
  /**
13475
13814
  * Function computeUsage will create price per one token based on the string value found on openai page
13476
13815
  *