UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

1,205 lines (1,203 loc) • 36.7 kB
import { AsyncCaller } from "./utils/async_caller.js"; import { getEnvironmentVariable } from "./utils/env.js"; import { mergeSignals } from "./utils/signals.js"; import { BytesLineDecoder, SSEDecoder } from "./utils/sse.js"; import { streamWithRetry } from "./utils/stream.js"; //#region src/client.ts function* iterateHeaders(headers) { let iter; let shouldClear = false; if (headers instanceof Headers) { const entries = []; headers.forEach((value, name) => { entries.push([name, value]); }); iter = entries; } else if (Array.isArray(headers)) iter = headers; else { shouldClear = true; iter = Object.entries(headers ?? {}); } for (const item of iter) { const name = item[0]; if (typeof name !== "string") throw new TypeError(`Expected header name to be a string, got ${typeof name}`); const values = Array.isArray(item[1]) ? item[1] : [item[1]]; let didClear = false; for (const value of values) { if (value === void 0) continue; if (shouldClear && !didClear) { didClear = true; yield [name, null]; } yield [name, value]; } } } function mergeHeaders(...headerObjects) { const outputHeaders = new Headers(); for (const headers of headerObjects) { if (!headers) continue; for (const [name, value] of iterateHeaders(headers)) if (value === null) outputHeaders.delete(name); else outputHeaders.append(name, value); } const headerEntries = []; outputHeaders.forEach((value, name) => { headerEntries.push([name, value]); }); return Object.fromEntries(headerEntries); } /** * Get the API key from the environment. * Precedence: * 1. explicit argument (if string) * 2. LANGGRAPH_API_KEY * 3. LANGSMITH_API_KEY * 4. LANGCHAIN_API_KEY * * @param apiKey - API key provided as an argument. If null, skips environment lookup. If undefined, tries environment. * @returns The API key if found, otherwise undefined */ function getApiKey(apiKey) { if (apiKey === null) return; if (apiKey) return apiKey; for (const prefix of [ "LANGGRAPH", "LANGSMITH", "LANGCHAIN" ]) { const envKey = getEnvironmentVariable(`${prefix}_API_KEY`); if (envKey) return envKey.trim().replace(/^["']|["']$/g, ""); } } const REGEX_RUN_METADATA = /(\/threads\/(?<thread_id>.+))?\/runs\/(?<run_id>.+)/; function getRunMetadataFromResponse(response) { const contentLocation = response.headers.get("Content-Location"); if (!contentLocation) return void 0; const match = REGEX_RUN_METADATA.exec(contentLocation); if (!match?.groups?.run_id) return void 0; return { run_id: match.groups.run_id, thread_id: match.groups.thread_id || void 0 }; } var BaseClient = class { asyncCaller; timeoutMs; apiUrl; defaultHeaders; onRequest; constructor(config) { const callerOptions = { maxRetries: 4, maxConcurrency: 4, ...config?.callerOptions }; let defaultApiUrl = "http://localhost:8123"; if (!config?.apiUrl && typeof globalThis === "object" && globalThis != null) { const fetchSmb = Symbol.for("langgraph_api:fetch"); const urlSmb = Symbol.for("langgraph_api:url"); const global = globalThis; if (global[fetchSmb]) callerOptions.fetch ??= global[fetchSmb]; if (global[urlSmb]) defaultApiUrl = global[urlSmb]; } this.asyncCaller = new AsyncCaller(callerOptions); this.timeoutMs = config?.timeoutMs; this.apiUrl = config?.apiUrl?.replace(/\/$/, "") || defaultApiUrl; this.defaultHeaders = config?.defaultHeaders || {}; this.onRequest = config?.onRequest; const apiKey = getApiKey(config?.apiKey); if (apiKey) this.defaultHeaders["x-api-key"] = apiKey; } prepareFetchOptions(path, options) { const mutatedOptions = { ...options, headers: mergeHeaders(this.defaultHeaders, options?.headers) }; if (mutatedOptions.json) { mutatedOptions.body = JSON.stringify(mutatedOptions.json); mutatedOptions.headers = mergeHeaders(mutatedOptions.headers, { "content-type": "application/json" }); delete mutatedOptions.json; } if (mutatedOptions.withResponse) delete mutatedOptions.withResponse; let timeoutSignal = null; if (typeof options?.timeoutMs !== "undefined") { if (options.timeoutMs != null) timeoutSignal = AbortSignal.timeout(options.timeoutMs); } else if (this.timeoutMs != null) 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; const strValue = typeof value === "string" || typeof value === "number" ? value.toString() : JSON.stringify(value); targetUrl.searchParams.append(key, strValue); } delete mutatedOptions.params; } return [targetUrl, mutatedOptions]; } async fetch(path, options) { const [url, init] = this.prepareFetchOptions(path, options); let finalInit = init; if (this.onRequest) finalInit = await this.onRequest(url, init); const response = await this.asyncCaller.fetch(url, finalInit); const body = (() => { if (response.status === 202 || response.status === 204) return; return response.json(); })(); if (options?.withResponse) return [await body, response]; return body; } /** * Protected helper for streaming with automatic retry logic. * Handles both initial requests and reconnections with SSE. */ async *streamWithRetry(config) { const makeRequest = async (reconnectParams) => { const requestEndpoint = reconnectParams?.reconnectPath || config.endpoint; const isReconnect = !!reconnectParams?.lastEventId; const method = isReconnect ? "GET" : config.method || "GET"; const requestHeaders = isReconnect && reconnectParams?.lastEventId ? { ...config.headers, "Last-Event-ID": reconnectParams.lastEventId } : config.headers; let [url, init] = this.prepareFetchOptions(requestEndpoint, { method, timeoutMs: null, signal: config.signal, headers: requestHeaders, params: config.params, json: isReconnect ? void 0 : config.json }); if (this.onRequest != null) init = await this.onRequest(url, init); const response = await this.asyncCaller.fetch(url, init); if (!isReconnect && config.onInitialResponse) await config.onInitialResponse(response); return { response, stream: (response.body || new ReadableStream({ start: (ctrl) => ctrl.close() })).pipeThrough(BytesLineDecoder()).pipeThrough(SSEDecoder()) }; }; yield* streamWithRetry(makeRequest, { maxRetries: config.maxRetries ?? 5, signal: config.signal, onReconnect: config.onReconnect }); } }; var CronsClient = class 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, assistantId, payload) { const json = { schedule: payload?.schedule, input: payload?.input, config: payload?.config, context: payload?.context, 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, checkpoint_during: payload?.checkpointDuring, durability: payload?.durability }; return this.fetch(`/threads/${threadId}/runs/crons`, { method: "POST", json, signal: payload?.signal }); } /** * * @param assistantId Assistant ID to use for this cron job. * @param payload Payload for creating a cron job. * @returns */ async create(assistantId, payload) { const json = { schedule: payload?.schedule, input: payload?.input, config: payload?.config, context: payload?.context, metadata: payload?.metadata, assistant_id: assistantId, interrupt_before: payload?.interruptBefore, interrupt_after: payload?.interruptAfter, webhook: payload?.webhook, on_run_completed: payload?.onRunCompleted, multitask_strategy: payload?.multitaskStrategy, if_not_exists: payload?.ifNotExists, checkpoint_during: payload?.checkpointDuring, durability: payload?.durability }; return this.fetch(`/runs/crons`, { method: "POST", json, signal: payload?.signal }); } /** * * @param cronId Cron ID of Cron job to delete. */ async delete(cronId, options) { await this.fetch(`/runs/crons/${cronId}`, { method: "DELETE", signal: options?.signal }); } /** * * @param query Query options. * @returns List of crons. */ async search(query) { return this.fetch("/runs/crons/search", { method: "POST", json: { assistant_id: query?.assistantId ?? void 0, thread_id: query?.threadId ?? void 0, limit: query?.limit ?? 10, offset: query?.offset ?? 0, sort_by: query?.sortBy ?? void 0, sort_order: query?.sortOrder ?? void 0, select: query?.select ?? void 0 }, signal: query?.signal }); } /** * Count cron jobs matching filters. * * @param query.assistantId Assistant ID to filter by. * @param query.threadId Thread ID to filter by. * @returns Number of cron jobs matching the criteria. */ async count(query) { return this.fetch(`/runs/crons/count`, { method: "POST", json: { assistant_id: query?.assistantId ?? void 0, thread_id: query?.threadId ?? void 0 }, signal: query?.signal }); } }; var AssistantsClient = class extends BaseClient { /** * Get an assistant by ID. * * @param assistantId The ID of the assistant. * @returns Assistant */ async get(assistantId, options) { return this.fetch(`/assistants/${assistantId}`, { signal: options?.signal }); } /** * 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, options) { return this.fetch(`/assistants/${assistantId}/graph`, { params: { xray: options?.xray }, signal: options?.signal }); } /** * 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, options) { return this.fetch(`/assistants/${assistantId}/schemas`, { signal: options?.signal }); } /** * 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, options) { if (options?.namespace) return this.fetch(`/assistants/${assistantId}/subgraphs/${options.namespace}`, { params: { recurse: options?.recurse }, signal: options?.signal }); return this.fetch(`/assistants/${assistantId}/subgraphs`, { params: { recurse: options?.recurse }, signal: options?.signal }); } /** * Create a new assistant. * @param payload Payload for creating an assistant. * @returns The created assistant. */ async create(payload) { return this.fetch("/assistants", { method: "POST", json: { graph_id: payload.graphId, config: payload.config, context: payload.context, metadata: payload.metadata, assistant_id: payload.assistantId, if_exists: payload.ifExists, name: payload.name, description: payload.description }, signal: payload.signal }); } /** * Update an assistant. * @param assistantId ID of the assistant. * @param payload Payload for updating the assistant. * @returns The updated assistant. */ async update(assistantId, payload) { return this.fetch(`/assistants/${assistantId}`, { method: "PATCH", json: { graph_id: payload.graphId, config: payload.config, context: payload.context, metadata: payload.metadata, name: payload.name, description: payload.description }, signal: payload.signal }); } /** * Delete an assistant. * * @param assistantId ID of the assistant. * @param deleteThreads If true, delete all threads with `metadata.assistant_id` equal to `assistantId`. Defaults to false. */ async delete(assistantId, options) { return this.fetch(`/assistants/${assistantId}?delete_threads=${options?.deleteThreads ?? false}`, { method: "DELETE", signal: options?.signal }); } async search(query) { const json = { graph_id: query?.graphId ?? void 0, name: query?.name ?? void 0, metadata: query?.metadata ?? void 0, limit: query?.limit ?? 10, offset: query?.offset ?? 0, sort_by: query?.sortBy ?? void 0, sort_order: query?.sortOrder ?? void 0, select: query?.select ?? void 0 }; const [assistants, response] = await this.fetch("/assistants/search", { method: "POST", json, withResponse: true, signal: query?.signal }); if (query?.includePagination) return { assistants, next: response.headers.get("X-Pagination-Next") }; return assistants; } /** * Count assistants matching filters. * * @param query.metadata Metadata to filter by. Exact match for each key/value. * @param query.graphId Optional graph id to filter by. * @param query.name Optional name to filter by. * @returns Number of assistants matching the criteria. */ async count(query) { return this.fetch(`/assistants/count`, { method: "POST", json: { metadata: query?.metadata ?? void 0, graph_id: query?.graphId ?? void 0, name: query?.name ?? void 0 }, signal: query?.signal }); } /** * List all versions of an assistant. * * @param assistantId ID of the assistant. * @returns List of assistant versions. */ async getVersions(assistantId, payload) { return this.fetch(`/assistants/${assistantId}/versions`, { method: "POST", json: { metadata: payload?.metadata ?? void 0, limit: payload?.limit ?? 10, offset: payload?.offset ?? 0 }, signal: payload?.signal }); } /** * 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, version, options) { return this.fetch(`/assistants/${assistantId}/latest`, { method: "POST", json: { version }, signal: options?.signal }); } }; var ThreadsClient = class extends BaseClient { /** * Get a thread by ID. * * @param threadId ID of the thread. * @returns The thread. */ async get(threadId, options) { return this.fetch(`/threads/${threadId}`, { signal: options?.signal }); } /** * Create a new thread. * * @param payload Payload for creating a thread. * @returns The created thread. */ async create(payload) { const ttlPayload = typeof payload?.ttl === "number" ? { ttl: payload.ttl, strategy: "delete" } : payload?.ttl; return this.fetch(`/threads`, { method: "POST", json: { metadata: { ...payload?.metadata, graph_id: payload?.graphId }, thread_id: payload?.threadId, if_exists: payload?.ifExists, supersteps: payload?.supersteps?.map((s) => ({ updates: s.updates.map((u) => ({ values: u.values, command: u.command, as_node: u.asNode })) })), ttl: ttlPayload }, signal: payload?.signal }); } /** * Copy an existing thread * @param threadId ID of the thread to be copied * @returns Newly copied thread */ async copy(threadId, options) { return this.fetch(`/threads/${threadId}/copy`, { method: "POST", signal: options?.signal }); } /** * Update a thread. * * @param threadId ID of the thread. * @param payload Payload for updating the thread. * @returns The updated thread. */ async update(threadId, payload) { const ttlPayload = typeof payload?.ttl === "number" ? { ttl: payload.ttl, strategy: "delete" } : payload?.ttl; return this.fetch(`/threads/${threadId}`, { method: "PATCH", json: { metadata: payload?.metadata, ttl: ttlPayload }, signal: payload?.signal }); } /** * Delete a thread. * * @param threadId ID of the thread. */ async delete(threadId, options) { return this.fetch(`/threads/${threadId}`, { method: "DELETE", signal: options?.signal }); } /** * List threads * * @param query Query options * @returns List of threads */ async search(query) { return this.fetch("/threads/search", { method: "POST", json: { metadata: query?.metadata ?? void 0, ids: query?.ids ?? void 0, limit: query?.limit ?? 10, offset: query?.offset ?? 0, status: query?.status, sort_by: query?.sortBy, sort_order: query?.sortOrder, select: query?.select ?? void 0, values: query?.values ?? void 0 }, signal: query?.signal }); } /** * Count threads matching filters. * * @param query.metadata Thread metadata to filter on. * @param query.values State values to filter on. * @param query.status Thread status to filter on. * @returns Number of threads matching the criteria. */ async count(query) { return this.fetch(`/threads/count`, { method: "POST", json: { metadata: query?.metadata ?? void 0, values: query?.values ?? void 0, status: query?.status ?? void 0 }, signal: query?.signal }); } /** * Get state for a thread. * * @param threadId ID of the thread. * @returns Thread state. */ async getState(threadId, checkpoint, options) { if (checkpoint != null) { if (typeof checkpoint !== "string") return this.fetch(`/threads/${threadId}/state/checkpoint`, { method: "POST", json: { checkpoint, subgraphs: options?.subgraphs }, signal: options?.signal }); return this.fetch(`/threads/${threadId}/state/${checkpoint}`, { params: { subgraphs: options?.subgraphs }, signal: options?.signal }); } return this.fetch(`/threads/${threadId}/state`, { params: { subgraphs: options?.subgraphs }, signal: options?.signal }); } /** * Add state to a thread. * * @param threadId The ID of the thread. * @returns */ async updateState(threadId, options) { return this.fetch(`/threads/${threadId}/state`, { method: "POST", json: { values: options.values, checkpoint_id: options.checkpointId, checkpoint: options.checkpoint, as_node: options?.asNode }, signal: options?.signal }); } /** * 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, metadata, options) { let threadId; 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(`/threads/${threadId}/state`, { method: "PATCH", json: { metadata }, signal: options?.signal }); } /** * Get all past states for a thread. * * @param threadId ID of the thread. * @param options Additional options. * @returns List of thread states. */ async getHistory(threadId, options) { return this.fetch(`/threads/${threadId}/history`, { method: "POST", json: { limit: options?.limit ?? 10, before: options?.before, metadata: options?.metadata, checkpoint: options?.checkpoint }, signal: options?.signal }); } async *joinStream(threadId, options) { yield* this.streamWithRetry({ endpoint: `/threads/${threadId}/stream`, method: "GET", signal: options?.signal, headers: options?.lastEventId ? { "Last-Event-ID": options.lastEventId } : void 0, params: options?.streamMode ? { stream_mode: options.streamMode } : void 0 }); } }; var RunsClient = class extends BaseClient { /** * 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, assistantId, payload) { const json = { input: payload?.input, command: payload?.command, config: payload?.config, context: payload?.context, metadata: payload?.metadata, stream_mode: payload?.streamMode, stream_subgraphs: payload?.streamSubgraphs, stream_resumable: payload?.streamResumable, 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, checkpoint_during: payload?.checkpointDuring, durability: payload?.durability }; yield* this.streamWithRetry({ endpoint: threadId == null ? `/runs/stream` : `/threads/${threadId}/runs/stream`, method: "POST", json, signal: payload?.signal, onInitialResponse: (response) => { const runMetadata = getRunMetadataFromResponse(response); if (runMetadata) payload?.onRunCreated?.(runMetadata); } }); } /** * 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, assistantId, payload) { const json = { input: payload?.input, command: payload?.command, config: payload?.config, context: payload?.context, metadata: payload?.metadata, stream_mode: payload?.streamMode, stream_subgraphs: payload?.streamSubgraphs, stream_resumable: payload?.streamResumable, 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, checkpoint_during: payload?.checkpointDuring, durability: payload?.durability, langsmith_tracer: payload?._langsmithTracer ? { project_name: payload?._langsmithTracer?.projectName, example_id: payload?._langsmithTracer?.exampleId } : void 0 }; const endpoint = threadId === null ? "/runs" : `/threads/${threadId}/runs`; const [run, response] = await this.fetch(endpoint, { method: "POST", json, signal: payload?.signal, withResponse: true }); const runMetadata = getRunMetadataFromResponse(response); if (runMetadata) payload?.onRunCreated?.(runMetadata); return run; } /** * 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, options) { const filteredPayloads = payloads.map((payload) => ({ ...payload, assistant_id: payload.assistantId })).map((payload) => { return Object.fromEntries(Object.entries(payload).filter(([_, v]) => v !== void 0)); }); return this.fetch("/runs/batch", { method: "POST", json: filteredPayloads, signal: options?.signal }); } /** * 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, assistantId, payload) { const json = { input: payload?.input, command: payload?.command, config: payload?.config, context: payload?.context, 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, checkpoint_during: payload?.checkpointDuring, durability: payload?.durability, langsmith_tracer: payload?._langsmithTracer ? { project_name: payload?._langsmithTracer?.projectName, example_id: payload?._langsmithTracer?.exampleId } : void 0 }; const endpoint = threadId == null ? `/runs/wait` : `/threads/${threadId}/runs/wait`; const [run, response] = await this.fetch(endpoint, { method: "POST", json, timeoutMs: null, signal: payload?.signal, withResponse: true }); const runMetadata = getRunMetadataFromResponse(response); if (runMetadata) payload?.onRunCreated?.(runMetadata); if ((payload?.raiseError !== void 0 ? payload.raiseError : true) && "__error__" in run && typeof run.__error__ === "object" && run.__error__ && "error" in run.__error__ && "message" in run.__error__) throw new Error(`${run.__error__?.error}: ${run.__error__?.message}`); return run; } /** * 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, options) { return this.fetch(`/threads/${threadId}/runs`, { params: { limit: options?.limit ?? 10, offset: options?.offset ?? 0, status: options?.status ?? void 0, select: options?.select ?? void 0 }, signal: options?.signal }); } /** * 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, runId, options) { return this.fetch(`/threads/${threadId}/runs/${runId}`, { signal: options?.signal }); } /** * 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, runId, wait = false, action = "interrupt", options = {}) { return this.fetch(`/threads/${threadId}/runs/${runId}/cancel`, { method: "POST", params: { wait: wait ? "1" : "0", action }, signal: options?.signal }); } /** * Block until a run is done. * * @param threadId The ID of the thread. * @param runId The ID of the run. * @returns */ async join(threadId, runId, options) { return this.fetch(`/threads/${threadId}/runs/${runId}/join`, { timeoutMs: null, signal: options?.signal }); } /** * Stream output from a run in real-time, until the run is done. * * @param threadId The ID of the thread. Can be set to `null` | `undefined` for stateless runs. * @param runId The ID of the run. * @param options Additional options for controlling the stream behavior: * - signal: An AbortSignal that can be used to cancel the stream request * - lastEventId: The ID of the last event received. Can be used to reconnect to a stream without losing events. * - cancelOnDisconnect: When true, automatically cancels the run if the client disconnects from the stream * - streamMode: Controls what types of events to receive from the stream (can be a single mode or array of modes) * Must be a subset of the stream modes passed when creating the run. Background runs default to having the union of all * stream modes enabled. * @returns An async generator yielding stream parts. */ async *joinStream(threadId, runId, options) { const opts = typeof options === "object" && options != null && options instanceof AbortSignal ? { signal: options } : options; yield* this.streamWithRetry({ endpoint: threadId != null ? `/threads/${threadId}/runs/${runId}/stream` : `/runs/${runId}/stream`, method: "GET", signal: opts?.signal, headers: opts?.lastEventId ? { "Last-Event-ID": opts.lastEventId } : void 0, params: { cancel_on_disconnect: opts?.cancelOnDisconnect ? "1" : "0", stream_mode: opts?.streamMode } }); } /** * Delete a run. * * @param threadId The ID of the thread. * @param runId The ID of the run. * @returns */ async delete(threadId, runId, options) { return this.fetch(`/threads/${threadId}/runs/${runId}`, { method: "DELETE", signal: options?.signal }); } }; var StoreClient = class 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. * @param options.index Controls search indexing - null (use defaults), false (disable), or list of field paths to index. * @param options.ttl Optional time-to-live in minutes for the item, or null for no expiration. * @returns Promise<void> * * @example * ```typescript * await client.store.putItem( * ["documents", "user123"], * "item456", * { title: "My Document", content: "Hello World" }, * { ttl: 60 } // expires in 60 minutes * ); * ``` */ async putItem(namespace, key, value, options) { namespace.forEach((label) => { if (label.includes(".")) throw new Error(`Invalid namespace label '${label}'. Namespace labels cannot contain periods ('.')`); }); const payload = { namespace, key, value, index: options?.index, ttl: options?.ttl }; return this.fetch("/store/items", { method: "PUT", json: payload, signal: options?.signal }); } /** * Retrieve a single item. * * @param namespace A list of strings representing the namespace path. * @param key The unique identifier for the item. * @param options.refreshTtl Whether to refresh the TTL on this read operation. If null, uses the store's default behavior. * @returns Promise<Item> * * @example * ```typescript * const item = await client.store.getItem( * ["documents", "user123"], * "item456", * { refreshTtl: true } * ); * console.log(item); * // { * // namespace: ["documents", "user123"], * // key: "item456", * // value: { title: "My Document", content: "Hello World" }, * // createdAt: "2024-07-30T12:00:00Z", * // updatedAt: "2024-07-30T12:00:00Z" * // } * ``` */ async getItem(namespace, key, options) { namespace.forEach((label) => { if (label.includes(".")) throw new Error(`Invalid namespace label '${label}'. Namespace labels cannot contain periods ('.')`); }); const params = { namespace: namespace.join("."), key }; if (options?.refreshTtl !== void 0) params.refresh_ttl = options.refreshTtl; const response = await this.fetch("/store/items", { params, signal: options?.signal }); 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, key, options) { namespace.forEach((label) => { if (label.includes(".")) throw new Error(`Invalid namespace label '${label}'. Namespace labels cannot contain periods ('.')`); }); return this.fetch("/store/items", { method: "DELETE", json: { namespace, key }, signal: options?.signal }); } /** * 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. * @param options.refreshTtl Whether to refresh the TTL on items returned by this search. If null, uses the store's default behavior. * @returns Promise<SearchItemsResponse> * * @example * ```typescript * const results = await client.store.searchItems( * ["documents"], * { * filter: { author: "John Doe" }, * limit: 5, * refreshTtl: true * } * ); * console.log(results); * // { * // items: [ * // { * // namespace: ["documents", "user123"], * // key: "item789", * // value: { title: "Another Document", author: "John Doe" }, * // createdAt: "2024-07-30T12:00:00Z", * // updatedAt: "2024-07-30T12:00:00Z" * // }, * // // ... additional items ... * // ] * // } * ``` */ async searchItems(namespacePrefix, options) { const payload = { namespace_prefix: namespacePrefix, filter: options?.filter, limit: options?.limit ?? 10, offset: options?.offset ?? 0, query: options?.query, refresh_ttl: options?.refreshTtl }; return { items: (await this.fetch("/store/items/search", { method: "POST", json: payload, signal: options?.signal })).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) { const payload = { prefix: options?.prefix, suffix: options?.suffix, max_depth: options?.maxDepth, limit: options?.limit ?? 100, offset: options?.offset ?? 0 }; return this.fetch("/store/namespaces", { method: "POST", json: payload, signal: options?.signal }); } }; var UiClient = class UiClient extends BaseClient { static promiseCache = {}; static getOrCached(key, fn) { if (UiClient.promiseCache[key] != null) return UiClient.promiseCache[key]; const promise = fn(); UiClient.promiseCache[key] = promise; return promise; } async getComponent(assistantId, agentName) { return UiClient.getOrCached(`${this.apiUrl}-${assistantId}-${agentName}`, async () => { let [url, init] = this.prepareFetchOptions(`/ui/${assistantId}`, { headers: { Accept: "text/html", "Content-Type": "application/json" }, method: "POST", json: { name: agentName } }); if (this.onRequest != null) init = await this.onRequest(url, init); return (await this.asyncCaller.fetch(url, init)).text(); }); } }; var Client = class { /** * The client for interacting with assistants. */ assistants; /** * The client for interacting with threads. */ threads; /** * The client for interacting with runs. */ runs; /** * The client for interacting with cron runs. */ crons; /** * The client for interacting with the KV store. */ store; /** * The client for interacting with the UI. * @internal Used by LoadExternalComponent and the API might change in the future. */ "~ui"; /** * @internal Used to obtain a stable key representing the client. */ "~configHash"; constructor(config) { this["~configHash"] = JSON.stringify({ apiUrl: config?.apiUrl, apiKey: config?.apiKey, timeoutMs: config?.timeoutMs, defaultHeaders: config?.defaultHeaders, maxConcurrency: config?.callerOptions?.maxConcurrency, maxRetries: config?.callerOptions?.maxRetries, callbacks: { onFailedResponseHook: config?.callerOptions?.onFailedResponseHook != null, onRequest: config?.onRequest != null, fetch: config?.callerOptions?.fetch != null } }); 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); this["~ui"] = new UiClient(config); } }; /** * @internal Used to obtain a stable key representing the client. */ function getClientConfigHash(client) { return client["~configHash"]; } //#endregion export { AssistantsClient, Client, CronsClient, RunsClient, StoreClient, ThreadsClient, getApiKey, getClientConfigHash }; //# sourceMappingURL=client.js.map