UNPKG

@langchain/langgraph-sdk

Version:

Client library for interacting with the LangGraph API

536 lines (535 loc) • 24.1 kB
import { Interrupt } from "../schema.js"; import { ClientConfig } from "../client/base.js"; import { AssembledToolCall } from "../client/stream/handles/tools.js"; import { AgentServerAdapter, TransportAdapter } from "../client/stream/transport.js"; import { ThreadStreamOptions } from "../client/stream/types.js"; import { ThreadStream } from "../client/stream/index.js"; import { Client } from "../client/index.js"; import { AnyHeadlessToolImplementation, OnToolCallback } from "../headless-tools.js"; import { StreamStore } from "./store.js"; import { DEFAULT_MESSAGES_KEY } from "./constants.js"; import { BaseMessage as BaseMessage$1 } from "@langchain/core/messages"; import { Channel, Event as Event$1, Goto } from "@langchain/protocol"; //#region src/stream/types.d.ts /** Why a run's active streaming phase ended. */ type RunExecutionReason = /** The run reached the protocol `completed` lifecycle event. */"success" /** The run reached the protocol `failed` lifecycle event. */ | "error" /** The run paused on a protocol `interrupted` lifecycle event. */ | "interrupt" /** The run was stopped by a client-side abort. */ | "stopped"; /** Payload for run-start callbacks. */ interface RunExecutionInfo { runId: string; } /** Options for {@link StreamController.stop} / framework `stop()`. */ interface StreamStopOptions { /** * When `true` (default), issue a server-side cancel via * `client.runs.cancel` for the active run before disconnecting the * client transport. Set to `false` for join/rejoin flows where the * agent should keep running after the client disconnects. */ cancel?: boolean; } /** * Options for {@link StreamController.respondAll} / framework * `respondAll()`. * * Carries run-level `config` / `metadata` onto the single run that * services the batched resume — the same fields as * {@link StreamRespondOptions}, minus the per-interrupt target (each * response in the map carries its own `interruptId` as the key). */ interface StreamRespondAllOptions<ConfigurableType extends object = Record<string, unknown>> { config?: { configurable?: ConfigurableType; recursion_limit?: number; tags?: string[]; [key: string]: unknown; }; metadata?: Record<string, unknown>; /** * State update applied in the **same superstep** as the resume, mapped to * LangGraph's `Command(update=...)`. The resumed run produces a single * checkpoint reflecting both the resume value and this update — no separate * `updateState` write, no intermediate checkpoint, no flicker. * * The canonical use case is a HITL flow where the UI pushes the interrupt * card (e.g. an `AIMessage`) into state at the moment it answers the * interrupt, so the card is committed before the resumed tool runs and stays * rendered without the backend re-emitting it. * * Accepts a state-keys object (shallow-merged via the graph's channel * reducers) or a list of `[key, value]` entries. * * Messages under the configured `messagesKey` may be either plain * message dicts (`{ type: "ai", content: "…" }`) or `@langchain/core` * `BaseMessage` instances (`new AIMessage("…")`) — instances are * serialized to dicts before transport, exactly like `submit()`. */ update?: Record<string, unknown> | [string, unknown][]; /** * Directed jump applied in the **same superstep** as the resume, mapped to * LangGraph's `Command(goto=...)`. A target node name, a `Send` * (`{ node, input }`), or a list mixing the two for fan-out. */ goto?: Goto; } /** * Options for {@link StreamController.respond} / framework `respond()`. * * Targets a single pending interrupt (`interruptId` / `namespace`) and * carries run-level `config` and `metadata` onto the resume so the * resumed run applies the same configurable values (model, user context, * timezone, …) and metadata (trigger source, test flags, …) a fresh * {@link StreamSubmitOptions} would. The server folds these into the run * it starts to service the `input.respond` command. * * To resume several interrupts pending at the same checkpoint, use * {@link StreamController.respondAll} instead. */ interface StreamRespondOptions<ConfigurableType extends object = Record<string, unknown>> extends StreamRespondAllOptions<ConfigurableType> { /** * Target a specific pending interrupt. Omit when exactly one * interrupt is pending to resume the newest unresolved one; pass it * when several can be active (parallel subagents, fan-out, nested * graphs) so you resume the interrupt the user acted on. */ interruptId?: string; /** * Namespace of the targeted interrupt. Root interrupts use `[]`. * When omitted, resolved from `thread.interrupts` / * `stream.interrupts` for the given `interruptId`. */ namespace?: string[]; } /** Payload for run-end callbacks. */ interface RunCompletedInfo extends Omit<RunExecutionInfo, "runId"> { /** Omitted when re-attaching to an in-flight run without local dispatch. */ runId?: string; /** Why the active streaming phase ended. */ reason: RunExecutionReason; } /** Options common to both transport branches of framework `useStream` APIs. */ interface UseStreamCommonOptions<StateType extends object, ThreadIdType = string | null> { threadId?: ThreadIdType; onThreadId?: (threadId: string) => void; /** * Convenience callback fired when this hook instance's run is accepted * by the server. Prefer `stream.isLoading` for UI; use this for * imperative run-execution side effects. */ onCreated?: (info: RunExecutionInfo) => void; /** * Convenience callback fired when a run's active streaming phase ends. * `runId` may be omitted for re-attached in-flight runs because no local * dispatch response was observed. */ onCompleted?: (info: RunCompletedInfo) => void; initialValues?: StateType; /** State key holding the message array. Defaults to {@link DEFAULT_MESSAGES_KEY}. */ messagesKey?: string; /** Headless tool implementations; auto-resumes matching interrupts. */ tools?: AnyHeadlessToolImplementation[]; /** Observe lifecycle events for registered {@link tools}. */ onTool?: OnToolCallback; /** * Optimistic UI for `submit()`. When enabled (the default), the input * passed to `submit()` is reflected in `values` / `messages` * immediately — before the server responds — then reconciled against * the authoritative server state as it streams in: * * - Messages in the input are appended right away. Any message * without an `id` is assigned a stable client id (sent to the * server, which `add_messages` preserves) so the server echo * reconciles by id instead of duplicating. Per-message progress * is exposed via `useMessageMetadata(stream, id).optimisticStatus` * (`"pending"` → `"sent"`, or `"failed"` if the run errors before * the message is echoed; failed optimistic messages are kept for * retry UIs and dropped on the next `hydrate()`). * - Other input keys are shallow-merged into `values` and converge * to server truth on the first `values` event (or are rolled back * if the run fails before any echo). * * Set to `false` to dispatch input verbatim with no client-side echo * or id minting (server-authoritative only) — useful for non-chat * state graphs or deterministic SSR/tests. * * @default true */ optimistic?: boolean; } /** * Agent-server branch: caller points `useStream` at an assistant on a * LangGraph-Platform-compatible server. Discriminated against * {@link CustomAdapterOptions} by `transport` being absent or a string. */ interface AgentServerOptions<StateType extends object, ThreadIdType = string | null, ApiUrlType = string | undefined, ApiKeyType = string | undefined> extends UseStreamCommonOptions<StateType, ThreadIdType> { assistantId: string; client?: Client; apiUrl?: ApiUrlType; apiKey?: ApiKeyType; callerOptions?: ClientConfig["callerOptions"]; defaultHeaders?: ClientConfig["defaultHeaders"]; /** Built-in wire transport. Defaults to `"sse"`. */ transport?: "sse" | "websocket"; /** Optional `fetch` override forwarded to the built-in SSE transport. */ fetch?: typeof fetch; /** Optional `WebSocket` factory for the built-in WS transport. */ webSocketFactory?: (url: string) => WebSocket; /** * Built-in transports only: max reconnect attempts after an unexpected * disconnect. Defaults to {@link DEFAULT_MAX_RECONNECT_ATTEMPTS}. * Independent of whether {@link fetch} is set. */ maxReconnectAttempts?: number; /** * Built-in SSE transport only: idle-reconnect policy. Defaults to * {@link DEFAULT_IDLE_RECONNECT}. * Pass `0` to disable. */ streamIdleReconnect?: ThreadStreamOptions["streamIdleReconnect"]; /** Built-in transports only: delay before each reconnect attempt. */ reconnectDelayMs?: ThreadStreamOptions["reconnectDelayMs"]; /** Built-in transports only: invoked before each reconnect attempt. */ onReconnect?: ThreadStreamOptions["onReconnect"]; /** Built-in transports only: invoked after every usable connection. */ onConnected?: ThreadStreamOptions["onConnected"]; /** * Back `"enqueue"` with real, durable server-side runs instead of an * in-memory client-only queue. Defaults to `"local"`. * * `"server"` requires the backend behind `apiUrl` to implement the * Runs REST endpoints — `POST` / `GET /threads/{thread_id}/runs` and * `POST /threads/{thread_id}/runs/{run_id}/cancel` — not just the * streaming/commands protocol. A server pointed at directly (LangGraph * Platform, or `langgraph dev`) already has these; a custom backend * proxying LangGraph does not unless it adds them itself. */ queue?: "local" | "server"; } /** * Custom-adapter branch: caller brings their own * {@link AgentServerAdapter}. Discriminated against * {@link AgentServerOptions} by `transport` being an adapter instance. */ interface CustomAdapterOptions<StateType extends object, ThreadIdType = string | null, CustomAssistantIdType = never> extends UseStreamCommonOptions<StateType, ThreadIdType> { /** * Custom {@link AgentServerAdapter} used for every command and * subscription. Replaces the built-in `sse`/`websocket` factories * entirely. */ transport: AgentServerAdapter; /** * Optional assistant id passed through to the adapter. Defaults to * `"_"`; adapters that don't multiplex on assistant id can ignore it. */ assistantId?: CustomAssistantIdType; client?: never; apiUrl?: never; apiKey?: never; callerOptions?: never; defaultHeaders?: never; fetch?: never; webSocketFactory?: never; maxReconnectAttempts?: never; streamIdleReconnect?: never; reconnectDelayMs?: never; onReconnect?: never; onConnected?: never; /** Not supported with a custom transport. */ queue?: never; } /** * Options accepted by framework `useStream` APIs. Discriminated on the * shape of `transport`: * * - omitted or a string (`"sse"` / `"websocket"`) -> agent-server branch * ({@link AgentServerOptions}); supply `assistantId` + `apiUrl`. * - an {@link AgentServerAdapter} instance -> custom-adapter branch * ({@link CustomAdapterOptions}); bring your own transport. */ type UseStreamOptions<StateType extends object = Record<string, unknown>, ThreadIdType = string | null, ApiUrlType = string | undefined, ApiKeyType = string | undefined, CustomAssistantIdType = never> = AgentServerOptions<StateType, ThreadIdType, ApiUrlType, ApiKeyType> | CustomAdapterOptions<StateType, ThreadIdType, CustomAssistantIdType>; /** * Read-only fan-out of the {@link StreamController}'s always-on root * subscription. Projections that only need a subset of the root pump's * channels at the root namespace can attach here instead of opening a * second server subscription. */ interface RootEventBus { /** Channels covered by the root pump. */ readonly channels: readonly Channel[]; /** Subscribe; returns an unsubscribe handle. */ subscribe(listener: (event: Event$1) => void): () => void; /** * Optional fast path for idle/stale threads: seed a scoped projection from * checkpoint history instead of opening a replaying `/events` subscription. * This produces a snapshot for finished-thread reconnects; active and * interrupted threads return `false` so projections subscribe normally. * Returns `false` when history cannot satisfy the projection and the caller * should fall back to its normal subscription. */ trySeedFromHistory?<T>(params: { kind: "messages" | "toolCalls"; namespace: readonly string[]; store: StreamStore<T>; }): Promise<boolean>; } /** * Always-on root snapshot surfaced by {@link StreamController.rootStore}. * * Populated by a single multi-channel subscription at the thread root * (`values`, `lifecycle`, `input`, `messages`, `tools`). Every app * pays for this — selector hooks for scoped projections layer on top. */ interface RootSnapshot<StateType extends object = Record<string, unknown>, InterruptType = unknown> { /** Latest state values from the `values` channel. */ values: StateType; /** Root-namespace messages (class instances). */ messages: BaseMessage$1[]; /** Root-namespace tool calls (assembled). */ toolCalls: AssembledToolCall[]; /** Interrupts observed on the root namespace. */ interrupts: Interrupt<InterruptType>[]; /** Convenience alias for `interrupts[0]`. */ interrupt: Interrupt<InterruptType> | undefined; /** True while a run is active / being started on the current thread. */ isLoading: boolean; /** True while the initial `thread.state.get()` hydration is in flight. */ isThreadLoading: boolean; /** Last error observed on the active run / hydration. */ error: unknown; /** Current thread id (may be `null` until the first `submit`). */ threadId: string | null; } interface StreamControllerOptions<StateType extends object = Record<string, unknown>> { /** Assistant the thread is bound to for its lifetime. */ assistantId: string; /** Client used to construct `ThreadStream`s. */ client: Client<StateType>; /** Initial thread id; if `null`, one is generated on first submit. */ threadId?: string | null; /** * How the controller talks to the agent server. Accepts either a * built-in transport string (`"sse"` / `"websocket"`) or a custom * {@link AgentServerAdapter} that bypasses the built-in factories * entirely. Forwarded to `client.threads.stream({ transport })`. */ transport?: ThreadStreamOptions["transport"]; /** Optional `fetch` override forwarded to the built-in SSE transport. */ fetch?: typeof fetch; /** Optional `WebSocket` factory forwarded to the built-in WS transport. */ webSocketFactory?: (url: string) => WebSocket; /** * Built-in transports only: max reconnect attempts after an unexpected * disconnect. Defaults to {@link DEFAULT_MAX_RECONNECT_ATTEMPTS}. * Independent of whether {@link fetch} is set — auth shims keep reconnect * enabled. Pass `0` to disable. */ maxReconnectAttempts?: number; /** * Built-in SSE transport only: idle-reconnect policy. Defaults to * {@link DEFAULT_IDLE_RECONNECT}. * Pass `0` to disable. See {@link ThreadStreamOptions.streamIdleReconnect}. */ streamIdleReconnect?: ThreadStreamOptions["streamIdleReconnect"]; /** Built-in transports only: delay before each reconnect attempt. */ reconnectDelayMs?: ThreadStreamOptions["reconnectDelayMs"]; /** Built-in transports only: invoked before each reconnect attempt. */ onReconnect?: ThreadStreamOptions["onReconnect"]; /** Built-in transports only: invoked after every usable connection. */ onConnected?: ThreadStreamOptions["onConnected"]; /** * Back `"enqueue"` with real, durable server-side runs instead of an * in-memory client-only queue. Defaults to `"local"`. Only meaningful * when `transport` isn't a custom `AgentServerAdapter` — see * {@link AgentServerOptions.queue}. */ queue?: "local" | "server"; /** Called when a thread id is first produced (new-thread submits). */ onThreadId?: (threadId: string) => void; /** * Convenience callback fired when this hook instance's run is accepted * by the server. Prefer `root.isLoading` for UI; use this for * imperative run-execution side effects. */ onCreated?: (info: RunExecutionInfo) => void; /** * Convenience callback fired when a run's active streaming phase ends. * `runId` may be omitted for re-attached in-flight runs because no local * dispatch response was observed. */ onCompleted?: (info: RunCompletedInfo) => void; /** Initial state for `root.values` before hydration lands. */ initialValues?: StateType; /** Key inside `values` that holds the message array. Defaults to {@link DEFAULT_MESSAGES_KEY}. */ messagesKey?: string; /** * Optimistic UI for `submit()`. Defaults to `true`. See * {@link UseStreamCommonOptions.optimistic} for the full contract. */ optimistic?: boolean; } interface StreamSubmitOptions<StateType extends object = Record<string, unknown>, ConfigurableType extends object = Record<string, unknown>> { config?: { configurable?: ConfigurableType; recursion_limit?: number; tags?: string[]; [key: string]: unknown; }; metadata?: Record<string, unknown>; /** * Fork the run from an explicit checkpoint instead of the thread's * latest. Ergonomic alias the SDK folds into * `config.configurable.checkpoint_id` before dispatching the run, so * the server receives the fork target via the single legacy-compliant * field (never a top-level `forkFrom`). */ forkFrom?: string; /** * Behaviour when a run is already in-flight on the thread. * * - `"rollback"` (default) — abort the active run client-side and * start the new one immediately. * - `"interrupt"` — server-side cancel of the in-flight run, then * start the new one. * - `"enqueue"` — do NOT abort the active run; the new submission * lands in {@link StreamController.queueStore} and is forwarded * once the current run terminates. * - `"reject"` — error out client-side when a run is already in * flight. * * `"rollback"`, `"reject"`, and `"enqueue"` are honoured client-side. * `"interrupt"` is accepted on the type surface but currently falls * through to `"rollback"` behaviour pending the matching server-side * cancel work. */ multitaskStrategy?: "rollback" | "interrupt" | "enqueue" | "reject"; signal?: AbortSignal; /** * Per-submit thread-id override. When provided, the controller * rebinds to this thread before dispatching the run; subsequent * submits stick with the new id unless the hook's `threadId` prop * changes. Useful when you want to start a new thread without * unmounting the component (e.g. "New Chat" buttons). */ threadId?: string | null; /** * Per-submit error callback. Invoked when the run errors out — * either before the first event lands (network/dispatch failure) * or mid-stream. Does NOT suppress the error from being written * to {@link RootSnapshot.error}; the callback is a local hook for * showing toasts or routing the submission error to a component * state slot, letting the rest of the UI keep using * `stream.error` for render-level error display. */ onError?: (error: unknown) => void; _stateType?: StateType; } /** * Lightweight discovery record for a subagent running inside the thread. * * Populated eagerly from the root subscription (`tools` + `lifecycle` * channels). Content projections (`messages`, `toolCalls`, custom * extensions) are opened lazily via selector hooks, keyed on * {@link namespace}. */ interface SubagentDiscoverySnapshot { /** Tool-call id that created the subagent. */ readonly id: string; /** Subagent type, e.g. `"researcher"`. */ readonly name: string; /** Namespace prefix for every event emitted by this subagent. */ readonly namespace: readonly string[]; /** Parent subagent id, or `null` if spawned from the root. */ readonly parentId: string | null; /** Nesting depth from the root (root = 0). */ readonly depth: number; /** Lifecycle status derived from task tool events. */ readonly status: "running" | "complete" | "error"; /** Task description passed to the subagent. */ readonly taskInput: string | undefined; /** Raw output payload once the subagent completes. */ readonly output: unknown; /** Error message if the subagent failed. */ readonly error: string | undefined; /** Wall-clock timestamp when the task tool started. */ readonly startedAt: Date; /** Wall-clock timestamp when the task tool terminated (null if running). */ readonly completedAt: Date | null; } /** * Lightweight discovery record for a subgraph running inside the thread. */ interface SubgraphDiscoverySnapshot { readonly id: string; readonly namespace: readonly string[]; /** * Name of the graph node that produced this subgraph invocation, * parsed from the last namespace segment. LangGraph assigns every * node invocation a checkpoint namespace shaped like * `<node_name>:<uuid>`; this is the `<node_name>` half, letting * callers key lookups on names they recognise from * `addNode(name, …)` without parsing the namespace themselves. */ readonly nodeName: string; readonly status: "running" | "complete" | "error"; readonly startedAt: Date; readonly completedAt: Date | null; } /** * Anything with a `namespace` can be passed to selector hooks as a * scope target. Both {@link SubagentDiscoverySnapshot} and * {@link SubgraphDiscoverySnapshot} satisfy this; callers can also * hand-build `{ namespace: [...] }` objects. */ type Target = undefined | { readonly namespace: readonly string[]; }; /** * A projection spec describes a single logical subscription managed * by the {@link ChannelRegistry}. Framework bindings don't construct * these directly — the per-kind factory functions in * `stream/projections/` emit them. */ interface ProjectionSpec<T> { /** * Identity key. Two specs with the same key share one registry * entry (and thus one server subscription + one store) across all * consumers. */ readonly key: string; /** Namespace this projection is scoped to (`[]` = root). */ readonly namespace: readonly string[]; /** Initial store value before any events arrive. */ readonly initial: T; /** * Open the projection against a bound {@link ThreadStream} and * start pumping events into `store`. Called once per registry entry * (not per consumer). The returned `dispose` function is invoked * when the registry's ref count for this entry drops to zero or * when the registry is rebound to a different thread. */ open(params: { thread: ThreadStream; store: StreamStore<T>; /** * Read-only fan-out of the controller's always-on root * subscription. Projections scoped to the root namespace whose * channel set is a subset of {@link RootEventBus.channels} should * subscribe here instead of opening a second server subscription. */ rootBus: RootEventBus; }): ProjectionRuntime; } interface ProjectionRuntime { dispose(): Promise<void> | void; } /** * Handle returned by `ChannelRegistry.acquire`. Framework bindings * use `store` as the reactivity source and must call `release()` when * the consumer tears down. */ interface AcquiredProjection<T> { readonly store: StreamStore<T>; release(): void; } //#endregion export { AcquiredProjection, AgentServerOptions, CustomAdapterOptions, ProjectionRuntime, ProjectionSpec, RootEventBus, RootSnapshot, RunCompletedInfo, RunExecutionInfo, RunExecutionReason, StreamControllerOptions, StreamRespondAllOptions, StreamRespondOptions, StreamStopOptions, StreamSubmitOptions, SubagentDiscoverySnapshot, SubgraphDiscoverySnapshot, Target, UseStreamCommonOptions, UseStreamOptions }; //# sourceMappingURL=types.d.ts.map