assistan-ts
Version:
A typesafe and code-first library to define and run OpenAI assistants
128 lines • 7.11 kB
JavaScript
;
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