@langchain/langgraph-sdk
Version:
Client library for interacting with the LangGraph API
253 lines (252 loc) • 11.5 kB
text/typescript
import { ThreadStream } from "../client/stream/index.cjs";
import { StreamStore } from "./store.cjs";
import { RootSnapshot, StreamControllerOptions, StreamRespondAllOptions, StreamRespondOptions, StreamStopOptions, StreamSubmitOptions } from "./types.cjs";
import { ChannelRegistry } from "./channel-registry.cjs";
import { SubagentMap } from "./discovery/subagents.cjs";
import { SubgraphByNodeMap, SubgraphMap } from "./discovery/subgraphs.cjs";
import { MessageMetadata as MessageMetadata$1, MessageMetadataMap } from "./message-metadata-tracker.cjs";
import { SubmissionQueueEntry, SubmissionQueueSnapshot } from "./queue-adapter.cjs";
import { Channel } from "@langchain/protocol";
//#region src/stream/controller.d.ts
/**
* Channel set covered by the always-on root subscription. Exported so
* projections (and transports) can reason about what the root pump
* already delivers before opening additional server subscriptions.
*/
declare const ROOT_PUMP_CHANNELS: readonly Channel[];
/**
* Coordinates one thread's protocol-v2 stream and exposes stable
* observable projections for framework bindings.
*
* The controller owns the root subscription, lazily binds scoped
* projections through {@link ChannelRegistry}, and normalizes protocol
* events into class-message, tool-call, discovery, interrupt, and queue
* stores.
*
* @typeParam StateType - Shape of the graph state exposed on `values`.
* @typeParam InterruptType - Shape of protocol interrupt payloads.
* @typeParam ConfigurableType - Shape of `config.configurable` accepted by submit.
*/
declare class StreamController<StateType extends object = Record<string, unknown>, InterruptType = unknown, ConfigurableType extends object = Record<string, unknown>> {
#private;
readonly rootStore: StreamStore<RootSnapshot<StateType, InterruptType>>;
readonly subagentStore: StreamStore<SubagentMap>;
readonly subgraphStore: StreamStore<SubgraphMap>;
readonly subgraphByNodeStore: StreamStore<SubgraphByNodeMap>;
readonly messageMetadataStore: StreamStore<MessageMetadataMap>;
readonly queueStore: StreamStore<SubmissionQueueSnapshot<StateType>>;
readonly registry: ChannelRegistry;
/**
* Create a controller around a LangGraph client and optional initial thread.
*
* @param options - Runtime configuration, client, thread id, and initial state.
*/
constructor(options: StreamControllerOptions<StateType>);
/**
* Promise that settles the first time {@link hydrate} finishes on
* the current thread. Resolves on a clean hydration, rejects when
* the thread-state fetch errors. A fresh promise is installed on
* every thread swap so `<Suspense>` wrappers re-suspend on
* `switchThread`.
*/
get hydrationPromise(): Promise<void>;
/**
* Fetch the checkpointed thread state and seed the root snapshot.
* Re-calling with a different `threadId` swaps the underlying
* {@link ThreadStream}, rewires the registry to the new thread, and
* resets assemblers.
*
* @param threadId - Optional replacement thread id; `null` clears the active thread.
*/
hydrate(threadId?: string | null): Promise<void>;
/**
* Lazily resolve a single subagent's execution namespace from
* checkpoint history. Intended call site: the first scoped
* `useMessages` / `useToolCalls` mount for a subagent whose namespace
* is still the default `tools:<toolCallId>`. A fallback for the
* hydrate-time bulk seed ({@link #seedDiscoveryFromHistory}) — most
* subagents are already promoted by the time a panel opens.
*
* Skips ids already promoted past default-only (SSE replay or a prior
* resolve). Concurrent calls for the same id share one `getHistory`
* walk via {@link #namespaceResolves}.
*
* @param toolCallId - Parent `task` tool-call id (the subagent's discovery key).
*/
resolveSubagentNamespace(toolCallId: string): Promise<void>;
/**
* Submit input to the active thread.
*
* To resume a pending interrupt, use {@link respond} instead.
*
* @param input - Input payload for a new run.
* @param options - Per-run config, metadata, multitask behavior, and callbacks.
*/
submit(input: unknown, options?: StreamSubmitOptions<StateType, ConfigurableType>): Promise<void>;
/**
* Disconnect the client from the active run and mark the controller
* idle. By default also cancels the run server-side; pass
* `{ cancel: false }` or call {@link disconnect} to keep the agent
* running (join/rejoin).
*/
stop(options?: StreamStopOptions): Promise<void>;
/**
* Disconnect the client without cancelling the run server-side.
* Alias for `stop({ cancel: false })`.
*/
disconnect(): Promise<void>;
/**
* Cancel a queued submission by id. Returns `true` when the entry
* was found and removed, `false` otherwise.
*
* Removes the client-side entry either way; also issues a server-side
* cancel when the configured queue adapter is server-backed (see
* `AgentServerAdapter.serverQueue`). A no-op for the default,
* client-only adapter.
*
* @param id - Client-side queue entry id to remove.
*/
cancelQueued(id: string): Promise<boolean>;
/**
* Drop every queued submission. Cancels the underlying server-side
* runs too when the configured queue adapter is server-backed.
*/
clearQueue(): Promise<void>;
/**
* Respond to a single pending protocol interrupt.
*
* When `options.interruptId` is omitted, resolution walks
* {@link ThreadStream.interrupts `thread.interrupts`} from newest to
* oldest and picks the first entry whose `interruptId` has not already
* been resolved by a prior `respond()` call. That entry may be at the
* root (`namespace: []`) or inside a subgraph (non-empty `namespace`).
* {@link RootSnapshot.interrupts `rootStore.interrupts`} /
* framework `stream.interrupts` mirrors the same pending interrupts
* (including nested namespaces) for UI rendering.
*
* Omitting `interruptId` is fine when exactly one interrupt is pending.
* When several can be active (parallel subagents, fan-out, nested
* graphs), pass an explicit `interruptId` so you resume the interrupt
* the user acted on. `namespace` is resolved automatically from
* `thread.interrupts` / `stream.interrupts` when omitted.
*
* To resume several interrupts pending at the same checkpoint in one
* command, use {@link respondAll} — sequential single `respond()` calls
* would not work, since the first resume starts a run, leaving the
* others with no interrupted run to respond to.
*
* The server validates `namespace` against the pending interrupt. Root
* interrupts use `namespace: []`. Subgraph interrupts carry the exact
* tuple on each {@link Interrupt} / {@link InterruptPayload} entry.
*
* @param response - Payload sent back to the interrupted namespace.
* @param options - Optional target (`interruptId` / `namespace`) and
* run-level `config` / `metadata` folded into the run that services
* the resume (model/user config, trigger source, test flags, …).
* Equivalent to the same fields on {@link StreamSubmitOptions}.
*
* @example Single pending interrupt (safe to omit a target)
* ```ts
* await controller.respond({ approved: true });
* ```
*
* @example Carry run config / metadata onto the resume
* ```ts
* await controller.respond(
* { approved: true },
* { config: { configurable: { model: "gpt-4o" } }, metadata: { source: "ui" } },
* );
* ```
*
* @example Multiple / nested interrupts — target by id
* ```tsx
* for (const intr of stream.interrupts) {
* await stream.respond(decide(intr.value), { interruptId: intr.id! });
* }
* ```
*
* Each {@link InterruptPayload} on `thread.interrupts` and each
* {@link Interrupt} on `stream.interrupts` mirrors an
* `input.requested` event with its protocol `namespace`.
*/
respond(response: unknown, options?: StreamRespondOptions<ConfigurableType>): Promise<void>;
/**
* Resume several pending interrupts at the same checkpoint in a single
* command.
*
* Required when a run pauses on multiple interrupts simultaneously
* (e.g. parallel tool-authorization prompts): a single
* `Command({ resume })` carrying every interrupt's payload resumes them
* together. Sequential {@link respond} calls would fail because the
* first resume starts a run, leaving the rest with no interrupted run to
* respond to.
*
* `responsesById` maps each pending `interruptId` to the payload sent
* back to it, so different interrupts can receive different responses
* (approve one, deny another). To send the *same* payload to several
* interrupts, build the map with that value for each id, e.g.
* `Object.fromEntries(ids.map((id) => [id, response]))`.
*
* The server resumes by `interruptId`, so namespaces are resolved
* internally from `getThread()?.interrupts` and need not be supplied.
*
* @param responsesById - Map of pending `interruptId` to its response
* payload. Must contain at least one entry.
* @param options - Optional run-level `config` / `metadata` folded into
* the single run that services the batched resume. Equivalent to the
* same fields on {@link StreamSubmitOptions}.
*
* @example Distinct payloads per interrupt
* ```tsx
* await stream.respondAll({
* [interruptA.id]: { approved: true },
* [interruptB.id]: { approved: false },
* });
* ```
*
* @example Same payload to every pending interrupt
* ```tsx
* await stream.respondAll(
* Object.fromEntries(stream.interrupts.map((i) => [i.id!, { approved: true }])),
* );
* ```
*/
respondAll(responsesById: Record<string, unknown>, options?: StreamRespondAllOptions<ConfigurableType>): Promise<void>;
/**
* Dispose the active thread, subscriptions, registry entries, and listeners.
*/
dispose(): Promise<void>;
/**
* StrictMode-safe lifecycle hook for framework bindings.
*
* React 18+ `StrictMode` intentionally mounts → unmounts → remounts
* components in dev to surface effect-cleanup bugs. A naive
* `useEffect(() => () => controller.dispose())` would permanently
* tear the controller down on that first synthetic unmount, leaving
* every subsequent `submit()` a silent no-op.
*
* Call {@link activate} from the bind site's effect and return the
* result as the effect's cleanup. The controller uses deferred
* disposal: a `release()` only schedules a dispose on the next
* microtask, which is cancelled if another `activate()` arrives
* before it fires (the normal StrictMode remount path).
*/
activate(): () => void;
/**
* Returns the bound {@link ThreadStream}, if one exists. Prefer
* {@link StreamController.rootStore} and selector projections for
* UI work; use this for low-level protocol access.
*/
getThread(): ThreadStream | undefined;
/**
* Listen for `ThreadStream` lifecycle (swap on thread-id change,
* detach on dispose). The listener fires immediately with the
* current thread (may be `undefined`).
*
* @param listener - Callback invoked immediately and on every thread swap.
*/
subscribeThread(listener: (thread: ThreadStream | undefined) => void): () => void;
}
//#endregion
export { ROOT_PUMP_CHANNELS, StreamController };
//# sourceMappingURL=controller.d.cts.map