@promptbook/cli 0.89.0-6 โ†’ 0.89.0-8

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/esm/index.es.js CHANGED
@@ -2,11 +2,13 @@ import colors from 'colors';
2
2
  import commander from 'commander';
3
3
  import spaceTrim, { spaceTrim as spaceTrim$1 } from 'spacetrim';
4
4
  import { forTime, forEver } from 'waitasecond';
5
+ import prompts from 'prompts';
5
6
  import { basename, join, dirname, relative } from 'path';
6
7
  import { stat, access, constants, readFile, writeFile, readdir, mkdir, unlink, rm, rename, rmdir } from 'fs/promises';
7
8
  import hexEncoder from 'crypto-js/enc-hex';
8
9
  import sha256 from 'crypto-js/sha256';
9
10
  import { randomBytes } from 'crypto';
11
+ import { io } from 'socket.io-client';
10
12
  import { Subject } from 'rxjs';
11
13
  import * as dotenv from 'dotenv';
12
14
  import { spawn } from 'child_process';
@@ -17,14 +19,12 @@ import { parse, unparse } from 'papaparse';
17
19
  import { SHA256 } from 'crypto-js';
18
20
  import { lookup, extension } from 'mime-types';
19
21
  import glob from 'glob-promise';
20
- import prompts from 'prompts';
21
22
  import moment from 'moment';
22
23
  import express from 'express';
23
24
  import http from 'http';
24
25
  import { Server } from 'socket.io';
25
26
  import swaggerJsdoc from 'swagger-jsdoc';
26
27
  import swaggerUi from 'swagger-ui-express';
27
- import { io } from 'socket.io-client';
28
28
  import Anthropic from '@anthropic-ai/sdk';
29
29
  import { OpenAIClient, AzureKeyCredential } from '@azure/openai';
30
30
  import OpenAI from 'openai';
@@ -46,7 +46,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
46
46
  * @generated
47
47
  * @see https://github.com/webgptorg/promptbook
48
48
  */
49
- const PROMPTBOOK_ENGINE_VERSION = '0.89.0-6';
49
+ const PROMPTBOOK_ENGINE_VERSION = '0.89.0-8';
50
50
  /**
51
51
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
52
52
  * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
@@ -215,6 +215,7 @@ const DEFAULT_MAX_EXECUTION_ATTEMPTS = 10; // <- TODO: [๐Ÿคนโ€โ™‚๏ธ]
215
215
  */
216
216
  const DEFAULT_BOOKS_DIRNAME = './books';
217
217
  // <- TODO: [๐Ÿ•] Make also `BOOKS_DIRNAME_ALTERNATIVES`
218
+ // TODO: !!!!!! Just .promptbook dir, hardocode others
218
219
  /**
219
220
  * Where to store the temporary downloads
220
221
  *
@@ -239,6 +240,21 @@ const DEFAULT_EXECUTION_CACHE_DIRNAME = './.promptbook/execution-cache';
239
240
  * @public exported from `@promptbook/core`
240
241
  */
241
242
  const DEFAULT_SCRAPE_CACHE_DIRNAME = './.promptbook/scrape-cache';
243
+ /**
244
+ * Id of application for the CLI when using remote server
245
+ *
246
+ * @public exported from `@promptbook/core`
247
+ */
248
+ const CLI_APP_ID = 'cli';
249
+ /*
250
+ TODO: [๐ŸŒƒ]
251
+ /**
252
+ * Id of application for the wizzard when using remote server
253
+ *
254
+ * @public exported from `@promptbook/core`
255
+ * /
256
+ ex-port const WIZZARD_APP_ID: string_app_id = 'wizzard';
257
+ */
242
258
  /**
243
259
  * The name of the builded pipeline collection made by CLI `ptbk make` and for lookup in `createCollectionFromDirectory`
244
260
  *
@@ -259,13 +275,7 @@ const MOMENT_ARG_THRESHOLDS = {
259
275
  *
260
276
  * @public exported from `@promptbook/core`
261
277
  */
262
- const DEFAULT_REMOTE_URL = 'https://api.pavolhejny.com/';
263
- /**
264
- * @@@
265
- *
266
- * @public exported from `@promptbook/core`
267
- */
268
- const DEFAULT_REMOTE_URL_PATH = '/promptbook/socket.io';
278
+ const DEFAULT_REMOTE_SERVER_URL = 'https://api.pavolhejny.com/promptbook';
269
279
  // <- TODO: [๐Ÿงœโ€โ™‚๏ธ]
270
280
  /**
271
281
  * @@@
@@ -307,7 +317,7 @@ const IS_PIPELINE_LOGIC_VALIDATED = just(
307
317
  true);
308
318
  /**
309
319
  * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
310
- * TODO: [๐Ÿง ][๐Ÿงœโ€โ™‚๏ธ] Maybe join remoteUrl and path into single value
320
+ * TODO: [๐Ÿง ][๐Ÿงœโ€โ™‚๏ธ] Maybe join remoteServerUrl and path into single value
311
321
  */
312
322
 
313
323
  /**
@@ -485,7 +495,8 @@ function $initializeHelloCommand(program) {
485
495
  helloCommand.alias('hi');
486
496
  helloCommand.argument('[name]', 'Your name', 'Paul');
487
497
  helloCommand.option('-g, --greeting <greeting>', `Greeting`, 'Hello');
488
- helloCommand.action(handleActionErrors(async (name, { greeting }) => {
498
+ helloCommand.action(handleActionErrors(async (name, cliOptions) => {
499
+ const { greeting } = cliOptions;
489
500
  console.info(colors.cyan(`${greeting} ${name}`));
490
501
  await forTime(1000);
491
502
  console.info(colors.rainbow(`Nice to meet you!`));
@@ -499,40 +510,27 @@ function $initializeHelloCommand(program) {
499
510
  */
500
511
 
501
512
  /**
502
- * Just marks a place of place where should be something implemented
503
- * No side effects.
504
- *
505
- * Note: It can be usefull suppressing eslint errors of unused variables
513
+ * This error type indicates that some part of the code is not implemented yet
506
514
  *
507
- * @param value any values
508
- * @returns void
509
- * @private within the repository
515
+ * @public exported from `@promptbook/core`
510
516
  */
511
- function TODO_USE(...value) {
512
- }
517
+ class NotYetImplementedError extends Error {
518
+ constructor(message) {
519
+ super(spaceTrim$1((block) => `
520
+ ${block(message)}
513
521
 
514
- /**
515
- * @@@
516
- *
517
- * @public exported from `@promptbook/node`
518
- */
519
- function $provideFilesystemForNode(options) {
520
- if (!$isRunningInNode()) {
521
- throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
522
+ Note: This feature is not implemented yet but it will be soon.
523
+
524
+ If you want speed up the implementation or just read more, look here:
525
+ https://github.com/webgptorg/promptbook
526
+
527
+ Or contact us on pavol@ptbk.io
528
+
529
+ `));
530
+ this.name = 'NotYetImplementedError';
531
+ Object.setPrototypeOf(this, NotYetImplementedError.prototype);
522
532
  }
523
- return {
524
- stat,
525
- access,
526
- constants,
527
- readFile,
528
- writeFile,
529
- readdir,
530
- mkdir,
531
- };
532
533
  }
533
- /**
534
- * Note: [๐ŸŸข] Code in this file should never be never released in packages that could be imported into browser environment
535
- */
536
534
 
537
535
  /**
538
536
  * Make error report URL for the given error
@@ -603,120 +601,522 @@ class UnexpectedError extends Error {
603
601
  }
604
602
 
605
603
  /**
606
- * Orders JSON object by keys
604
+ * @@@
607
605
  *
608
- * @returns The same type of object as the input re-ordered
609
- * @public exported from `@promptbook/utils`
606
+ * Note: `$` is used to indicate that this function is not a pure function - it access global scope
607
+ *
608
+ * @private internal function of `$Register`
610
609
  */
611
- function orderJson(options) {
612
- const { value, order } = options;
613
- const orderedValue = {
614
- ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
615
- ...value,
616
- };
617
- return orderedValue;
610
+ function $getGlobalScope() {
611
+ return Function('return this')();
618
612
  }
619
613
 
620
614
  /**
621
- * Freezes the given object and all its nested objects recursively
622
- *
623
- * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
624
- * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
615
+ * @@@
625
616
  *
626
- * @returns The same object as the input, but deeply frozen
617
+ * @param text @@@
618
+ * @returns @@@
619
+ * @example 'HELLO_WORLD'
620
+ * @example 'I_LOVE_PROMPTBOOK'
627
621
  * @public exported from `@promptbook/utils`
628
622
  */
629
- function $deepFreeze(objectValue) {
630
- if (Array.isArray(objectValue)) {
631
- return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
632
- }
633
- const propertyNames = Object.getOwnPropertyNames(objectValue);
634
- for (const propertyName of propertyNames) {
635
- const value = objectValue[propertyName];
636
- if (value && typeof value === 'object') {
637
- $deepFreeze(value);
623
+ function normalizeTo_SCREAMING_CASE(text) {
624
+ let charType;
625
+ let lastCharType = 'OTHER';
626
+ let normalizedName = '';
627
+ for (const char of text) {
628
+ let normalizedChar;
629
+ if (/^[a-z]$/.test(char)) {
630
+ charType = 'LOWERCASE';
631
+ normalizedChar = char.toUpperCase();
632
+ }
633
+ else if (/^[A-Z]$/.test(char)) {
634
+ charType = 'UPPERCASE';
635
+ normalizedChar = char;
636
+ }
637
+ else if (/^[0-9]$/.test(char)) {
638
+ charType = 'NUMBER';
639
+ normalizedChar = char;
640
+ }
641
+ else {
642
+ charType = 'OTHER';
643
+ normalizedChar = '_';
644
+ }
645
+ if (charType !== lastCharType &&
646
+ !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
647
+ !(lastCharType === 'NUMBER') &&
648
+ !(charType === 'NUMBER')) {
649
+ normalizedName += '_';
638
650
  }
651
+ normalizedName += normalizedChar;
652
+ lastCharType = charType;
639
653
  }
640
- Object.freeze(objectValue);
641
- return objectValue;
654
+ normalizedName = normalizedName.replace(/_+/g, '_');
655
+ normalizedName = normalizedName.replace(/_?\/_?/g, '/');
656
+ normalizedName = normalizedName.replace(/^_/, '');
657
+ normalizedName = normalizedName.replace(/_$/, '');
658
+ return normalizedName;
642
659
  }
643
660
  /**
644
- * TODO: [๐Ÿง ] Is there a way how to meaningfully test this utility
661
+ * TODO: Tests
662
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
663
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ฤ›ลกฤล™ลพลพรฝรกรญรบลฏ' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
664
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
665
+ * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
666
+ * TODO: [๐ŸŒบ] Use some intermediate util splitWords
645
667
  */
646
668
 
647
669
  /**
648
- * Checks if the value is [๐Ÿš‰] serializable as JSON
649
- * If not, throws an UnexpectedError with a rich error message and tracking
650
- *
651
- * - Almost all primitives are serializable BUT:
652
- * - `undefined` is not serializable
653
- * - `NaN` is not serializable
654
- * - Objects and arrays are serializable if all their properties are serializable
655
- * - Functions are not serializable
656
- * - Circular references are not serializable
657
- * - `Date` objects are not serializable
658
- * - `Map` and `Set` objects are not serializable
659
- * - `RegExp` objects are not serializable
660
- * - `Error` objects are not serializable
661
- * - `Symbol` objects are not serializable
662
- * - And much more...
670
+ * @@@
663
671
  *
664
- * @throws UnexpectedError if the value is not serializable as JSON
672
+ * @param text @@@
673
+ * @returns @@@
674
+ * @example 'hello_world'
675
+ * @example 'i_love_promptbook'
665
676
  * @public exported from `@promptbook/utils`
666
677
  */
667
- function checkSerializableAsJson(options) {
668
- const { value, name, message } = options;
669
- if (value === undefined) {
670
- throw new UnexpectedError(`${name} is undefined`);
671
- }
672
- else if (value === null) {
673
- return;
674
- }
675
- else if (typeof value === 'boolean') {
676
- return;
677
- }
678
- else if (typeof value === 'number' && !isNaN(value)) {
679
- return;
680
- }
681
- else if (typeof value === 'string') {
682
- return;
683
- }
684
- else if (typeof value === 'symbol') {
685
- throw new UnexpectedError(`${name} is symbol`);
686
- }
687
- else if (typeof value === 'function') {
688
- throw new UnexpectedError(`${name} is function`);
689
- }
690
- else if (typeof value === 'object' && Array.isArray(value)) {
691
- for (let i = 0; i < value.length; i++) {
692
- checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
693
- }
694
- }
695
- else if (typeof value === 'object') {
696
- if (value instanceof Date) {
697
- throw new UnexpectedError(spaceTrim((block) => `
698
- \`${name}\` is Date
699
-
700
- Use \`string_date_iso8601\` instead
678
+ function normalizeTo_snake_case(text) {
679
+ return normalizeTo_SCREAMING_CASE(text).toLowerCase();
680
+ }
701
681
 
702
- Additional message for \`${name}\`:
703
- ${block(message || '(nothing)')}
704
- `));
682
+ /**
683
+ * Register is @@@
684
+ *
685
+ * Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
686
+ *
687
+ * @private internal utility, exported are only signleton instances of this class
688
+ */
689
+ class $Register {
690
+ constructor(registerName) {
691
+ this.registerName = registerName;
692
+ const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
693
+ const globalScope = $getGlobalScope();
694
+ if (globalScope[storageName] === undefined) {
695
+ globalScope[storageName] = [];
705
696
  }
706
- else if (value instanceof Map) {
707
- throw new UnexpectedError(`${name} is Map`);
697
+ else if (!Array.isArray(globalScope[storageName])) {
698
+ throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
708
699
  }
709
- else if (value instanceof Set) {
710
- throw new UnexpectedError(`${name} is Set`);
700
+ this.storage = globalScope[storageName];
701
+ }
702
+ list() {
703
+ // <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>>
704
+ return this.storage;
705
+ }
706
+ register(registered) {
707
+ const { packageName, className } = registered;
708
+ const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className);
709
+ const existingRegistration = this.storage[existingRegistrationIndex];
710
+ if (!existingRegistration) {
711
+ this.storage.push(registered);
711
712
  }
712
- else if (value instanceof RegExp) {
713
- throw new UnexpectedError(`${name} is RegExp`);
713
+ else {
714
+ this.storage[existingRegistrationIndex] = registered;
714
715
  }
715
- else if (value instanceof Error) {
716
- throw new UnexpectedError(spaceTrim((block) => `
717
- \`${name}\` is unserialized Error
718
-
719
- Use function \`serializeError\`
716
+ return {
717
+ registerName: this.registerName,
718
+ packageName,
719
+ className,
720
+ get isDestroyed() {
721
+ return false;
722
+ },
723
+ destroy() {
724
+ throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`);
725
+ },
726
+ };
727
+ }
728
+ }
729
+
730
+ /**
731
+ * @@@
732
+ *
733
+ * Note: `$` is used to indicate that this interacts with the global scope
734
+ * @singleton Only one instance of each register is created per build, but thare can be more @@@
735
+ * @public exported from `@promptbook/core`
736
+ */
737
+ const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
738
+ /**
739
+ * TODO: [ยฎ] DRY Register logic
740
+ */
741
+
742
+ /**
743
+ * @@@
744
+ *
745
+ * Note: `$` is used to indicate that this interacts with the global scope
746
+ * @singleton Only one instance of each register is created per build, but thare can be more @@@
747
+ * @public exported from `@promptbook/core`
748
+ */
749
+ const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
750
+ /**
751
+ * TODO: [ยฎ] DRY Register logic
752
+ */
753
+
754
+ /**
755
+ * Path to the `.env` file which was used to configure LLM tools
756
+ *
757
+ * Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
758
+ */
759
+ let $usedEnvFilename = null;
760
+ /**
761
+ * Pass the `.env` file which was used to configure LLM tools
762
+ *
763
+ * Note: `$` is used to indicate that this variable is making side effect
764
+ *
765
+ * @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
766
+ */
767
+ function $setUsedEnvFilename(filepath) {
768
+ $usedEnvFilename = filepath;
769
+ }
770
+ /**
771
+ * Creates a message with all registered LLM tools
772
+ *
773
+ * Note: This function is used to create a (error) message when there is no constructor for some LLM provider
774
+ *
775
+ * @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
776
+ */
777
+ function $registeredLlmToolsMessage() {
778
+ let env;
779
+ if ($isRunningInNode()) {
780
+ env = process.env;
781
+ // <- TODO: [โš›] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
782
+ }
783
+ else {
784
+ env = {};
785
+ }
786
+ /**
787
+ * Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
788
+ */
789
+ const all = [];
790
+ for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
791
+ if (all.some((item) => item.packageName === packageName && item.className === className)) {
792
+ continue;
793
+ }
794
+ all.push({ title, packageName, className, envVariables });
795
+ }
796
+ for (const { packageName, className } of $llmToolsRegister.list()) {
797
+ if (all.some((item) => item.packageName === packageName && item.className === className)) {
798
+ continue;
799
+ }
800
+ all.push({ packageName, className });
801
+ }
802
+ const metadata = all.map((metadata) => {
803
+ var _a, _b;
804
+ const isMetadataAviailable = $llmToolsMetadataRegister
805
+ .list()
806
+ .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
807
+ const isInstalled = $llmToolsRegister
808
+ .list()
809
+ .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
810
+ const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
811
+ const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
812
+ // <- Note: [๐Ÿ—จ]
813
+ return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
814
+ });
815
+ const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
816
+ if (metadata.length === 0) {
817
+ return spaceTrim((block) => `
818
+ No LLM providers are available.
819
+
820
+ ${block(usedEnvMessage)}
821
+ `);
822
+ }
823
+ return spaceTrim((block) => `
824
+
825
+ ${block(usedEnvMessage)}
826
+
827
+ Relevant environment variables:
828
+ ${block(Object.keys(env)
829
+ .filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
830
+ .map((envVariableName) => `- \`${envVariableName}\``)
831
+ .join('\n'))}
832
+
833
+ Available LLM providers are:
834
+ ${block(metadata
835
+ .map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
836
+ const morePieces = [];
837
+ if (just(false)) ;
838
+ else if (!isMetadataAviailable && !isInstalled) {
839
+ // TODO: [๏ฟฝ][๏ฟฝ] Maybe do allow to do auto-install if package not registered and not found
840
+ morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
841
+ }
842
+ else if (isMetadataAviailable && !isInstalled) {
843
+ // TODO: [๏ฟฝ][๏ฟฝ]
844
+ morePieces.push(`Not installed`);
845
+ }
846
+ else if (!isMetadataAviailable && isInstalled) {
847
+ morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
848
+ }
849
+ else if (isMetadataAviailable && isInstalled) {
850
+ morePieces.push(`Installed`);
851
+ }
852
+ else {
853
+ morePieces.push(`unknown state, looks like a unexpected behavior`);
854
+ } /* not else */
855
+ if (isFullyConfigured) {
856
+ morePieces.push(`Configured`);
857
+ }
858
+ else if (isPartiallyConfigured) {
859
+ morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
860
+ }
861
+ else {
862
+ if (envVariables !== null) {
863
+ morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
864
+ }
865
+ else {
866
+ morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
867
+ }
868
+ }
869
+ let providerMessage = spaceTrim(`
870
+ ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
871
+ ${morePieces.join('; ')}
872
+ `);
873
+ if ($isRunningInNode) {
874
+ if (isInstalled && isFullyConfigured) {
875
+ providerMessage = colors.green(providerMessage);
876
+ }
877
+ else if (isInstalled && isPartiallyConfigured) {
878
+ providerMessage = colors.yellow(providerMessage);
879
+ }
880
+ else {
881
+ providerMessage = colors.gray(providerMessage);
882
+ }
883
+ }
884
+ return providerMessage;
885
+ })
886
+ .join('\n'))}
887
+ `);
888
+ }
889
+ /**
890
+ * TODO: [ยฎ] DRY Register logic
891
+ * TODO: [๐Ÿง ][โš›] Maybe pass env as argument
892
+ */
893
+
894
+ /**
895
+ * Just says that the variable is not used but should be kept
896
+ * No side effects.
897
+ *
898
+ * Note: It can be usefull for:
899
+ *
900
+ * 1) Suppressing eager optimization of unused imports
901
+ * 2) Suppressing eslint errors of unused variables in the tests
902
+ * 3) Keeping the type of the variable for type testing
903
+ *
904
+ * @param value any values
905
+ * @returns void
906
+ * @private within the repository
907
+ */
908
+ function keepUnused(...valuesToKeep) {
909
+ }
910
+
911
+ /**
912
+ * Just says that the variable is not used directlys but should be kept because the existence of the variable is important
913
+ *
914
+ * @param value any values
915
+ * @returns void
916
+ * @private within the repository
917
+ */
918
+ function $sideEffect(...sideEffectSubjects) {
919
+ keepUnused(...sideEffectSubjects);
920
+ }
921
+
922
+ /**
923
+ * Stores data in memory (HEAP)
924
+ *
925
+ * @public exported from `@promptbook/core`
926
+ */
927
+ class MemoryStorage {
928
+ constructor() {
929
+ this.storage = {};
930
+ }
931
+ /**
932
+ * Returns the number of key/value pairs currently present in the list associated with the object.
933
+ */
934
+ get length() {
935
+ return Object.keys(this.storage).length;
936
+ }
937
+ /**
938
+ * Empties the list associated with the object of all key/value pairs, if there are any.
939
+ */
940
+ clear() {
941
+ this.storage = {};
942
+ }
943
+ /**
944
+ * 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.
945
+ */
946
+ getItem(key) {
947
+ return this.storage[key] || null;
948
+ }
949
+ /**
950
+ * 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.
951
+ */
952
+ key(index) {
953
+ return Object.keys(this.storage)[index] || null;
954
+ }
955
+ /**
956
+ * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
957
+ */
958
+ setItem(key, value) {
959
+ this.storage[key] = value;
960
+ }
961
+ /**
962
+ * 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.
963
+ */
964
+ removeItem(key) {
965
+ delete this.storage[key];
966
+ }
967
+ }
968
+
969
+ /**
970
+ * Just marks a place of place where should be something implemented
971
+ * No side effects.
972
+ *
973
+ * Note: It can be usefull suppressing eslint errors of unused variables
974
+ *
975
+ * @param value any values
976
+ * @returns void
977
+ * @private within the repository
978
+ */
979
+ function TODO_USE(...value) {
980
+ }
981
+
982
+ /**
983
+ * @@@
984
+ *
985
+ * @public exported from `@promptbook/node`
986
+ */
987
+ function $provideFilesystemForNode(options) {
988
+ if (!$isRunningInNode()) {
989
+ throw new EnvironmentMismatchError('Function `$provideFilesystemForNode` works only in Node.js environment');
990
+ }
991
+ return {
992
+ stat,
993
+ access,
994
+ constants,
995
+ readFile,
996
+ writeFile,
997
+ readdir,
998
+ mkdir,
999
+ };
1000
+ }
1001
+ /**
1002
+ * Note: [๐ŸŸข] Code in this file should never be never released in packages that could be imported into browser environment
1003
+ */
1004
+
1005
+ /**
1006
+ * Orders JSON object by keys
1007
+ *
1008
+ * @returns The same type of object as the input re-ordered
1009
+ * @public exported from `@promptbook/utils`
1010
+ */
1011
+ function orderJson(options) {
1012
+ const { value, order } = options;
1013
+ const orderedValue = {
1014
+ ...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
1015
+ ...value,
1016
+ };
1017
+ return orderedValue;
1018
+ }
1019
+
1020
+ /**
1021
+ * Freezes the given object and all its nested objects recursively
1022
+ *
1023
+ * Note: `$` is used to indicate that this function is not a pure function - it mutates given object
1024
+ * Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
1025
+ *
1026
+ * @returns The same object as the input, but deeply frozen
1027
+ * @public exported from `@promptbook/utils`
1028
+ */
1029
+ function $deepFreeze(objectValue) {
1030
+ if (Array.isArray(objectValue)) {
1031
+ return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
1032
+ }
1033
+ const propertyNames = Object.getOwnPropertyNames(objectValue);
1034
+ for (const propertyName of propertyNames) {
1035
+ const value = objectValue[propertyName];
1036
+ if (value && typeof value === 'object') {
1037
+ $deepFreeze(value);
1038
+ }
1039
+ }
1040
+ Object.freeze(objectValue);
1041
+ return objectValue;
1042
+ }
1043
+ /**
1044
+ * TODO: [๐Ÿง ] Is there a way how to meaningfully test this utility
1045
+ */
1046
+
1047
+ /**
1048
+ * Checks if the value is [๐Ÿš‰] serializable as JSON
1049
+ * If not, throws an UnexpectedError with a rich error message and tracking
1050
+ *
1051
+ * - Almost all primitives are serializable BUT:
1052
+ * - `undefined` is not serializable
1053
+ * - `NaN` is not serializable
1054
+ * - Objects and arrays are serializable if all their properties are serializable
1055
+ * - Functions are not serializable
1056
+ * - Circular references are not serializable
1057
+ * - `Date` objects are not serializable
1058
+ * - `Map` and `Set` objects are not serializable
1059
+ * - `RegExp` objects are not serializable
1060
+ * - `Error` objects are not serializable
1061
+ * - `Symbol` objects are not serializable
1062
+ * - And much more...
1063
+ *
1064
+ * @throws UnexpectedError if the value is not serializable as JSON
1065
+ * @public exported from `@promptbook/utils`
1066
+ */
1067
+ function checkSerializableAsJson(options) {
1068
+ const { value, name, message } = options;
1069
+ if (value === undefined) {
1070
+ throw new UnexpectedError(`${name} is undefined`);
1071
+ }
1072
+ else if (value === null) {
1073
+ return;
1074
+ }
1075
+ else if (typeof value === 'boolean') {
1076
+ return;
1077
+ }
1078
+ else if (typeof value === 'number' && !isNaN(value)) {
1079
+ return;
1080
+ }
1081
+ else if (typeof value === 'string') {
1082
+ return;
1083
+ }
1084
+ else if (typeof value === 'symbol') {
1085
+ throw new UnexpectedError(`${name} is symbol`);
1086
+ }
1087
+ else if (typeof value === 'function') {
1088
+ throw new UnexpectedError(`${name} is function`);
1089
+ }
1090
+ else if (typeof value === 'object' && Array.isArray(value)) {
1091
+ for (let i = 0; i < value.length; i++) {
1092
+ checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
1093
+ }
1094
+ }
1095
+ else if (typeof value === 'object') {
1096
+ if (value instanceof Date) {
1097
+ throw new UnexpectedError(spaceTrim((block) => `
1098
+ \`${name}\` is Date
1099
+
1100
+ Use \`string_date_iso8601\` instead
1101
+
1102
+ Additional message for \`${name}\`:
1103
+ ${block(message || '(nothing)')}
1104
+ `));
1105
+ }
1106
+ else if (value instanceof Map) {
1107
+ throw new UnexpectedError(`${name} is Map`);
1108
+ }
1109
+ else if (value instanceof Set) {
1110
+ throw new UnexpectedError(`${name} is Set`);
1111
+ }
1112
+ else if (value instanceof RegExp) {
1113
+ throw new UnexpectedError(`${name} is RegExp`);
1114
+ }
1115
+ else if (value instanceof Error) {
1116
+ throw new UnexpectedError(spaceTrim((block) => `
1117
+ \`${name}\` is unserialized Error
1118
+
1119
+ Use function \`serializeError\`
720
1120
 
721
1121
  Additional message for \`${name}\`:
722
1122
  ${block(message || '(nothing)')}
@@ -1472,32 +1872,177 @@ class FileCacheStorage {
1472
1872
  // TODO: [๐ŸŒ—]
1473
1873
  return value;
1474
1874
  }
1475
- /**
1476
- * @@@ Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
1477
- */
1478
- async setItem(key, value) {
1479
- const filename = this.getFilenameForKey(key);
1480
- if (!isSerializableAsJson(value)) {
1481
- throw new UnexpectedError(`The "${key}" you want to store in JSON file is not serializable as JSON`);
1482
- }
1483
- const fileContent = stringifyPipelineJson(value);
1484
- await mkdir(dirname(filename), { recursive: true }); // <- [0]
1485
- await writeFile(filename, fileContent, 'utf-8');
1875
+ /**
1876
+ * @@@ Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
1877
+ */
1878
+ async setItem(key, value) {
1879
+ const filename = this.getFilenameForKey(key);
1880
+ if (!isSerializableAsJson(value)) {
1881
+ throw new UnexpectedError(`The "${key}" you want to store in JSON file is not serializable as JSON`);
1882
+ }
1883
+ const fileContent = stringifyPipelineJson(value);
1884
+ await mkdir(dirname(filename), { recursive: true }); // <- [0]
1885
+ await writeFile(filename, fileContent, 'utf-8');
1886
+ }
1887
+ /**
1888
+ * @@@ 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.
1889
+ */
1890
+ async removeItem(key) {
1891
+ const filename = this.getFilenameForKey(key);
1892
+ // TODO: [๐Ÿง ] What to use `unlink` or `rm`
1893
+ await unlink(filename);
1894
+ // <- TODO: [๐Ÿฟ][๐Ÿง ] Maybe remove empty folders
1895
+ // [0] When `setItem` and `removeItem` called, the state of the file system should be the same
1896
+ }
1897
+ }
1898
+ /**
1899
+ * TODO: [๐ŸŒ—] Maybe some checkers, not all valid JSONs are desired and valid values
1900
+ * Note: [๐ŸŸข] Code in this file should never be never released in packages that could be imported into browser environment
1901
+ */
1902
+
1903
+ /**
1904
+ * This error indicates problems parsing the format value
1905
+ *
1906
+ * For example, when the format value is not a valid JSON or CSV
1907
+ * This is not thrown directly but in extended classes
1908
+ *
1909
+ * @public exported from `@promptbook/core`
1910
+ */
1911
+ class AbstractFormatError extends Error {
1912
+ // Note: To allow instanceof do not put here error `name`
1913
+ // public readonly name = 'AbstractFormatError';
1914
+ constructor(message) {
1915
+ super(message);
1916
+ Object.setPrototypeOf(this, AbstractFormatError.prototype);
1917
+ }
1918
+ }
1919
+
1920
+ /**
1921
+ * This error indicates problem with parsing of CSV
1922
+ *
1923
+ * @public exported from `@promptbook/core`
1924
+ */
1925
+ class CsvFormatError extends AbstractFormatError {
1926
+ constructor(message) {
1927
+ super(message);
1928
+ this.name = 'CsvFormatError';
1929
+ Object.setPrototypeOf(this, CsvFormatError.prototype);
1930
+ }
1931
+ }
1932
+
1933
+ /**
1934
+ * AuthenticationError is thrown from login function which is dependency of remote server
1935
+ *
1936
+ * @public exported from `@promptbook/core`
1937
+ */
1938
+ class AuthenticationError extends Error {
1939
+ constructor(message) {
1940
+ super(message);
1941
+ this.name = 'AuthenticationError';
1942
+ Object.setPrototypeOf(this, AuthenticationError.prototype);
1943
+ }
1944
+ }
1945
+
1946
+ /**
1947
+ * This error indicates that the pipeline collection cannot be propperly loaded
1948
+ *
1949
+ * @public exported from `@promptbook/core`
1950
+ */
1951
+ class CollectionError extends Error {
1952
+ constructor(message) {
1953
+ super(message);
1954
+ this.name = 'CollectionError';
1955
+ Object.setPrototypeOf(this, CollectionError.prototype);
1956
+ }
1957
+ }
1958
+
1959
+ /**
1960
+ * This error occurs when some expectation is not met in the execution of the pipeline
1961
+ *
1962
+ * @public exported from `@promptbook/core`
1963
+ * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
1964
+ * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
1965
+ * Note: This is a kindof subtype of PipelineExecutionError
1966
+ */
1967
+ class ExpectError extends Error {
1968
+ constructor(message) {
1969
+ super(message);
1970
+ this.name = 'ExpectError';
1971
+ Object.setPrototypeOf(this, ExpectError.prototype);
1972
+ }
1973
+ }
1974
+
1975
+ /**
1976
+ * This error indicates that the promptbook can not retrieve knowledge from external sources
1977
+ *
1978
+ * @public exported from `@promptbook/core`
1979
+ */
1980
+ class KnowledgeScrapeError extends Error {
1981
+ constructor(message) {
1982
+ super(message);
1983
+ this.name = 'KnowledgeScrapeError';
1984
+ Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
1985
+ }
1986
+ }
1987
+
1988
+ /**
1989
+ * This error type indicates that some limit was reached
1990
+ *
1991
+ * @public exported from `@promptbook/core`
1992
+ */
1993
+ class LimitReachedError extends Error {
1994
+ constructor(message) {
1995
+ super(message);
1996
+ this.name = 'LimitReachedError';
1997
+ Object.setPrototypeOf(this, LimitReachedError.prototype);
1998
+ }
1999
+ }
2000
+
2001
+ /**
2002
+ * This error type indicates that some tools are missing for pipeline execution or preparation
2003
+ *
2004
+ * @public exported from `@promptbook/core`
2005
+ */
2006
+ class MissingToolsError extends Error {
2007
+ constructor(message) {
2008
+ super(spaceTrim$1((block) => `
2009
+ ${block(message)}
2010
+
2011
+ Note: You have probbably forgot to provide some tools for pipeline execution or preparation
2012
+
2013
+ `));
2014
+ this.name = 'MissingToolsError';
2015
+ Object.setPrototypeOf(this, MissingToolsError.prototype);
2016
+ }
2017
+ }
2018
+
2019
+ /**
2020
+ * This error indicates that promptbook not found in the collection
2021
+ *
2022
+ * @public exported from `@promptbook/core`
2023
+ */
2024
+ class NotFoundError extends Error {
2025
+ constructor(message) {
2026
+ super(message);
2027
+ this.name = 'NotFoundError';
2028
+ Object.setPrototypeOf(this, NotFoundError.prototype);
1486
2029
  }
1487
- /**
1488
- * @@@ 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.
1489
- */
1490
- async removeItem(key) {
1491
- const filename = this.getFilenameForKey(key);
1492
- // TODO: [๐Ÿง ] What to use `unlink` or `rm`
1493
- await unlink(filename);
1494
- // <- TODO: [๐Ÿฟ][๐Ÿง ] Maybe remove empty folders
1495
- // [0] When `setItem` and `removeItem` called, the state of the file system should be the same
2030
+ }
2031
+
2032
+ /**
2033
+ * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
2034
+ *
2035
+ * @public exported from `@promptbook/core`
2036
+ */
2037
+ class ParseError extends Error {
2038
+ constructor(message) {
2039
+ super(message);
2040
+ this.name = 'ParseError';
2041
+ Object.setPrototypeOf(this, ParseError.prototype);
1496
2042
  }
1497
2043
  }
1498
2044
  /**
1499
- * TODO: [๐ŸŒ—] Maybe some checkers, not all valid JSONs are desired and valid values
1500
- * Note: [๐ŸŸข] Code in this file should never be never released in packages that could be imported into browser environment
2045
+ * TODO: Maybe split `ParseError` and `ApplyError`
1501
2046
  */
1502
2047
 
1503
2048
  /**
@@ -1535,51 +2080,260 @@ class PipelineExecutionError extends Error {
1535
2080
  */
1536
2081
 
1537
2082
  /**
1538
- * Stores data in memory (HEAP)
2083
+ * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
1539
2084
  *
1540
2085
  * @public exported from `@promptbook/core`
1541
2086
  */
1542
- class MemoryStorage {
1543
- constructor() {
1544
- this.storage = {};
2087
+ class PipelineLogicError extends Error {
2088
+ constructor(message) {
2089
+ super(message);
2090
+ this.name = 'PipelineLogicError';
2091
+ Object.setPrototypeOf(this, PipelineLogicError.prototype);
2092
+ }
2093
+ }
2094
+
2095
+ /**
2096
+ * This error indicates errors in referencing promptbooks between each other
2097
+ *
2098
+ * @public exported from `@promptbook/core`
2099
+ */
2100
+ class PipelineUrlError extends Error {
2101
+ constructor(message) {
2102
+ super(message);
2103
+ this.name = 'PipelineUrlError';
2104
+ Object.setPrototypeOf(this, PipelineUrlError.prototype);
2105
+ }
2106
+ }
2107
+
2108
+ /**
2109
+ * Index of all custom errors
2110
+ *
2111
+ * @public exported from `@promptbook/core`
2112
+ */
2113
+ const PROMPTBOOK_ERRORS = {
2114
+ AbstractFormatError,
2115
+ CsvFormatError,
2116
+ CollectionError,
2117
+ EnvironmentMismatchError,
2118
+ ExpectError,
2119
+ KnowledgeScrapeError,
2120
+ LimitReachedError,
2121
+ MissingToolsError,
2122
+ NotFoundError,
2123
+ NotYetImplementedError,
2124
+ ParseError,
2125
+ PipelineExecutionError,
2126
+ PipelineLogicError,
2127
+ PipelineUrlError,
2128
+ UnexpectedError,
2129
+ // TODO: [๐Ÿช‘]> VersionMismatchError,
2130
+ };
2131
+ /**
2132
+ * Index of all javascript errors
2133
+ *
2134
+ * @private for internal usage
2135
+ */
2136
+ const COMMON_JAVASCRIPT_ERRORS = {
2137
+ Error,
2138
+ EvalError,
2139
+ RangeError,
2140
+ ReferenceError,
2141
+ SyntaxError,
2142
+ TypeError,
2143
+ URIError,
2144
+ AggregateError,
2145
+ AuthenticationError,
2146
+ /*
2147
+ Note: Not widely supported
2148
+ > InternalError,
2149
+ > ModuleError,
2150
+ > HeapError,
2151
+ > WebAssemblyCompileError,
2152
+ > WebAssemblyRuntimeError,
2153
+ */
2154
+ };
2155
+ /**
2156
+ * Index of all errors
2157
+ *
2158
+ * @private for internal usage
2159
+ */
2160
+ const ALL_ERRORS = {
2161
+ ...PROMPTBOOK_ERRORS,
2162
+ ...COMMON_JAVASCRIPT_ERRORS,
2163
+ };
2164
+ /**
2165
+ * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
2166
+ */
2167
+
2168
+ /**
2169
+ * Deserializes the error object
2170
+ *
2171
+ * @public exported from `@promptbook/utils`
2172
+ */
2173
+ function deserializeError(error) {
2174
+ const { name, stack, id } = error; // Added id
2175
+ let { message } = error;
2176
+ let ErrorClass = ALL_ERRORS[error.name];
2177
+ if (ErrorClass === undefined) {
2178
+ ErrorClass = Error;
2179
+ message = `${name}: ${message}`;
2180
+ }
2181
+ if (stack !== undefined && stack !== '') {
2182
+ message = spaceTrim((block) => `
2183
+ ${block(message)}
2184
+
2185
+ Original stack trace:
2186
+ ${block(stack || '')}
2187
+ `);
2188
+ }
2189
+ const deserializedError = new ErrorClass(message);
2190
+ deserializedError.id = id; // Assign id to the error object
2191
+ return deserializedError;
2192
+ }
2193
+
2194
+ /**
2195
+ * Creates a connection to the remote proxy server.
2196
+ *
2197
+ * Note: This function creates a connection to the remote server and returns a socket but responsibility of closing the connection is on the caller
2198
+ *
2199
+ * @private internal utility function
2200
+ */
2201
+ async function createRemoteClient(options) {
2202
+ const { remoteServerUrl } = options;
2203
+ let path = new URL(remoteServerUrl).pathname;
2204
+ if (path.endsWith('/')) {
2205
+ path = path.slice(0, -1);
2206
+ }
2207
+ path = `${path}/socket.io`;
2208
+ return new Promise((resolve, reject) => {
2209
+ const socket = io(remoteServerUrl, {
2210
+ retries: CONNECTION_RETRIES_LIMIT,
2211
+ timeout: CONNECTION_TIMEOUT_MS,
2212
+ path,
2213
+ transports: [/*'websocket', <- TODO: [๐ŸŒฌ] Make websocket transport work */ 'polling'],
2214
+ });
2215
+ // console.log('Connecting to', this.options.remoteServerUrl.href, { socket });
2216
+ socket.on('connect', () => {
2217
+ resolve(socket);
2218
+ });
2219
+ // TODO: [๐Ÿ’ฉ] Better timeout handling
2220
+ setTimeout(() => {
2221
+ reject(new Error(`Timeout while connecting to ${remoteServerUrl}`));
2222
+ }, CONNECTION_TIMEOUT_MS);
2223
+ });
2224
+ }
2225
+
2226
+ /**
2227
+ * Remote server is a proxy server that uses its execution tools internally and exposes the executor interface externally.
2228
+ *
2229
+ * You can simply use `RemoteExecutionTools` on client-side javascript and connect to your remote server.
2230
+ * This is useful to make all logic on browser side but not expose your API keys or no need to use customer's GPU.
2231
+ *
2232
+ * @see https://github.com/webgptorg/promptbook#remote-server
2233
+ * @public exported from `@promptbook/remote-client`
2234
+ */
2235
+ class RemoteLlmExecutionTools {
2236
+ /* <- TODO: [๐Ÿš] `, Destroyable` */
2237
+ constructor(options) {
2238
+ this.options = options;
2239
+ }
2240
+ get title() {
2241
+ // TODO: [๐Ÿง ] Maybe fetch title+description from the remote server (as well as if model methods are defined)
2242
+ return 'Remote server';
2243
+ }
2244
+ get description() {
2245
+ return 'Use all models by your remote server';
1545
2246
  }
1546
2247
  /**
1547
- * Returns the number of key/value pairs currently present in the list associated with the object.
2248
+ * Check the configuration of all execution tools
1548
2249
  */
1549
- get length() {
1550
- return Object.keys(this.storage).length;
2250
+ async checkConfiguration() {
2251
+ const socket = await createRemoteClient(this.options);
2252
+ socket.disconnect();
2253
+ // TODO: [main] !!3 Check version of the remote server and compatibility
2254
+ // TODO: [๐ŸŽ] Send checkConfiguration
1551
2255
  }
1552
2256
  /**
1553
- * Empties the list associated with the object of all key/value pairs, if there are any.
2257
+ * List all available models that can be used
1554
2258
  */
1555
- clear() {
1556
- this.storage = {};
2259
+ async listModels() {
2260
+ // TODO: [๐Ÿ‘’] Listing models (and checking configuration) probbably should go through REST API not Socket.io
2261
+ const socket = await createRemoteClient(this.options);
2262
+ socket.emit('listModels-request', {
2263
+ identification: this.options.identification,
2264
+ } /* <- Note: [๐Ÿค›] */);
2265
+ const promptResult = await new Promise((resolve, reject) => {
2266
+ socket.on('listModels-response', (response) => {
2267
+ resolve(response.models);
2268
+ socket.disconnect();
2269
+ });
2270
+ socket.on('error', (error) => {
2271
+ reject(deserializeError(error));
2272
+ socket.disconnect();
2273
+ });
2274
+ });
2275
+ socket.disconnect();
2276
+ return promptResult;
1557
2277
  }
1558
2278
  /**
1559
- * 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.
2279
+ * Calls remote proxy server to use a chat model
1560
2280
  */
1561
- getItem(key) {
1562
- return this.storage[key] || null;
2281
+ callChatModel(prompt) {
2282
+ if (this.options.isVerbose) {
2283
+ console.info(`๐Ÿ–‹ Remote callChatModel call`);
2284
+ }
2285
+ return /* not await */ this.callCommonModel(prompt);
1563
2286
  }
1564
2287
  /**
1565
- * 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.
2288
+ * Calls remote proxy server to use a completion model
1566
2289
  */
1567
- key(index) {
1568
- return Object.keys(this.storage)[index] || null;
2290
+ callCompletionModel(prompt) {
2291
+ if (this.options.isVerbose) {
2292
+ console.info(`๐Ÿ’ฌ Remote callCompletionModel call`);
2293
+ }
2294
+ return /* not await */ this.callCommonModel(prompt);
1569
2295
  }
1570
2296
  /**
1571
- * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.
2297
+ * Calls remote proxy server to use a embedding model
1572
2298
  */
1573
- setItem(key, value) {
1574
- this.storage[key] = value;
2299
+ callEmbeddingModel(prompt) {
2300
+ if (this.options.isVerbose) {
2301
+ console.info(`๐Ÿ’ฌ Remote callEmbeddingModel call`);
2302
+ }
2303
+ return /* not await */ this.callCommonModel(prompt);
1575
2304
  }
2305
+ // <- Note: [๐Ÿค–] callXxxModel
1576
2306
  /**
1577
- * 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.
2307
+ * Calls remote proxy server to use both completion or chat model
1578
2308
  */
1579
- removeItem(key) {
1580
- delete this.storage[key];
2309
+ async callCommonModel(prompt) {
2310
+ const socket = await createRemoteClient(this.options);
2311
+ socket.emit('prompt-request', {
2312
+ identification: this.options.identification,
2313
+ prompt,
2314
+ } /* <- Note: [๐Ÿค›] */);
2315
+ const promptResult = await new Promise((resolve, reject) => {
2316
+ socket.on('prompt-response', (response) => {
2317
+ resolve(response.promptResult);
2318
+ socket.disconnect();
2319
+ });
2320
+ socket.on('error', (error) => {
2321
+ reject(deserializeError(error));
2322
+ socket.disconnect();
2323
+ });
2324
+ });
2325
+ socket.disconnect();
2326
+ return promptResult;
1581
2327
  }
1582
2328
  }
2329
+ /**
2330
+ * TODO: Maybe use `$exportJson`
2331
+ * TODO: [๐Ÿง ][๐Ÿ›] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
2332
+ * TODO: [๐Ÿ“] Allow to list compatible models with each variant
2333
+ * TODO: [๐Ÿ—ฏ] RemoteLlmExecutionTools should extend Destroyable and implement IDestroyable
2334
+ * TODO: [๐Ÿง ][๐ŸŒฐ] Allow to pass `title` for tracking purposes
2335
+ * TODO: [๐Ÿง ] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
2336
+ */
1583
2337
 
1584
2338
  /**
1585
2339
  * Simple wrapper `new Date().toISOString()`
@@ -1860,344 +2614,30 @@ function countUsage(llmTools) {
1860
2614
  return proxyTools;
1861
2615
  }
1862
2616
  /**
1863
- * TODO: [๐Ÿง ][๐Ÿ’ธ] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
1864
- * TODO: [๐Ÿง ] Is there some meaningfull way how to test this util
1865
- * TODO: [๐Ÿง ][๐ŸŒฏ] Maybe a way how to hide ability to `get totalUsage`
1866
- * > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
1867
- * TODO: [๐Ÿ‘ทโ€โ™‚๏ธ] @@@ Manual about construction of llmTools
1868
- */
1869
-
1870
- /**
1871
- * This error type indicates that some part of the code is not implemented yet
1872
- *
1873
- * @public exported from `@promptbook/core`
1874
- */
1875
- class NotYetImplementedError extends Error {
1876
- constructor(message) {
1877
- super(spaceTrim$1((block) => `
1878
- ${block(message)}
1879
-
1880
- Note: This feature is not implemented yet but it will be soon.
1881
-
1882
- If you want speed up the implementation or just read more, look here:
1883
- https://github.com/webgptorg/promptbook
1884
-
1885
- Or contact us on pavol@ptbk.io
1886
-
1887
- `));
1888
- this.name = 'NotYetImplementedError';
1889
- Object.setPrototypeOf(this, NotYetImplementedError.prototype);
1890
- }
1891
- }
1892
-
1893
- /**
1894
- * @@@
1895
- *
1896
- * Note: `$` is used to indicate that this function is not a pure function - it access global scope
1897
- *
1898
- * @private internal function of `$Register`
1899
- */
1900
- function $getGlobalScope() {
1901
- return Function('return this')();
1902
- }
1903
-
1904
- /**
1905
- * @@@
1906
- *
1907
- * @param text @@@
1908
- * @returns @@@
1909
- * @example 'HELLO_WORLD'
1910
- * @example 'I_LOVE_PROMPTBOOK'
1911
- * @public exported from `@promptbook/utils`
1912
- */
1913
- function normalizeTo_SCREAMING_CASE(text) {
1914
- let charType;
1915
- let lastCharType = 'OTHER';
1916
- let normalizedName = '';
1917
- for (const char of text) {
1918
- let normalizedChar;
1919
- if (/^[a-z]$/.test(char)) {
1920
- charType = 'LOWERCASE';
1921
- normalizedChar = char.toUpperCase();
1922
- }
1923
- else if (/^[A-Z]$/.test(char)) {
1924
- charType = 'UPPERCASE';
1925
- normalizedChar = char;
1926
- }
1927
- else if (/^[0-9]$/.test(char)) {
1928
- charType = 'NUMBER';
1929
- normalizedChar = char;
1930
- }
1931
- else {
1932
- charType = 'OTHER';
1933
- normalizedChar = '_';
1934
- }
1935
- if (charType !== lastCharType &&
1936
- !(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
1937
- !(lastCharType === 'NUMBER') &&
1938
- !(charType === 'NUMBER')) {
1939
- normalizedName += '_';
1940
- }
1941
- normalizedName += normalizedChar;
1942
- lastCharType = charType;
1943
- }
1944
- normalizedName = normalizedName.replace(/_+/g, '_');
1945
- normalizedName = normalizedName.replace(/_?\/_?/g, '/');
1946
- normalizedName = normalizedName.replace(/^_/, '');
1947
- normalizedName = normalizedName.replace(/_$/, '');
1948
- return normalizedName;
1949
- }
1950
- /**
1951
- * TODO: Tests
1952
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
1953
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ฤ›ลกฤล™ลพลพรฝรกรญรบลฏ' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
1954
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
1955
- * > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
1956
- * TODO: [๐ŸŒบ] Use some intermediate util splitWords
1957
- */
1958
-
1959
- /**
1960
- * @@@
1961
- *
1962
- * @param text @@@
1963
- * @returns @@@
1964
- * @example 'hello_world'
1965
- * @example 'i_love_promptbook'
1966
- * @public exported from `@promptbook/utils`
1967
- */
1968
- function normalizeTo_snake_case(text) {
1969
- return normalizeTo_SCREAMING_CASE(text).toLowerCase();
1970
- }
1971
-
1972
- /**
1973
- * Register is @@@
1974
- *
1975
- * Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
1976
- *
1977
- * @private internal utility, exported are only signleton instances of this class
1978
- */
1979
- class $Register {
1980
- constructor(registerName) {
1981
- this.registerName = registerName;
1982
- const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
1983
- const globalScope = $getGlobalScope();
1984
- if (globalScope[storageName] === undefined) {
1985
- globalScope[storageName] = [];
1986
- }
1987
- else if (!Array.isArray(globalScope[storageName])) {
1988
- throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
1989
- }
1990
- this.storage = globalScope[storageName];
1991
- }
1992
- list() {
1993
- // <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>>
1994
- return this.storage;
1995
- }
1996
- register(registered) {
1997
- const { packageName, className } = registered;
1998
- const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className);
1999
- const existingRegistration = this.storage[existingRegistrationIndex];
2000
- if (!existingRegistration) {
2001
- this.storage.push(registered);
2002
- }
2003
- else {
2004
- this.storage[existingRegistrationIndex] = registered;
2005
- }
2006
- return {
2007
- registerName: this.registerName,
2008
- packageName,
2009
- className,
2010
- get isDestroyed() {
2011
- return false;
2012
- },
2013
- destroy() {
2014
- throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`);
2015
- },
2016
- };
2017
- }
2018
- }
2019
-
2020
- /**
2021
- * @@@
2022
- *
2023
- * Note: `$` is used to indicate that this interacts with the global scope
2024
- * @singleton Only one instance of each register is created per build, but thare can be more @@@
2025
- * @public exported from `@promptbook/core`
2026
- */
2027
- const $llmToolsMetadataRegister = new $Register('llm_tools_metadata');
2028
- /**
2029
- * TODO: [ยฎ] DRY Register logic
2030
- */
2031
-
2032
- /**
2033
- * Determines if the given path is a root path.
2034
- *
2035
- * Note: This does not check if the file exists only if the path is valid
2036
- * @public exported from `@promptbook/utils`
2037
- */
2038
- function isRootPath(value) {
2039
- if (value === '/') {
2040
- return true;
2041
- }
2042
- if (/^[A-Z]:\\$/i.test(value)) {
2043
- return true;
2044
- }
2045
- return false;
2046
- }
2047
- /**
2048
- * TODO: [๐Ÿ] Make for MacOS paths
2049
- */
2050
-
2051
- /**
2052
- * @@@
2053
- *
2054
- * Note: `$` is used to indicate that this interacts with the global scope
2055
- * @singleton Only one instance of each register is created per build, but thare can be more @@@
2056
- * @public exported from `@promptbook/core`
2057
- */
2058
- const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
2059
- /**
2060
- * TODO: [ยฎ] DRY Register logic
2061
- */
2062
-
2063
- /**
2064
- * Path to the `.env` file which was used to configure LLM tools
2065
- *
2066
- * Note: `$` is used to indicate that this variable is changed by side effect in `$provideLlmToolsConfigurationFromEnv` through `$setUsedEnvFilename`
2067
- */
2068
- let $usedEnvFilename = null;
2069
- /**
2070
- * Pass the `.env` file which was used to configure LLM tools
2071
- *
2072
- * Note: `$` is used to indicate that this variable is making side effect
2073
- *
2074
- * @private internal log of `$provideLlmToolsConfigurationFromEnv` and `$registeredLlmToolsMessage`
2075
- */
2076
- function $setUsedEnvFilename(filepath) {
2077
- $usedEnvFilename = filepath;
2078
- }
2079
- /**
2080
- * Creates a message with all registered LLM tools
2081
- *
2082
- * Note: This function is used to create a (error) message when there is no constructor for some LLM provider
2617
+ * TODO: [๐Ÿง ][๐Ÿ’ธ] Maybe make some common abstraction `interceptLlmTools` and use here (or use javascript Proxy?)
2618
+ * TODO: [๐Ÿง ] Is there some meaningfull way how to test this util
2619
+ * TODO: [๐Ÿง ][๐ŸŒฏ] Maybe a way how to hide ability to `get totalUsage`
2620
+ * > const [llmToolsWithUsage,getUsage] = countTotalUsage(llmTools);
2621
+ * TODO: [๐Ÿ‘ทโ€โ™‚๏ธ] @@@ Manual about construction of llmTools
2622
+ */
2623
+
2624
+ /**
2625
+ * Determines if the given path is a root path.
2083
2626
  *
2084
- * @private internal function of `createLlmToolsFromConfiguration` and `$provideLlmToolsFromEnv`
2627
+ * Note: This does not check if the file exists only if the path is valid
2628
+ * @public exported from `@promptbook/utils`
2085
2629
  */
2086
- function $registeredLlmToolsMessage() {
2087
- let env;
2088
- if ($isRunningInNode()) {
2089
- env = process.env;
2090
- // <- TODO: [โš›] Some DRY way how to get to `process.env` and pass it into functions - ACRY search for `env`
2091
- }
2092
- else {
2093
- env = {};
2094
- }
2095
- /**
2096
- * Mixes registered LLM tools from $llmToolsMetadataRegister and $llmToolsRegister
2097
- */
2098
- const all = [];
2099
- for (const { title, packageName, className, envVariables } of $llmToolsMetadataRegister.list()) {
2100
- if (all.some((item) => item.packageName === packageName && item.className === className)) {
2101
- continue;
2102
- }
2103
- all.push({ title, packageName, className, envVariables });
2104
- }
2105
- for (const { packageName, className } of $llmToolsRegister.list()) {
2106
- if (all.some((item) => item.packageName === packageName && item.className === className)) {
2107
- continue;
2108
- }
2109
- all.push({ packageName, className });
2630
+ function isRootPath(value) {
2631
+ if (value === '/') {
2632
+ return true;
2110
2633
  }
2111
- const metadata = all.map((metadata) => {
2112
- var _a, _b;
2113
- const isMetadataAviailable = $llmToolsMetadataRegister
2114
- .list()
2115
- .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
2116
- const isInstalled = $llmToolsRegister
2117
- .list()
2118
- .find(({ packageName, className }) => metadata.packageName === packageName && metadata.className === className);
2119
- const isFullyConfigured = ((_a = metadata.envVariables) === null || _a === void 0 ? void 0 : _a.every((envVariableName) => env[envVariableName] !== undefined)) || false;
2120
- const isPartiallyConfigured = ((_b = metadata.envVariables) === null || _b === void 0 ? void 0 : _b.some((envVariableName) => env[envVariableName] !== undefined)) || false;
2121
- // <- Note: [๐Ÿ—จ]
2122
- return { ...metadata, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured };
2123
- });
2124
- const usedEnvMessage = $usedEnvFilename === null ? `Unknown \`.env\` file` : `Used \`.env\` file:\n${$usedEnvFilename}`;
2125
- if (metadata.length === 0) {
2126
- return spaceTrim((block) => `
2127
- No LLM providers are available.
2128
-
2129
- ${block(usedEnvMessage)}
2130
- `);
2634
+ if (/^[A-Z]:\\$/i.test(value)) {
2635
+ return true;
2131
2636
  }
2132
- return spaceTrim((block) => `
2133
-
2134
- ${block(usedEnvMessage)}
2135
-
2136
- Relevant environment variables:
2137
- ${block(Object.keys(env)
2138
- .filter((envVariableName) => metadata.some(({ envVariables }) => envVariables === null || envVariables === void 0 ? void 0 : envVariables.includes(envVariableName)))
2139
- .map((envVariableName) => `- \`${envVariableName}\``)
2140
- .join('\n'))}
2141
-
2142
- Available LLM providers are:
2143
- ${block(metadata
2144
- .map(({ title, packageName, className, envVariables, isMetadataAviailable, isInstalled, isFullyConfigured, isPartiallyConfigured, }, i) => {
2145
- const morePieces = [];
2146
- if (just(false)) ;
2147
- else if (!isMetadataAviailable && !isInstalled) {
2148
- // TODO: [๏ฟฝ][๏ฟฝ] Maybe do allow to do auto-install if package not registered and not found
2149
- morePieces.push(`Not installed and no metadata, looks like a unexpected behavior`);
2150
- }
2151
- else if (isMetadataAviailable && !isInstalled) {
2152
- // TODO: [๏ฟฝ][๏ฟฝ]
2153
- morePieces.push(`Not installed`);
2154
- }
2155
- else if (!isMetadataAviailable && isInstalled) {
2156
- morePieces.push(`No metadata but installed, looks like a unexpected behavior`);
2157
- }
2158
- else if (isMetadataAviailable && isInstalled) {
2159
- morePieces.push(`Installed`);
2160
- }
2161
- else {
2162
- morePieces.push(`unknown state, looks like a unexpected behavior`);
2163
- } /* not else */
2164
- if (isFullyConfigured) {
2165
- morePieces.push(`Configured`);
2166
- }
2167
- else if (isPartiallyConfigured) {
2168
- morePieces.push(`Partially confugured, missing ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.filter((envVariable) => env[envVariable] === undefined).join(' + ')}`);
2169
- }
2170
- else {
2171
- if (envVariables !== null) {
2172
- morePieces.push(`Not configured, to configure set env ${envVariables === null || envVariables === void 0 ? void 0 : envVariables.join(' + ')}`);
2173
- }
2174
- else {
2175
- morePieces.push(`Not configured`); // <- Note: Can not be configured via environment variables
2176
- }
2177
- }
2178
- let providerMessage = spaceTrim(`
2179
- ${i + 1}) **${title}** \`${className}\` from \`${packageName}\`
2180
- ${morePieces.join('; ')}
2181
- `);
2182
- if ($isRunningInNode) {
2183
- if (isInstalled && isFullyConfigured) {
2184
- providerMessage = colors.green(providerMessage);
2185
- }
2186
- else if (isInstalled && isPartiallyConfigured) {
2187
- providerMessage = colors.yellow(providerMessage);
2188
- }
2189
- else {
2190
- providerMessage = colors.gray(providerMessage);
2191
- }
2192
- }
2193
- return providerMessage;
2194
- })
2195
- .join('\n'))}
2196
- `);
2637
+ return false;
2197
2638
  }
2198
2639
  /**
2199
- * TODO: [ยฎ] DRY Register logic
2200
- * TODO: [๐Ÿง ][โš›] Maybe pass env as argument
2640
+ * TODO: [๐Ÿ] Make for MacOS paths
2201
2641
  */
2202
2642
 
2203
2643
  /**
@@ -2560,10 +3000,33 @@ async function $provideLlmToolsForWizzardOrCli(options) {
2560
3000
  if (!$isRunningInNode()) {
2561
3001
  throw new EnvironmentMismatchError('Function `$provideLlmToolsForWizzardOrCli` works only in Node.js environment');
2562
3002
  }
2563
- const { isCacheReloaded } = options !== null && options !== void 0 ? options : {};
3003
+ options = options !== null && options !== void 0 ? options : { strategy: 'BRING_YOUR_OWN_KEYS' };
3004
+ const { strategy, isCacheReloaded } = options;
3005
+ let llmExecutionTools;
3006
+ if (strategy === 'REMOTE_SERVER') {
3007
+ const { remoteServerUrl = DEFAULT_REMOTE_SERVER_URL, loginPrompt } = options;
3008
+ const storage = new MemoryStorage(); // <- TODO: !!!!!! Save to `.promptbook` folder
3009
+ const key = `${remoteServerUrl}-identification`;
3010
+ let identification = await storage.getItem(key);
3011
+ if (identification === null) {
3012
+ identification = await loginPrompt();
3013
+ await storage.setItem(key, identification);
3014
+ }
3015
+ llmExecutionTools = new RemoteLlmExecutionTools({
3016
+ remoteServerUrl,
3017
+ identification,
3018
+ });
3019
+ }
3020
+ else if (strategy === 'BRING_YOUR_OWN_KEYS') {
3021
+ llmExecutionTools = await $provideLlmToolsFromEnv();
3022
+ }
3023
+ else {
3024
+ throw new UnexpectedError(`\`$provideLlmToolsForWizzardOrCli\` wrong strategy "${strategy}"`);
3025
+ }
2564
3026
  return cacheLlmTools(countUsage(
3027
+ // <- TODO: [๐ŸŒฏ] We dont use countUsage at all, maybe just unwrap it
2565
3028
  // <- Note: for example here we don`t want the [๐ŸŒฏ]
2566
- await $provideLlmToolsFromEnv()), {
3029
+ llmExecutionTools), {
2567
3030
  storage: new FileCacheStorage({ fs: $provideFilesystemForNode() }, {
2568
3031
  rootFolderPath: join(process.cwd(), DEFAULT_EXECUTION_CACHE_DIRNAME),
2569
3032
  }),
@@ -2579,31 +3042,116 @@ async function $provideLlmToolsForWizzardOrCli(options) {
2579
3042
  */
2580
3043
 
2581
3044
  /**
2582
- * Just says that the variable is not used but should be kept
2583
- * No side effects.
2584
- *
2585
- * Note: It can be usefull for:
2586
- *
2587
- * 1) Suppressing eager optimization of unused imports
2588
- * 2) Suppressing eslint errors of unused variables in the tests
2589
- * 3) Keeping the type of the variable for type testing
3045
+ * Checks if value is valid email
2590
3046
  *
2591
- * @param value any values
2592
- * @returns void
2593
- * @private within the repository
3047
+ * @public exported from `@promptbook/utils`
2594
3048
  */
2595
- function keepUnused(...valuesToKeep) {
3049
+ function isValidEmail(email) {
3050
+ if (typeof email !== 'string') {
3051
+ return false;
3052
+ }
3053
+ if (email.split('\n').length > 1) {
3054
+ return false;
3055
+ }
3056
+ return /^.+@.+\..+$/.test(email);
2596
3057
  }
2597
3058
 
2598
3059
  /**
2599
- * Just says that the variable is not used directlys but should be kept because the existence of the variable is important
2600
- *
2601
- * @param value any values
2602
- * @returns void
2603
- * @private within the repository
3060
+ * @private utility of CLI
2604
3061
  */
2605
- function $sideEffect(...sideEffectSubjects) {
2606
- keepUnused(...sideEffectSubjects);
3062
+ function $provideLlmToolsForCli(options) {
3063
+ const { cliOptions: {
3064
+ /* TODO: Use verbose: isVerbose, */ interactive: isInteractive, provider, remoteServerUrl: remoteServerUrlRaw, }, } = options;
3065
+ let strategy;
3066
+ if (/^b/i.test(provider)) {
3067
+ strategy = 'BRING_YOUR_OWN_KEYS';
3068
+ }
3069
+ else if (/^r/i.test(provider)) {
3070
+ strategy = 'REMOTE_SERVER';
3071
+ }
3072
+ else {
3073
+ console.log(colors.red(`Unknown provider: "${provider}", please use "BRING_YOUR_OWN_KEYS" or "REMOTE_SERVER"`));
3074
+ process.exit(1);
3075
+ }
3076
+ if (strategy === 'BRING_YOUR_OWN_KEYS') {
3077
+ return /* not await */ $provideLlmToolsForWizzardOrCli({ strategy, ...options });
3078
+ }
3079
+ else if (strategy === 'REMOTE_SERVER') {
3080
+ if (!isValidUrl(remoteServerUrlRaw)) {
3081
+ console.log(colors.red(`Invalid URL of remote server: "${remoteServerUrlRaw}"`));
3082
+ process.exit(1);
3083
+ }
3084
+ const remoteServerUrl = remoteServerUrlRaw.endsWith('/') ? remoteServerUrlRaw.slice(0, -1) : remoteServerUrlRaw;
3085
+ return /* not await */ $provideLlmToolsForWizzardOrCli({
3086
+ strategy,
3087
+ appId: CLI_APP_ID,
3088
+ remoteServerUrl,
3089
+ ...options,
3090
+ async loginPrompt() {
3091
+ if (!isInteractive) {
3092
+ console.log(colors.red(`You can not login to remote server in non-interactive mode`));
3093
+ process.exit(1);
3094
+ }
3095
+ const { username, password } = await prompts([
3096
+ {
3097
+ type: 'text',
3098
+ name: 'username',
3099
+ message: 'Enter your email:',
3100
+ validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
3101
+ },
3102
+ {
3103
+ type: 'password',
3104
+ name: 'password',
3105
+ message: 'Enter your password:',
3106
+ validate: (value) => value.length /* <- TODO: [๐Ÿง ] Better password validation */ > 0
3107
+ ? true
3108
+ : 'Password is required',
3109
+ },
3110
+ ]);
3111
+ const loginUrl = `${remoteServerUrl}/login`;
3112
+ const response = await fetch(loginUrl, {
3113
+ method: 'POST',
3114
+ headers: {
3115
+ 'Content-Type': 'application/json',
3116
+ },
3117
+ body: JSON.stringify({
3118
+ appId: CLI_APP_ID,
3119
+ username,
3120
+ password,
3121
+ }),
3122
+ });
3123
+ console.log('!!!', {
3124
+ loginUrl,
3125
+ username,
3126
+ password,
3127
+ // type: response.type,
3128
+ // text: await response.text(),
3129
+ });
3130
+ const { isSuccess, message, error, identification } = (await response.json());
3131
+ if (message) {
3132
+ if (isSuccess) {
3133
+ console.log(colors.green(message));
3134
+ }
3135
+ else {
3136
+ console.log(colors.red(message));
3137
+ }
3138
+ }
3139
+ if (!isSuccess) {
3140
+ // Note: Login failed
3141
+ process.exit(1);
3142
+ }
3143
+ if (!identification) {
3144
+ // Note: Do not get identification here, but server signalizes the success so exiting but with code 0
3145
+ // This can mean for example that user needs to verify email
3146
+ process.exit(0);
3147
+ }
3148
+ return identification;
3149
+ },
3150
+ });
3151
+ }
3152
+ else {
3153
+ throw new UnexpectedError(`\`$provideLlmToolsForCli\` wrong strategy "${strategy}"`);
3154
+ }
2607
3155
  }
2608
3156
 
2609
3157
  /**
@@ -2620,8 +3168,10 @@ function $initializeListModelsCommand(program) {
2620
3168
  `));
2621
3169
  listModelsCommand.alias('models');
2622
3170
  listModelsCommand.alias('llm');
2623
- listModelsCommand.action(handleActionErrors(async () => {
2624
- const llm = await $provideLlmToolsForWizzardOrCli({});
3171
+ listModelsCommand.action(handleActionErrors(async (cliOptions) => {
3172
+ console.log('!!!', cliOptions);
3173
+ // TODO: !!!!!! Not relevant for remote server and also for `about` command
3174
+ const llm = await $provideLlmToolsForCli({ cliOptions });
2625
3175
  $sideEffect(llm);
2626
3176
  // <- Note: Providing LLM tools will make a side effect of registering all available LLM tools to show the message
2627
3177
  console.info($registeredLlmToolsMessage());
@@ -3181,49 +3731,73 @@ function $initializeListScrapersCommand(program) {
3181
3731
  */
3182
3732
 
3183
3733
  /**
3184
- * Converts PipelineCollection to serialized JSON
3734
+ * Initializes `login` command for Promptbook CLI utilities
3185
3735
  *
3186
- * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
3736
+ * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
3187
3737
  *
3188
- * @public exported from `@promptbook/core`
3189
- */
3190
- async function collectionToJson(collection) {
3191
- const pipelineUrls = await collection.listPipelines();
3192
- const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
3193
- return promptbooks;
3194
- }
3195
- /**
3196
- * TODO: [๐Ÿง ] Maybe clear `sourceFile` or clear when exposing through API or remote server
3738
+ * @private internal function of `promptbookCli`
3197
3739
  */
3740
+ function $initializeLoginCommand(program) {
3741
+ const loginCommand = program.command('login');
3742
+ loginCommand.description(spaceTrim(`
3743
+ Login to the remote Promptbook server
3744
+ `));
3745
+ loginCommand.action(handleActionErrors(async () => {
3746
+ // @@@
3747
+ console.error(colors.green(spaceTrim(`
3748
+ You will be logged in to https://promptbook.studio server.
3749
+ If you don't have an account, it will be created automatically.
3750
+ `)));
3751
+ // !!!!!!!!! Remove from here and use $provideLlmToolsForCli
3752
+ const { email, password } = await prompts([
3753
+ {
3754
+ type: 'text',
3755
+ name: 'email',
3756
+ message: 'Enter your email:',
3757
+ validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
3758
+ },
3759
+ {
3760
+ type: 'password',
3761
+ name: 'password',
3762
+ message: 'Enter your password:',
3763
+ validate: (value) => value.length /* <- TODO: [๐Ÿง ] Better password validation */ > 0 ? true : 'Password is required',
3764
+ },
3765
+ ]);
3766
+ TODO_USE(email, password);
3767
+ await forTime(1000);
3768
+ console.error(colors.green(spaceTrim(`
3769
+ Your account ${email} was successfully created.
3198
3770
 
3199
- /**
3200
- * This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
3201
- *
3202
- * @public exported from `@promptbook/core`
3203
- */
3204
- class ParseError extends Error {
3205
- constructor(message) {
3206
- super(message);
3207
- this.name = 'ParseError';
3208
- Object.setPrototypeOf(this, ParseError.prototype);
3209
- }
3771
+ Please verify your email:
3772
+ https://brj.app/api/v1/customer/register-account?apiKey=PRODdh003eNKaec7PoO1AzU244tsL4WO
3773
+
3774
+ After verification, you will receive 500 000 credits for free ๐ŸŽ‰
3775
+ `)));
3776
+ return process.exit(0);
3777
+ }));
3210
3778
  }
3211
3779
  /**
3212
- * TODO: Maybe split `ParseError` and `ApplyError`
3780
+ * TODO: Pass remote server URL (and path)
3781
+ * TODO: Implement non-interactive login
3782
+ * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
3783
+ * Note: [๐ŸŸก] Code in this file should never be published outside of `@promptbook/cli`
3213
3784
  */
3214
3785
 
3215
3786
  /**
3216
- * This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
3787
+ * Converts PipelineCollection to serialized JSON
3788
+ *
3789
+ * Note: Functions `collectionToJson` and `createCollectionFromJson` are complementary
3217
3790
  *
3218
3791
  * @public exported from `@promptbook/core`
3219
3792
  */
3220
- class PipelineLogicError extends Error {
3221
- constructor(message) {
3222
- super(message);
3223
- this.name = 'PipelineLogicError';
3224
- Object.setPrototypeOf(this, PipelineLogicError.prototype);
3225
- }
3793
+ async function collectionToJson(collection) {
3794
+ const pipelineUrls = await collection.listPipelines();
3795
+ const promptbooks = await Promise.all(pipelineUrls.map((url) => collection.getPipelineByUrl(url)));
3796
+ return promptbooks;
3226
3797
  }
3798
+ /**
3799
+ * TODO: [๐Ÿง ] Maybe clear `sourceFile` or clear when exposing through API or remote server
3800
+ */
3227
3801
 
3228
3802
  /**
3229
3803
  * Tests if given string is valid semantic version
@@ -3621,21 +4195,6 @@ async function loadArchive(filePath, fs) {
3621
4195
 
3622
4196
  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"}];
3623
4197
 
3624
- /**
3625
- * Checks if value is valid email
3626
- *
3627
- * @public exported from `@promptbook/utils`
3628
- */
3629
- function isValidEmail(email) {
3630
- if (typeof email !== 'string') {
3631
- return false;
3632
- }
3633
- if (email.split('\n').length > 1) {
3634
- return false;
3635
- }
3636
- return /^.+@.+\..+$/.test(email);
3637
- }
3638
-
3639
4198
  /**
3640
4199
  * Function isValidJsonString will tell you if the string is valid JSON or not
3641
4200
  *
@@ -3869,32 +4428,6 @@ function taskParameterJsonToString(taskParameterJson) {
3869
4428
  * TODO: [๐Ÿง ] Should be in generated .book.md file GENERATOR_WARNING
3870
4429
  */
3871
4430
 
3872
- /**
3873
- * This error indicates that promptbook not found in the collection
3874
- *
3875
- * @public exported from `@promptbook/core`
3876
- */
3877
- class NotFoundError extends Error {
3878
- constructor(message) {
3879
- super(message);
3880
- this.name = 'NotFoundError';
3881
- Object.setPrototypeOf(this, NotFoundError.prototype);
3882
- }
3883
- }
3884
-
3885
- /**
3886
- * This error indicates errors in referencing promptbooks between each other
3887
- *
3888
- * @public exported from `@promptbook/core`
3889
- */
3890
- class PipelineUrlError extends Error {
3891
- constructor(message) {
3892
- super(message);
3893
- this.name = 'PipelineUrlError';
3894
- Object.setPrototypeOf(this, PipelineUrlError.prototype);
3895
- }
3896
- }
3897
-
3898
4431
  /**
3899
4432
  * Parses the task and returns the list of all parameter names
3900
4433
  *
@@ -4065,24 +4598,6 @@ function createCollectionFromJson(...promptbooks) {
4065
4598
  return new SimplePipelineCollection(...promptbooks);
4066
4599
  }
4067
4600
 
4068
- /**
4069
- * This error type indicates that some tools are missing for pipeline execution or preparation
4070
- *
4071
- * @public exported from `@promptbook/core`
4072
- */
4073
- class MissingToolsError extends Error {
4074
- constructor(message) {
4075
- super(spaceTrim$1((block) => `
4076
- ${block(message)}
4077
-
4078
- Note: You have probbably forgot to provide some tools for pipeline execution or preparation
4079
-
4080
- `));
4081
- this.name = 'MissingToolsError';
4082
- Object.setPrototypeOf(this, MissingToolsError.prototype);
4083
- }
4084
- }
4085
-
4086
4601
  /**
4087
4602
  * Determine if the pipeline is fully prepared
4088
4603
  *
@@ -4113,212 +4628,42 @@ function isPipelinePrepared(pipeline) {
4113
4628
  /**
4114
4629
  * TODO: [๐Ÿ”ƒ][main] If the pipeline was prepared with different version or different set of models, prepare it once again
4115
4630
  * TODO: [๐Ÿ ] Maybe base this on `makeValidator`
4116
- * TODO: [๐ŸงŠ] Pipeline can be partially prepared, this should return true ONLY if fully prepared
4117
- * TODO: [๐Ÿงฟ] Maybe do same process with same granularity and subfinctions as `preparePipeline`
4118
- * - [๐Ÿ] ? Is context in each task
4119
- * - [โ™จ] Are examples prepared
4120
- * - [โ™จ] Are tasks prepared
4121
- */
4122
-
4123
- /**
4124
- * Recursively converts JSON strings to JSON objects
4125
-
4126
- * @public exported from `@promptbook/utils`
4127
- */
4128
- function jsonStringsToJsons(object) {
4129
- if (object === null) {
4130
- return object;
4131
- }
4132
- if (Array.isArray(object)) {
4133
- return object.map(jsonStringsToJsons);
4134
- }
4135
- if (typeof object !== 'object') {
4136
- return object;
4137
- }
4138
- const newObject = { ...object };
4139
- for (const [key, value] of Object.entries(object)) {
4140
- if (typeof value === 'string' && isValidJsonString(value)) {
4141
- newObject[key] = JSON.parse(value);
4142
- }
4143
- else {
4144
- newObject[key] = jsonStringsToJsons(value);
4145
- }
4146
- }
4147
- return newObject;
4148
- }
4149
- /**
4150
- * TODO: Type the return type correctly
4151
- */
4152
-
4153
- /**
4154
- * This error indicates problems parsing the format value
4155
- *
4156
- * For example, when the format value is not a valid JSON or CSV
4157
- * This is not thrown directly but in extended classes
4158
- *
4159
- * @public exported from `@promptbook/core`
4160
- */
4161
- class AbstractFormatError extends Error {
4162
- // Note: To allow instanceof do not put here error `name`
4163
- // public readonly name = 'AbstractFormatError';
4164
- constructor(message) {
4165
- super(message);
4166
- Object.setPrototypeOf(this, AbstractFormatError.prototype);
4167
- }
4168
- }
4169
-
4170
- /**
4171
- * This error indicates problem with parsing of CSV
4172
- *
4173
- * @public exported from `@promptbook/core`
4174
- */
4175
- class CsvFormatError extends AbstractFormatError {
4176
- constructor(message) {
4177
- super(message);
4178
- this.name = 'CsvFormatError';
4179
- Object.setPrototypeOf(this, CsvFormatError.prototype);
4180
- }
4181
- }
4182
-
4183
- /**
4184
- * This error indicates that the pipeline collection cannot be propperly loaded
4185
- *
4186
- * @public exported from `@promptbook/core`
4187
- */
4188
- class CollectionError extends Error {
4189
- constructor(message) {
4190
- super(message);
4191
- this.name = 'CollectionError';
4192
- Object.setPrototypeOf(this, CollectionError.prototype);
4193
- }
4194
- }
4195
-
4196
- /**
4197
- * This error occurs when some expectation is not met in the execution of the pipeline
4198
- *
4199
- * @public exported from `@promptbook/core`
4200
- * Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
4201
- * Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
4202
- * Note: This is a kindof subtype of PipelineExecutionError
4203
- */
4204
- class ExpectError extends Error {
4205
- constructor(message) {
4206
- super(message);
4207
- this.name = 'ExpectError';
4208
- Object.setPrototypeOf(this, ExpectError.prototype);
4209
- }
4210
- }
4211
-
4212
- /**
4213
- * This error indicates that the promptbook can not retrieve knowledge from external sources
4214
- *
4215
- * @public exported from `@promptbook/core`
4216
- */
4217
- class KnowledgeScrapeError extends Error {
4218
- constructor(message) {
4219
- super(message);
4220
- this.name = 'KnowledgeScrapeError';
4221
- Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
4222
- }
4223
- }
4224
-
4225
- /**
4226
- * This error type indicates that some limit was reached
4227
- *
4228
- * @public exported from `@promptbook/core`
4229
- */
4230
- class LimitReachedError extends Error {
4231
- constructor(message) {
4232
- super(message);
4233
- this.name = 'LimitReachedError';
4234
- Object.setPrototypeOf(this, LimitReachedError.prototype);
4235
- }
4236
- }
4237
-
4238
- /**
4239
- * Index of all custom errors
4240
- *
4241
- * @public exported from `@promptbook/core`
4242
- */
4243
- const PROMPTBOOK_ERRORS = {
4244
- AbstractFormatError,
4245
- CsvFormatError,
4246
- CollectionError,
4247
- EnvironmentMismatchError,
4248
- ExpectError,
4249
- KnowledgeScrapeError,
4250
- LimitReachedError,
4251
- MissingToolsError,
4252
- NotFoundError,
4253
- NotYetImplementedError,
4254
- ParseError,
4255
- PipelineExecutionError,
4256
- PipelineLogicError,
4257
- PipelineUrlError,
4258
- UnexpectedError,
4259
- // TODO: [๐Ÿช‘]> VersionMismatchError,
4260
- };
4261
- /**
4262
- * Index of all javascript errors
4263
- *
4264
- * @private for internal usage
4265
- */
4266
- const COMMON_JAVASCRIPT_ERRORS = {
4267
- Error,
4268
- EvalError,
4269
- RangeError,
4270
- ReferenceError,
4271
- SyntaxError,
4272
- TypeError,
4273
- URIError,
4274
- AggregateError,
4275
- /*
4276
- Note: Not widely supported
4277
- > InternalError,
4278
- > ModuleError,
4279
- > HeapError,
4280
- > WebAssemblyCompileError,
4281
- > WebAssemblyRuntimeError,
4282
- */
4283
- };
4284
- /**
4285
- * Index of all errors
4286
- *
4287
- * @private for internal usage
4288
- */
4289
- const ALL_ERRORS = {
4290
- ...PROMPTBOOK_ERRORS,
4291
- ...COMMON_JAVASCRIPT_ERRORS,
4292
- };
4293
- /**
4294
- * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
4631
+ * TODO: [๐ŸงŠ] Pipeline can be partially prepared, this should return true ONLY if fully prepared
4632
+ * TODO: [๐Ÿงฟ] Maybe do same process with same granularity and subfinctions as `preparePipeline`
4633
+ * - [๐Ÿ] ? Is context in each task
4634
+ * - [โ™จ] Are examples prepared
4635
+ * - [โ™จ] Are tasks prepared
4295
4636
  */
4296
4637
 
4297
4638
  /**
4298
- * Deserializes the error object
4299
- *
4639
+ * Recursively converts JSON strings to JSON objects
4640
+
4300
4641
  * @public exported from `@promptbook/utils`
4301
4642
  */
4302
- function deserializeError(error) {
4303
- const { name, stack, id } = error; // Added id
4304
- let { message } = error;
4305
- let ErrorClass = ALL_ERRORS[error.name];
4306
- if (ErrorClass === undefined) {
4307
- ErrorClass = Error;
4308
- message = `${name}: ${message}`;
4643
+ function jsonStringsToJsons(object) {
4644
+ if (object === null) {
4645
+ return object;
4309
4646
  }
4310
- if (stack !== undefined && stack !== '') {
4311
- message = spaceTrim((block) => `
4312
- ${block(message)}
4313
-
4314
- Original stack trace:
4315
- ${block(stack || '')}
4316
- `);
4647
+ if (Array.isArray(object)) {
4648
+ return object.map(jsonStringsToJsons);
4317
4649
  }
4318
- const deserializedError = new ErrorClass(message);
4319
- deserializedError.id = id; // Assign id to the error object
4320
- return deserializedError;
4650
+ if (typeof object !== 'object') {
4651
+ return object;
4652
+ }
4653
+ const newObject = { ...object };
4654
+ for (const [key, value] of Object.entries(object)) {
4655
+ if (typeof value === 'string' && isValidJsonString(value)) {
4656
+ newObject[key] = JSON.parse(value);
4657
+ }
4658
+ else {
4659
+ newObject[key] = jsonStringsToJsons(value);
4660
+ }
4661
+ }
4662
+ return newObject;
4321
4663
  }
4664
+ /**
4665
+ * TODO: Type the return type correctly
4666
+ */
4322
4667
 
4323
4668
  /**
4324
4669
  * Asserts that the execution of a Promptbook is successful
@@ -4471,6 +4816,10 @@ function serializeError(error) {
4471
4816
 
4472
4817
  Cannot serialize error with name "${name}"
4473
4818
 
4819
+ Authors of Promptbook probably forgot to add this error into the list of errors:
4820
+ https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
4821
+
4822
+
4474
4823
  ${block(stack || message)}
4475
4824
 
4476
4825
  `));
@@ -11381,7 +11730,6 @@ function $initializeMakeCommand(program) {
11381
11730
  makeCommand.option('--no-validation', `Do not validate logic of pipelines in collection`, true);
11382
11731
  makeCommand.option('--validation', `Types of validations separated by comma (options "logic","imports")`, 'logic,imports');
11383
11732
  makeCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
11384
- makeCommand.option('-v, --verbose', `Is output verbose`, false);
11385
11733
  makeCommand.option('-o, --output <path>', spaceTrim(`
11386
11734
  Where to save the builded collection
11387
11735
 
@@ -11395,7 +11743,8 @@ function $initializeMakeCommand(program) {
11395
11743
  Note: This can be used only with "javascript" or "typescript" format
11396
11744
 
11397
11745
  `), DEFAULT_GET_PIPELINE_COLLECTION_FUNCTION_NAME);
11398
- makeCommand.action(handleActionErrors(async (path, { projectName, rootUrl, format, functionName, validation, reload: isCacheReloaded, verbose: isVerbose, output, }) => {
11746
+ makeCommand.action(handleActionErrors(async (path, cliOptions) => {
11747
+ const { projectName, rootUrl, format, functionName, validation, reload: isCacheReloaded, verbose: isVerbose, output, } = cliOptions;
11399
11748
  if (!isValidJavascriptName(functionName)) {
11400
11749
  console.error(colors.red(`Function name "${functionName}" is not valid javascript name`));
11401
11750
  return process.exit(1);
@@ -11423,7 +11772,10 @@ function $initializeMakeCommand(program) {
11423
11772
  isCacheReloaded,
11424
11773
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
11425
11774
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
11426
- const llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
11775
+ const llm = await $provideLlmToolsForCli({
11776
+ cliOptions,
11777
+ ...prepareAndScrapeOptions,
11778
+ });
11427
11779
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
11428
11780
  const tools = {
11429
11781
  llm,
@@ -11691,8 +12043,8 @@ function $initializePrettifyCommand(program) {
11691
12043
  // <- TODO: [๐ŸงŸโ€โ™‚๏ธ] Unite path to promptbook collection argument
11692
12044
  'Pipelines to prettify as glob pattern');
11693
12045
  prettifyCommand.option('-i, --ignore <glob>', `Ignore as glob pattern`);
11694
- prettifyCommand.option('-v, --verbose', `Is output verbose`, false);
11695
- prettifyCommand.action(handleActionErrors(async (filesGlob, { ignore, verbose: isVerbose }) => {
12046
+ prettifyCommand.action(handleActionErrors(async (filesGlob, cliOptions) => {
12047
+ const { ignore, verbose: isVerbose } = cliOptions;
11696
12048
  const filenames = await glob(filesGlob, { ignore });
11697
12049
  // <- TODO: [๐Ÿ˜ถ]
11698
12050
  for (const filename of filenames) {
@@ -12293,13 +12645,12 @@ function $initializeRunCommand(program) {
12293
12645
  // TODO: [๐Ÿง…] DRY command arguments
12294
12646
  runCommand.argument('[pipelineSource]', 'Path to book file OR URL to book file, if not provided it will be asked');
12295
12647
  runCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
12296
- runCommand.option('-v, --verbose', `Is output verbose`, false);
12297
- runCommand.option('--no-interactive', `Input is not interactive, if true you need to pass all the input parameters through --json`);
12298
12648
  runCommand.option('--no-formfactor', `When set, behavior of the interactive mode is not changed by the formfactor of the pipeline`);
12299
12649
  runCommand.option('-j, --json <json>', `Pass all or some input parameters as JSON record, if used the output is also returned as JSON`);
12300
12650
  runCommand.option('-s, --save-report <path>', `Save report to file`);
12301
- runCommand.action(handleActionErrors(async (pipelineSource, options) => {
12302
- const { reload: isCacheReloaded, interactive: isInteractive, formfactor: isFormfactorUsed, json, verbose: isVerbose, saveReport, } = options;
12651
+ runCommand.action(handleActionErrors(async (pipelineSource, cliOptions) => {
12652
+ console.log('!!!', cliOptions);
12653
+ const { reload: isCacheReloaded, interactive: isInteractive, formfactor: isFormfactorUsed, json, verbose: isVerbose, saveReport, } = cliOptions;
12303
12654
  if (pipelineSource.includes('-') && normalizeToKebabCase(pipelineSource) === pipelineSource) {
12304
12655
  console.error(colors.red(`""${pipelineSource}" is not a valid command or book. See 'ptbk --help'.`));
12305
12656
  return process.exit(1);
@@ -12324,7 +12675,7 @@ function $initializeRunCommand(program) {
12324
12675
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
12325
12676
  let llm;
12326
12677
  try {
12327
- llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
12678
+ llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
12328
12679
  }
12329
12680
  catch (error) {
12330
12681
  if (!(error instanceof Error)) {
@@ -12381,7 +12732,7 @@ function $initializeRunCommand(program) {
12381
12732
  fs,
12382
12733
  fetch: scraperFetch,
12383
12734
  scrapers: await $provideScrapersForNode({ fs, llm, executables }, prepareAndScrapeOptions),
12384
- script: [new JavascriptExecutionTools(options)],
12735
+ script: [new JavascriptExecutionTools(cliOptions)],
12385
12736
  };
12386
12737
  if (isVerbose) {
12387
12738
  console.info(colors.gray('--- Getting the book ---'));
@@ -12545,11 +12896,12 @@ function $initializeRunCommand(program) {
12545
12896
  * @public exported from `@promptbook/remote-server`
12546
12897
  */
12547
12898
  function startRemoteServer(options) {
12548
- const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, } = {
12899
+ const { port, collection, createLlmExecutionTools, isAnonymousModeAllowed, isApplicationModeAllowed, isVerbose = DEFAULT_IS_VERBOSE, login, } = {
12549
12900
  isAnonymousModeAllowed: false,
12550
12901
  isApplicationModeAllowed: false,
12551
12902
  collection: null,
12552
12903
  createLlmExecutionTools: null,
12904
+ login: null,
12553
12905
  ...options,
12554
12906
  };
12555
12907
  // <- TODO: [๐Ÿฆช] Some helper type to be able to use discriminant union types with destructuring
@@ -12627,13 +12979,14 @@ function startRemoteServer(options) {
12627
12979
  servers: [
12628
12980
  {
12629
12981
  url: `http://localhost:${port}${rootPath}`,
12982
+ // <- TODO: !!!!! Probbably: Pass `remoteServerUrl` instead of `port` and `rootPath`
12630
12983
  },
12631
12984
  ],
12632
12985
  },
12633
12986
  apis: ['./src/remote-server/**/*.ts'], // Adjust path as needed
12634
12987
  };
12635
12988
  const swaggerSpec = swaggerJsdoc(swaggerOptions);
12636
- app.use(`${rootPath}/api-docs`, swaggerUi.serve, swaggerUi.setup(swaggerSpec));
12989
+ app.use([`/api-docs`, `${rootPath}/api-docs`], swaggerUi.serve, swaggerUi.setup(swaggerSpec));
12637
12990
  const runningExecutionTasks = [];
12638
12991
  // <- TODO: [๐Ÿคฌ] Identify the users
12639
12992
  // TODO: [๐Ÿง ] Do here some garbage collection of finished tasks
@@ -12683,9 +13036,12 @@ function startRemoteServer(options) {
12683
13036
 
12684
13037
  ## Paths
12685
13038
 
12686
- ${block(app._router.stack
12687
- .map(({ route }) => (route === null || route === void 0 ? void 0 : route.path) || null)
12688
- .filter((path) => path !== null)
13039
+ ${block([
13040
+ ...app._router.stack
13041
+ .map(({ route }) => (route === null || route === void 0 ? void 0 : route.path) || null)
13042
+ .filter((path) => path !== null),
13043
+ '/api-docs',
13044
+ ]
12689
13045
  .map((path) => `- ${path}`)
12690
13046
  .join('\n'))}
12691
13047
 
@@ -12703,7 +13059,79 @@ function startRemoteServer(options) {
12703
13059
  https://github.com/webgptorg/promptbook
12704
13060
  `));
12705
13061
  });
12706
- // TODO: !!!!!! Add login route
13062
+ /**
13063
+ * @swagger
13064
+ *
13065
+ * /login:
13066
+ * post:
13067
+ * summary: Login to the server
13068
+ * description: Login to the server and get identification.
13069
+ * requestBody:
13070
+ * required: true
13071
+ * content:
13072
+ * application/json:
13073
+ * schema:
13074
+ * type: object
13075
+ * properties:
13076
+ * username:
13077
+ * type: string
13078
+ * password:
13079
+ * type: string
13080
+ * appId:
13081
+ * type: string
13082
+ * responses:
13083
+ * 200:
13084
+ * description: Successful login
13085
+ * content:
13086
+ * application/json:
13087
+ * schema:
13088
+ * type: object
13089
+ * properties:
13090
+ * identification:
13091
+ * type: object
13092
+ */
13093
+ app.post([`/login`, `${rootPath}/login`], async (request, response) => {
13094
+ if (!isApplicationModeAllowed || login === null) {
13095
+ response.status(400).send('Application mode is not allowed');
13096
+ return;
13097
+ }
13098
+ try {
13099
+ const username = request.body.username;
13100
+ const password = request.body.password;
13101
+ const appId = request.body.appId;
13102
+ const { isSuccess, error, message, identification } = await login({
13103
+ username,
13104
+ password,
13105
+ appId,
13106
+ rawRequest: request,
13107
+ rawResponse: response,
13108
+ });
13109
+ response.status(201).send({
13110
+ isSuccess,
13111
+ message,
13112
+ error: error ? serializeError(error) : undefined,
13113
+ identification,
13114
+ });
13115
+ return;
13116
+ }
13117
+ catch (error) {
13118
+ if (!(error instanceof Error)) {
13119
+ throw error;
13120
+ }
13121
+ if (error instanceof AuthenticationError) {
13122
+ response.status(401).send({
13123
+ isSuccess: false,
13124
+ message: error.message,
13125
+ error: serializeError(error),
13126
+ });
13127
+ }
13128
+ console.warn(`Login function thrown different error than AuthenticationError`, {
13129
+ error,
13130
+ serializedError: serializeError(error),
13131
+ });
13132
+ response.status(400).send({ error: serializeError(error) });
13133
+ }
13134
+ });
12707
13135
  /**
12708
13136
  * @swagger
12709
13137
  * /books:
@@ -12720,7 +13148,7 @@ function startRemoteServer(options) {
12720
13148
  * items:
12721
13149
  * type: string
12722
13150
  */
12723
- app.get(`${rootPath}/books`, async (request, response) => {
13151
+ app.get([`/books`, `${rootPath}/books`], async (request, response) => {
12724
13152
  if (collection === null) {
12725
13153
  response.status(500).send('No collection available');
12726
13154
  return;
@@ -12753,7 +13181,7 @@ function startRemoteServer(options) {
12753
13181
  * 404:
12754
13182
  * description: Book not found.
12755
13183
  */
12756
- app.get(`${rootPath}/books/*`, async (request, response) => {
13184
+ app.get([`/books/*`, `${rootPath}/books/*`], async (request, response) => {
12757
13185
  try {
12758
13186
  if (collection === null) {
12759
13187
  response.status(500).send('No collection nor books available');
@@ -12823,10 +13251,10 @@ function startRemoteServer(options) {
12823
13251
  * items:
12824
13252
  * type: object
12825
13253
  */
12826
- app.get(`${rootPath}/executions`, async (request, response) => {
13254
+ app.get([`/executions`, `${rootPath}/executions`], async (request, response) => {
12827
13255
  response.send(runningExecutionTasks.map((runningExecutionTask) => exportExecutionTask(runningExecutionTask, false)));
12828
13256
  });
12829
- app.get(`${rootPath}/executions/last`, async (request, response) => {
13257
+ app.get([`/executions/last`, `${rootPath}/executions/last`], async (request, response) => {
12830
13258
  // TODO: [๐Ÿคฌ] Filter only for user
12831
13259
  if (runningExecutionTasks.length === 0) {
12832
13260
  response.status(404).send('No execution tasks found');
@@ -12835,7 +13263,7 @@ function startRemoteServer(options) {
12835
13263
  const lastExecutionTask = runningExecutionTasks[runningExecutionTasks.length - 1];
12836
13264
  response.send(exportExecutionTask(lastExecutionTask, true));
12837
13265
  });
12838
- app.get(`${rootPath}/executions/:taskId`, async (request, response) => {
13266
+ app.get([`/executions/:taskId`, `${rootPath}/executions/:taskId`], async (request, response) => {
12839
13267
  const { taskId } = request.params;
12840
13268
  // TODO: [๐Ÿคฌ] Filter only for user
12841
13269
  const executionTask = runningExecutionTasks.find((executionTask) => executionTask.taskId === taskId);
@@ -12876,7 +13304,7 @@ function startRemoteServer(options) {
12876
13304
  * 400:
12877
13305
  * description: Invalid input.
12878
13306
  */
12879
- app.post(`${rootPath}/executions/new`, async (request, response) => {
13307
+ app.post([`/executions/new`, `${rootPath}/executions/new`], async (request, response) => {
12880
13308
  try {
12881
13309
  const { inputParameters, identification /* <- [๐Ÿคฌ] */ } = request.body;
12882
13310
  const pipelineUrl = request.body.pipelineUrl || request.body.book;
@@ -13112,12 +13540,12 @@ function $initializeStartServerCommand(program) {
13112
13540
  `));
13113
13541
  startServerCommand.option('--allow-anonymous', `Is anonymous mode allowed`, false);
13114
13542
  startServerCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache`, false);
13115
- startServerCommand.option('-v, --verbose', `Is output verbose`, false);
13116
13543
  startServerCommand.description(spaceTrim(`
13117
13544
  Starts a remote server to execute books
13118
13545
  `));
13119
13546
  startServerCommand.alias('server');
13120
- startServerCommand.action(handleActionErrors(async (path, { port: portRaw, url: rawUrl, allowAnonymous: isAnonymousModeAllowed, reload: isCacheReloaded, verbose: isVerbose, }) => {
13547
+ startServerCommand.action(handleActionErrors(async (path, cliOptions) => {
13548
+ const { port: portRaw, url: rawUrl, allowAnonymous: isAnonymousModeAllowed, reload: isCacheReloaded, verbose: isVerbose, } = cliOptions;
13121
13549
  if (rawUrl && !isValidUrl(rawUrl)) {
13122
13550
  console.error(colors.red(`Invalid URL: ${rawUrl}`));
13123
13551
  return process.exit(1);
@@ -13146,7 +13574,7 @@ function $initializeStartServerCommand(program) {
13146
13574
  isCacheReloaded,
13147
13575
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
13148
13576
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
13149
- const llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
13577
+ const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13150
13578
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
13151
13579
  const tools = {
13152
13580
  llm,
@@ -13170,6 +13598,9 @@ function $initializeStartServerCommand(program) {
13170
13598
  isAnonymousModeAllowed,
13171
13599
  isApplicationModeAllowed: true,
13172
13600
  collection,
13601
+ async login() {
13602
+ throw new AuthenticationError('You can not login to the server started by `ptbk start-server` in cli, use `startRemoteServer` function instead.');
13603
+ },
13173
13604
  createLlmExecutionTools(options) {
13174
13605
  const { appId, userId } = options;
13175
13606
  TODO_USE({ appId, userId });
@@ -13206,8 +13637,8 @@ function $initializeTestCommand(program) {
13206
13637
  testCommand.option('--no-validation', `Do not validate logic of pipelines in collection`, true);
13207
13638
  testCommand.option('--no-prepare', `Do not prepare the pipelines, ideal when no LLM tools or scrapers available`, true);
13208
13639
  testCommand.option('-r, --reload', `Call LLM models even if same prompt with result is in the cache `, false);
13209
- testCommand.option('-v, --verbose', `Is output verbose`, false);
13210
- testCommand.action(handleActionErrors(async (filesGlob, { ignore: ignoreRaw = '', validation: isValidated, prepare: isPrepared, reload: isCacheReloaded, verbose: isVerbose, }) => {
13640
+ testCommand.action(handleActionErrors(async (filesGlob, cliOptions) => {
13641
+ const { ignore: ignoreRaw = '', validation: isValidated, prepare: isPrepared, reload: isCacheReloaded, verbose: isVerbose, } = cliOptions;
13211
13642
  let tools = undefined;
13212
13643
  if (isPrepared) {
13213
13644
  // TODO: DRY [โ—ฝ]
@@ -13216,7 +13647,7 @@ function $initializeTestCommand(program) {
13216
13647
  isCacheReloaded,
13217
13648
  }; /* <- TODO: ` satisfies PrepareAndScrapeOptions` */
13218
13649
  const fs = $provideFilesystemForNode(prepareAndScrapeOptions);
13219
- const llm = await $provideLlmToolsForWizzardOrCli(prepareAndScrapeOptions);
13650
+ const llm = await $provideLlmToolsForCli({ cliOptions, ...prepareAndScrapeOptions });
13220
13651
  const executables = await $provideExecutablesForNode(prepareAndScrapeOptions);
13221
13652
  tools = {
13222
13653
  llm,
@@ -13277,56 +13708,16 @@ function $initializeTestCommand(program) {
13277
13708
  */
13278
13709
 
13279
13710
  /**
13280
- * Initializes `login` command for Promptbook CLI utilities
13281
- *
13282
- * Note: `$` is used to indicate that this function is not a pure function - it registers a command in the CLI
13711
+ * Note: `$` is used to indicate that this function is not a pure function - it registers an option in the CLI
13283
13712
  *
13284
- * @private internal function of `promptbookCli`
13713
+ * @private utility of CLI
13285
13714
  */
13286
- function $initializeLoginCommand(program) {
13287
- const loginCommand = program.command('login');
13288
- loginCommand.description(spaceTrim(`
13289
- Login to the remote Promptbook server
13290
- `));
13291
- loginCommand.action(handleActionErrors(async () => {
13292
- // @@@
13293
- console.error(colors.green(spaceTrim(`
13294
- You will be logged in to https://promptbook.studio server.
13295
- If you don't have an account, it will be created automatically.
13296
- `)));
13297
- const { email, password } = await prompts([
13298
- {
13299
- type: 'text',
13300
- name: 'email',
13301
- message: 'Enter your email:',
13302
- validate: (value) => (isValidEmail(value) ? true : 'Valid email is required'),
13303
- },
13304
- {
13305
- type: 'password',
13306
- name: 'password',
13307
- message: 'Enter your password:',
13308
- validate: (value) => value.length /* <- TODO: [๐Ÿง ] Better password validation */ > 0 ? true : 'Password is required',
13309
- },
13310
- ]);
13311
- TODO_USE(email, password);
13312
- await forTime(1000);
13313
- console.error(colors.green(spaceTrim(`
13314
- Your account ${email} was successfully created.
13315
-
13316
- Please verify your email:
13317
- https://brj.app/api/v1/customer/register-account?apiKey=PRODdh003eNKaec7PoO1AzU244tsL4WO
13318
-
13319
- After verification, you will receive 500 000 credits for free ๐ŸŽ‰
13320
- `)));
13321
- return process.exit(0);
13322
- }));
13715
+ function $addGlobalOptionsToCommand(command) {
13716
+ command.option('-v, --verbose', `Log more details`, false);
13717
+ 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`);
13718
+ command.option('-p, --provider <provider>', `Which LLM provider to use: "BYOK" / "BRING_YOUR_OWN_KEYS" or "REMOTE_SERVER" / "RS"`, 'REMOTE_SERVER');
13719
+ command.option('--remote-server-url <url>', `URL of remote server to use when `, DEFAULT_REMOTE_SERVER_URL);
13323
13720
  }
13324
- /**
13325
- * TODO: Pass remote server URL (and path)
13326
- * TODO: Implement non-interactive login
13327
- * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
13328
- * Note: [๐ŸŸก] Code in this file should never be published outside of `@promptbook/cli`
13329
- */
13330
13721
 
13331
13722
  /**
13332
13723
  * Runs CLI utilities of Promptbook package
@@ -13352,6 +13743,7 @@ async function promptbookCli() {
13352
13743
  program.alias('ptbk');
13353
13744
  program.version(PROMPTBOOK_ENGINE_VERSION);
13354
13745
  program.description(CLAIM);
13746
+ // Note: Theese options are valid for all commands
13355
13747
  $initializeAboutCommand(program);
13356
13748
  $initializeRunCommand(program);
13357
13749
  $initializeLoginCommand(program);
@@ -13362,6 +13754,8 @@ async function promptbookCli() {
13362
13754
  $initializeListModelsCommand(program);
13363
13755
  $initializeListScrapersCommand(program);
13364
13756
  $initializeStartServerCommand(program);
13757
+ // TODO: [๐Ÿง ] Should it be here or not> $addGlobalOptionsToCommand(program);
13758
+ program.commands.forEach($addGlobalOptionsToCommand);
13365
13759
  program.parse(process.argv);
13366
13760
  }
13367
13761
  /**
@@ -13411,8 +13805,7 @@ const _AnthropicClaudeMetadataRegistration = $llmToolsMetadataRegister.register(
13411
13805
  options: {
13412
13806
  apiKey: 'sk-ant-api03-',
13413
13807
  isProxied: true,
13414
- remoteUrl: DEFAULT_REMOTE_URL,
13415
- path: DEFAULT_REMOTE_URL_PATH,
13808
+ remoteServerUrl: DEFAULT_REMOTE_SERVER_URL,
13416
13809
  },
13417
13810
  };
13418
13811
  },
@@ -13435,146 +13828,6 @@ const _AnthropicClaudeMetadataRegistration = $llmToolsMetadataRegister.register(
13435
13828
  * Note: [๐Ÿ’ž] Ignore a discrepancy between file name and entity name
13436
13829
  */
13437
13830
 
13438
- /**
13439
- * Creates a connection to the remote proxy server.
13440
- *
13441
- * Note: This function creates a connection to the remote server and returns a socket but responsibility of closing the connection is on the caller
13442
- *
13443
- * @private internal utility function
13444
- */
13445
- async function createRemoteClient(options) {
13446
- const { remoteUrl, path } = options;
13447
- return new Promise((resolve, reject) => {
13448
- const socket = io(remoteUrl, {
13449
- retries: CONNECTION_RETRIES_LIMIT,
13450
- timeout: CONNECTION_TIMEOUT_MS,
13451
- path,
13452
- // path: `${this.remoteUrl.pathname}/socket.io`,
13453
- transports: [/*'websocket', <- TODO: [๐ŸŒฌ] Make websocket transport work */ 'polling'],
13454
- });
13455
- // console.log('Connecting to', this.options.remoteUrl.href, { socket });
13456
- socket.on('connect', () => {
13457
- resolve(socket);
13458
- });
13459
- // TODO: [๐Ÿ’ฉ] Better timeout handling
13460
- setTimeout(() => {
13461
- reject(new Error(`Timeout while connecting to ${remoteUrl}`));
13462
- }, CONNECTION_TIMEOUT_MS);
13463
- });
13464
- }
13465
-
13466
- /**
13467
- * Remote server is a proxy server that uses its execution tools internally and exposes the executor interface externally.
13468
- *
13469
- * You can simply use `RemoteExecutionTools` on client-side javascript and connect to your remote server.
13470
- * This is useful to make all logic on browser side but not expose your API keys or no need to use customer's GPU.
13471
- *
13472
- * @see https://github.com/webgptorg/promptbook#remote-server
13473
- * @public exported from `@promptbook/remote-client`
13474
- */
13475
- class RemoteLlmExecutionTools {
13476
- /* <- TODO: [๐Ÿš] `, Destroyable` */
13477
- constructor(options) {
13478
- this.options = options;
13479
- }
13480
- get title() {
13481
- // TODO: [๐Ÿง ] Maybe fetch title+description from the remote server (as well as if model methods are defined)
13482
- return 'Remote server';
13483
- }
13484
- get description() {
13485
- return 'Use all models by your remote server';
13486
- }
13487
- /**
13488
- * Check the configuration of all execution tools
13489
- */
13490
- async checkConfiguration() {
13491
- const socket = await createRemoteClient(this.options);
13492
- socket.disconnect();
13493
- // TODO: [main] !!3 Check version of the remote server and compatibility
13494
- // TODO: [๐ŸŽ] Send checkConfiguration
13495
- }
13496
- /**
13497
- * List all available models that can be used
13498
- */
13499
- async listModels() {
13500
- // TODO: [๐Ÿ‘’] Listing models (and checking configuration) probbably should go through REST API not Socket.io
13501
- const socket = await createRemoteClient(this.options);
13502
- socket.emit('listModels-request', {
13503
- identification: this.options.identification,
13504
- } /* <- Note: [๐Ÿค›] */);
13505
- const promptResult = await new Promise((resolve, reject) => {
13506
- socket.on('listModels-response', (response) => {
13507
- resolve(response.models);
13508
- socket.disconnect();
13509
- });
13510
- socket.on('error', (error) => {
13511
- reject(deserializeError(error));
13512
- socket.disconnect();
13513
- });
13514
- });
13515
- socket.disconnect();
13516
- return promptResult;
13517
- }
13518
- /**
13519
- * Calls remote proxy server to use a chat model
13520
- */
13521
- callChatModel(prompt) {
13522
- if (this.options.isVerbose) {
13523
- console.info(`๐Ÿ–‹ Remote callChatModel call`);
13524
- }
13525
- return /* not await */ this.callCommonModel(prompt);
13526
- }
13527
- /**
13528
- * Calls remote proxy server to use a completion model
13529
- */
13530
- callCompletionModel(prompt) {
13531
- if (this.options.isVerbose) {
13532
- console.info(`๐Ÿ’ฌ Remote callCompletionModel call`);
13533
- }
13534
- return /* not await */ this.callCommonModel(prompt);
13535
- }
13536
- /**
13537
- * Calls remote proxy server to use a embedding model
13538
- */
13539
- callEmbeddingModel(prompt) {
13540
- if (this.options.isVerbose) {
13541
- console.info(`๐Ÿ’ฌ Remote callEmbeddingModel call`);
13542
- }
13543
- return /* not await */ this.callCommonModel(prompt);
13544
- }
13545
- // <- Note: [๐Ÿค–] callXxxModel
13546
- /**
13547
- * Calls remote proxy server to use both completion or chat model
13548
- */
13549
- async callCommonModel(prompt) {
13550
- const socket = await createRemoteClient(this.options);
13551
- socket.emit('prompt-request', {
13552
- identification: this.options.identification,
13553
- prompt,
13554
- } /* <- Note: [๐Ÿค›] */);
13555
- const promptResult = await new Promise((resolve, reject) => {
13556
- socket.on('prompt-response', (response) => {
13557
- resolve(response.promptResult);
13558
- socket.disconnect();
13559
- });
13560
- socket.on('error', (error) => {
13561
- reject(deserializeError(error));
13562
- socket.disconnect();
13563
- });
13564
- });
13565
- socket.disconnect();
13566
- return promptResult;
13567
- }
13568
- }
13569
- /**
13570
- * TODO: Maybe use `$exportJson`
13571
- * TODO: [๐Ÿง ][๐Ÿ›] Maybe not `isAnonymous: boolean` BUT `mode: 'ANONYMOUS'|'COLLECTION'`
13572
- * TODO: [๐Ÿ“] Allow to list compatible models with each variant
13573
- * TODO: [๐Ÿ—ฏ] RemoteLlmExecutionTools should extend Destroyable and implement IDestroyable
13574
- * TODO: [๐Ÿง ][๐ŸŒฐ] Allow to pass `title` for tracking purposes
13575
- * TODO: [๐Ÿง ] Maybe remove `@promptbook/remote-client` and just use `@promptbook/core`
13576
- */
13577
-
13578
13831
  /**
13579
13832
  * Function computeUsage will create price per one token based on the string value found on openai page
13580
13833
  *