@promptbook/google
Version:
Promptbook: Create persistent AI agents that turn your company's scattered knowledge into action
3,111 lines โข 112 kB
JavaScript
import CryptoJS from 'crypto-js';
import { spaceTrim as spaceTrim$1 } from 'spacetrim';
import colors from 'colors';
// โ ๏ธ WARNING: This code has been generated so that any manual changes will be overwritten
/**
* The version of the Book language
*
* @generated
* @see https://github.com/webgptorg/book
*/
const BOOK_LANGUAGE_VERSION = '2.0.0';
/**
* The version of the Promptbook engine
*
* @generated
* @see https://github.com/webgptorg/promptbook
*/
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-50';
/**
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Generates random token
*
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic
* Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
*
* @returns secure random token
*
* @private internal helper function
*/
function $randomToken(randomness) {
return CryptoJS.lib.WordArray.random(randomness).toString(CryptoJS.enc.Hex);
}
// TODO: [๐คถ] Maybe export through `@promptbook/utils` or `@promptbook/random` package
// TODO: Maybe use nanoid instead https://github.com/ai/nanoid
/**
* This error indicates errors during the execution of the pipeline
*
* @public exported from `@promptbook/core`
*/
class PipelineExecutionError extends Error {
constructor(message) {
// Added id parameter
super(message);
this.name = 'PipelineExecutionError';
// TODO: [๐] DRY - Maybe $randomId
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
}
}
// TODO: [๐ง ][๐] Add id to all errors
/**
* Counts number of characters in the text
*
* @public exported from `@promptbook/utils`
*/
function countCharacters(text) {
// Remove null characters
text = text.replace(/\0/g, '');
// Replace emojis (and also ZWJ sequence) with hyphens
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
text = text.replace(/\p{Extended_Pictographic}(\u{200D}\p{Extended_Pictographic})*/gu, '-');
return text.length;
}
// TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
// TODO: [๐ง ][โ๏ธ] Make some Promptbook-native token system
/**
* Number of characters per standard line with 11pt Arial font size.
*
* @public exported from `@promptbook/utils`
*/
const CHARACTERS_PER_STANDARD_LINE = 63;
/**
* Number of lines per standard A4 page with 11pt Arial font size and standard margins and spacing.
*
* @public exported from `@promptbook/utils`
*/
const LINES_PER_STANDARD_PAGE = 44;
// TODO: [๐ง ] Should be this `constants.ts` or `config.ts`?
// Note: [๐] Ignore a discrepancy between file name and entity name
/**
* Counts number of lines in the text
*
* Note: This does not check only for the presence of newlines, but also for the length of the standard line.
*
* @public exported from `@promptbook/utils`
*/
function countLines(text) {
if (text === '') {
return 0;
}
text = text.replace('\r\n', '\n');
text = text.replace('\r', '\n');
const lines = text.split(/\r?\n/);
return lines.reduce((count, line) => count + Math.max(Math.ceil(line.length / CHARACTERS_PER_STANDARD_LINE), 1), 0);
}
// TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
// TODO: [๐ง ][โ๏ธ] Make some Promptbook-native token system
/**
* Counts number of pages in the text
*
* Note: This does not check only for the count of newlines, but also for the length of the standard line and length of the standard page.
*
* @public exported from `@promptbook/utils`
*/
function countPages(text) {
return Math.ceil(countLines(text) / LINES_PER_STANDARD_PAGE);
}
// TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
// TODO: [๐ง ][โ๏ธ] Make some Promptbook-native token system
/**
* Counts number of paragraphs in the text
*
* @public exported from `@promptbook/utils`
*/
function countParagraphs(text) {
return text.split(/\n\s*\n/).filter((paragraph) => paragraph.trim() !== '').length;
}
// TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
// TODO: [๐ง ][โ๏ธ] Make some Promptbook-native token system
/**
* Split text into sentences
*
* @public exported from `@promptbook/utils`
*/
function splitIntoSentences(text) {
return text.split(/[.!?]+/).filter((sentence) => sentence.trim() !== '');
}
/**
* Counts number of sentences in the text
*
* @public exported from `@promptbook/utils`
*/
function countSentences(text) {
return splitIntoSentences(text).length;
}
// TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
// TODO: [๐ง ][โ๏ธ] Make some Promptbook-native token system
/**
* Collection of default diacritics removal map.
*/
const defaultDiacriticsRemovalMap = [
{
base: 'A',
letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F',
},
{ base: 'AA', letters: '\uA732' },
{ base: 'AE', letters: '\u00C6\u01FC\u01E2' },
{ base: 'AO', letters: '\uA734' },
{ base: 'AU', letters: '\uA736' },
{ base: 'AV', letters: '\uA738\uA73A' },
{ base: 'AY', letters: '\uA73C' },
{
base: 'B',
letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181',
},
{
base: 'C',
letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E',
},
{
base: 'D',
letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0',
},
{ base: 'DZ', letters: '\u01F1\u01C4' },
{ base: 'Dz', letters: '\u01F2\u01C5' },
{
base: 'E',
letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E',
},
{ base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' },
{
base: 'G',
letters: '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E',
},
{
base: 'H',
letters: '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D',
},
{
base: 'I',
letters: '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197',
},
{ base: 'J', letters: '\u004A\u24BF\uFF2A\u0134\u0248' },
{
base: 'K',
letters: '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2',
},
{
base: 'L',
letters: '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780',
},
{ base: 'LJ', letters: '\u01C7' },
{ base: 'Lj', letters: '\u01C8' },
{ base: 'M', letters: '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' },
{
base: 'N',
letters: '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4',
},
{ base: 'NJ', letters: '\u01CA' },
{ base: 'Nj', letters: '\u01CB' },
{
base: 'O',
letters: '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C',
},
{ base: 'OI', letters: '\u01A2' },
{ base: 'OO', letters: '\uA74E' },
{ base: 'OU', letters: '\u0222' },
{ base: 'OE', letters: '\u008C\u0152' },
{ base: 'oe', letters: '\u009C\u0153' },
{
base: 'P',
letters: '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754',
},
{ base: 'Q', letters: '\u0051\u24C6\uFF31\uA756\uA758\u024A' },
{
base: 'R',
letters: '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782',
},
{
base: 'S',
letters: '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784',
},
{
base: 'T',
letters: '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786',
},
{ base: 'TZ', letters: '\uA728' },
{
base: 'U',
letters: '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244',
},
{ base: 'V', letters: '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' },
{ base: 'VY', letters: '\uA760' },
{
base: 'W',
letters: '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72',
},
{ base: 'X', letters: '\u0058\u24CD\uFF38\u1E8A\u1E8C' },
{
base: 'Y',
letters: '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE',
},
{
base: 'Z',
letters: '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762',
},
{
base: 'a',
letters: '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250',
},
{ base: 'aa', letters: '\uA733' },
{ base: 'ae', letters: '\u00E6\u01FD\u01E3' },
{ base: 'ao', letters: '\uA735' },
{ base: 'au', letters: '\uA737' },
{ base: 'av', letters: '\uA739\uA73B' },
{ base: 'ay', letters: '\uA73D' },
{
base: 'b',
letters: '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253',
},
{
base: 'c',
letters: '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184',
},
{
base: 'd',
letters: '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A',
},
{ base: 'dz', letters: '\u01F3\u01C6' },
{
base: 'e',
letters: '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD',
},
{ base: 'f', letters: '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' },
{
base: 'g',
letters: '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F',
},
{
base: 'h',
letters: '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265',
},
{ base: 'hv', letters: '\u0195' },
{
base: 'i',
letters: '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131',
},
{ base: 'j', letters: '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' },
{
base: 'k',
letters: '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3',
},
{
base: 'l',
letters: '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747',
},
{ base: 'lj', letters: '\u01C9' },
{ base: 'm', letters: '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' },
{
base: 'n',
letters: '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5',
},
{ base: 'nj', letters: '\u01CC' },
{
base: 'o',
letters: '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275',
},
{ base: 'oi', letters: '\u01A3' },
{ base: 'ou', letters: '\u0223' },
{ base: 'oo', letters: '\uA74F' },
{
base: 'p',
letters: '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755',
},
{ base: 'q', letters: '\u0071\u24E0\uFF51\u024B\uA757\uA759' },
{
base: 'r',
letters: '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783',
},
{
base: 's',
letters: '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B',
},
{
base: 't',
letters: '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787',
},
{ base: 'tz', letters: '\uA729' },
{
base: 'u',
letters: '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289',
},
{ base: 'v', letters: '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' },
{ base: 'vy', letters: '\uA761' },
{
base: 'w',
letters: '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73',
},
{ base: 'x', letters: '\u0078\u24E7\uFF58\u1E8B\u1E8D' },
{
base: 'y',
letters: '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF',
},
{
base: 'z',
letters: '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763',
},
];
/**
* Map of letters from diacritic variant to diacritless variant
* Contains lowercase and uppercase separatelly
*
* > "รก" => "a"
* > "ฤ" => "e"
* > "ฤ" => "A"
* > ...
*
* @public exported from `@promptbook/utils`
*/
const DIACRITIC_VARIANTS_LETTERS = {};
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
const letters = defaultDiacriticsRemovalMap[i].letters;
// tslint:disable-next-line: prefer-for-of
for (let j = 0; j < letters.length; j++) {
DIACRITIC_VARIANTS_LETTERS[letters[j]] = defaultDiacriticsRemovalMap[i].base;
}
}
// <- TODO: [๐] Put to maker function to save execution time if not needed
/*
@see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Removes diacritic marks (accents) from characters in a string.
*
* Note: [๐] This function is idempotent.
*
* @param input The string containing diacritics to be normalized.
* @returns The string with diacritics removed or normalized.
*
* @public exported from `@promptbook/utils`
*/
function removeDiacritics(input) {
/*eslint no-control-regex: "off"*/
return input.replace(/[^\u0000-\u007E]/g, (character) => {
return DIACRITIC_VARIANTS_LETTERS[character] || character;
});
}
// TODO: [ะ] Variant for cyrillic (and in general non-latin) letters
/**
* Counts number of words in the text
*
* @public exported from `@promptbook/utils`
*/
function countWords(text) {
text = text.replace(/[\p{Extended_Pictographic}]/gu, 'a');
text = removeDiacritics(text);
// Add spaces before uppercase letters preceded by lowercase letters (for camelCase)
text = text.replace(/([a-z])([A-Z])/g, '$1 $2');
return text.split(/[^a-zะฐ-ั0-9]+/i).filter((word) => word.length > 0).length;
}
// TODO: [๐ฅด] Implement counting in formats - like JSON, CSV, XML,...
// TODO: [๐ง ][โ๏ธ] Make some Promptbook-native token system
// TODO: [โ๏ธ] `countWords` should be just `splitWords(...).length`, and all other counters should use this pattern as well
/**
* Helper of usage compute
*
* @param content the content of prompt or response
* @returns part of UsageCounts
*
* @private internal utility of LlmExecutionTools
*/
function computeUsageCounts(content) {
return {
charactersCount: { value: countCharacters(content) },
wordsCount: { value: countWords(content) },
sentencesCount: { value: countSentences(content) },
linesCount: { value: countLines(content) },
paragraphsCount: { value: countParagraphs(content) },
pagesCount: { value: countPages(content) },
};
}
/**
* Freezes the given object and all its nested objects recursively
*
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
*
* @returns The same object as the input, but deeply frozen
*
* @public exported from `@promptbook/utils`
*/
function $deepFreeze(objectValue) {
if (Array.isArray(objectValue)) {
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
}
const propertyNames = Object.getOwnPropertyNames(objectValue);
for (const propertyName of propertyNames) {
const value = objectValue[propertyName];
if (value && typeof value === 'object') {
$deepFreeze(value);
}
}
Object.freeze(objectValue);
return objectValue;
}
// TODO: [๐ง ] Is there a way how to meaningfully test this utility
/**
* Represents the uncertain value
*
* @public exported from `@promptbook/core`
*/
const ZERO_VALUE = $deepFreeze({ value: 0 });
/**
* Represents the uncertain value
*
* @public exported from `@promptbook/core`
*/
const UNCERTAIN_ZERO_VALUE = $deepFreeze({ value: 0, isUncertain: true });
/**
* Represents the usage with no resources consumed
*
* @public exported from `@promptbook/core`
*/
$deepFreeze({
price: ZERO_VALUE,
duration: ZERO_VALUE,
input: {
tokensCount: ZERO_VALUE,
charactersCount: ZERO_VALUE,
wordsCount: ZERO_VALUE,
sentencesCount: ZERO_VALUE,
linesCount: ZERO_VALUE,
paragraphsCount: ZERO_VALUE,
pagesCount: ZERO_VALUE,
},
output: {
tokensCount: ZERO_VALUE,
charactersCount: ZERO_VALUE,
wordsCount: ZERO_VALUE,
sentencesCount: ZERO_VALUE,
linesCount: ZERO_VALUE,
paragraphsCount: ZERO_VALUE,
pagesCount: ZERO_VALUE,
},
});
/**
* Represents the usage with unknown resources consumed
*
* @public exported from `@promptbook/core`
*/
$deepFreeze({
price: UNCERTAIN_ZERO_VALUE,
duration: UNCERTAIN_ZERO_VALUE,
input: {
tokensCount: UNCERTAIN_ZERO_VALUE,
charactersCount: UNCERTAIN_ZERO_VALUE,
wordsCount: UNCERTAIN_ZERO_VALUE,
sentencesCount: UNCERTAIN_ZERO_VALUE,
linesCount: UNCERTAIN_ZERO_VALUE,
paragraphsCount: UNCERTAIN_ZERO_VALUE,
pagesCount: UNCERTAIN_ZERO_VALUE,
},
output: {
tokensCount: UNCERTAIN_ZERO_VALUE,
charactersCount: UNCERTAIN_ZERO_VALUE,
wordsCount: UNCERTAIN_ZERO_VALUE,
sentencesCount: UNCERTAIN_ZERO_VALUE,
linesCount: UNCERTAIN_ZERO_VALUE,
paragraphsCount: UNCERTAIN_ZERO_VALUE,
pagesCount: UNCERTAIN_ZERO_VALUE,
},
});
// Note: [๐] Ignore a discrepancy between file name and entity name
/**
* Make UncertainNumber
*
* @param value value of the uncertain number, if `NaN` or `undefined`, it will be set to 0 and `isUncertain=true`
* @param isUncertain if `true`, the value is uncertain, otherwise depends on the value
*
* @private utility for initializating UncertainNumber
*/
function uncertainNumber(value, isUncertain) {
if (value === null || value === undefined || Number.isNaN(value)) {
return UNCERTAIN_ZERO_VALUE;
}
if (isUncertain === true) {
return { value, isUncertain };
}
return { value };
}
/**
* Detects if the code is running in jest environment
*
* Note: `$` is used to indicate that this function is not a pure function - it looks at the global object to determine the environment
*
* @public exported from `@promptbook/utils`
*/
function $isRunningInJest() {
var _a;
try {
return typeof process !== 'undefined' && ((_a = process.env) === null || _a === void 0 ? void 0 : _a.JEST_WORKER_ID) !== undefined;
}
catch (e) {
return false;
}
}
// TODO: [๐บ]
/**
* Simple wrapper `new Date().toISOString()`
*
* Note: `$` is used to indicate that this function is not a pure function - it is not deterministic because it depends on the current time
*
* @returns string_date branded type
*
* @public exported from `@promptbook/utils`
*/
function $getCurrentDate() {
return new Date().toISOString();
}
/**
* Trims string from all 4 sides
*
* Note: This is a re-exported function from the `spacetrim` package which is
* Developed by same author @hejny as this package
*
* @see https://github.com/hejny/spacetrim#usage
*
* @public exported from `@promptbook/utils`
*/
const spaceTrim = spaceTrim$1;
/**
* Class implementing take chain.
*
* @de
*
* @private util of `@promptbook/color`
*/
class TakeChain {
constructor(value) {
this.value = value;
}
then(callback) {
const newValue = callback(this.value);
return take(newValue);
}
}
/**
* A function that takes an initial value and returns a proxy object with chainable methods.
*
* @param {*} initialValue - The initial value.
* @returns {Proxy<WithTake<TValue>>} - A proxy object with a `take` method.
* @deprecated [๐คก] Use some better functional library instead of `TakeChain`
*
* @private util of `@promptbook/color`
*/
function take(initialValue) {
if (initialValue instanceof TakeChain) {
return initialValue;
}
return new Proxy(new TakeChain(initialValue), {
get(target, property, receiver) {
if (Reflect.has(target, property)) {
return Reflect.get(target, property, receiver);
}
else if (Reflect.has(initialValue, property)) {
return Reflect.get(initialValue, property, receiver);
}
else {
return undefined;
}
},
});
}
/**
* Validates that a channel value is a valid number within the range of 0 to 255.
* Throws an error if the value is not valid.
*
* @param channelName - The name of the channel being validated.
* @param value - The value of the channel to validate.
* @throws Will throw an error if the value is not a valid channel number.
*
* @private util of `@promptbook/color`
*/
function checkChannelValue(channelName, value) {
if (typeof value !== 'number') {
throw new Error(`${channelName} channel value is not number but ${typeof value}`);
}
if (isNaN(value)) {
throw new Error(`${channelName} channel value is NaN`);
}
if (Math.round(value) !== value) {
throw new Error(`${channelName} channel is not whole number, it is ${value}`);
}
if (value < 0) {
throw new Error(`${channelName} channel is lower than 0, it is ${value}`);
}
if (value > 255) {
throw new Error(`${channelName} channel is greater than 255, it is ${value}`);
}
}
/**
* Shared immutable channel storage and serialization helpers for `Color`.
*
* @private base class of Color
*/
class ColorValue {
constructor(red, green, blue, alpha = 255) {
this.red = red;
this.green = green;
this.blue = blue;
this.alpha = alpha;
checkChannelValue('Red', red);
checkChannelValue('Green', green);
checkChannelValue('Blue', blue);
checkChannelValue('Alpha', alpha);
}
/**
* Shortcut for `red` property
* Number from 0 to 255
* @alias red
*/
get r() {
return this.red;
}
/**
* Shortcut for `green` property
* Number from 0 to 255
* @alias green
*/
get g() {
return this.green;
}
/**
* Shortcut for `blue` property
* Number from 0 to 255
* @alias blue
*/
get b() {
return this.blue;
}
/**
* Shortcut for `alpha` property
* Number from 0 (transparent) to 255 (opaque)
* @alias alpha
*/
get a() {
return this.alpha;
}
/**
* Shortcut for `alpha` property
* Number from 0 (transparent) to 255 (opaque)
* @alias alpha
*/
get opacity() {
return this.alpha;
}
/**
* Shortcut for 1-`alpha` property
*/
get transparency() {
return 255 - this.alpha;
}
clone() {
return take(this.createColor(this.red, this.green, this.blue, this.alpha));
}
toString() {
return this.toHex();
}
toHex() {
if (this.alpha === 255) {
return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
.toString(16)
.padStart(2, '0')}`;
}
else {
return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue
.toString(16)
.padStart(2, '0')}${this.alpha.toString(16).padStart(2, '0')}`;
}
}
toRgb() {
if (this.alpha === 255) {
return `rgb(${this.red}, ${this.green}, ${this.blue})`;
}
else {
return `rgba(${this.red}, ${this.green}, ${this.blue}, ${Math.round((this.alpha / 255) * 100)}%)`;
}
}
toHsl() {
throw new Error(`Getting HSL is not implemented`);
}
}
/**
* ๐จ List of all 140 color names which are supported by CSS
*
* @public exported from `@promptbook/color`
*/
const CSS_COLORS = {
promptbook: '#79EAFD',
transparent: 'rgba(0,0,0,0)',
aliceblue: '#f0f8ff',
antiquewhite: '#faebd7',
aqua: '#00ffff',
aquamarine: '#7fffd4',
azure: '#f0ffff',
beige: '#f5f5dc',
bisque: '#ffe4c4',
black: '#000000',
blanchedalmond: '#ffebcd',
blue: '#0000ff',
blueviolet: '#8a2be2',
brown: '#a52a2a',
burlywood: '#deb887',
cadetblue: '#5f9ea0',
chartreuse: '#7fff00',
chocolate: '#d2691e',
coral: '#ff7f50',
cornflowerblue: '#6495ed',
cornsilk: '#fff8dc',
crimson: '#dc143c',
cyan: '#00ffff',
darkblue: '#00008b',
darkcyan: '#008b8b',
darkgoldenrod: '#b8860b',
darkgray: '#a9a9a9',
darkgrey: '#a9a9a9',
darkgreen: '#006400',
darkkhaki: '#bdb76b',
darkmagenta: '#8b008b',
darkolivegreen: '#556b2f',
darkorange: '#ff8c00',
darkorchid: '#9932cc',
darkred: '#8b0000',
darksalmon: '#e9967a',
darkseagreen: '#8fbc8f',
darkslateblue: '#483d8b',
darkslategray: '#2f4f4f',
darkslategrey: '#2f4f4f',
darkturquoise: '#00ced1',
darkviolet: '#9400d3',
deeppink: '#ff1493',
deepskyblue: '#00bfff',
dimgray: '#696969',
dimgrey: '#696969',
dodgerblue: '#1e90ff',
firebrick: '#b22222',
floralwhite: '#fffaf0',
forestgreen: '#228b22',
fuchsia: '#ff00ff',
gainsboro: '#dcdcdc',
ghostwhite: '#f8f8ff',
gold: '#ffd700',
goldenrod: '#daa520',
gray: '#808080',
grey: '#808080',
green: '#008000',
greenyellow: '#adff2f',
honeydew: '#f0fff0',
hotpink: '#ff69b4',
indianred: '#cd5c5c',
indigo: '#4b0082',
ivory: '#fffff0',
khaki: '#f0e68c',
lavender: '#e6e6fa',
lavenderblush: '#fff0f5',
lawngreen: '#7cfc00',
lemonchiffon: '#fffacd',
lightblue: '#add8e6',
lightcoral: '#f08080',
lightcyan: '#e0ffff',
lightgoldenrodyellow: '#fafad2',
lightgray: '#d3d3d3',
lightgrey: '#d3d3d3',
lightgreen: '#90ee90',
lightpink: '#ffb6c1',
lightsalmon: '#ffa07a',
lightseagreen: '#20b2aa',
lightskyblue: '#87cefa',
lightslategray: '#778899',
lightslategrey: '#778899',
lightsteelblue: '#b0c4de',
lightyellow: '#ffffe0',
lime: '#00ff00',
limegreen: '#32cd32',
linen: '#faf0e6',
magenta: '#ff00ff',
maroon: '#800000',
mediumaquamarine: '#66cdaa',
mediumblue: '#0000cd',
mediumorchid: '#ba55d3',
mediumpurple: '#9370db',
mediumseagreen: '#3cb371',
mediumslateblue: '#7b68ee',
mediumspringgreen: '#00fa9a',
mediumturquoise: '#48d1cc',
mediumvioletred: '#c71585',
midnightblue: '#191970',
mintcream: '#f5fffa',
mistyrose: '#ffe4e1',
moccasin: '#ffe4b5',
navajowhite: '#ffdead',
navy: '#000080',
oldlace: '#fdf5e6',
olive: '#808000',
olivedrab: '#6b8e23',
orange: '#ffa500',
orangered: '#ff4500',
orchid: '#da70d6',
palegoldenrod: '#eee8aa',
palegreen: '#98fb98',
paleturquoise: '#afeeee',
palevioletred: '#db7093',
papayawhip: '#ffefd5',
peachpuff: '#ffdab9',
peru: '#cd853f',
pink: '#ffc0cb',
plum: '#dda0dd',
powderblue: '#b0e0e6',
purple: '#800080',
rebeccapurple: '#663399',
red: '#ff0000',
rosybrown: '#bc8f8f',
royalblue: '#4169e1',
saddlebrown: '#8b4513',
salmon: '#fa8072',
sandybrown: '#f4a460',
seagreen: '#2e8b57',
seashell: '#fff5ee',
sienna: '#a0522d',
silver: '#c0c0c0',
skyblue: '#87ceeb',
slateblue: '#6a5acd',
slategray: '#708090',
slategrey: '#708090',
snow: '#fffafa',
springgreen: '#00ff7f',
steelblue: '#4682b4',
tan: '#d2b48c',
teal: '#008080',
thistle: '#d8bfd8',
tomato: '#ff6347',
turquoise: '#40e0d0',
violet: '#ee82ee',
wheat: '#f5deb3',
white: '#ffffff',
whitesmoke: '#f5f5f5',
yellow: '#ffff00',
yellowgreen: '#9acd32',
};
// Note: [๐] Ignore a discrepancy between file name and entity name
/**
* Checks if the given value is a valid hex color string
*
* @param value - value to check
* @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
*
* @private function of Color
*/
function isHexColorString(value) {
return (typeof value === 'string' && /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/.test(value));
}
/**
* Constant for short hex lengths.
*/
const SHORT_HEX_LENGTHS = new Set([3, 4]);
/**
* Constant for long hex lengths.
*/
const LONG_HEX_LENGTHS = new Set([6, 8]);
/**
* Parses a hex string into RGBA channel values.
*
* @param hex - Hex value such as `#09d`, `009edd`, `#009eddff`.
* @returns RGBA channel values.
*
* @private function of Color
*/
function parseHexColor(hex) {
const sanitized = hex.startsWith('#') ? hex.substring(1) : hex;
const throwInvalidHex = () => {
throw new Error(`Can not parse color from hex string "${hex}"`);
};
if (SHORT_HEX_LENGTHS.has(sanitized.length)) {
return {
red: parseShortHexChannel(sanitized.charAt(0), throwInvalidHex),
green: parseShortHexChannel(sanitized.charAt(1), throwInvalidHex),
blue: parseShortHexChannel(sanitized.charAt(2), throwInvalidHex),
alpha: sanitized.length === 4 ? parseShortHexChannel(sanitized.charAt(3), throwInvalidHex) : 255,
};
}
if (LONG_HEX_LENGTHS.has(sanitized.length)) {
return {
red: parseLongHexChannel(sanitized, 0, throwInvalidHex),
green: parseLongHexChannel(sanitized, 2, throwInvalidHex),
blue: parseLongHexChannel(sanitized, 4, throwInvalidHex),
alpha: sanitized.length === 8 ? parseLongHexChannel(sanitized, 6, throwInvalidHex) : 255,
};
}
return throwInvalidHex();
}
/**
* Parses short hex channel.
*/
function parseShortHexChannel(char, onError) {
if (!char) {
return onError();
}
const parsed = parseInt(char, 16);
if (Number.isNaN(parsed)) {
return onError();
}
return parsed * 16;
}
/**
* Parses long hex channel.
*/
function parseLongHexChannel(hex, start, onError) {
const segment = hex.substr(start, 2);
if (segment.length < 2) {
return onError();
}
const parsed = parseInt(segment, 16);
if (Number.isNaN(parsed)) {
return onError();
}
return parsed;
}
/**
* Pattern matching hsl.
*/
const HSL_REGEX = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
/**
* Parses an HSL string into RGBA channel values.
*
* @param hsl - HSL string such as `hsl(197.1, 100%, 43.3%)`.
* @returns RGBA channel values.
*
* @private function of Color
*/
function parseHslColor(hsl) {
const match = hsl.match(HSL_REGEX);
if (!match) {
throw new Error(`Invalid hsl string format: "${hsl}"`);
}
const hue = parseFloat(match[1]);
const saturation = parseFloat(match[2]) / 100;
const lightness = parseFloat(match[3]) / 100;
const { red, green, blue } = convertHslToRgb(hue, saturation, lightness);
return {
red,
green,
blue,
alpha: 255,
};
}
/**
* Handles convert hsl to Rgb.
*/
function convertHslToRgb(h, s, l) {
const c = (1 - Math.abs(2 * l - 1)) * s;
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
const m = l - c / 2;
let r1 = 0;
let g1 = 0;
let b1 = 0;
if (h >= 0 && h < 60) {
r1 = c;
g1 = x;
}
else if (h >= 60 && h < 120) {
r1 = x;
g1 = c;
}
else if (h >= 120 && h < 180) {
g1 = c;
b1 = x;
}
else if (h >= 180 && h < 240) {
g1 = x;
b1 = c;
}
else if (h >= 240 && h < 300) {
r1 = x;
b1 = c;
}
else if (h >= 300 && h < 360) {
r1 = c;
b1 = x;
}
return {
red: Math.round((r1 + m) * 255),
green: Math.round((g1 + m) * 255),
blue: Math.round((b1 + m) * 255),
};
}
/**
* Pattern matching RGB.
*/
const RGB_REGEX = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
/**
* Pattern matching rgba.
*/
const RGBA_REGEX = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
/**
* Parses an RGB string into RGBA channel values.
*
* @param rgb - RGB string such as `rgb(0%, 62%, 86.7%)`.
* @returns RGBA channel values.
*
* @private function of Color
*/
function parseRgbColor(rgb) {
const match = rgb.match(RGB_REGEX);
if (!match) {
throw new Error(`Invalid rgb string format: "${rgb}"`);
}
return {
red: parseChannelValue(match[1]),
green: parseChannelValue(match[2]),
blue: parseChannelValue(match[3]),
alpha: 255,
};
}
/**
* Parses an RGBA string into RGBA channel values.
*
* @param rgba - RGBA string such as `rgba(0, 158, 221, 0.5)`.
* @returns RGBA channel values.
*
* @private function of Color
*/
function parseRgbaColor(rgba) {
const match = rgba.match(RGBA_REGEX);
if (!match) {
throw new Error(`Invalid rgba string format: "${rgba}"`);
}
return {
red: parseChannelValue(match[1]),
green: parseChannelValue(match[2]),
blue: parseChannelValue(match[3]),
alpha: parseAlphaValue(match[4]),
};
}
/**
* Parses channel value.
*/
function parseChannelValue(value) {
if (value.endsWith('%')) {
const percent = parseFloat(value);
return Math.round((percent / 100) * 255);
}
return Math.round(parseFloat(value));
}
/**
* Parses alpha value.
*/
function parseAlphaValue(value) {
if (value.endsWith('%')) {
const percent = parseFloat(value);
return Math.round((percent / 100) * 255);
}
const parsed = parseFloat(value);
if (parsed <= 1) {
return Math.round(parsed * 255);
}
return Math.round(parsed);
}
/**
* Pattern matching hsl regex.
*
* @private function of Color
*/
const HSL_REGEX_PATTERN = /^hsl\(\s*([0-9.]+)\s*,\s*([0-9.]+)%\s*,\s*([0-9.]+)%\s*\)$/;
/**
* Pattern matching RGB regex.
*
* @private function of Color
*/
const RGB_REGEX_PATTERN = /^rgb\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
/**
* Pattern matching rgba regex.
*
* @private function of Color
*/
const RGBA_REGEX_PATTERN = /^rgba\(\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*,\s*([0-9.%-]+)\s*\)$/;
/**
* Parses a supported color string into RGBA channels.
*
* @param color as a string for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`, `red`, `darkgrey`,...
* @returns RGBA channel values.
*
* @private function of Color
*/
function parseColorString(color) {
const trimmed = color.trim();
const cssColor = CSS_COLORS[trimmed];
if (cssColor) {
return parseColorString(cssColor);
}
else if (isHexColorString(trimmed)) {
return parseHexColor(trimmed);
}
if (HSL_REGEX_PATTERN.test(trimmed)) {
return parseHslColor(trimmed);
}
else if (RGB_REGEX_PATTERN.test(trimmed)) {
return parseRgbColor(trimmed);
}
else if (RGBA_REGEX_PATTERN.test(trimmed)) {
return parseRgbaColor(trimmed);
}
else {
throw new Error(`Can not create a new Color instance from string "${trimmed}".`);
}
}
/**
* Color object represents an RGB color with alpha channel
*
* Note: There is no fromObject/toObject because the most logical way to serialize color is as a hex string (#009edd)
*
* @public exported from `@promptbook/color`
*/
class Color extends ColorValue {
/**
* Creates a new Color instance from miscellaneous formats
* - It can receive Color instance and just return the same instance
* - It can receive color in string format for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`
*
* Note: This is not including fromImage because detecting color from an image is heavy task which requires async stuff and we cannot safely determine with overloading if return value will be a promise
*
* @param color
* @returns Color object
*/
static from(color, _isSingleValue = false) {
if (color === '') {
throw new Error(`Can not create color from empty string`);
}
else if (color instanceof Color) {
return take(color);
}
else if (Color.isColor(color)) {
return take(color);
}
else if (typeof color === 'string') {
try {
return Color.fromString(color);
}
catch (error) {
// <- Note: Can not use `assertsError(error)` here because it causes circular dependency
if (_isSingleValue) {
throw error;
}
const parts = color.split(/[\s+,;|]/);
if (parts.length > 0) {
return Color.from(parts[0].trim(), true);
}
else {
throw new Error(`Can not create color from given string "${color}"`);
}
}
}
else {
console.error({ color });
throw new Error(`Can not create color from given object`);
}
}
/**
* Creates a new Color instance from miscellaneous formats
* It just does not throw error when it fails, it returns PROMPTBOOK_COLOR instead
*
* @param color
* @returns Color object
*/
static fromSafe(color) {
try {
return Color.from(color);
}
catch (error) {
// <- Note: Can not use `assertsError(error)` here because it causes circular dependency
console.warn(spaceTrim((block) => `
Color.fromSafe error:
${block(error.message)}
Returning default PROMPTBOOK_COLOR.
`));
return Color.fromString('promptbook');
}
}
/**
* Creates a new Color instance from miscellaneous string formats
*
* @param color as a string for example `#009edd`, `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`, `hsl(197.1,100%,43.3%)`, `red`, `darkgrey`,...
* @returns Color object
*/
static fromString(color) {
return Color.fromColorChannels(parseColorString(color));
}
/**
* Gets common color
*
* @param key as a css string like `midnightblue`
* @returns Color object
*/
static get(key) {
if (!CSS_COLORS[key]) {
throw new Error(`"${key}" is not a common css color.`);
}
return Color.fromString(CSS_COLORS[key]);
}
/**
* Creates a new Color instance from average color of given image
*
* @param image as a source for example `data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAA1JREFUGFdjYJh39z8ABJgCe/ZvAS4AAAAASUVORK5CYII=`
* @returns Color object
*/
static async fromImage(image) {
return Color.fromHex(`#009edd`);
}
/**
* Creates a new Color instance from color in hex format
*
* @param color in hex for example `#009edd`, `009edd`, `#555`,...
* @returns Color object
*/
static fromHex(hex) {
return Color.fromColorChannels(parseHexColor(hex));
}
/**
* Creates a new Color instance from color in hsl format
*
* @param color as a hsl for example `hsl(197.1,100%,43.3%)`
* @returns Color object
*/
static fromHsl(hsl) {
return Color.fromColorChannels(parseHslColor(hsl));
}
/**
* Creates a new Color instance from color in rgb format
*
* @param color as a rgb for example `rgb(0,158,221)`, `rgb(0%,62%,86.7%)`
* @returns Color object
*/
static fromRgbString(rgb) {
return Color.fromColorChannels(parseRgbColor(rgb));
}
/**
* Creates a new Color instance from color in rbga format
*
* @param color as a rgba for example `rgba(0,158,221,0.5)`, `rgb(0%,62%,86.7%,50%)`
* @returns Color object
*/
static fromRgbaString(rgba) {
return Color.fromColorChannels(parseRgbaColor(rgba));
}
/**
* Creates a new Color for color channels values
*
* @param red number from 0 to 255
* @param green number from 0 to 255
* @param blue number from 0 to 255
* @param alpha number from 0 (transparent) to 255 (opaque = default)
* @returns Color object
*/
static fromValues(red, green, blue, alpha = 255) {
return Color.fromColorChannels({ red, green, blue, alpha });
}
/**
* Checks if the given value is a valid Color object.
*
* @param {unknown} value - The value to check.
* @return {value is WithTake<Color>} Returns true if the value is a valid Color object, false otherwise.
*/
static isColor(value) {
if (typeof value !== 'object') {
return false;
}
if (value === null) {
return false;
}
if (typeof value.red !== 'number' ||
typeof value.green !== 'number' ||
typeof value.blue !== 'number' ||
typeof value.alpha !== 'number') {
return false;
}
if (typeof value.then !== 'function') {
return false;
}
return true;
}
/**
* Checks if the given value is a valid hex color string
*
* @param value - value to check
* @returns true if the value is a valid hex color string (e.g., `#009edd`, `#fff`, etc.)
*/
static isHexColorString(value) {
return isHexColorString(value);
}
/**
* Creates new Color object
*
* Note: Consider using one of static methods like `from` or `fromString`
*
* @param red number from 0 to 255
* @param green number from 0 to 255
* @param blue number from 0 to 255
* @param alpha number from 0 (transparent) to 255 (opaque)
*/
constructor(red, green, blue, alpha = 255) {
super(red, green, blue, alpha);
}
createColor(red, green, blue, alpha) {
return new Color(red, green, blue, alpha);
}
static fromColorChannels({ red, green, blue, alpha }) {
return take(new Color(red, green, blue, alpha));
}
}
/**
* Makes color transformer which returns a grayscale version of the color
*
* @param amount from 0 to 1
*
* @public exported from `@promptbook/color`
*/
function grayscale(amount) {
return ({ red, green, blue, alpha }) => {
const average = (red + green + blue) / 3;
red = Math.round(average * amount + red * (1 - amount));
green = Math.round(average * amount + green * (1 - amount));
blue = Math.round(average * amount + blue * (1 - amount));
return Color.fromValues(red, green, blue, alpha);
};
}
/**
* Converts HSL values to RGB values
*
* @param hue [0-1]
* @param saturation [0-1]
* @param lightness [0-1]
* @returns [red, green, blue] [0-255]
*
* @private util of `@promptbook/color`
*/
function hslToRgb(hue, saturation, lightness) {
let red;
let green;
let blue;
if (saturation === 0) {
// achromatic
red = lightness;
green = lightness;
blue = lightness;
}
else {
// TODO: Extract to separate function
const hue2rgb = (p, q, t) => {
if (t < 0)
t += 1;
if (t > 1)
t -= 1;
if (t < 1 / 6)
return p + (q - p) * 6 * t;
if (t < 1 / 2)
return q;
if (t < 2 / 3)
return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = lightness < 0.5 ? lightness * (1 + saturation) : lightness + saturation - lightness * saturation;
const p = 2 * lightness - q;
red = hue2rgb(p, q, hue + 1 / 3);
green = hue2rgb(p, q, hue);
blue = hue2rgb(p, q, hue - 1 / 3);
}
return [Math.round(red * 255), Math.round(green * 255), Math.round(blue * 255)];
}
// TODO: Properly name all used internal variables
/**
* Converts RGB values to HSL values
*
* @param red [0-255]
* @param green [0-255]
* @param blue [0-255]
* @returns [hue, saturation, lightness] [0-1]
*
* @private util of `@promptbook/color`
*/
function rgbToHsl(red, green, blue) {
red /= 255;
green /= 255;
blue /= 255;
const max = Math.max(red, green, blue);
const min = Math.min(red, green, blue);
let hue;
let saturation;
const lightness = (max + min) / 2;
if (max === min) {
// achromatic
hue = 0;
saturation = 0;
}
else {
const d = max - min;
saturation = lightness > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case red:
hue = (green - blue) / d + (green < blue ? 6 : 0);
break;
case green:
hue = (blue - red) / d + 2;
break;
case blue:
hue = (red - green) / d + 4;
break;
default:
hue = 0;
}
hue /= 6;
}
return [hue, saturation, lightness];
}
// TODO: Properly name all used internal variables
/**
* Makes color transformer which lighten the given color
*
* @param amount from 0 to 1
*
* @public exported from `@promptbook/color`
*/
function lighten(amount) {
return ({ red, green, blue, alpha }) => {
const [h, s, lInitial] = rgbToHsl(red, green, blue);
let l = lInitial + amount;
l = Math.max(0, Math.min(l, 1)); // Replace lodash clamp with Math.max and Math.min
const [r, g, b] = hslToRgb(h, s, l);
return Color.fromValues(r, g, b, alpha);
};
}
// TODO: Maybe implement by mix+hsl
/**
* Makes color transformer which saturate the given color
*
* @param amount from -1 to 1
*
* @public exported from `@promptbook/color`
*/
function saturate(amount) {
return ({ red, green, blue, alpha }) => {
const [h, sInitial, l] = rgbToHsl(red, green, blue);
let s = sInitial + amount;
s = Math.max(0, Math.min(s, 1));
const [r, g, b] = hslToRgb(h, s, l);
return Color.fromValues(r, g, b, alpha);
};
}
// TODO: Maybe implement by mix+hsl
/**
* Stable root directory used for Promptbook-owned temporary files and caches.
*
* @private internal utility for Promptbook temporary folders
*/
const PROMPTBOOK_TEMPORARY_DIRECTORY = '.promptbook';
/**
* Builds one normalized project-relative path inside Promptbook's dedicated temporary root.
*
* The returned path intentionally uses `/` separators so the same helper can be reused from
* Node.js and edge-safe code without depending on the Node `path` module.
*
* @private internal utility for Promptbook temporary folders
*/
function getPromptbookTemporaryPath(...pathSegments) {
const normalizedPathSegments = pathSegments.flatMap(splitPathSegments).filter(Boolean);
return [PROMPTBOOK_TEMPORARY_DIRECTORY, ...normalizedPathSegments].join('/');
}
/**
* Normalizes one raw path segment into slash-delimited pieces without empty items.
*/
function splitPathSegments(pathSegment) {
return pathSegment
.split(/[\\/]+/u)
.map((segment) => segment.trim())
.filter(Boolean);
}
// Note: [๐] Ignore a discrepancy between file name and entity name
/**
* Name for the Promptbook
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
const NAME = `Promptbook`;
/**
* Email of the responsible person
*
* @public exported from `@promptbook/core`
*/
const ADMIN_EMAIL = 'pavol@ptbk.io';
/**
* Name of the responsible person for the Promptbook on GitHub
*
* @public exported from `@promptbook/core`
*/
const ADMIN_GITHUB_NAME = 'hejny';
// <- TODO: [๐] Pick the best claim
/**
* Color of the Promptbook
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
const PROMPTBOOK_COLOR = Color.fromString('promptbook');
// <- TODO: [๐ง ][๐ต] Using `Color` here increases the package size approx 3kb, maybe remove it
/**
* Colors for syntax highlighting in the `<BookEditor/>`
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
({
TITLE: Color.fromHex('#244EA8'),
LINE: Color.fromHex('#eeeeee'),
SEPARATOR: Color.fromHex('#cccccc'),
COMMITMENT: Color.fromHex('#DA0F78'),
NOTE_COMMITMENT: Color.fromHex('#8080807e'),
TODO_COMMITMENT_TEXT: Color.fromHex('#000000'),
TODO_COMMITMENT_BACKGROUND: Color.fromHex('#FFEB3B'),
PARAMETER: Color.fromHex('#8e44ad'),
CODE_BLOCK: Color.fromHex('#7700ffff'),
});
// <- TODO: [๐ง ][๐ต] Using `Color` here increases the package size approx 3kb, maybe remove it
/**
* Chat color of the Promptbook (in chat)
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
PROMPTBOOK_COLOR.then(lighten(0.1)).then(saturate(0.9)).then(grayscale(0.9));
// <- TODO: [๐ง ][๐ต] Using `Color` and `lighten`, `saturate`,... here increases the package size approx 3kb, maybe remove it
/**
* Color of the user (in chat)
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
Color.fromHex('#1D4ED8');
// <- TODO: [๐ง ] Better system for generator warnings - not always "code" and "by `@promptbook/cli`"
/**
* The maximum number of iterations for a loops
*
* @private within the repository - too low-level in comparison with other `MAX_...`
*/
const LOOP_LIMIT = 1000;
/**
* Strings to represent various values in the context of parameter values
*
* @public exported from `@promptbook/utils`
*/
const VALUE_STRINGS = {
empty: '(nothing; empty string)',
null: '(no value; null)',
undefined: '(unknown value; undefined)',
nan: '(not a number; NaN)',
infinity: '(infinity; โ)',
negativeInfinity: '(negative infinity; -โ)',
unserializable: '(unserializable value)',
circular: '(circular JSON)',
};
/**
* Small number limit
*
* @public exported from `@promptbook/utils`
*/
const SMALL_NUMBER = 0.001;
// <- TODO: [๐] Make also `AGENTS_DIRNAME_ALTERNATIVES`
/**
* Where to store the temporary downloads
*
* Note: When the folder does not exist, it is created recursively
*
* @public exported from `@promptbook/core`
*/
`./${getPromptbookTemporaryPath('download-cache')}`;
/**
* Where to store the cache of executions for promptbook CLI
*
* Note: When the folder does not exist, it is created recursively
*
* @public exported from `@promptbook/core`
*/
`./${getPromptbookTemporaryPath('execution-cache')}`;
/**
* Where to store the scrape cache
*
* Note: When the folder does not exist, it is created recursively
*
* @public exported from `@promptbook/core`
*/
`./${getPromptbookTemporaryPath('scrape-cache')}`;
// <- TODO: [๐งโโ๏ธ]
/**
* Default settings for parsing and generating CSV files in Promptbook.
*
* @public exported from `@promptbook/core`
*/
Object.freeze({
delimiter: ',',
quoteChar: '"',
newline: '\n',
skipEmptyLines: true,
});
/**
* API request timeout in milliseconds
* Can be overridden via API_REQUEST_TIMEOUT environment variable
*
* @public exported from `@promptbook/core`
*/
parseInt(process.env.API_REQUEST_TIMEOUT || '90000');
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
* TODO: [๐ง ][๐งโโ๏ธ] Maybe join remoteServerUrl and path into single value
*/
/**
* Orders JSON object by keys
*
* @returns The same type of object as the input re-ordered
*
* @public exported from `@promptbook/utils`
*/
function orderJson(options) {
const { value, order } = options;
const orderedValue = {
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
...value,
};
return orderedValue;
}
/**
* Make error report URL for the given error
*
* @private private within the repository
*/
function getErrorReportUrl(error) {
const report = {
title: `๐ Error report from ${NAME}`,
body: spaceTrim$1((block) => `
\`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
\`\`\`
${block(error.message || '(no error message)')}
\`\`\`
## More info:
- **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
- **Book language version:** ${BOOK_LANGUAGE_VERSION}
- **Time:** ${new Date().toISOString()}
<details>
<summary>Stack trace:</summary>
## Stack trace:
\`\`\`stacktrace
${block(error.stack || '(empty)')}
\`\`\`
</details>
`),
};
const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
reportUrl.searchParams.set('labels', 'bug');
reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
reportUrl.searchParams.set('title', report.title);
reportUrl.searchParams.set('body', report.body);
return reportUrl;
}
/**
* This error type indicates that the error should not happen and its last check before crashing with some other error
*
* @public exported from `@promptbook/core`
*/
class UnexpectedError extends Error {
constructor(message) {
super(spaceTrim$1((block) => `
${block(message)}
Note: This error should not happen.
It's probably a bug in the pipeline collection
Please report issue:
${block(getErrorReportUrl(new Error(message)).href)}
Or contact us on ${ADMIN_EMAIL}
`));
this.name = 'UnexpectedError';
Object.setPrototypeOf(this, UnexpectedError.prototype);
}
}
/**
* This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
*
* @public exported from `@promptbook/core`
*/
class WrappedError extends Error {
constructor(whatWasThrown) {
const tag = `[๐คฎ]`;
console.error(tag, whatWasThrown);
super(spaceTrim$1(`
Non-Error object was thrown
Note: Look for ${tag} in the console for more details
Please report issue on ${ADMIN_EMAIL}
`));
this.name = 'WrappedError';
Object.setPrototypeOf(this, WrappedError.prototype);
}
}
/**
* Helper used in catch blocks to assert that the error is an instance of `Error`
*
* @param whatWasThrown Any object that was thrown
* @returns Nothing if the error is an instance of `Error`
* @throws `WrappedError` or `UnexpectedError` if the error is not standard
*
* @private within the repository
*/
function assertsError(whatWasThrown) {
// Case 1: Handle error which was rethrown as `WrappedError`
if (whatWasThrown instanceof WrappedError) {
const wrappedError = whatWasThrown;
throw wrappedError;
}
// Case 2: Handle unexpected errors
if (whatWasThrown instanceof UnexpectedError) {
const unexpectedError = whatWasThrown;
throw unexpectedError;
}
// Case 3: Handle standard errors - keep them up to consumer
if (whatWasThrown instanceof Error) {
return;
}
// Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
throw new WrappedError(whatWasThrown);
}
/**
* Checks if the value is [๐] serializable as JSON
* If not, throws an UnexpectedError with a rich error message and tracking
*
* - Almost all primitives are serializable BUT:
* - `undefined` is not serializable
* - `NaN` is not serializable
* - Objects and arrays are serializable if all their properties are serializable
* - Functions are not serializable
* - Circular references are not serializable
* - `Date` objects are not serializable
* - `Map` and `Set` objects are not serializable
* - `RegExp` objects are not serializable
* - `Error` objects are not serializable
* - `Symbol` objects are not serializable
* - And much more...
*
* @throws UnexpectedError if the value is not serializable as JSON
*
* @public exported from `@promptbook/utils`
*/
function checkSerializableAsJson(options) {
checkSerializableValue(options);
}
// TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
// TODO: [๐ง ][main] !!3 In-memory cache of same values to prevent multiple checks
// Note: [๐ ] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
/**
* Checks one value and dispatches to the appropriate specialized validator.
*
* @private function of `checkSerializableAsJson`
*/
function checkSerializableValue(options) {
const { value } = options;
if (isSerializablePrimitive(value)) {
return;
}
if (value === undefined) {
throw new UnexpectedError(`${options.name} is undefined`);
}
if (typeof value === 'symbol') {
throw new UnexpectedError(`${options.name} is symbol`);
}
if (typeof value === 'function') {
throw new UnexpectedError(`${options.name} is function`);
}
if (Array.isArray(value)) {
checkSerializableArray(options, value);
return;
}
if (value !== null && typeof value === 'object') {
checkSerializableObject(options, value);
return;
}
throwUnknownTypeError(options);
}
/**
* Checks the primitive values that are directly JSON serializable.
*
* @private function of `checkSerializableAsJson`
*/
function isSerializablePrimitive(value) {
return (value === null ||
typeof value === 'boolean' ||
(typeof value === 'number' && !isNaN(value)) ||
typeof value === 'string');
}
/**
* Recursively checks JSON array items.
*
* @private function of `checkSerializableAsJson`
*/
function checkSerializableArray(context, arrayValue) {
for (let index = 0; index < arrayValue.length; index++) {
checkSerializableAsJson({
...context,
name: `${context.name}[${index}]`,
value: arrayValue[index],
});
}
}
/**
* Checks object-like values and dispatches special unsupported built-ins.
*
* @private function of `checkSerializableAsJson`
*/
function checkSerializableObject(context, objectValue) {
checkUnsupportedObjectType(context, objectValue);
checkSerializableObjectEntries(context, objectValue);
assertJsonStringificationSucceeds(context, objectValue);
}
/**
* Rejects built-in objects that must be converted before JSON serialization.
*
* @private function of `checkSerializableAsJson`
*/
function checkUnsupportedObjectType(context, objectValue) {
if (objectValue instanceof Date) {
throw new UnexpectedError(spaceTrim$1((block) => `
\`${context.name}\` is Date
Use \`string_date_iso8601\` instead
Additional message for \`${context.name}\`:
${block(context.message || '(nothing)')}
`));
}
if (objectValue instanceof Map) {
throw new UnexpectedError(`${context.name} is Map`);
}
if (objectValue instanceof Set) {
throw new UnexpectedError(`${context.name} is Set`);
}
if (objectValue instanceof RegExp) {
throw new UnexpectedError(`${context.name} is RegExp`);
}
if (objectValue instanceof Error) {
throw new UnexpectedError(spaceTrim$1((block) => `
\`${context.name}\` is unserialized Error
Use function \`serializeError\`
Additional message for \`${context.name}\`:
${block(context.message || '(nothing)')}
`));
}
}
/**
* Recursively checks object properties while preserving omitted `undefined` keys.
*
* @private function of `checkSerializableAsJson`
*/
function checkSerializableObjectEntries(context, objectValue) {
for (const [subName, subValue] of Object.entries(objectValue)) {
if (subValue === undefined) {
// Note: undefined in object is serializable - it is just omitted
continue;
}
checkSerializableAsJson({
...context,
name: `${context.name}.${subName}`,
value: subValue,
});
}
}
/**
* Uses `JSON.stringify` as the final guard for cases like circular references.
*
* @private function of `checkSerializableAsJson`
*/
function assertJsonStringificationSucceeds(context, objectValue) {
try {
JSON.stringify(objectValue); // <- TODO: [0]
}
catch (error) {
assertsError(error);
throw new UnexpectedError(spaceTrim$1((block) => `
\`${context.name}\` is not serializable
${block(error.stack || error.message)}
Additional message for \`${context.name}\`:
${block(context.message || '(nothing)')}
`));
}
/*
TODO: [0] Is there some more elegant way to check circular references?
const seen = new Set();
const stack = [{ value }];
while (stack.length > 0) {
const { value } = stack.pop()!;
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
throw new UnexpectedError(`${name} has circular reference`);
}
seen.add(value);
if (Array.isArray(value)) {
stack.push(...value.map((value) => ({ value })));
} else {
stack.push(...Object.values(value).map((value) => ({ value })));
}
}
}
*/
}
/**
* Throws the fallback error for unsupported value types like `bigint` and `NaN`.
*
* @private function of `checkSerializableAsJson`
*/
function throwUnknownTypeError(context) {
throw new UnexpectedError(spaceTrim$1((block) => `
\`${context.name}\` is unknown type
Additional message for \`${context.name}\`:
${block(context.message || '(nothing)')}
`));
}
/**
* Creates a deep clone of the given object
*
* Note: [๐] This function is idempotent.
* Note: This method only works for objects that are fully serializable to JSON and do not contain functions, Dates, or special types.
*
* @param objectValue The object to clone.
* @returns A deep, writable clone of the input object.
*
* @public exported from `@promptbook/utils`
*/
function deepClone(objectValue) {
return JSON.parse(JSON.stringify(objectValue));
/*
TODO: [๐ง ] Is there a better implementation?
> const propertyNames = Object.getOwnPropertyNames(objectValue);
> for (const propertyName of propertyNames) {
> const value = (objectValue as chococake)[propertyName];
> if (value && typeof value === 'object') {
> deepClone(value);
> }
> }
> return Object.assign({}, objectValue);
*/
}
// TODO: [๐ง ] Is there a way how to meaningfully test this utility
/**
* Utility to export a JSON object from a function
*
* 1) Checks if the value is serializable as JSON
* 2) Makes a deep clone of the object
* 2) Orders the object properties
* 2) Deeply freezes the cloned object
*
* Note: This function does not mutates the given object
*
* @returns The same type of object as the input but read-only and re-ordered
*
* @public exported from `@promptbook/utils`
*/
function exportJson(options) {
const { name, value, order, message } = options;
checkSerializableAsJson({ name, value, message });
const orderedValue =
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
order === undefined
? deepClone(value)
: orderJson({
value: value,
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
order: order,
});
$deepFreeze(orderedValue);
return orderedValue;
}
// TODO: [๐ง ] Is there a way how to meaningfully test this utility
/**
* Nonce which is used for replacing things in strings
*
* @private within the repository
*/
const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
/**
* Placeholder value indicating a parameter is missing its value.
*
* @private within the repository
*/
const RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
/**
* Placeholder value indicating a parameter is restricted and cannot be used directly.
*
* @private within the repository
*/
const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
/**
* The names of the parameters that are reserved for special purposes
*
* @public exported from `@promptbook/core`
*/
exportJson({
name: 'RESERVED_PARAMETER_NAMES',
message: `The names of the parameters that are reserved for special purposes`,
value: [
'content',
'context',
'knowledge',
'examples',
'modelName',
'currentDate',
// <- TODO: list here all command names
// <- TODO: Add more like 'date', 'modelName',...
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
],
});
// Note: [๐] Ignore a discrepancy between file name and entity name
/**
* This error type indicates that some limit was reached
*
* @public exported from `@promptbook/core`
*/
class LimitReachedError extends Error {
constructor(message) {
super(message);
this.name = 'LimitReachedError';
Object.setPrototypeOf(this, LimitReachedError.prototype);
}
}
/**
* Format either small or big number
*
* @public exported from `@promptbook/utils`
*/
function numberToString(value) {
if (value === 0) {
return '0';
}
else if (Number.isNaN(value)) {
return VALUE_STRINGS.nan;
}
else if (value === Infinity) {
return VALUE_STRINGS.infinity;
}
else if (value === -Infinity) {
return VALUE_STRINGS.negativeInfinity;
}
for (let exponent = 0; exponent < 15; exponent++) {
const factor = 10 ** exponent;
const valueRounded = Math.round(value * factor) / factor;
if (Math.abs(value - valueRounded) / value < SMALL_NUMBER) {
return valueRounded.toFixed(exponent);
}
}
return value.toString();
}
/**
* Function `valueToString` will convert the given value to string
* This is useful and used in the `templateParameters` function
*
* Note: This function is not just calling `toString` method
* It's more complex and can handle this conversion specifically for LLM models
* See `VALUE_STRINGS`
*
* Note: There are 2 similar functions
* - `valueToString` converts value to string for LLM models as human-readable string
* - `asSerializable` converts value to string to preserve full information to be able to convert it back
*
* @public exported from `@promptbook/utils`
*/
function valueToString(value) {
try {
if (value === '') {
return VALUE_STRINGS.empty;
}
else if (value === null) {
return VALUE_STRINGS.null;
}
else if (value === undefined) {
return VALUE_STRINGS.undefined;
}
else if (typeof value === 'string') {
return value;
}
else if (typeof value === 'number') {
return numberToString(value);
}
else if (value instanceof Date) {
return value.toISOString();
}
else {
try {
return JSON.stringify(value);
}
catch (error) {
if (error instanceof TypeError && error.message.includes('circular structure')) {
return VALUE_STRINGS.circular;
}
throw error;
}
}
}
catch (error) {
assertsError(error);
console.error(error);
return VALUE_STRINGS.unserializable;
}
}
/**
* Replaces parameters in template with values from parameters object
*
* Note: This function is not places strings into string,
* It's more complex and can handle this operation specifically for LLM models
*
* @param template the template with parameters in {curly} braces
* @param parameters the object with parameters
* @returns the template with replaced parameters
* @throws {PipelineExecutionError} if parameter is not defined, not closed, or not opened
*
* @public exported from `@promptbook/utils`
*/
function templateParameters(template, parameters) {
for (const [parameterName, parameterValue] of Object.entries(parameters)) {
if (parameterValue === RESERVED_PARAMETER_MISSING_VALUE) {
throw new UnexpectedError(`Parameter \`{${parameterName}}\` has missing value`);
}
else if (parameterValue === RESERVED_PARAMETER_RESTRICTED) {
// TODO: [๐ต]
throw new UnexpectedError(`Parameter \`{${parameterName}}\` is restricted to use`);
}
}
let replacedTemplates = template;
let match;
let loopLimit = LOOP_LIMIT;
while ((match = /^(?<precol>.*){(?<parameterName>\w+)}(.*)/m /* <- Not global */
.exec(replacedTemplates))) {
if (loopLimit-- < 0) {
throw new LimitReachedError('Loop limit reached during parameters replacement in `templateParameters`');
}
const precol = match.groups.precol;
const parameterName = match.groups.parameterName;
if (parameterName === '') {
// Note: Skip empty placeholders. It's used to avoid confusion with JSON-like strings
continue;
}
if (parameterName.indexOf('{') !== -1 || parameterName.indexOf('}') !== -1) {
throw new PipelineExecutionError('Parameter is already opened or not closed');
}
if (parameters[parameterName] === undefined) {
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
}
let parameterValue = parameters[parameterName];
if (parameterValue === undefined) {
throw new PipelineExecutionError(`Parameter \`{${parameterName}}\` is not defined`);
}
parameterValue = valueToString(parameterValue);
// Escape curly braces in parameter values to prevent prompt-injection
parameterValue = parameterValue.replace(/[{}]/g, '\\$&');
if (parameterValue.includes('\n') && /^\s*\W{0,3}\s*$/.test(precol)) {
parameterValue = parameterValue
.split(/\r?\n/)
.map((line, index) => (index === 0 ? line : `${precol}${line}`))
.join('\n');
}
replacedTemplates =
replacedTemplates.substring(0, match.index + precol.length) +
parameterValue +
replacedTemplates.substring(match.index + precol.length + parameterName.length + 2);
}
// [๐ซ] Check if there are parameters that are not closed properly
if (/{\w+$/.test(replacedTemplates)) {
throw new PipelineExecutionError('Parameter is not closed');
}
// [๐ซ] Check if there are parameters that are not opened properly
if (/^\w+}/.test(replacedTemplates)) {
throw new PipelineExecutionError('Parameter is not opened');
}
return replacedTemplates;
}
/**
* Function `asSerializable` will convert values which are not serializable to serializable values
* It walks deeply through the object and converts all values
*
* For example:
* - `Date` objects will be converted to string
*
* Note: There are 2 similar functions
* - `valueToString` converts value to string for LLM models as human-readable string
* - `asSerializable` converts value to string to preserve full information to be able to convert it back
*
* @private Internal helper function
*/
function asSerializable(value) {
if (value instanceof Date) {
return value.toISOString();
}
else if (Array.isArray(value)) {
return value.map(asSerializable);
}
else if (value !== null && typeof value === 'object') {
const result = {};
for (const key in value) {
if (Object.prototype.hasOwnProperty.call(value, key)) {
result[key] = asSerializable(value[key]);
}
}
return result;
}
else {
return value;
}
}
/**
* Profile for Vercel AI adapter
*/
const VERCEL_PROVIDER_PROFILE = {
name: 'VERCEL',
fullname: 'Vercel AI',
color: '#000000',
};
/**
* Adapter which creates Promptbook execution tools from Vercel provider
*
* @public exported from `@promptbook/vercel`
*/
function createExecutionToolsFromVercelProvider(options) {
let { title, description } = options;
const { vercelProvider, availableModels, additionalChatSettings = {} } = options;
// <- Note: `userId` is not forwarded, because Vercel AI SDK moved provider-specific settings like `user` from
// `vercelProvider.chat(modelName, settings)` into `providerOptions` which are keyed by the provider
if (!/Vercel/i.test(title)) {
title = `${title} (through Vercel)`;
// <- TODO: [๐ง] Maybe standartize the suffix
} /* not else */
if (description === undefined) {
description = `Implementation of ${title} through Vercel`;
} /* not else */
if (!/Vercel/i.test(description)) {
description = `${description} (through Vercel)`;
// <- TODO: [๐ง] Maybe standartize the suffix
} /* not else */
return {
title,
description,
profile: VERCEL_PROVIDER_PROFILE,
checkConfiguration() {
// Note: There is no way how to check configuration of Vercel provider
return Promise.resolve();
},
async listModels() {
return availableModels;
},
async callChatModel(prompt) {
var _a;
const { content, parameters, modelRequirements } = prompt;
if (modelRequirements.modelVariant !== 'CHAT') {
throw new PipelineExecutionError('Use callChatModel only for CHAT variant');
}
const modelName = modelRequirements.modelName ||
((_a = availableModels.find(({ modelVariant }) => modelVariant === 'CHAT')) === null || _a === void 0 ? void 0 : _a.modelName);
if (!modelName) {
throw new PipelineExecutionError(spaceTrim$1(`
Can not determine which model to use.
You need to provide at least one of:
1) In \`createExecutionToolsFromVercelProvider\` options, provide \`availableModels\` with at least one model
2) In \`prompt.modelRequirements\`, provide \`modelName\` with the name of the model to use
`));
}
const model = await vercelProvider.chat(modelName);
const rawPromptContent = templateParameters(content, { ...parameters, modelName });
// Support for passing a chat thread (multi-message conversation)
let promptMessages;
if ('thread' in prompt && Array.isArray(prompt.thread)) {
promptMessages = prompt.thread.map((msg) => ({
role: msg.role === 'assistant' ? 'assistant' : msg.role === 'system' ? 'system' : 'user',
content: [
{
type: 'text',
text: msg.content,
},
],
}));
}
else {
promptMessages = [
...(modelRequirements.systemMessage === undefined
? []
: [
{
role: 'system',
content: modelRequirements.systemMessage,
},
]),
{
role: 'user',
content: [
{
type: 'text',
text: rawPromptContent,
},
],
},
];
}
const rawRequest = {
// <- TODO: [โ]
prompt: promptMessages,
// <- TODO: Pass the tools via `tools`
...additionalChatSettings,
};
const start = $getCurrentDate();
if (options.isVerbose) {
console.info(colors.bgWhite('rawRequest'), JSON.stringify(rawRequest, null, 4));
}
const rawResponse = await (async () => await model.doGenerate(rawRequest))().catch((error) => {
// <- Note: This weird structure is here to catch errors in both sync and async `doGenerate`
if (options.isVerbose) {
console.info(colors.bgRed('error'), error);
}
throw error;
});
await model.doGenerate(rawRequest);
if (options.isVerbose) {
console.info(colors.bgWhite('rawResponse'), JSON.stringify(rawResponse, null, 4));
}
// Note: Vercel AI SDK returns ordered content parts, the chat message is made only of the text parts
const responseTextParts = rawResponse.content.flatMap((contentPart) => contentPart.type === 'text' ? [contentPart.text] : []);
if (responseTextParts.length === 0) {
throw new PipelineExecutionError('No response message');
}
const responseText = responseTextParts.join('');
const complete = $getCurrentDate();
const duration = uncertainNumber((new Date(complete).getTime() - new Date(start).getTime()) / 1000);
const usage = {
price: UNCERTAIN_ZERO_VALUE,
duration,
input: {
tokensCount: uncertainNumber(rawResponse.usage.inputTokens),
...computeUsageCounts(rawPromptContent),
},
output: {
tokensCount: uncertainNumber(rawResponse.usage.outputTokens),
...computeUsageCounts(responseText),
},
};
return exportJson({
name: 'promptResult',
message: `Result of \`createExecutionToolsFromVercelProvider.callChatModel\``,
value: {
content: responseText,
modelName,
timing: {
start,
complete,
},
usage,
rawPromptContent,
rawRequest,
rawResponse: asSerializable(rawResponse),
// <- [๐ฏ]
},
});
},
};
}
/**
* Create price per one token based on the string value found on openai page
*
* @private within the repository, used only as internal helper for `OPENAI_MODELS`
*/
function pricing(value) {
const [price, tokens] = value.split(' / ');
return parseFloat(price.replace('$', '')) / parseFloat(tokens.replace('M tokens', '')) / 1000000;
}
/**
* List of available Google models with descriptions
*
* Note: Synced with official API docs at 2026-03-22
*
* @see https://ai.google.dev/models/gemini
*
* @public exported from `@promptbook/google`
*/
const GOOGLE_MODELS = exportJson({
name: 'GOOGLE_MODELS',
value: [
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 3 Pro',
modelName: 'gemini-3-pro-preview',
modelDescription: 'The best model in the world for multimodal understanding, and our most powerful agentic and vibe-coding model yet, delivering richer visuals and deeper interactivity, all built on a foundation of state-of-the-art reasoning.',
// TODO: [๐ง ] Add pricing information for Gemini 3 Pro
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 2.5 Pro',
modelName: 'gemini-2.5-pro',
modelDescription: 'State-of-the-art thinking model with 1M token context window capable of reasoning over complex problems in code, math, and STEM. Features enhanced thinking capabilities, advanced multimodal understanding, and superior performance on analytical tasks. Ideal for complex enterprise applications requiring maximum intelligence and reasoning.',
pricing: {
prompt: pricing(`$1.25 / 1M tokens`),
output: pricing(`$10.00 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 2.5 Flash',
modelName: 'gemini-2.5-flash',
modelDescription: 'Best model in terms of price-performance with 1M token context window offering well-rounded capabilities. Features adaptive thinking, cost efficiency, and enhanced reasoning for large-scale processing. Ideal for low-latency, high-volume tasks that require thinking and agentic use cases.',
pricing: {
prompt: pricing(`$0.30 / 1M tokens`),
output: pricing(`$2.50 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 2.5 Flash Lite',
modelName: 'gemini-2.5-flash-lite',
modelDescription: 'Cost-efficient Gemini 2.5 Flash model optimized for high throughput with 1M token context window. Features thinking capabilities while maintaining the most cost-efficient pricing. Perfect for real-time, low-latency use cases requiring good quality at scale.',
pricing: {
prompt: pricing(`$0.10 / 1M tokens`),
output: pricing(`$0.40 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 2.0 Flash',
modelName: 'gemini-2.0-flash',
modelDescription: 'Next-generation model with 1M token context window delivering improved capabilities, superior speed, and realtime streaming. Features enhanced function calling, code execution, and search capabilities. Ideal for applications requiring cutting-edge AI capabilities with fast response times.',
pricing: {
prompt: pricing(`$0.25 / 1M tokens`),
output: pricing(`$0.75 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 2.0 Flash Lite',
modelName: 'gemini-2.0-flash-lite',
modelDescription: 'Cost-efficient Gemini 2.0 Flash model optimized for low latency with 1M token context window. Balances performance and cost with enhanced efficiency for high-volume applications. Perfect for applications requiring good quality responses at minimal cost.',
pricing: {
prompt: pricing(`$0.15 / 1M tokens`),
output: pricing(`$0.45 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 2.0 Flash Thinking',
modelName: 'gemini-2.0-flash-thinking-exp-01-21',
modelDescription: 'Experimental model with 128K context window focused on enhanced reasoning with explicit chain-of-thought processes. Specializes in structured problem-solving approaches for mathematics, logic, and analytical tasks. Provides visible intermediate reasoning steps for better transparency and verification. Ideal for educational applications, analytical systems, and step-by-step problem solving.',
pricing: {
prompt: pricing(`$0.35 / 1M tokens`),
output: pricing(`$1.05 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash',
modelName: 'gemini-1.5-flash',
modelDescription: 'Efficient model with 1M token context window balancing speed and quality. Features 2x faster inference than Pro models while maintaining good multimodal capabilities across text, code, and images. Supports complex document analysis with full context utilization. Well-suited for applications requiring good performance at scale with cost efficiency.',
pricing: {
prompt: pricing(`$0.25 / 1M tokens`),
output: pricing(`$0.75 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash Latest',
modelName: 'gemini-1.5-flash-latest',
modelDescription: 'Dynamic pointer to the most recent version of Gemini 1.5 Flash with 1M token context window. Automatically incorporates latest improvements, bug fixes, and performance enhancements while maintaining API compatibility. Recommended for production applications that want to stay current with model improvements without manual updates.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash 001',
modelName: 'gemini-1.5-flash-001',
modelDescription: 'First stable release of Gemini 1.5 Flash with 1M token context window. Features consistent performance characteristics for production applications with reliable behavior across text, code, and basic image understanding tasks. Good option for applications requiring version stability and predictable performance.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash 002',
modelName: 'gemini-1.5-flash-002',
modelDescription: 'Improved version of Gemini 1.5 Flash with 1M token context window featuring enhanced instruction following and more consistent outputs. Includes 15% better performance on reasoning benchmarks and reduced hallucination rates compared to 001 version. Offers better application integration while maintaining efficiency.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash Exp',
modelName: 'gemini-1.5-flash-exp-0827',
modelDescription: 'Experimental version of Gemini 1.5 Flash with 1M token context window featuring new capabilities being tested. Includes improvements to context utilization, memory efficiency, and prompt adherence. May offer enhanced performance but with potential behavior differences from stable releases. Suitable for testing and non-critical applications.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash 8B',
modelName: 'gemini-1.5-flash-8b',
modelDescription: 'Compact 8B parameter model with 128K context window optimized for deployment in resource-constrained environments. Features remarkably strong performance despite smaller size, with specialized distillation techniques to maintain quality. Ideal for mobile applications, edge devices, and scenarios with limited computational resources.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash 8B Latest',
modelName: 'gemini-1.5-flash-8b-latest',
modelDescription: 'Points to the most recent version of the compact 8B parameter model with 128K context window. Automatically incorporates latest optimizations and improvements while maintaining small resource footprint. Best for applications that need the most current compact model without manual version management.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash 8B Exp',
modelName: 'gemini-1.5-flash-8b-exp-0924',
modelDescription: 'Experimental version of the 8B parameter model with 128K context window featuring new compression techniques and architectural optimizations. Includes testing of enhanced reasoning capabilities while maintaining efficiency. Suitable for evaluating cutting-edge compact model performance in non-critical applications.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Flash 8B Exp',
modelName: 'gemini-1.5-flash-8b-exp-0827',
modelDescription: 'August experimental release of the efficient 8B parameter model with 128K context window. Features specific improvements to reasoning capabilities with 20% better performance on logical tasks compared to stable release. Maintains small footprint while pushing the boundaries of compact model capabilities.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Pro Latest',
modelName: 'gemini-1.5-pro-latest',
modelDescription: 'Points to the most recent version of the flagship Gemini 1.5 Pro model with 1M token context window. Automatically incorporates latest capabilities, knowledge updates, and performance improvements. Recommended for production applications requiring the most current full-featured model capabilities.',
pricing: {
prompt: pricing(`$7.00 / 1M tokens`),
output: pricing(`$21.00 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Pro',
modelName: 'gemini-1.5-pro',
modelDescription: 'Flagship multimodal model with 1M token context window featuring exceptional performance across text, code, vision, and audio tasks. Supports analysis of long documents, videos, and complex multimodal inputs with contextual understanding. Features strong reasoning, code generation, and multilingual capabilities. Ideal for enterprise applications requiring deep comprehension.',
pricing: {
prompt: pricing(`$6.00 / 1M tokens`),
output: pricing(`$18.00 / 1M tokens`),
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Pro 001',
modelName: 'gemini-1.5-pro-001',
modelDescription: 'First stable release of Gemini 1.5 Pro with 1M token context window featuring consistent performance characteristics and reliable behavior. Provides strong foundation for production applications with predictable outputs and stable API integration. Good choice for applications requiring version stability.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Pro 002',
modelName: 'gemini-1.5-pro-002',
modelDescription: 'Refined version of Gemini 1.5 Pro with 1M token context window featuring improved instruction following, enhanced multimodal understanding, and more consistent outputs. Includes 20% reduction in hallucination rates and better performance on complex reasoning tasks compared to 001 version.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.5 Pro Exp',
modelName: 'gemini-1.5-pro-exp-0827',
modelDescription: 'Experimental version of Gemini 1.5 Pro with 1M token context window featuring new capabilities being tested before wider release. Includes enhanced reasoning, improved code generation, and better context utilization. May offer improved performance but with potential behavior differences from stable releases.',
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini 1.0 Pro',
modelName: 'gemini-1.0-pro',
modelDescription: 'Original Gemini series foundation model with 32K context window featuring solid multimodal capabilities. Provides good performance on text, code, and basic vision tasks with reliable instruction following. Legacy model maintained for backward compatibility with established applications and workflows.',
pricing: {
prompt: pricing(`$0.35 / 1M tokens`),
output: pricing(`$1.05 / 1M tokens`),
},
},
{
modelVariant: 'EMBEDDING',
modelTitle: 'Gemini Embedder',
modelName: 'gemini-embedder-v1',
modelDescription: 'High-quality embedding model generating 768-dimensional vector representations optimized for semantic search and retrieval. Features strong cross-lingual capabilities, robust document clustering, and excellent performance on both similarity and retrieval tasks. Handles contexts up to 4K tokens with efficient processing. Ideal for RAG systems and semantic applications.',
pricing: {
prompt: pricing(`$0.08 / 1M tokens`),
output: 0,
},
},
{
modelVariant: 'CHAT',
modelTitle: 'Gemini Pro Vision',
modelName: 'gemini-pro-vision-v1',
modelDescription: 'Advanced multimodal model with 32K context window specializing in combined vision and language understanding. Processes high-resolution images (up to 20MP) with detailed visual analysis capabilities including object recognition, scene understanding, and text extraction from images. Particularly effective for document analysis, visual question answering, and multimodal content creation.',
pricing: {
prompt: pricing(`$9.00 / 1M tokens`),
output: pricing(`$27.00 / 1M tokens`),
},
},
{
modelVariant: 'IMAGE_GENERATION',
modelName: 'gemini-2.5-flash-image',
modelTitle: 'Gemini 2.5 Flash Image (Nano Banana)',
modelDescription: 'This model is designed for speed and efficiency, optimized for high-volume, low-latency tasks. All generated images include a SynthID watermark.',
documentationUrl: 'https://ai.google.dev/gemini-api/docs/image-generation',
},
{
modelVariant: 'IMAGE_GENERATION',
modelName: 'gemini-3-pro-image-preview',
modelTitle: 'Gemini 3 Pro Image Preview (Nano Banana Pro)',
modelDescription: 'This model is designed for professional asset production, utilizing advanced reasoning ("Thinking") to follow complex instructions and render high-fidelity text. All generated images include a SynthID watermark.',
documentationUrl: 'https://ai.google.dev/gemini-api/docs/image-generation',
},
],
});
// TODO: [๐ง ] Add information about context window sizes, capabilities, and relative performance characteristics
// TODO: [๐ฐ] Some mechanism to auto-update available models
// TODO: [๐ง ] Verify pricing information is current with Google's official documentation
// Note: [๐] Ignore a discrepancy between file name and entity name
/**
* Profile for Google Gemini provider
*/
const GOOGLE_PROVIDER_PROFILE = {
name: 'GOOGLE',
fullname: 'Google Gemini',
color: '#4285f4',
};
/**
* Constant for default google generative AI base URL.
*/
const DEFAULT_GOOGLE_GENERATIVE_AI_BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
/**
* Map of google image response modalities.
*/
const GOOGLE_IMAGE_RESPONSE_MODALITIES = {
responseModalities: ['TEXT', 'IMAGE'],
};
/**
* Resolve the Google API key from options or environment.
*/
function resolveGoogleApiKey(options) {
const apiKey = options.apiKey || process.env.GOOGLE_GENERATIVE_AI_API_KEY;
if (!apiKey) {
throw new PipelineExecutionError('Missing Google Generative AI API key. Set GOOGLE_GENERATIVE_AI_API_KEY or pass apiKey.');
}
return apiKey;
}
/**
* Normalize the Google Generative AI base URL.
*/
function normalizeGoogleBaseUrl(baseUrl) {
return (baseUrl || DEFAULT_GOOGLE_GENERATIVE_AI_BASE_URL).replace(/\/+$/u, '');
}
/**
* Extract inline image data from a Google image generation response.
*/
function extractGoogleImageData(rawResponse) {
var _a, _b, _c;
const parts = ((_c = (_b = (_a = rawResponse.candidates) === null || _a === void 0 ? void 0 : _a[0]) === null || _b === void 0 ? void 0 : _b.content) === null || _c === void 0 ? void 0 : _c.parts) || [];
const imagePart = parts.find((part) => { var _a, _b; return ((_a = part.inlineData) === null || _a === void 0 ? void 0 : _a.data) || ((_b = part.inline_data) === null || _b === void 0 ? void 0 : _b.data); });
const inlineData = (imagePart === null || imagePart === void 0 ? void 0 : imagePart.inlineData) || (imagePart === null || imagePart === void 0 ? void 0 : imagePart.inline_data);
if (!(inlineData === null || inlineData === void 0 ? void 0 : inlineData.data)) {
throw new PipelineExecutionError('No image data returned by Google image generation model');
}
return {
base64: inlineData.data,
contentType: inlineData.mimeType || 'image/png',
};
}
/**
* Execution Tools for calling Google Gemini API.
*
* @public exported from `@promptbook/google`
*/
const createGoogleExecutionTools = Object.assign((options) => {
if ($isRunningInJest()) {
// Note: [๐]
throw new Error('GoogleExecutionTools are not supported in Jest environment');
}
// Note: [๐] There is a compatibility when using import from '@ai-sdk/google'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { createGoogleGenerativeAI } = require('@ai-sdk/google');
const googleGeminiVercelProvider = createGoogleGenerativeAI({
...options,
/// apiKey: process.env.GOOGLE_GENERATIVE_AI_API_KEY,
});
const baseTools = createExecutionToolsFromVercelProvider({
title: 'Google',
description: 'Implementation of Google models',
vercelProvider: googleGeminiVercelProvider,
availableModels: GOOGLE_MODELS,
...options,
});
return {
...baseTools,
profile: GOOGLE_PROVIDER_PROFILE,
async callImageGenerationModel(prompt) {
const { content, parameters, modelRequirements } = prompt;
// TODO: [โ] Use here more modelRequirements
if (modelRequirements.modelVariant !== 'IMAGE_GENERATION') {
throw new PipelineExecutionError('Use callImageGenerationModel only for IMAGE_GENERATION variant');
}
const modelName = modelRequirements.modelName || 'gemini-3-pro-image-preview';
const rawPromptContent = templateParameters(content, { ...parameters, modelName });
const start = $getCurrentDate();
const apiKey = resolveGoogleApiKey(options);
const baseUrl = normalizeGoogleBaseUrl(options.baseURL);
const fetchFunction = options.fetch || fetch;
const rawRequest = {
contents: [
{
role: 'user',
parts: [
{
text: rawPromptContent,
},
],
},
],
generationConfig: GOOGLE_IMAGE_RESPONSE_MODALITIES,
};
const rawResponseText = await fetchFunction(`${baseUrl}/models/${modelName}:generateContent`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-goog-api-key': apiKey,
...options.headers,
},
body: JSON.stringify(rawRequest),
})
.then(async (response) => {
const responseText = await response.text();
if (!response.ok) {
throw new PipelineExecutionError(`Google image generation failed (${response.status} ${response.statusText}): ${responseText || 'No response body'}`);
}
return responseText;
})
.catch((error) => {
if (error instanceof PipelineExecutionError) {
throw error;
}
const message = error instanceof Error ? error.message : 'Unknown error';
throw new PipelineExecutionError(`Google image generation request failed: ${message}`);
});
let rawResponse;
try {
rawResponse = JSON.parse(rawResponseText);
}
catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
throw new PipelineExecutionError(`Failed to parse Google image response: ${message}`);
}
const image = extractGoogleImageData(rawResponse);
const complete = $getCurrentDate();
const duration = uncertainNumber((new Date(complete).getTime() - new Date(start).getTime()) / 1000);
const usageMetadata = rawResponse.usageMetadata;
return exportJson({
name: 'promptResult',
message: `Result of \`GoogleExecutionTools.callImageGenerationModel\``,
order: [],
value: {
content: `data:${image.contentType};base64,${image.base64}`,
modelName,
timing: {
start,
complete,
},
usage: {
price: uncertainNumber(),
duration,
input: {
tokensCount: uncertainNumber(usageMetadata === null || usageMetadata === void 0 ? void 0 : usageMetadata.promptTokenCount),
...computeUsageCounts(rawPromptContent),
},
output: {
tokensCount: uncertainNumber(usageMetadata === null || usageMetadata === void 0 ? void 0 : usageMetadata.candidatesTokenCount),
...computeUsageCounts(''),
},
},
rawPromptContent,
rawRequest: {
prompt: rawPromptContent,
googleRequest: rawRequest,
},
rawResponse: {
image,
googleResponse: rawResponse,
},
},
});
},
};
}, {
packageName: '@promptbook/google',
className: 'GoogleExecutionTools',
}); /* <- Note: [๐ค] */
// TODO: !!!!! Rename to `createGoogleLlmExecutionTools`, `...GoogleLlmExecutionTools`
// TODO: [๐ถ] Naming "constructor" vs "creator" vs "factory"
/**
* This error type indicates that some part of the code is not implemented yet
*
* @public exported from `@promptbook/core`
*/
class NotYetImplementedError extends Error {
constructor(message) {
super(spaceTrim$1((block) => `
${block(message)}
Note: This feature is not implemented yet but it will be soon.
If you want speed up the implementation or just read more, look here:
https://github.com/webgptorg/promptbook
Or contact us on pavol@ptbk.io
`));
this.name = 'NotYetImplementedError';
Object.setPrototypeOf(this, NotYetImplementedError.prototype);
}
}
/**
* Safely retrieves the global scope object (window in browser, global in Node.js)
* regardless of the JavaScript environment in which the code is running
*
* Note: `$` is used to indicate that this function is not a pure function - it access global scope
*
* @private internal function of `$Register`
*/
function $getGlobalScope() {
return Function('return this')();
}
/**
* Normalizes a text string to SCREAMING_CASE (all uppercase with underscores).
*
* Note: [๐] This function is idempotent.
*
* @param text The text string to be converted to SCREAMING_CASE format.
* @returns The normalized text in SCREAMING_CASE format.
* @example 'HELLO_WORLD'
* @example 'I_LOVE_PROMPTBOOK'
*
* @public exported from `@promptbook/utils`
*/
function normalizeTo_SCREAMING_CASE(text) {
let charType;
let lastCharType = 'OTHER';
let normalizedName = '';
for (const char of text) {
let normalizedChar;
if (/^[a-z]$/.test(char)) {
charType = 'LOWERCASE';
normalizedChar = char.toUpperCase();
}
else if (/^[A-Z]$/.test(char)) {
charType = 'UPPERCASE';
normalizedChar = char;
}
else if (/^[0-9]$/.test(char)) {
charType = 'NUMBER';
normalizedChar = char;
}
else {
charType = 'OTHER';
normalizedChar = '_';
}
if (charType !== lastCharType &&
!(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
!(lastCharType === 'NUMBER') &&
!(charType === 'NUMBER')) {
normalizedName += '_';
}
normalizedName += normalizedChar;
lastCharType = charType;
}
normalizedName = normalizedName.replace(/_+/g, '_');
normalizedName = normalizedName.replace(/_?\/_?/g, '/');
normalizedName = normalizedName.replace(/^_/, '');
normalizedName = normalizedName.replace(/_$/, '');
return normalizedName;
}
/**
* TODO: Tests
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'Moje tabule' })).toEqual('/VtG7sR9rRJqwNEdM2/Moje tabule');
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: 'ฤลกฤลลพลพรฝรกรญรบลฏ' })).toEqual('/VtG7sR9rRJqwNEdM2/escrzyaieuu');
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj');
* > expect(encodeRoutePath({ uriId: 'VtG7sR9rRJqwNEdM2', name: ' ahoj_ahojAhoj ahoj ' })).toEqual('/VtG7sR9rRJqwNEdM2/ahoj-ahoj-ahoj-ahoj');
* TODO: [๐บ] Use some intermediate util splitWords
*/
/**
* Normalizes a text string to snake_case format.
*
* Note: [๐] This function is idempotent.
*
* @param text The text string to be converted to snake_case format.
* @returns The normalized text in snake_case format.
* @example 'hello_world'
* @example 'i_love_promptbook'
*
* @public exported from `@promptbook/utils`
*/
function normalizeTo_snake_case(text) {
return normalizeTo_SCREAMING_CASE(text).toLowerCase();
}
/**
* Global registry for storing and managing registered entities of a given type.
*
* Note: `$` is used to indicate that this function is not a pure function - it accesses and adds variables in global scope.
*
* @private internal utility, exported are only singleton instances of this class
*/
class $Register {
constructor(registerName) {
this.registerName = registerName;
const storageName = `_promptbook_${normalizeTo_snake_case(registerName)}`;
const globalScope = $getGlobalScope();
if (globalScope[storageName] === undefined) {
globalScope[storageName] = [];
}
else if (!Array.isArray(globalScope[storageName])) {
throw new UnexpectedError(`Expected (global) ${storageName} to be an array, but got ${typeof globalScope[storageName]}`);
}
this.storage = globalScope[storageName];
}
list() {
// <- TODO: ReadonlyDeep<ReadonlyArray<TRegistered>>
return this.storage;
}
register(registered) {
const { packageName, className } = registered;
const existingRegistrationIndex = this.storage.findIndex((item) => item.packageName === packageName && item.className === className);
const existingRegistration = this.storage[existingRegistrationIndex];
if (!existingRegistration) {
this.storage.push(registered);
}
else {
this.storage[existingRegistrationIndex] = registered;
}
return {
registerName: this.registerName,
packageName,
className,
get isDestroyed() {
return false;
},
destroy() {
throw new NotYetImplementedError(`Registration to ${this.registerName} is permanent in this version of Promptbook`);
},
};
}
}
/**
* Register for LLM tools.
*
* Note: `$` is used to indicate that this interacts with the global scope
*
* @singleton Only one instance of each register is created per build, but there can be more instances across different builds or environments.
*
* @public exported from `@promptbook/core`
*/
const $llmToolsRegister = new $Register('llm_execution_tools_constructors');
// TODO: [ยฎ] DRY Register logic
/**
* Registration of LLM provider
*
* Warning: This is not useful for the end user, it is just a side effect of the mechanism that handles all available LLM tools
*
* @public exported from `@promptbook/google`
* @public exported from `@promptbook/wizard`
* @public exported from `@promptbook/cli`
*/
const _GoogleRegistration = $llmToolsRegister.register(createGoogleExecutionTools);
// TODO: [๐ถ] Naming "constructor" vs "creator" vs "factory"
// Note: [๐] Ignore a discrepancy between file name and entity name
export { BOOK_LANGUAGE_VERSION, GOOGLE_MODELS, PROMPTBOOK_ENGINE_VERSION, _GoogleRegistration, createGoogleExecutionTools };
//# sourceMappingURL=index.es.js.map