import { conat } from "@cocalc/conat/client";
import { isValidUUID } from "@cocalc/util/misc";
import type { Subscription } from "@cocalc/conat/core/client";
export const SUBJECT = process.env.COCALC_TEST_MODE ? "llm-test" : "llm";
export function llmSubject({
account_id,
project_id,
}: {
account_id?: string;
project_id?: string;
}) {
if (account_id) {
return `${SUBJECT}.account-${account_id}.api`;
} else if (project_id) {
return `${SUBJECT}.project-${project_id}.api`;
} else {
return `${SUBJECT}.hub.api`;
}
}
function getUserId(subject: string): string {
if (subject.startsWith(`${SUBJECT}.account-`)) {
return subject.slice(
`${SUBJECT}.account-`.length,
`${SUBJECT}.account-`.length + 36,
);
}
if (subject.startsWith(`${SUBJECT}.project-`)) {
return subject.slice(
`${SUBJECT}.project-`.length,
`${SUBJECT}.project-`.length + 36,
);
}
return "hub";
}
let sub: Subscription | null = null;
export async function init(evaluate) {
const cn = await conat();
sub = await cn.subscribe(`${SUBJECT}.*.api`, { queue: "q" });
listen(evaluate);
}
export async function close() {
if (sub == null) {
return;
}
sub.drain();
sub = null;
}
async function listen(evaluate) {
if (sub == null) {
throw Error("must init first");
}
for await (const mesg of sub) {
handleMessage(mesg, evaluate);
}
}
async function handleMessage(mesg, evaluate) {
const options = mesg.data;
let seq = 0;
const respond = ({ text, error }: { text?: string; error?: string }) => {
mesg.respondSync({ text, error, seq });
seq += 1;
};
let done = false;
const end = () => {
if (done) return;
done = true;
mesg.respondSync(null);
};
const stream = (text?) => {
if (done) return;
if (text != null) {
respond({ text });
} else {
end();
}
};
try {
if (!isValidUUID(options.account_id)) {
throw Error("account_id must be a valid uuid");
}
if (options.account_id != getUserId(mesg.subject)) {
throw Error("account_id is invalid");
}
await evaluate({ ...options, stream });
} catch (err) {
if (!done) {
respond({ error: `${err}` });
end();
}
}
}