mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-21 15:12:26 +02:00
1321 lines
34 KiB
TypeScript
1321 lines
34 KiB
TypeScript
import {
|
|
Assistant,
|
|
AssistantGraph,
|
|
CancelAction,
|
|
Config,
|
|
DefaultValues,
|
|
GraphSchema,
|
|
Metadata,
|
|
Run,
|
|
RunStatus,
|
|
Thread,
|
|
ThreadState,
|
|
Cron,
|
|
AssistantVersion,
|
|
Subgraphs,
|
|
Checkpoint,
|
|
SearchItemsResponse,
|
|
ListNamespaceResponse,
|
|
Item,
|
|
ThreadStatus,
|
|
CronCreateResponse,
|
|
CronCreateForThreadResponse,
|
|
} from "./schema.js";
|
|
import { AsyncCaller, AsyncCallerParams } from "./utils/async_caller.js";
|
|
import {
|
|
EventSourceParser,
|
|
createParser,
|
|
} from "./utils/eventsource-parser/index.js";
|
|
import { IterableReadableStream } from "./utils/stream.js";
|
|
import {
|
|
RunsCreatePayload,
|
|
RunsStreamPayload,
|
|
RunsWaitPayload,
|
|
StreamEvent,
|
|
CronsCreatePayload,
|
|
OnConflictBehavior,
|
|
} from "./types.js";
|
|
import { mergeSignals } from "./utils/signals.js";
|
|
import { getEnvironmentVariable } from "./utils/env.js";
|
|
import { _getFetchImplementation } from "./singletons/fetch.js";
|
|
/**
|
|
* Get the API key from the environment.
|
|
* Precedence:
|
|
* 1. explicit argument
|
|
* 2. LANGGRAPH_API_KEY
|
|
* 3. LANGSMITH_API_KEY
|
|
* 4. LANGCHAIN_API_KEY
|
|
*
|
|
* @param apiKey - Optional API key provided as an argument
|
|
* @returns The API key if found, otherwise undefined
|
|
*/
|
|
export function getApiKey(apiKey?: string): string | undefined {
|
|
if (apiKey) {
|
|
return apiKey;
|
|
}
|
|
|
|
const prefixes = ["LANGGRAPH", "LANGSMITH", "LANGCHAIN"];
|
|
|
|
for (const prefix of prefixes) {
|
|
const envKey = getEnvironmentVariable(`${prefix}_API_KEY`);
|
|
if (envKey) {
|
|
// Remove surrounding quotes
|
|
return envKey.trim().replace(/^["']|["']$/g, "");
|
|
}
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
interface ClientConfig {
|
|
apiUrl?: string;
|
|
apiKey?: string;
|
|
callerOptions?: AsyncCallerParams;
|
|
timeoutMs?: number;
|
|
defaultHeaders?: Record<string, string | null | undefined>;
|
|
}
|
|
|
|
class BaseClient {
|
|
protected asyncCaller: AsyncCaller;
|
|
|
|
protected timeoutMs: number;
|
|
|
|
protected apiUrl: string;
|
|
|
|
protected defaultHeaders: Record<string, string | null | undefined>;
|
|
|
|
constructor(config?: ClientConfig) {
|
|
this.asyncCaller = new AsyncCaller({
|
|
maxRetries: 4,
|
|
maxConcurrency: 4,
|
|
...config?.callerOptions,
|
|
});
|
|
|
|
this.timeoutMs = config?.timeoutMs || 12_000;
|
|
|
|
// default limit being capped by Chrome
|
|
// https://github.com/nodejs/undici/issues/1373
|
|
// Regex to remove trailing slash, if present
|
|
this.apiUrl = config?.apiUrl?.replace(/\/$/, "") || "http://localhost:8123";
|
|
this.defaultHeaders = config?.defaultHeaders || {};
|
|
const apiKey = getApiKey(config?.apiKey);
|
|
if (apiKey) {
|
|
this.defaultHeaders["X-Api-Key"] = apiKey;
|
|
}
|
|
}
|
|
|
|
protected prepareFetchOptions(
|
|
path: string,
|
|
options?: RequestInit & {
|
|
json?: unknown;
|
|
params?: Record<string, unknown>;
|
|
timeoutMs?: number | null;
|
|
},
|
|
): [url: URL, init: RequestInit] {
|
|
const mutatedOptions = {
|
|
...options,
|
|
headers: { ...this.defaultHeaders, ...options?.headers },
|
|
};
|
|
|
|
if (mutatedOptions.json) {
|
|
mutatedOptions.body = JSON.stringify(mutatedOptions.json);
|
|
mutatedOptions.headers = {
|
|
...mutatedOptions.headers,
|
|
"Content-Type": "application/json",
|
|
};
|
|
delete mutatedOptions.json;
|
|
}
|
|
|
|
let timeoutSignal: AbortSignal | null = null;
|
|
if (typeof options?.timeoutMs !== "undefined") {
|
|
if (options.timeoutMs != null) {
|
|
timeoutSignal = AbortSignal.timeout(options.timeoutMs);
|
|
}
|
|
} else {
|
|
timeoutSignal = AbortSignal.timeout(this.timeoutMs);
|
|
}
|
|
|
|
mutatedOptions.signal = mergeSignals(timeoutSignal, mutatedOptions.signal);
|
|
const targetUrl = new URL(`${this.apiUrl}${path}`);
|
|
|
|
if (mutatedOptions.params) {
|
|
for (const [key, value] of Object.entries(mutatedOptions.params)) {
|
|
if (value == null) continue;
|
|
|
|
let strValue =
|
|
typeof value === "string" || typeof value === "number"
|
|
? value.toString()
|
|
: JSON.stringify(value);
|
|
|
|
targetUrl.searchParams.append(key, strValue);
|
|
}
|
|
delete mutatedOptions.params;
|
|
}
|
|
|
|
return [targetUrl, mutatedOptions];
|
|
}
|
|
|
|
protected async fetch<T>(
|
|
path: string,
|
|
options?: RequestInit & {
|
|
json?: unknown;
|
|
params?: Record<string, unknown>;
|
|
timeoutMs?: number | null;
|
|
signal?: AbortSignal;
|
|
},
|
|
): Promise<T> {
|
|
const response = await this.asyncCaller.call(
|
|
_getFetchImplementation(),
|
|
...this.prepareFetchOptions(path, options),
|
|
);
|
|
if (response.status === 202 || response.status === 204) {
|
|
return undefined as T;
|
|
}
|
|
return response.json() as T;
|
|
}
|
|
}
|
|
|
|
export class CronsClient extends BaseClient {
|
|
/**
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param assistantId Assistant ID to use for this cron job.
|
|
* @param payload Payload for creating a cron job.
|
|
* @returns The created background run.
|
|
*/
|
|
async createForThread(
|
|
threadId: string,
|
|
assistantId: string,
|
|
payload?: CronsCreatePayload,
|
|
): Promise<CronCreateForThreadResponse> {
|
|
const json: Record<string, any> = {
|
|
schedule: payload?.schedule,
|
|
input: payload?.input,
|
|
config: payload?.config,
|
|
metadata: payload?.metadata,
|
|
assistant_id: assistantId,
|
|
interrupt_before: payload?.interruptBefore,
|
|
interrupt_after: payload?.interruptAfter,
|
|
webhook: payload?.webhook,
|
|
multitask_strategy: payload?.multitaskStrategy,
|
|
if_not_exists: payload?.ifNotExists,
|
|
};
|
|
return this.fetch<CronCreateForThreadResponse>(
|
|
`/threads/${threadId}/runs/crons`,
|
|
{
|
|
method: "POST",
|
|
json,
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param assistantId Assistant ID to use for this cron job.
|
|
* @param payload Payload for creating a cron job.
|
|
* @returns
|
|
*/
|
|
async create(
|
|
assistantId: string,
|
|
payload?: CronsCreatePayload,
|
|
): Promise<CronCreateResponse> {
|
|
const json: Record<string, any> = {
|
|
schedule: payload?.schedule,
|
|
input: payload?.input,
|
|
config: payload?.config,
|
|
metadata: payload?.metadata,
|
|
assistant_id: assistantId,
|
|
interrupt_before: payload?.interruptBefore,
|
|
interrupt_after: payload?.interruptAfter,
|
|
webhook: payload?.webhook,
|
|
multitask_strategy: payload?.multitaskStrategy,
|
|
if_not_exists: payload?.ifNotExists,
|
|
};
|
|
return this.fetch<CronCreateResponse>(`/runs/crons`, {
|
|
method: "POST",
|
|
json,
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param cronId Cron ID of Cron job to delete.
|
|
*/
|
|
async delete(cronId: string): Promise<void> {
|
|
await this.fetch<void>(`/runs/crons/${cronId}`, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param query Query options.
|
|
* @returns List of crons.
|
|
*/
|
|
async search(query?: {
|
|
assistantId?: string;
|
|
threadId?: string;
|
|
limit?: number;
|
|
offset?: number;
|
|
}): Promise<Cron[]> {
|
|
return this.fetch<Cron[]>("/runs/crons/search", {
|
|
method: "POST",
|
|
json: {
|
|
assistant_id: query?.assistantId ?? undefined,
|
|
thread_id: query?.threadId ?? undefined,
|
|
limit: query?.limit ?? 10,
|
|
offset: query?.offset ?? 0,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
export class AssistantsClient extends BaseClient {
|
|
/**
|
|
* Get an assistant by ID.
|
|
*
|
|
* @param assistantId The ID of the assistant.
|
|
* @returns Assistant
|
|
*/
|
|
async get(assistantId: string): Promise<Assistant> {
|
|
return this.fetch<Assistant>(`/assistants/${assistantId}`);
|
|
}
|
|
|
|
/**
|
|
* Get the JSON representation of the graph assigned to a runnable
|
|
* @param assistantId The ID of the assistant.
|
|
* @param options.xray Whether to include subgraphs in the serialized graph representation. If an integer value is provided, only subgraphs with a depth less than or equal to the value will be included.
|
|
* @returns Serialized graph
|
|
*/
|
|
async getGraph(
|
|
assistantId: string,
|
|
options?: { xray?: boolean | number },
|
|
): Promise<AssistantGraph> {
|
|
return this.fetch<AssistantGraph>(`/assistants/${assistantId}/graph`, {
|
|
params: { xray: options?.xray },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get the state and config schema of the graph assigned to a runnable
|
|
* @param assistantId The ID of the assistant.
|
|
* @returns Graph schema
|
|
*/
|
|
async getSchemas(assistantId: string): Promise<GraphSchema> {
|
|
return this.fetch<GraphSchema>(`/assistants/${assistantId}/schemas`);
|
|
}
|
|
|
|
/**
|
|
* Get the schemas of an assistant by ID.
|
|
*
|
|
* @param assistantId The ID of the assistant to get the schema of.
|
|
* @param options Additional options for getting subgraphs, such as namespace or recursion extraction.
|
|
* @returns The subgraphs of the assistant.
|
|
*/
|
|
async getSubgraphs(
|
|
assistantId: string,
|
|
options?: {
|
|
namespace?: string;
|
|
recurse?: boolean;
|
|
},
|
|
): Promise<Subgraphs> {
|
|
if (options?.namespace) {
|
|
return this.fetch<Subgraphs>(
|
|
`/assistants/${assistantId}/subgraphs/${options.namespace}`,
|
|
{ params: { recurse: options?.recurse } },
|
|
);
|
|
}
|
|
return this.fetch<Subgraphs>(`/assistants/${assistantId}/subgraphs`, {
|
|
params: { recurse: options?.recurse },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a new assistant.
|
|
* @param payload Payload for creating an assistant.
|
|
* @returns The created assistant.
|
|
*/
|
|
async create(payload: {
|
|
graphId: string;
|
|
config?: Config;
|
|
metadata?: Metadata;
|
|
assistantId?: string;
|
|
ifExists?: OnConflictBehavior;
|
|
name?: string;
|
|
}): Promise<Assistant> {
|
|
return this.fetch<Assistant>("/assistants", {
|
|
method: "POST",
|
|
json: {
|
|
graph_id: payload.graphId,
|
|
config: payload.config,
|
|
metadata: payload.metadata,
|
|
assistant_id: payload.assistantId,
|
|
if_exists: payload.ifExists,
|
|
name: payload.name,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update an assistant.
|
|
* @param assistantId ID of the assistant.
|
|
* @param payload Payload for updating the assistant.
|
|
* @returns The updated assistant.
|
|
*/
|
|
async update(
|
|
assistantId: string,
|
|
payload: {
|
|
graphId?: string;
|
|
config?: Config;
|
|
metadata?: Metadata;
|
|
name?: string;
|
|
},
|
|
): Promise<Assistant> {
|
|
return this.fetch<Assistant>(`/assistants/${assistantId}`, {
|
|
method: "PATCH",
|
|
json: {
|
|
graph_id: payload.graphId,
|
|
config: payload.config,
|
|
metadata: payload.metadata,
|
|
name: payload.name,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete an assistant.
|
|
*
|
|
* @param assistantId ID of the assistant.
|
|
*/
|
|
async delete(assistantId: string): Promise<void> {
|
|
return this.fetch<void>(`/assistants/${assistantId}`, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* List assistants.
|
|
* @param query Query options.
|
|
* @returns List of assistants.
|
|
*/
|
|
async search(query?: {
|
|
graphId?: string;
|
|
metadata?: Metadata;
|
|
limit?: number;
|
|
offset?: number;
|
|
}): Promise<Assistant[]> {
|
|
return this.fetch<Assistant[]>("/assistants/search", {
|
|
method: "POST",
|
|
json: {
|
|
graph_id: query?.graphId ?? undefined,
|
|
metadata: query?.metadata ?? undefined,
|
|
limit: query?.limit ?? 10,
|
|
offset: query?.offset ?? 0,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* List all versions of an assistant.
|
|
*
|
|
* @param assistantId ID of the assistant.
|
|
* @returns List of assistant versions.
|
|
*/
|
|
async getVersions(
|
|
assistantId: string,
|
|
payload?: {
|
|
metadata?: Metadata;
|
|
limit?: number;
|
|
offset?: number;
|
|
},
|
|
): Promise<AssistantVersion[]> {
|
|
return this.fetch<AssistantVersion[]>(
|
|
`/assistants/${assistantId}/versions`,
|
|
{
|
|
method: "POST",
|
|
json: {
|
|
metadata: payload?.metadata ?? undefined,
|
|
limit: payload?.limit ?? 10,
|
|
offset: payload?.offset ?? 0,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Change the version of an assistant.
|
|
*
|
|
* @param assistantId ID of the assistant.
|
|
* @param version The version to change to.
|
|
* @returns The updated assistant.
|
|
*/
|
|
async setLatest(assistantId: string, version: number): Promise<Assistant> {
|
|
return this.fetch<Assistant>(`/assistants/${assistantId}/latest`, {
|
|
method: "POST",
|
|
json: { version },
|
|
});
|
|
}
|
|
}
|
|
|
|
export class ThreadsClient extends BaseClient {
|
|
/**
|
|
* Get a thread by ID.
|
|
*
|
|
* @param threadId ID of the thread.
|
|
* @returns The thread.
|
|
*/
|
|
async get(threadId: string): Promise<Thread> {
|
|
return this.fetch<Thread>(`/threads/${threadId}`);
|
|
}
|
|
|
|
/**
|
|
* Create a new thread.
|
|
*
|
|
* @param payload Payload for creating a thread.
|
|
* @returns The created thread.
|
|
*/
|
|
async create(payload?: {
|
|
/**
|
|
* Metadata for the thread.
|
|
*/
|
|
metadata?: Metadata;
|
|
threadId?: string;
|
|
ifExists?: OnConflictBehavior;
|
|
}): Promise<Thread> {
|
|
return this.fetch<Thread>(`/threads`, {
|
|
method: "POST",
|
|
json: {
|
|
metadata: payload?.metadata,
|
|
thread_id: payload?.threadId,
|
|
if_exists: payload?.ifExists,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Copy an existing thread
|
|
* @param threadId ID of the thread to be copied
|
|
* @returns Newly copied thread
|
|
*/
|
|
async copy(threadId: string): Promise<Thread> {
|
|
return this.fetch<Thread>(`/threads/${threadId}/copy`, {
|
|
method: "POST",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update a thread.
|
|
*
|
|
* @param threadId ID of the thread.
|
|
* @param payload Payload for updating the thread.
|
|
* @returns The updated thread.
|
|
*/
|
|
async update(
|
|
threadId: string,
|
|
payload?: {
|
|
/**
|
|
* Metadata for the thread.
|
|
*/
|
|
metadata?: Metadata;
|
|
},
|
|
): Promise<Thread> {
|
|
return this.fetch<Thread>(`/threads/${threadId}`, {
|
|
method: "PATCH",
|
|
json: { metadata: payload?.metadata },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Delete a thread.
|
|
*
|
|
* @param threadId ID of the thread.
|
|
*/
|
|
async delete(threadId: string): Promise<void> {
|
|
return this.fetch<void>(`/threads/${threadId}`, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
|
|
/**
|
|
* List threads
|
|
*
|
|
* @param query Query options
|
|
* @returns List of threads
|
|
*/
|
|
async search(query?: {
|
|
/**
|
|
* Metadata to filter threads by.
|
|
*/
|
|
metadata?: Metadata;
|
|
/**
|
|
* Maximum number of threads to return.
|
|
* Defaults to 10
|
|
*/
|
|
limit?: number;
|
|
/**
|
|
* Offset to start from.
|
|
*/
|
|
offset?: number;
|
|
/**
|
|
* Thread status to filter on.
|
|
* Must be one of 'idle', 'busy', 'interrupted' or 'error'.
|
|
*/
|
|
status?: ThreadStatus;
|
|
}): Promise<Thread[]> {
|
|
return this.fetch<Thread[]>("/threads/search", {
|
|
method: "POST",
|
|
json: {
|
|
metadata: query?.metadata ?? undefined,
|
|
limit: query?.limit ?? 10,
|
|
offset: query?.offset ?? 0,
|
|
status: query?.status,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get state for a thread.
|
|
*
|
|
* @param threadId ID of the thread.
|
|
* @returns Thread state.
|
|
*/
|
|
async getState<ValuesType = DefaultValues>(
|
|
threadId: string,
|
|
checkpoint?: Checkpoint | string,
|
|
options?: { subgraphs?: boolean },
|
|
): Promise<ThreadState<ValuesType>> {
|
|
if (checkpoint != null) {
|
|
if (typeof checkpoint !== "string") {
|
|
return this.fetch<ThreadState<ValuesType>>(
|
|
`/threads/${threadId}/state/checkpoint`,
|
|
{
|
|
method: "POST",
|
|
json: { checkpoint, subgraphs: options?.subgraphs },
|
|
},
|
|
);
|
|
}
|
|
|
|
// deprecated
|
|
return this.fetch<ThreadState<ValuesType>>(
|
|
`/threads/${threadId}/state/${checkpoint}`,
|
|
{ params: { subgraphs: options?.subgraphs } },
|
|
);
|
|
}
|
|
|
|
return this.fetch<ThreadState<ValuesType>>(`/threads/${threadId}/state`, {
|
|
params: { subgraphs: options?.subgraphs },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add state to a thread.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @returns
|
|
*/
|
|
async updateState<ValuesType = DefaultValues>(
|
|
threadId: string,
|
|
options: {
|
|
values: ValuesType;
|
|
checkpoint?: Checkpoint;
|
|
checkpointId?: string;
|
|
asNode?: string;
|
|
},
|
|
): Promise<Pick<Config, "configurable">> {
|
|
return this.fetch<Pick<Config, "configurable">>(
|
|
`/threads/${threadId}/state`,
|
|
{
|
|
method: "POST",
|
|
json: {
|
|
values: options.values,
|
|
checkpoint_id: options.checkpointId,
|
|
checkpoint: options.checkpoint,
|
|
as_node: options?.asNode,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Patch the metadata of a thread.
|
|
*
|
|
* @param threadIdOrConfig Thread ID or config to patch the state of.
|
|
* @param metadata Metadata to patch the state with.
|
|
*/
|
|
async patchState(
|
|
threadIdOrConfig: string | Config,
|
|
metadata: Metadata,
|
|
): Promise<void> {
|
|
let threadId: string;
|
|
|
|
if (typeof threadIdOrConfig !== "string") {
|
|
if (typeof threadIdOrConfig.configurable.thread_id !== "string") {
|
|
throw new Error(
|
|
"Thread ID is required when updating state with a config.",
|
|
);
|
|
}
|
|
threadId = threadIdOrConfig.configurable.thread_id;
|
|
} else {
|
|
threadId = threadIdOrConfig;
|
|
}
|
|
|
|
return this.fetch<void>(`/threads/${threadId}/state`, {
|
|
method: "PATCH",
|
|
json: { metadata: metadata },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get all past states for a thread.
|
|
*
|
|
* @param threadId ID of the thread.
|
|
* @param options Additional options.
|
|
* @returns List of thread states.
|
|
*/
|
|
async getHistory<ValuesType = DefaultValues>(
|
|
threadId: string,
|
|
options?: {
|
|
limit?: number;
|
|
before?: Config;
|
|
checkpoint?: Partial<Omit<Checkpoint, "thread_id">>;
|
|
metadata?: Metadata;
|
|
},
|
|
): Promise<ThreadState<ValuesType>[]> {
|
|
return this.fetch<ThreadState<ValuesType>[]>(
|
|
`/threads/${threadId}/history`,
|
|
{
|
|
method: "POST",
|
|
json: {
|
|
limit: options?.limit ?? 10,
|
|
before: options?.before,
|
|
metadata: options?.metadata,
|
|
checkpoint: options?.checkpoint,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
export class RunsClient extends BaseClient {
|
|
stream(
|
|
threadId: null,
|
|
assistantId: string,
|
|
payload?: Omit<RunsStreamPayload, "multitaskStrategy" | "onCompletion">,
|
|
): AsyncGenerator<{
|
|
event: StreamEvent;
|
|
data: any;
|
|
}>;
|
|
|
|
stream(
|
|
threadId: string,
|
|
assistantId: string,
|
|
payload?: RunsStreamPayload,
|
|
): AsyncGenerator<{
|
|
event: StreamEvent;
|
|
data: any;
|
|
}>;
|
|
|
|
/**
|
|
* Create a run and stream the results.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param assistantId Assistant ID to use for this run.
|
|
* @param payload Payload for creating a run.
|
|
*/
|
|
async *stream(
|
|
threadId: string | null,
|
|
assistantId: string,
|
|
payload?: RunsStreamPayload,
|
|
): AsyncGenerator<{
|
|
event: StreamEvent;
|
|
data: any;
|
|
}> {
|
|
const json: Record<string, any> = {
|
|
input: payload?.input,
|
|
command: payload?.command,
|
|
config: payload?.config,
|
|
metadata: payload?.metadata,
|
|
stream_mode: payload?.streamMode,
|
|
stream_subgraphs: payload?.streamSubgraphs,
|
|
feedback_keys: payload?.feedbackKeys,
|
|
assistant_id: assistantId,
|
|
interrupt_before: payload?.interruptBefore,
|
|
interrupt_after: payload?.interruptAfter,
|
|
checkpoint: payload?.checkpoint,
|
|
checkpoint_id: payload?.checkpointId,
|
|
webhook: payload?.webhook,
|
|
multitask_strategy: payload?.multitaskStrategy,
|
|
on_completion: payload?.onCompletion,
|
|
on_disconnect: payload?.onDisconnect,
|
|
after_seconds: payload?.afterSeconds,
|
|
if_not_exists: payload?.ifNotExists,
|
|
};
|
|
|
|
const endpoint =
|
|
threadId == null ? `/runs/stream` : `/threads/${threadId}/runs/stream`;
|
|
const response = await this.asyncCaller.call(
|
|
_getFetchImplementation(),
|
|
...this.prepareFetchOptions(endpoint, {
|
|
method: "POST",
|
|
json,
|
|
timeoutMs: null,
|
|
signal: payload?.signal,
|
|
}),
|
|
);
|
|
|
|
let parser: EventSourceParser;
|
|
let onEndEvent: () => void;
|
|
const textDecoder = new TextDecoder();
|
|
|
|
const stream: ReadableStream<{ event: string; data: any }> = (
|
|
response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })
|
|
).pipeThrough(
|
|
new TransformStream({
|
|
async start(ctrl) {
|
|
parser = createParser((event) => {
|
|
if (
|
|
(payload?.signal && payload.signal.aborted) ||
|
|
(event.type === "event" && event.data === "[DONE]")
|
|
) {
|
|
ctrl.terminate();
|
|
return;
|
|
}
|
|
|
|
if ("data" in event) {
|
|
ctrl.enqueue({
|
|
event: event.event ?? "message",
|
|
data: JSON.parse(event.data),
|
|
});
|
|
}
|
|
});
|
|
onEndEvent = () => {
|
|
ctrl.enqueue({ event: "end", data: undefined });
|
|
};
|
|
},
|
|
async transform(chunk) {
|
|
const payload = textDecoder.decode(chunk);
|
|
parser.feed(payload);
|
|
|
|
// eventsource-parser will ignore events
|
|
// that are not terminated by a newline
|
|
if (payload.trim() === "event: end") onEndEvent();
|
|
},
|
|
}),
|
|
);
|
|
|
|
yield* IterableReadableStream.fromReadableStream(stream);
|
|
}
|
|
|
|
/**
|
|
* Create a run.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param assistantId Assistant ID to use for this run.
|
|
* @param payload Payload for creating a run.
|
|
* @returns The created run.
|
|
*/
|
|
async create(
|
|
threadId: string,
|
|
assistantId: string,
|
|
payload?: RunsCreatePayload,
|
|
): Promise<Run> {
|
|
const json: Record<string, any> = {
|
|
input: payload?.input,
|
|
command: payload?.command,
|
|
config: payload?.config,
|
|
metadata: payload?.metadata,
|
|
stream_mode: payload?.streamMode,
|
|
stream_subgraphs: payload?.streamSubgraphs,
|
|
assistant_id: assistantId,
|
|
interrupt_before: payload?.interruptBefore,
|
|
interrupt_after: payload?.interruptAfter,
|
|
webhook: payload?.webhook,
|
|
checkpoint: payload?.checkpoint,
|
|
checkpoint_id: payload?.checkpointId,
|
|
multitask_strategy: payload?.multitaskStrategy,
|
|
after_seconds: payload?.afterSeconds,
|
|
if_not_exists: payload?.ifNotExists,
|
|
};
|
|
return this.fetch<Run>(`/threads/${threadId}/runs`, {
|
|
method: "POST",
|
|
json,
|
|
signal: payload?.signal,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Create a batch of stateless background runs.
|
|
*
|
|
* @param payloads An array of payloads for creating runs.
|
|
* @returns An array of created runs.
|
|
*/
|
|
async createBatch(
|
|
payloads: (RunsCreatePayload & { assistantId: string })[],
|
|
): Promise<Run[]> {
|
|
const filteredPayloads = payloads
|
|
.map((payload) => ({ ...payload, assistant_id: payload.assistantId }))
|
|
.map((payload) => {
|
|
return Object.fromEntries(
|
|
Object.entries(payload).filter(([_, v]) => v !== undefined),
|
|
);
|
|
});
|
|
|
|
return this.fetch<Run[]>("/runs/batch", {
|
|
method: "POST",
|
|
json: filteredPayloads,
|
|
});
|
|
}
|
|
|
|
async wait(
|
|
threadId: null,
|
|
assistantId: string,
|
|
payload?: Omit<RunsWaitPayload, "multitaskStrategy" | "onCompletion">,
|
|
): Promise<ThreadState["values"]>;
|
|
|
|
async wait(
|
|
threadId: string,
|
|
assistantId: string,
|
|
payload?: RunsWaitPayload,
|
|
): Promise<ThreadState["values"]>;
|
|
|
|
/**
|
|
* Create a run and wait for it to complete.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param assistantId Assistant ID to use for this run.
|
|
* @param payload Payload for creating a run.
|
|
* @returns The last values chunk of the thread.
|
|
*/
|
|
async wait(
|
|
threadId: string | null,
|
|
assistantId: string,
|
|
payload?: RunsWaitPayload,
|
|
): Promise<ThreadState["values"]> {
|
|
const json: Record<string, any> = {
|
|
input: payload?.input,
|
|
command: payload?.command,
|
|
config: payload?.config,
|
|
metadata: payload?.metadata,
|
|
assistant_id: assistantId,
|
|
interrupt_before: payload?.interruptBefore,
|
|
interrupt_after: payload?.interruptAfter,
|
|
checkpoint: payload?.checkpoint,
|
|
checkpoint_id: payload?.checkpointId,
|
|
webhook: payload?.webhook,
|
|
multitask_strategy: payload?.multitaskStrategy,
|
|
on_completion: payload?.onCompletion,
|
|
on_disconnect: payload?.onDisconnect,
|
|
after_seconds: payload?.afterSeconds,
|
|
if_not_exists: payload?.ifNotExists,
|
|
};
|
|
const endpoint =
|
|
threadId == null ? `/runs/wait` : `/threads/${threadId}/runs/wait`;
|
|
const response = await this.fetch<ThreadState["values"]>(endpoint, {
|
|
method: "POST",
|
|
json,
|
|
timeoutMs: null,
|
|
signal: payload?.signal,
|
|
});
|
|
const raiseError =
|
|
payload?.raiseError !== undefined ? payload.raiseError : true;
|
|
if (
|
|
raiseError &&
|
|
"__error__" in response &&
|
|
typeof response.__error__ === "object" &&
|
|
response.__error__ &&
|
|
"error" in response.__error__ &&
|
|
"message" in response.__error__
|
|
) {
|
|
throw new Error(
|
|
`${response.__error__?.error}: ${response.__error__?.message}`,
|
|
);
|
|
}
|
|
return response;
|
|
}
|
|
|
|
/**
|
|
* List all runs for a thread.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param options Filtering and pagination options.
|
|
* @returns List of runs.
|
|
*/
|
|
async list(
|
|
threadId: string,
|
|
options?: {
|
|
/**
|
|
* Maximum number of runs to return.
|
|
* Defaults to 10
|
|
*/
|
|
limit?: number;
|
|
|
|
/**
|
|
* Offset to start from.
|
|
* Defaults to 0.
|
|
*/
|
|
offset?: number;
|
|
|
|
/**
|
|
* Status of the run to filter by.
|
|
*/
|
|
status?: RunStatus;
|
|
},
|
|
): Promise<Run[]> {
|
|
return this.fetch<Run[]>(`/threads/${threadId}/runs`, {
|
|
params: {
|
|
limit: options?.limit ?? 10,
|
|
offset: options?.offset ?? 0,
|
|
status: options?.status ?? undefined,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Get a run by ID.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param runId The ID of the run.
|
|
* @returns The run.
|
|
*/
|
|
async get(threadId: string, runId: string): Promise<Run> {
|
|
return this.fetch<Run>(`/threads/${threadId}/runs/${runId}`);
|
|
}
|
|
|
|
/**
|
|
* Cancel a run.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param runId The ID of the run.
|
|
* @param wait Whether to block when canceling
|
|
* @param action Action to take when cancelling the run. Possible values are `interrupt` or `rollback`. Default is `interrupt`.
|
|
* @returns
|
|
*/
|
|
async cancel(
|
|
threadId: string,
|
|
runId: string,
|
|
wait: boolean = false,
|
|
action: CancelAction = "interrupt",
|
|
): Promise<void> {
|
|
return this.fetch<void>(`/threads/${threadId}/runs/${runId}/cancel`, {
|
|
method: "POST",
|
|
params: {
|
|
wait: wait ? "1" : "0",
|
|
action: action,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Block until a run is done.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param runId The ID of the run.
|
|
* @returns
|
|
*/
|
|
async join(
|
|
threadId: string,
|
|
runId: string,
|
|
options?: { signal?: AbortSignal },
|
|
): Promise<void> {
|
|
return this.fetch<void>(`/threads/${threadId}/runs/${runId}/join`, {
|
|
timeoutMs: null,
|
|
signal: options?.signal,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Stream output from a run in real-time, until the run is done.
|
|
* Output is not buffered, so any output produced before this call will
|
|
* not be received here.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param runId The ID of the run.
|
|
* @returns An async generator yielding stream parts.
|
|
*/
|
|
async *joinStream(
|
|
threadId: string,
|
|
runId: string,
|
|
options?:
|
|
| { signal?: AbortSignal; cancelOnDisconnect?: boolean }
|
|
| AbortSignal,
|
|
): AsyncGenerator<{ event: StreamEvent; data: any }> {
|
|
const opts =
|
|
typeof options === "object" &&
|
|
options != null &&
|
|
options instanceof AbortSignal
|
|
? { signal: options }
|
|
: options;
|
|
|
|
const response = await this.asyncCaller.call(
|
|
_getFetchImplementation(),
|
|
...this.prepareFetchOptions(`/threads/${threadId}/runs/${runId}/stream`, {
|
|
method: "GET",
|
|
timeoutMs: null,
|
|
signal: opts?.signal,
|
|
params: { cancel_on_disconnect: opts?.cancelOnDisconnect ? "1" : "0" },
|
|
}),
|
|
);
|
|
|
|
let parser: EventSourceParser;
|
|
let onEndEvent: () => void;
|
|
const textDecoder = new TextDecoder();
|
|
|
|
const stream: ReadableStream<{ event: string; data: any }> = (
|
|
response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })
|
|
).pipeThrough(
|
|
new TransformStream({
|
|
async start(ctrl) {
|
|
parser = createParser((event) => {
|
|
if (
|
|
(opts?.signal && opts.signal.aborted) ||
|
|
(event.type === "event" && event.data === "[DONE]")
|
|
) {
|
|
ctrl.terminate();
|
|
return;
|
|
}
|
|
|
|
if ("data" in event) {
|
|
ctrl.enqueue({
|
|
event: event.event ?? "message",
|
|
data: JSON.parse(event.data),
|
|
});
|
|
}
|
|
});
|
|
onEndEvent = () => {
|
|
ctrl.enqueue({ event: "end", data: undefined });
|
|
};
|
|
},
|
|
async transform(chunk) {
|
|
const payload = textDecoder.decode(chunk);
|
|
parser.feed(payload);
|
|
|
|
// eventsource-parser will ignore events
|
|
// that are not terminated by a newline
|
|
if (payload.trim() === "event: end") onEndEvent();
|
|
},
|
|
}),
|
|
);
|
|
|
|
yield* IterableReadableStream.fromReadableStream(stream);
|
|
}
|
|
|
|
/**
|
|
* Delete a run.
|
|
*
|
|
* @param threadId The ID of the thread.
|
|
* @param runId The ID of the run.
|
|
* @returns
|
|
*/
|
|
async delete(threadId: string, runId: string): Promise<void> {
|
|
return this.fetch<void>(`/threads/${threadId}/runs/${runId}`, {
|
|
method: "DELETE",
|
|
});
|
|
}
|
|
}
|
|
|
|
interface APIItem {
|
|
namespace: string[];
|
|
key: string;
|
|
value: Record<string, any>;
|
|
created_at: string;
|
|
updated_at: string;
|
|
}
|
|
interface APISearchItemsResponse {
|
|
items: APIItem[];
|
|
}
|
|
|
|
export class StoreClient extends BaseClient {
|
|
/**
|
|
* Store or update an item.
|
|
*
|
|
* @param namespace A list of strings representing the namespace path.
|
|
* @param key The unique identifier for the item within the namespace.
|
|
* @param value A dictionary containing the item's data.
|
|
* @returns Promise<void>
|
|
*/
|
|
async putItem(
|
|
namespace: string[],
|
|
key: string,
|
|
value: Record<string, any>,
|
|
): Promise<void> {
|
|
namespace.forEach((label) => {
|
|
if (label.includes(".")) {
|
|
throw new Error(
|
|
`Invalid namespace label '${label}'. Namespace labels cannot contain periods ('.')`,
|
|
);
|
|
}
|
|
});
|
|
|
|
const payload = {
|
|
namespace,
|
|
key,
|
|
value,
|
|
};
|
|
|
|
return this.fetch<void>("/store/items", {
|
|
method: "PUT",
|
|
json: payload,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Retrieve a single item.
|
|
*
|
|
* @param namespace A list of strings representing the namespace path.
|
|
* @param key The unique identifier for the item.
|
|
* @returns Promise<Item>
|
|
*/
|
|
async getItem(namespace: string[], key: string): Promise<Item | null> {
|
|
namespace.forEach((label) => {
|
|
if (label.includes(".")) {
|
|
throw new Error(
|
|
`Invalid namespace label '${label}'. Namespace labels cannot contain periods ('.')`,
|
|
);
|
|
}
|
|
});
|
|
|
|
const response = await this.fetch<APIItem>("/store/items", {
|
|
params: { namespace: namespace.join("."), key },
|
|
});
|
|
|
|
return response
|
|
? {
|
|
...response,
|
|
createdAt: response.created_at,
|
|
updatedAt: response.updated_at,
|
|
}
|
|
: null;
|
|
}
|
|
|
|
/**
|
|
* Delete an item.
|
|
*
|
|
* @param namespace A list of strings representing the namespace path.
|
|
* @param key The unique identifier for the item.
|
|
* @returns Promise<void>
|
|
*/
|
|
async deleteItem(namespace: string[], key: string): Promise<void> {
|
|
namespace.forEach((label) => {
|
|
if (label.includes(".")) {
|
|
throw new Error(
|
|
`Invalid namespace label '${label}'. Namespace labels cannot contain periods ('.')`,
|
|
);
|
|
}
|
|
});
|
|
|
|
return this.fetch<void>("/store/items", {
|
|
method: "DELETE",
|
|
json: { namespace, key },
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Search for items within a namespace prefix.
|
|
*
|
|
* @param namespacePrefix List of strings representing the namespace prefix.
|
|
* @param options.filter Optional dictionary of key-value pairs to filter results.
|
|
* @param options.limit Maximum number of items to return (default is 10).
|
|
* @param options.offset Number of items to skip before returning results (default is 0).
|
|
* @param options.query Optional search query.
|
|
* @returns Promise<SearchItemsResponse>
|
|
*/
|
|
async searchItems(
|
|
namespacePrefix: string[],
|
|
options?: {
|
|
filter?: Record<string, any>;
|
|
limit?: number;
|
|
offset?: number;
|
|
query?: string;
|
|
},
|
|
): Promise<SearchItemsResponse> {
|
|
const payload = {
|
|
namespace_prefix: namespacePrefix,
|
|
filter: options?.filter,
|
|
limit: options?.limit ?? 10,
|
|
offset: options?.offset ?? 0,
|
|
query: options?.query,
|
|
};
|
|
|
|
const response = await this.fetch<APISearchItemsResponse>(
|
|
"/store/items/search",
|
|
{
|
|
method: "POST",
|
|
json: payload,
|
|
},
|
|
);
|
|
return {
|
|
items: response.items.map((item) => ({
|
|
...item,
|
|
createdAt: item.created_at,
|
|
updatedAt: item.updated_at,
|
|
})),
|
|
};
|
|
}
|
|
|
|
/**
|
|
* List namespaces with optional match conditions.
|
|
*
|
|
* @param options.prefix Optional list of strings representing the prefix to filter namespaces.
|
|
* @param options.suffix Optional list of strings representing the suffix to filter namespaces.
|
|
* @param options.maxDepth Optional integer specifying the maximum depth of namespaces to return.
|
|
* @param options.limit Maximum number of namespaces to return (default is 100).
|
|
* @param options.offset Number of namespaces to skip before returning results (default is 0).
|
|
* @returns Promise<ListNamespaceResponse>
|
|
*/
|
|
async listNamespaces(options?: {
|
|
prefix?: string[];
|
|
suffix?: string[];
|
|
maxDepth?: number;
|
|
limit?: number;
|
|
offset?: number;
|
|
}): Promise<ListNamespaceResponse> {
|
|
const payload = {
|
|
prefix: options?.prefix,
|
|
suffix: options?.suffix,
|
|
max_depth: options?.maxDepth,
|
|
limit: options?.limit ?? 100,
|
|
offset: options?.offset ?? 0,
|
|
};
|
|
|
|
return this.fetch<ListNamespaceResponse>("/store/namespaces", {
|
|
method: "POST",
|
|
json: payload,
|
|
});
|
|
}
|
|
}
|
|
|
|
export class Client {
|
|
/**
|
|
* The client for interacting with assistants.
|
|
*/
|
|
public assistants: AssistantsClient;
|
|
|
|
/**
|
|
* The client for interacting with threads.
|
|
*/
|
|
public threads: ThreadsClient;
|
|
|
|
/**
|
|
* The client for interacting with runs.
|
|
*/
|
|
public runs: RunsClient;
|
|
|
|
/**
|
|
* The client for interacting with cron runs.
|
|
*/
|
|
public crons: CronsClient;
|
|
|
|
/**
|
|
* The client for interacting with the KV store.
|
|
*/
|
|
public store: StoreClient;
|
|
|
|
constructor(config?: ClientConfig) {
|
|
this.assistants = new AssistantsClient(config);
|
|
this.threads = new ThreadsClient(config);
|
|
this.runs = new RunsClient(config);
|
|
this.crons = new CronsClient(config);
|
|
this.store = new StoreClient(config);
|
|
}
|
|
}
|