UNPKG

assistan-ts

Version:

A typesafe and code-first library to define and run OpenAI assistants

128 lines 7.11 kB
"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.link = void 0; const definition_1 = require("./definition"); const utils_1 = require("./lib/utils"); const value_1 = require("@sinclair/typebox/value"); const link = (definition) => (openai, options) => __awaiter(void 0, void 0, void 0, function* () { var _a, _b, _c, _d, _e; const { assistantId, allowCreate = true, updateMode = "update", afterCreate, afterUpdate, beforeUpdate = () => true, fileMode = "update", pruneFiles = false, } = options; const local = (0, definition_1.toPayload)(definition); let remote; if (assistantId) { remote = yield openai.beta.assistants.retrieve(assistantId); } else { const assistants = yield openai.beta.assistants.list({ limit: 100 }); remote = assistants.data.find((assistant) => { var _a; return ((_a = assistant.metadata) === null || _a === void 0 ? void 0 : _a[definition_1.METADATA_KEY]) === definition.key; }); } if (remote) { let toUpdate = null; // handle update if (updateMode !== "skip") { const differences = findDifferences(remote, local); if (differences.length > 0) { if (updateMode === "update" && beforeUpdate(differences, local, remote)) { toUpdate = local; } else { throw new Error(`Assistant with key ${definition.key} is out of sync with remote. To automatically update, set 'updateMode' to 'update'`); } } } let file_ids = null; if (((_a = definition.files) === null || _a === void 0 ? void 0 : _a.resolve) && fileMode !== "skip") { const { matchedFiles, filesToPrune, filesToUpload } = yield compareFiles(openai, remote, definition); if (fileMode === "throw" && filesToUpload.length > 0) { throw new Error(`The following files are not uploaded to the assistant: ${filesToUpload .map((it) => it.name) .join(", ")}. Set 'fileMode' to 'update' to automatically upload files.`); } if (pruneFiles && filesToPrune.length > 0) { yield Promise.all(filesToPrune.map((it) => openai.files.del(it.id))); } // upload const uploaded = yield Promise.all(filesToUpload.map((file) => { return openai.files.create({ file, purpose: "assistants" }); })); file_ids = [ ...((_b = definition.files.file_ids) !== null && _b !== void 0 ? _b : []), ...uploaded.map((it) => it.id), ...matchedFiles.map((it) => it.id), ]; } if (toUpdate || file_ids) { //update the assistant. // Note: In testing, this seems to use "json patch" style updates where it only changes explicitly set fields remote = yield openai.beta.assistants.update(remote.id, Object.assign(Object.assign({}, (toUpdate !== null && toUpdate !== void 0 ? toUpdate : {})), { file_ids: file_ids !== null && file_ids !== void 0 ? file_ids : undefined })); afterUpdate === null || afterUpdate === void 0 ? void 0 : afterUpdate(remote); } } //create the assistant if (!remote && allowCreate) { // upload files if a resolver is set const file_ids = (_d = (_c = definition.files) === null || _c === void 0 ? void 0 : _c.file_ids) !== null && _d !== void 0 ? _d : []; if (fileMode != "skip" && ((_e = definition.files) === null || _e === void 0 ? void 0 : _e.resolve)) { const resolvedFiles = yield definition.files.resolve(); const uploaded = yield Promise.all(resolvedFiles.map((file) => openai.files.create({ file, purpose: "assistants" }))); file_ids.push(...uploaded.map((it) => it.id)); } remote = yield openai.beta.assistants.create(Object.assign(Object.assign({}, local), { file_ids: file_ids })); afterCreate === null || afterCreate === void 0 ? void 0 : afterCreate(remote); } if (!remote) { throw new Error(); } return Object.assign(Object.assign({}, definition), { openai, id: remote.id, remote }); }); exports.link = link; const findDifferences = (remote, local) => { const comparisons = { name: remote.name === local.name, // description: remote.description === local.description, instructions: remote.instructions === local.instructions, model: remote.model === local.model, tools: compareTools(remote.tools, local.tools), }; return Object.keys(comparisons).filter((key) => !comparisons[key]); }; const compareTools = (remote, local) => { remote === null || remote === void 0 ? void 0 : remote.sort(); local === null || local === void 0 ? void 0 : local.sort(); return value_1.Value.Hash(remote) === value_1.Value.Hash(local); }; const compareFiles = (openai, remote, definition) => __awaiter(void 0, void 0, void 0, function* () { var _f, _g, _h, _j; const resolvedFiles = yield definition.files.resolve(); const remoteFiles = yield Promise.all(remote.file_ids.map((fId) => openai.files.retrieve(fId))); const getResolvedKey = (_g = (_f = definition.files.keyFns) === null || _f === void 0 ? void 0 : _f.resolved) !== null && _g !== void 0 ? _g : ((it) => it.name); const getRemoteKey = (_j = (_h = definition.files.keyFns) === null || _h === void 0 ? void 0 : _h.remote) !== null && _j !== void 0 ? _j : ((it) => it.filename); const resolvedByKey = (0, utils_1.groupBy)(resolvedFiles, getResolvedKey); const remoteByKey = (0, utils_1.groupBy)(remoteFiles, getRemoteKey); const matches = []; const notMatchedResolved = new Set(Object.keys(resolvedByKey)); const notMatchedRemote = new Set(Object.keys(remoteByKey)); for (const key in resolvedByKey) { if (notMatchedRemote.has(key)) { matches.push(key); notMatchedResolved.delete(key); notMatchedRemote.delete(key); } } return { matchedFiles: matches.map((it) => remoteByKey[it]), filesToPrune: Array.from(notMatchedRemote).map((key) => remoteByKey[key]), filesToUpload: Array.from(notMatchedResolved).map((key) => resolvedByKey[key]), }; }); //# sourceMappingURL=link.js.map