UNPKG

retort-js

Version:

Intuitive, production-ready prompt chaining in Javascript

237 lines (236 loc) 9.03 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.unescapeSegment = exports.unescape = exports.isTemplateStringsArray = exports.templateContent = exports.RetortMessage = void 0; const create_stream_cloner_1 = __importDefault(require("./create-stream-cloner")); const id_1 = require("./id"); class RetortMessage { get content() { if (this._data === null) { throw new Error("Message not yet resolved; To fix this, you can await message.promise"); } return this._data.content; } get result() { if (this.json) { return JSON.parse(this.content); } else { return this.content; } } static createId() { return (0, id_1.id)("msg"); } static async *createStreamFromPromise(promise) { yield { content: (await promise).content, contentDelta: (await promise).content }; } constructor(options) { this._data = null; this.json = options.json ?? false; this.id = options.id || RetortMessage.createId(); this.role = options.role; if ("content" in options) { this._data = { content: options.content }; this.promise = Promise.resolve(this); this.promise.getStream = async function* () { yield { content: options.content, contentDelta: options.content }; return; }; } else if ("stream" in options) { let content = ""; let getStream = (0, create_stream_cloner_1.default)(options.stream); this.promise = (async () => { for await (const chunk of getStream()) { content += chunk.contentDelta; } this._data = { content }; return this; })(); this.promise.getStream = getStream; } else if ("promise" in options) { this.promise = options.promise.then((content) => { this._data = { content }; return this; }); let promise = this.promise; this.promise.getStream = async function* () { yield { content: (await promise).content, contentDelta: (await promise).content }; return; }; } else { throw new Error("Invalid options passed to RetortMessage constructor; must include either 'content' or 'stream'"); } this.promise.id = this.id; this.promise.role = this.role; this.promise.message = this; let stream = this.promise.getStream(); this.promise.stream = (async function* () { for await (let chunk of stream) { yield chunk.content; } })(); let self = this; (async () => { for await (let chunk of self.promise.getStream()) { if (chunk.promptTokens) { self.promptTokens = chunk.promptTokens; } if (chunk.completionTokens) { self.completionTokens = chunk.completionTokens; } if (chunk.totalTokens) { self.totalTokens = chunk.totalTokens; } } })(); } toString() { return this.content; } toJSON() { return { id: this.id, role: this.role, content: this.content, }; } } exports.RetortMessage = RetortMessage; function templateContent(templateStrings, ...values) { // Get the strings in raw form. let strings = templateStrings.raw.map((x) => x); // Remove carriage returns from strings. strings = strings.map((str) => str.replace(/\r/g, "")); // Remove any whitespace at the start of a line in each of the strings - except that which is explicitly specified. strings = strings.map((str) => str.replace(/\n[^\S\r\n]+/g, "\n")); // Remove any whitespace at the end of a line in each of the strings - except that which is explicitly specified. strings = strings.map((str) => str.replace(/[^\S\r\n]+\n/g, "\n")); // Allow line continuations. TODO: Allow line continuations with an even number of preceding backslashes. strings = strings.map((str) => str.replace(/(?<!\\)((?:\\\\)*)\\\n/g, "$1")); // Remove leading whitespace from the first string, if any. strings[0] = (strings[0] || "").trimStart(); // Remove trailing whitespace from the last string, if any. strings[strings.length - 1] = (strings[strings.length - 1] || "").trimEnd(); // Now, finally, encode the strings. strings = strings.map((str) => unescape(str)); let content = strings[0] || ""; for (let i = 1, l = strings.length; i < l; i++) { let currentValue = values[i - 1]; let insertion = retortValueToString(currentValue); content += insertion; content += strings[i] || ""; } return content; } exports.templateContent = templateContent; function retortValueToString(currentValue) { let insertion = ""; if (currentValue === null) { insertion = ""; } else if (typeof currentValue === "number") { insertion = currentValue.toString(); } else if (typeof currentValue === "string") { insertion = currentValue; } else if (typeof currentValue === "boolean") { insertion = currentValue.toString(); } else if (typeof currentValue === "object") { if (currentValue.toString === {}.toString) { // Check if the object is thenable if (currentValue.then && typeof currentValue.then === "function") { throw new Error("Promise passed to retort template. Use 'await' on the promise."); } else { throw new Error("Plain object passed to retort template. If you want to see the object, you should use JSON.stringify."); } } insertion = currentValue.toString(); } else if (currentValue === undefined) { throw new Error("Undefined passed to retort template"); } else if (typeof currentValue === "function") { throw new Error("Function passed to retort template"); } else if (typeof currentValue === "symbol") { throw new Error("Symbol passed to retort template"); } else if (typeof currentValue === "bigint") { throw new Error("BigInt not yet supported"); } else { throw new Error("Unsupported value inserted into template"); } return insertion; } function isTemplateStringsArray(templateStrings) { return (Array.isArray(templateStrings) && Array.isArray(templateStrings.raw)); } exports.isTemplateStringsArray = isTemplateStringsArray; function unescape(str) { let segments = str .split(/(\\x[a-fA-F0-9]{2}|\\u[a-fA-F0-9]{4}|\\u\{[a-fA-F0-9]{1,6}\}|\\.)/g) .filter((x) => x); return segments.map(unescapeSegment).join(""); } exports.unescape = unescape; function unescapeSegment(str) { if (str.startsWith("\\")) { if (str[1] === "x") { // Use a regex to check that this is a valid escape sequence if (!/\\x[a-fA-F0-9][a-fA-F0-9]/.test(str)) { throw new Error(`Malformed Latin-1 escape sequence; should be like "\\x00"`); } return String.fromCharCode(parseInt(str.slice(2), 16)); } else if (str[1] === "u") { if (str[2] === "{") { // Use a regex to check that this is a valid escape sequence if (!/\\u\{[a-fA-F0-9]{1,6}\}/.test(str)) { throw new Error(`Malformed unicode escape sequence; should be like "\\u{000000} or "\\u0000`); } return String.fromCodePoint(parseInt(str.slice(3, -1), 16)); } else { // Use a regex to check that this is a valid escape sequence if (!/\\u[a-fA-F0-9]{4}/.test(str)) { throw new Error(`Malformed unicode escape sequence; should be like "\\u{000000} or "\\u0000`); } return String.fromCharCode(parseInt(str.slice(2, 6), 16)); } } else if (str[1] === "n") { return "\n"; } else if (str[1] === "r") { return "\r"; } else if (str[1] === "t") { return "\t"; } else if (str[1] === "\\") { return "\\"; } else if (str[1] === "s") { // Special space escaping behaviour. return " "; } else { throw new Error(`Unsupported escape sequence: "${str}"`); } } else { return str; } } exports.unescapeSegment = unescapeSegment;