assistan-ts
Version:
A typesafe and code-first library to define and run OpenAI assistants
266 lines (251 loc) • 7.98 kB
text/typescript
import { mock } from "bun:test";
import { Assistant, AssistantCreateParams, OpenAI } from "../../types/openai";
let _assistants: Record<string, Assistant> = {};
let _threads: Record<string, OpenAI.Beta.Threads.Thread> = {};
// threadId -> messages
let _threadMessages: Record<string, OpenAI.Beta.Threads.ThreadMessagesPage> =
{};
let _runs: Record<string, OpenAI.Beta.Threads.Runs.Run> = {};
let _files: Record<string, OpenAI.Files.FileObject> = {};
export const reset = () => {
_assistants = {};
_threads = {};
_threadMessages = {};
_runs = {};
mocked.beta.assistants.create.mockClear();
mocked.beta.assistants.update.mockClear();
mocked.beta.assistants.retrieve.mockClear();
mocked.beta.assistants.update.mockClear();
mocked.beta.threads.create.mockClear();
mocked.beta.threads.messages.list.mockClear();
mocked.beta.threads.runs.create.mockClear();
mocked.beta.threads.runs.retrieve.mockClear();
mocked.beta.threads.runs.update.mockClear();
mocked.beta.threads.runs.submitToolOutputs.mockClear();
mocked.files.create.mockClear();
mocked.files.retrieve.mockClear();
mocked.files.del.mockClear();
};
export const mocked = {
files: {
create: mock(
(params: {
purpose: "assistants";
file: File;
}): Promise<OpenAI.Files.FileObject> => {
const newFile: OpenAI.Files.FileObject = {
id: generateId(),
object: "file",
created_at: new Date().getTime(),
bytes: params.file.size,
status: "uploaded",
filename: params.file.name,
purpose: params.purpose,
};
_files[newFile.id] = newFile;
return Promise.resolve(newFile);
}
),
retrieve: mock((file_id: string) => {
return Promise.resolve(_files[file_id]);
}),
del: mock((file_id: string) => {
delete _files[file_id];
return Promise.resolve();
}),
},
beta: {
assistants: {
retrieve: mock(
(assistant_id: string): Promise<Assistant> =>
Promise.resolve(_assistants[assistant_id])
),
list: mock((params: any): Promise<OpenAI.Beta.AssistantsPage> => {
return Promise.resolve({
data: Object.values(_assistants),
} as OpenAI.Beta.AssistantsPage);
}),
create: mock((params: AssistantCreateParams) => {
const newAssistant: Assistant = {
id: generateId(),
object: "assistant",
created_at: new Date().getTime(),
description: "",
instructions: "",
name: "",
tools: [],
metadata: {},
file_ids: [],
...params,
};
_assistants[newAssistant.id] = newAssistant;
return Promise.resolve(newAssistant);
}),
update: mock((assistant_id: string, params: Assistant) => {
_assistants[assistant_id] = {
..._assistants[assistant_id],
...params,
};
return Promise.resolve(_assistants[assistant_id]);
}),
},
threads: {
create: mock((params: any) => {
const newThread: OpenAI.Beta.Threads.Thread = {
id: "thread_" + generateId(),
object: "thread",
created_at: new Date().getTime(),
metadata: {},
...params,
};
_threadMessages[newThread.id] = {
data: [],
} as any as OpenAI.Beta.Threads.Messages.ThreadMessagesPage;
_threads[newThread.id] = newThread;
return Promise.resolve(newThread);
}),
retrieve: mock((thread_id: string) => {
return Promise.resolve(_threads[thread_id]);
}),
messages: {
list: mock(
(
thread_id: string,
params: OpenAI.Beta.Threads.Messages.MessageListParams
) => {
const messages = _threadMessages[thread_id];
return Promise.resolve(messages);
}
),
},
runs: {
create: mock(
(
thread_id: string,
params: OpenAI.Beta.Threads.Runs.RunCreateParams
) => {
const { model, tools, instructions, ...rest } = params;
const newRun: OpenAI.Beta.Threads.Runs.Run = {
model: model ?? "gpt-4",
tools: tools ?? [],
id: "run_" + generateId(),
started_at: Date.now(),
status: "queued",
instructions: instructions ?? "",
metadata: {},
cancelled_at: null,
completed_at: null,
expires_at: Date.now() + 1000 * 60 * 60 * 24 * 7,
failed_at: null,
file_ids: [],
last_error: null,
object: "thread.run",
required_action: null,
created_at: new Date().getTime(),
thread_id,
...rest,
};
_runs[newRun.id] = newRun;
return Promise.resolve(newRun);
}
),
retrieve: mock((threadId: string, run_id: string) => {
return Promise.resolve(_runs[run_id]);
}),
update: mock(
(
run_id: string,
params: OpenAI.Beta.Threads.Runs.RunUpdateParams
) => {
_runs[run_id] = {
..._runs[run_id],
...params,
};
return Promise.resolve(_runs[run_id]);
}
),
submitToolOutputs: mock(
(
thread_id: string,
run_id: string,
params: OpenAI.Beta.Threads.Runs.RunSubmitToolOutputsParams
) => {
const run = _runs[run_id];
run.required_action = null;
run.status = "in_progress";
return Promise.resolve(run);
}
),
},
},
},
};
export default mocked as any as OpenAI;
/** Used to simulate status change */
export const simulate = (runId: string) => {
const run = _runs[runId];
if (!run) {
throw new Error(`Run ${runId} not found`);
}
let _messageContent: OpenAI.Beta.Threads.Messages.MessageContentText.Text | null =
null;
let _requiredAction: OpenAI.Beta.Threads.Runs.Run["required_action"] | null =
null;
const simulation = async () => {
while (run.status !== "completed") {
await new Promise((resolve) => setTimeout(resolve, 100));
if (run.status === "queued") {
run.status = "in_progress";
continue;
}
if (run.status === "in_progress" && _messageContent) {
run.status = "completed";
run.completed_at = Date.now();
const message: OpenAI.Beta.Threads.ThreadMessage = {
id: generateId(),
run_id: run.id,
thread_id: run.thread_id,
object: "thread.message",
assistant_id: run.assistant_id,
metadata: {},
role: "user",
content: [{ type: "text", text: _messageContent }],
created_at: Date.now(),
file_ids: [],
};
_threadMessages[run.thread_id].data.push(message);
continue;
}
if (run.status === "in_progress" && _requiredAction) {
run.required_action = _requiredAction;
run.status = "requires_action";
continue;
}
}
};
return {
simulation: simulation(),
setCompleted: (
content: OpenAI.Beta.Threads.Messages.MessageContentText.Text
) => {
_messageContent = content;
},
setRequiresAction: (
toolCalls: OpenAI.Beta.Threads.Runs.RequiredActionFunctionToolCall[]
) => {
_requiredAction = {
type: "submit_tool_outputs",
submit_tool_outputs: {
tool_calls: toolCalls,
},
};
},
};
};
function generateId(): string {
const now = Date.now(); // Current timestamp in milliseconds
const randomDigits = Array.from({ length: 10 }, () =>
Math.floor(Math.random() * 10)
).join("");
return `${now}${randomDigits}`;
}