@butlerbot/sdk
Version:
The official ButlerBot SDK
273 lines (272 loc) • 11 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Conversation = void 0;
const eventsource_1 = require("eventsource");
const config_1 = require("../config");
const url_formatter_1 = require("../util/url_formatter");
const DEFAULT_CONVO_API_V = "v4";
class Conversation {
constructor(config) {
this.events = new Map();
this.convoId = config.convoId;
const serverUrl = config.serverUrl || config_1.CONFIG.server;
this.endpoints = {
conversation: serverUrl + (config.convoPath || config.path || config_1.CONFIG.paths.conversation[config.chatApiV || DEFAULT_CONVO_API_V].base),
history: serverUrl + (config.historyPath || config_1.CONFIG.paths.history.chat.v1.base),
progressStream: serverUrl + (config.progressStreamPath || config_1.CONFIG.paths.progress.v4.stream),
progress: serverUrl + (config.progressPath || config_1.CONFIG.paths.progress.v4.base),
};
this.apiKey = config.apiKey;
this.debug = config.debug || false;
}
// SETTERS
/**
* Sets the endpoint where the request is sent to, appended to server URL
* @deprecated Use setConversationEndpoint() instead
*/
setEndpoint(endpoint) {
this.endpoints.conversation = endpoint;
return this;
}
/** Sets the conversation endpoint where the request is sent to, appended to server URL */
setConversationEndpoint(conversationEndpoint) {
this.endpoints.conversation = conversationEndpoint;
return this;
}
/** Sets the history endpoint where the quest is sent to, appended to server URL */
setHistoryEndpoint(historyEndpoint) {
this.endpoints.history = historyEndpoint;
return this;
}
/** Sets the progress stream endpoint where the request is sent to, appended to server URL */
setProgressStreamEndpoint(progressStreamEndpoint) {
this.endpoints.progressStream = progressStreamEndpoint;
return this;
}
/** Sets the progress endpoint where the request is sent to, appended to server URL */
setProgressEndpoint(progressEndpoint) {
this.endpoints.progress = progressEndpoint;
return this;
}
/** Sets the conversation ID, has to be an existing conversation ID, undefined otherwise */
setConvoId(convoId) {
this.convoId = convoId;
return this;
}
/** Sets the AI model used for the next interaction, e.g Claude-Sonnet, GPT-4 */
setModel(model) {
this.options = Object.assign(Object.assign({}, this.options), { model });
return this;
}
/** Sets additional instructions for location-specific context */
setInstructions(instructions) {
this.options = Object.assign(Object.assign({}, this.options), { instructions });
return this;
}
/** Sets the platform where the chat is occurring, used internally for logging - ignore in most contexts */
setPlatform(platform) {
this.options = Object.assign(Object.assign({}, this.options), { platform });
return this;
}
/** Sets a custom personality configuration for the AI */
setPersonality(personality) {
this.options = Object.assign(Object.assign({}, this.options), { personality });
return this;
}
// GETTERS
/** Gets the current conversation ID */
getConvoId() {
return this.convoId;
}
/**
* Gets the current endpoint
* @deprecated Use getConversationEndpoint() instead
*/
getEndpoint() {
return this.endpoints.conversation;
}
/** Gets the current conversation endpoint */
getConversationEndpoint() {
return this.endpoints.conversation;
}
/** Gets the current history endpoint */
getHistoryEndpoint() {
return this.endpoints.history;
}
/** Gets the current progress stream endpoint */
getProgressStreamEndpoint() {
return this.endpoints.progressStream;
}
/** Gets the current progress endpoint */
getProgressEndpoint() {
return this.endpoints.progress;
}
/** Gets the current options object */
getModel() {
var _a;
return (_a = this.options) === null || _a === void 0 ? void 0 : _a.model;
}
/** Gets the current location-specific instructions */
getInstructions() {
var _a;
return (_a = this.options) === null || _a === void 0 ? void 0 : _a.instructions;
}
/** Gets the current platform */
getPlatform() {
var _a;
return (_a = this.options) === null || _a === void 0 ? void 0 : _a.platform;
}
/** Gets the current personality configuration */
getPersonality() {
var _a;
return (_a = this.options) === null || _a === void 0 ? void 0 : _a.personality;
}
// EVENT EMITTER
hasEmitter(event) {
return this.events.has(event);
}
addEmitter(event) {
if (!this.hasEmitter(event))
this.events.set(event, new Map());
}
removeEmitter(event) {
this.events.delete(event);
}
addListener(event, cb) {
if (!this.hasEmitter(event))
this.addEmitter(event);
const listeners = this.events.get(event);
const id = crypto.randomUUID();
listeners.set(id, cb);
return id;
}
removeListener(event, id) {
const listeners = this.events.get(event);
if (listeners)
listeners.delete(id);
}
emit(event, ...args) {
const listeners = this.events.get(event);
if (listeners)
listeners.forEach(listener => listener(...args));
}
/**
* Fires when the conversation ID is set
* if convoId is already set when this is called, fires immediately
* */
onConvoId(cb) {
if (this.convoId)
cb(this.convoId);
return this.addListener("convoId", cb);
}
/** Removes a convoId listener */
offConvoId(listenerId) {
this.removeListener("convoId", listenerId);
}
/**
* Fires once when the conversation ID is set, then removes the listener.
* If convoId is already set, fires immediately
*/
onceConvoId(cb) {
if (this.convoId) {
cb(this.convoId);
return;
}
const id = this.addListener("convoId", (convoId) => {
cb(convoId);
this.offConvoId(id);
});
return id;
}
// SSE HANDLER
handleSSE(url, cb, options = {}) {
const sse = new eventsource_1.EventSource(url);
sse.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
const convoId = data.success ? data.data.convoId : undefined;
if (convoId && options.onConvoId)
options.onConvoId(convoId);
cb(data);
if (data.data.quitStream)
sse.close();
});
sse.addEventListener("error", (event) => {
if (this.debug)
console.warn(`[Stream Error: ${url}]`, event);
});
return sse;
}
// GETTERS
/** Fetches the conversation state from the server, including message history and metadata */
fetchState() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.convoId)
throw new Error("Conversation ID is not set");
const url = (0, url_formatter_1.formatURL)(`${this.endpoints.history}/${this.convoId}`, undefined, { apiKey: this.apiKey, debug: this.debug });
const response = yield fetch(url);
if (!response.ok) {
const errorText = yield response.text();
throw new Error(`Failed to fetch conversation state: ${response.status} ${response.statusText} - ${errorText}`);
}
const data = yield response.json();
return data;
});
}
/** Fetches the conversation progress stream from the server */
fetchProgressStream(cb) {
if (!this.convoId)
throw new Error("Conversation ID is not set");
const url = (0, url_formatter_1.formatURL)(this.endpoints.progressStream, { chatId: this.convoId }, { apiKey: this.apiKey, debug: this.debug });
return this.handleSSE(url, cb);
}
/**
* Fetches the conversation progress from the server
* Returns undefined if no active turn progress
*/
fetchProgress(options) {
return __awaiter(this, void 0, void 0, function* () {
if (!this.convoId)
throw new Error("Conversation ID is not set");
const payload = {
chatId: this.convoId,
};
if ((options === null || options === void 0 ? void 0 : options.includeCompleted) !== undefined)
payload["includeCompleted"] = options.includeCompleted;
const url = (0, url_formatter_1.formatURL)(this.endpoints.progress, payload, { apiKey: this.apiKey, debug: this.debug });
const response = yield fetch(url, { headers: (options === null || options === void 0 ? void 0 : options.lastEventId) ? { "last-event-id": options.lastEventId } : undefined });
if (!response.ok) {
const errorText = yield response.text();
throw new Error(`Failed to fetch conversation progress: ${response.status} ${response.statusText} - ${errorText}`);
}
if (response.status === 204)
return undefined; // No content
const data = yield response.json();
return data.events;
});
}
// LIFE CYCLE
/** Sends a message into the conversation */
send(message, cb, options) {
const payload = Object.assign(Object.assign({ message }, this.options), options // overwrite convos for this call
);
if (this.convoId)
payload.chatId = this.convoId;
const url = (0, url_formatter_1.formatURL)(this.endpoints.conversation, payload, { apiKey: this.apiKey, debug: this.debug });
return this.handleSSE(url, cb, {
onConvoId: (convoId) => {
this.convoId = convoId;
this.emit("convoId", convoId);
}
});
}
}
exports.Conversation = Conversation;