import { MessageTupleManager, ensureHistoryMessageInstances, ensureMessageInstances, toMessageClass } from "./messages.js";
import { flushPendingHeadlessToolInterrupts } from "../headless-tools.js";
import { extractInterrupts, userFacingInterruptsFromThreadTasks, userFacingInterruptsFromValuesArray } from "./interrupts.js";
import { StreamError } from "./errors.js";
import { getToolCallsWithResults } from "../utils/tools.js";
import { StreamManager } from "./manager.js";
import { filterStream, onFinishRequiresThreadState, unique } from "./utils.js";
import { getBranchContext, getMessagesMetadataMap } from "./branching.js";
import { PendingRunsTracker } from "./queue.js";
//#region src/ui/orchestrator.ts
/**
* Fetch the history of a thread.
* @param client - The client to use.
* @param threadId - The ID of the thread to fetch the history of.
* @param options - The options to use.
* @returns The history of the thread.
*/
function fetchHistory(client, threadId, options) {
	if (options?.limit === false) return client.threads.getState(threadId).then((state) => {
		if (state.checkpoint == null) return [];
		return [state];
	});
	const limit = typeof options?.limit === "number" ? options.limit : 10;
	return client.threads.getHistory(threadId, { limit });
}
/**
* Resolve the run metadata storage.
* @param reconnectOnMount - The reconnect on mount option.
* @returns The run metadata storage.
*/
function resolveRunMetadataStorage(reconnectOnMount) {
	if (typeof globalThis.window === "undefined") return null;
	if (reconnectOnMount === true) return globalThis.window.sessionStorage;
	if (typeof reconnectOnMount === "function") return reconnectOnMount();
	return null;
}
/**
* Resolve the callback stream modes.
* @param options - The options to use.
* @returns The callback stream modes.
*/
function resolveCallbackStreamModes(options) {
	const modes = [];
	if (options.onUpdateEvent) modes.push("updates");
	if (options.onCustomEvent) modes.push("custom");
	if (options.onCheckpointEvent) modes.push("checkpoints");
	if (options.onTaskEvent) modes.push("tasks");
	if ("onDebugEvent" in options && options.onDebugEvent) modes.push("debug");
	if ("onLangChainEvent" in options && options.onLangChainEvent) modes.push("events");
	return modes;
}
/**
* Framework-agnostic orchestrator for LangGraph Platform streams.
*
* Encapsulates all business logic shared across React, Vue, Svelte, and Angular:
* thread management, history fetching, stream lifecycle, queue management,
* branching, subagent management, and auto-reconnect.
*
* Framework adapters subscribe to state changes via {@link subscribe} and
* map the orchestrator's getters to framework-specific reactive primitives.
*/
var StreamOrchestrator = class {
	stream;
	messageManager;
	pendingRuns;
	#options;
	#accessors;
	historyLimit;
	#runMetadataStorage;
	#callbackStreamModes;
	#trackedStreamModes = [];
	#threadId;
	#threadIdPromise = null;
	#threadIdStreaming = null;
	#history;
	#branch = "";
	#submitting = false;
	#listeners = /* @__PURE__ */ new Set();
	#version = 0;
	#streamUnsub = null;
	#queueUnsub = null;
	#disposed = false;
	#handledHeadlessToolInterruptIds = /* @__PURE__ */ new Set();
	/**
	* Create a new StreamOrchestrator.
	*
	* @param options - Configuration options for the stream, including callbacks,
	*   throttle settings, reconnect behaviour, and subagent filters.
	* @param accessors - Framework-specific accessors that resolve reactive
	*   primitives (client, assistant ID, messages key) at call time.
	*/
	constructor(options, accessors) {
		this.#options = options;
		this.#accessors = accessors;
		this.#runMetadataStorage = resolveRunMetadataStorage(options.reconnectOnMount);
		this.#callbackStreamModes = resolveCallbackStreamModes(options);
		this.historyLimit = typeof options.fetchStateHistory === "object" && options.fetchStateHistory != null ? options.fetchStateHistory.limit ?? false : options.fetchStateHistory ?? false;
		this.messageManager = new MessageTupleManager();
		this.stream = new StreamManager(this.messageManager, {
			throttle: options.throttle ?? false,
			subagentToolNames: options.subagentToolNames,
			filterSubagentMessages: options.filterSubagentMessages,
			toMessage: options.toMessage ?? toMessageClass
		});
		this.pendingRuns = new PendingRunsTracker();
		this.#threadId = void 0;
		this.#history = {
			data: void 0,
			error: void 0,
			isLoading: false,
			mutate: this.#mutate
		};
		this.#streamUnsub = this.stream.subscribe(() => {
			this.#flushPendingHeadlessToolInterrupts(this.stream.values);
			this.#notify();
		});
		this.#queueUnsub = this.pendingRuns.subscribe(() => {
			this.#notify();
		});
	}
	/**
	* Register a listener that is called whenever the orchestrator's internal
	* state changes (stream updates, queue changes, history mutations, etc.).
	*
	* @param listener - Callback invoked on every state change.
	* @returns An unsubscribe function that removes the listener.
	*/
	subscribe(listener) {
		this.#listeners.add(listener);
		return () => {
			this.#listeners.delete(listener);
		};
	}
	/**
	* Return the current version number, incremented on every state change.
	* Useful as a React `useSyncExternalStore` snapshot.
	*
	* @returns The current monotonically increasing version counter.
	*/
	getSnapshot() {
		return this.#version;
	}
	/**
	* Increment the version counter and invoke all registered listeners.
	* No-op if the orchestrator has been disposed.
	*/
	#notify() {
		if (this.#disposed) return;
		this.#version += 1;
		for (const listener of this.#listeners) listener();
	}
	/**
	* The current thread ID, or `undefined` if no thread is active.
	*/
	get threadId() {
		return this.#threadId;
	}
	/**
	* Update thread ID from an external source (e.g. reactive prop change).
	* Clears the current stream and triggers a history fetch.
	* @param newId - The new thread ID to set.
	* @returns The new thread ID.
	*/
	setThreadId(newId) {
		if (newId === this.#threadId) return;
		this.#threadId = newId;
		this.#handledHeadlessToolInterruptIds.clear();
		this.stream.clear();
		this.#fetchHistoryForThread(newId);
		this.#notify();
	}
	/**
	* Update the thread ID from within a submit flow. Sets both the
	* streaming and canonical thread IDs, fires the `onThreadId` callback,
	* and notifies listeners.
	*
	* @param newId - The newly created or resolved thread ID.
	*/
	#setThreadIdFromSubmit(newId) {
		this.#threadIdStreaming = newId;
		this.#threadId = newId;
		this.#handledHeadlessToolInterruptIds.clear();
		this.#options.onThreadId?.(newId);
		this.#notify();
	}
	#fetchHistoryForThread(threadId) {
		if (this.#threadIdStreaming != null && this.#threadIdStreaming === threadId) return;
		if (threadId != null) {
			this.#history = {
				...this.#history,
				isLoading: true,
				mutate: this.#mutate
			};
			this.#notify();
			this.#mutate(threadId);
		} else {
			this.#history = {
				data: void 0,
				error: void 0,
				isLoading: false,
				mutate: this.#mutate
			};
			this.#notify();
		}
	}
	/**
	* The current thread history fetch state, including data, loading status,
	* error, and a {@link UseStreamThread.mutate | mutate} function to
	* manually re-fetch.
	*/
	get historyData() {
		return this.#history;
	}
	async #mutate(mutateId) {
		const tid = mutateId ?? this.#threadId;
		if (!tid) return void 0;
		try {
			const data = await fetchHistory(this.#accessors.getClient(), tid, { limit: this.historyLimit });
			this.#history = {
				data,
				error: void 0,
				isLoading: false,
				mutate: this.#mutate
			};
			this.#flushPendingHeadlessToolInterrupts(data?.at(0)?.values);
			this.#notify();
			return data;
		} catch (err) {
			this.#history = {
				...this.#history,
				error: err,
				isLoading: false
			};
			this.#notify();
			this.#options.onError?.(err, void 0);
			return;
		}
	}
	/**
	* Trigger initial history fetch for the current thread ID.
	* Should be called once after construction when the initial threadId is known.
	*/
	initThreadId(threadId) {
		this.#threadId = threadId;
		this.#handledHeadlessToolInterruptIds.clear();
		this.#fetchHistoryForThread(threadId);
	}
	/**
	* The currently active branch identifier. An empty string represents
	* the main (default) branch.
	*/
	get branch() {
		return this.#branch;
	}
	/**
	* Set the active branch and notify listeners if the value changed.
	*
	* @param value - The branch identifier to switch to.
	*/
	setBranch(value) {
		if (value === this.#branch) return;
		this.#branch = value;
		this.#notify();
	}
	/**
	* Derived branch context computed from the current branch and thread
	* history. Contains the thread head, branch tree, and checkpoint-to-branch
	* mapping for the active branch.
	*/
	get branchContext() {
		return getBranchContext(this.#branch, this.#history.data ?? void 0);
	}
	#getMessages(value) {
		const messagesKey = this.#accessors.getMessagesKey();
		return Array.isArray(value[messagesKey]) ? value[messagesKey] : [];
	}
	#setMessages(current, messages) {
		const messagesKey = this.#accessors.getMessagesKey();
		return {
			...current,
			[messagesKey]: messages
		};
	}
	/**
	* The state values from the thread head of the current branch history,
	* falling back to {@link AnyStreamOptions.initialValues | initialValues}
	* or an empty object.
	*/
	get historyValues() {
		return this.branchContext.threadHead?.values ?? this.#options.initialValues ?? {};
	}
	/**
	* The error from the last task in the thread head, if any.
	* Attempts to parse structured {@link StreamError} instances from JSON.
	*/
	get historyError() {
		const error = this.branchContext.threadHead?.tasks?.at(-1)?.error;
		if (error == null) return void 0;
		try {
			const parsed = JSON.parse(error);
			if (StreamError.isStructuredError(parsed)) return new StreamError(parsed);
			return parsed;
		} catch {}
		return error;
	}
	/**
	* The latest state values received from the active stream, or `null` if
	* no stream is running or no values have been received yet.
	*/
	get streamValues() {
		return this.stream.values;
	}
	/**
	* The error from the active stream, if one occurred during streaming.
	*/
	get streamError() {
		return this.stream.error;
	}
	/**
	* The merged state values, preferring live stream values over history.
	* This is the primary way to read the current thread state.
	*/
	get values() {
		return this.stream.values ?? this.historyValues;
	}
	/**
	* The first available error from the stream, history, or thread fetch.
	* Returns `undefined` when no error is present.
	*/
	get error() {
		return this.stream.error ?? this.historyError ?? this.#history.error;
	}
	/**
	* Whether the stream is currently active and receiving events.
	*/
	get isLoading() {
		return this.stream.isLoading;
	}
	/**
	* The messages array extracted from the current {@link values} using the
	* configured messages key.
	*/
	get messages() {
		return this.#getMessages(this.values);
	}
	/**
	* The current messages converted to LangChain {@link BaseMessage} instances.
	* Automatically tracks the `"messages-tuple"` stream mode.
	*/
	get messageInstances() {
		this.trackStreamMode("messages-tuple");
		return ensureMessageInstances(this.messages);
	}
	/**
	* All tool calls with their corresponding results extracted from
	* the current messages. Automatically tracks the `"messages-tuple"`
	* stream mode.
	*/
	get toolCalls() {
		this.trackStreamMode("messages-tuple");
		return getToolCallsWithResults(this.#getMessages(this.values));
	}
	/**
	* Get tool calls with results for a specific AI message.
	* Automatically tracks the `"messages-tuple"` stream mode.
	*
	* @param message - The AI message to extract tool calls from.
	* @returns Tool calls whose AI message ID matches the given message.
	*/
	getToolCalls(message) {
		this.trackStreamMode("messages-tuple");
		return getToolCallsWithResults(this.#getMessages(this.values)).filter((tc) => tc.aiMessage.id === message.id);
	}
	/**
	* All active interrupts for the current thread state.
	* Returns an empty array when the stream is loading or no interrupts
	* are present. Falls back to a `{ when: "breakpoint" }` sentinel when
	* there are pending next nodes but no explicit interrupt data.
	*/
	get interrupts() {
		const v = this.values;
		if (v != null && "__interrupt__" in v && Array.isArray(v.__interrupt__)) return userFacingInterruptsFromValuesArray(v.__interrupt__);
		if (this.isLoading) return [];
		const taskInterrupts = userFacingInterruptsFromThreadTasks((this.branchContext.threadHead?.tasks ?? []).flatMap((t) => t.interrupts ?? []));
		if (taskInterrupts != null) return taskInterrupts;
		if (!(this.branchContext.threadHead?.next ?? []).length || this.error != null) return [];
		return [{ when: "breakpoint" }];
	}
	/**
	* The single most relevant interrupt for the current thread state,
	* or `undefined` if no interrupt is active. Convenience accessor that
	* delegates to {@link extractInterrupts}.
	*/
	get interrupt() {
		return extractInterrupts(this.values, {
			isLoading: this.isLoading,
			threadState: this.branchContext.threadHead,
			error: this.error
		});
	}
	/**
	* Flattened history messages as LangChain {@link BaseMessage} instances,
	* ordered chronologically across all branch checkpoints.
	*
	* @throws If `fetchStateHistory` was not enabled in the options.
	*/
	get flatHistory() {
		if (this.historyLimit === false) throw new Error("`fetchStateHistory` must be set to `true` to use `history`");
		return ensureHistoryMessageInstances(this.branchContext.flatHistory, this.#accessors.getMessagesKey());
	}
	/**
	* Whether the initial thread history is still being loaded and no data
	* is available yet. Returns `false` once the first fetch completes.
	*/
	get isThreadLoading() {
		return this.#history.isLoading && this.#history.data == null;
	}
	/**
	* The full branch tree structure for the current thread history.
	*
	* @experimental This API may change in future releases.
	* @throws If `fetchStateHistory` was not enabled in the options.
	*/
	get experimental_branchTree() {
		if (this.historyLimit === false) throw new Error("`fetchStateHistory` must be set to `true` to use `experimental_branchTree`");
		return this.branchContext.branchTree;
	}
	/**
	* A map of metadata entries for all messages, derived from history
	* and branch context. Used internally by {@link getMessagesMetadata}.
	*/
	get messageMetadata() {
		return getMessagesMetadataMap({
			initialValues: this.#options.initialValues,
			history: this.#history.data,
			getMessages: (value) => this.#getMessages(value),
			branchContext: this.branchContext
		});
	}
	/**
	* Look up metadata for a specific message, merging stream-time metadata
	* with history-derived metadata.
	*
	* @param message - The message to look up metadata for.
	* @param index - Optional positional index used as a fallback identifier.
	* @returns The merged metadata, or `undefined` if none is available.
	*/
	getMessagesMetadata(message, index) {
		const streamMetadata = this.messageManager.get(message.id)?.metadata;
		const historyMetadata = this.messageMetadata?.find((m) => m.messageId === (message.id ?? index));
		if (streamMetadata != null || historyMetadata != null) return {
			...historyMetadata,
			streamMetadata
		};
	}
	/**
	* The list of pending run entries currently waiting in the queue.
	*/
	get queueEntries() {
		return this.pendingRuns.entries;
	}
	/**
	* The number of pending runs in the queue.
	*/
	get queueSize() {
		return this.pendingRuns.size;
	}
	/**
	* Cancel and remove a specific pending run from the queue.
	* If the run exists and a thread is active, the run is also cancelled
	* on the server.
	*
	* @param id - The run ID to cancel.
	* @returns `true` if the run was found and removed, `false` otherwise.
	*/
	async cancelQueueItem(id) {
		const tid = this.#threadId;
		const removed = this.pendingRuns.remove(id);
		if (removed && tid) await this.#accessors.getClient().runs.cancel(tid, id);
		return removed;
	}
	/**
	* Remove all pending runs from the queue and cancel them on the server.
	*/
	async clearQueue() {
		const tid = this.#threadId;
		const removed = this.pendingRuns.removeAll();
		if (tid && removed.length > 0) await Promise.all(removed.map((e) => this.#accessors.getClient().runs.cancel(tid, e.id)));
	}
	/**
	* A map of all known subagent stream interfaces, keyed by tool call ID.
	*/
	get subagents() {
		return this.stream.getSubagents();
	}
	/**
	* The subset of subagents that are currently active (streaming).
	*/
	get activeSubagents() {
		return this.stream.getActiveSubagents();
	}
	/**
	* Retrieve a specific subagent stream interface by its tool call ID.
	*
	* @param toolCallId - The tool call ID that spawned the subagent.
	* @returns The subagent interface, or `undefined` if not found.
	*/
	getSubagent(toolCallId) {
		return this.stream.getSubagent(toolCallId);
	}
	/**
	* Retrieve all subagent stream interfaces that match a given agent type.
	*
	* @param type - The agent type name to filter by.
	* @returns An array of matching subagent interfaces.
	*/
	getSubagentsByType(type) {
		return this.stream.getSubagentsByType(type);
	}
	/**
	* Retrieve all subagent stream interfaces associated with a specific
	* AI message.
	*
	* @param messageId - The ID of the parent AI message.
	* @returns An array of subagent interfaces spawned by that message.
	*/
	getSubagentsByMessage(messageId) {
		return this.stream.getSubagentsByMessage(messageId);
	}
	/**
	* Reconstruct subagents from history messages if applicable.
	* Call this when history finishes loading and the stream isn't active.
	* Returns an AbortController for cancelling the subagent history fetch,
	* or null if no reconstruction was needed.
	*/
	reconstructSubagentsIfNeeded() {
		const hvMessages = this.#getMessages(this.historyValues);
		if (!(this.#options.filterSubagentMessages && !this.isLoading && !this.#history.isLoading && hvMessages.length > 0)) return null;
		this.stream.reconstructSubagents(hvMessages, { skipIfPopulated: true });
		const tid = this.#threadId;
		if (this.historyLimit !== false && tid) {
			const controller = new AbortController();
			this.stream.fetchSubagentHistory(this.#accessors.getClient().threads, tid, {
				messagesKey: this.#accessors.getMessagesKey(),
				historyLimit: typeof this.historyLimit === "number" ? this.historyLimit : void 0,
				signal: controller.signal
			});
			return controller;
		}
		return null;
	}
	/**
	* Register additional stream modes that should be included in future
	* stream requests. Modes are deduplicated automatically.
	*
	* @param modes - One or more stream modes to track.
	*/
	trackStreamMode(...modes) {
		for (const mode of modes) if (!this.#trackedStreamModes.includes(mode)) this.#trackedStreamModes.push(mode);
	}
	/**
	* Stop the currently active stream. If reconnect metadata storage is
	* configured, also cancels the run on the server and cleans up stored
	* run metadata.
	*/
	stop() {
		this.stream.stop(this.historyValues, { onStop: (args) => {
			if (this.#runMetadataStorage && this.#threadId) {
				const runId = this.#runMetadataStorage.getItem(`lg:stream:${this.#threadId}`);
				if (runId) this.#accessors.getClient().runs.cancel(this.#threadId, runId);
				this.#runMetadataStorage.removeItem(`lg:stream:${this.#threadId}`);
			}
			this.#options.onStop?.(args);
		} });
	}
	/**
	* Join an existing run's event stream by run ID. Used for reconnecting
	* to in-progress runs or consuming queued runs.
	*
	* @param runId - The ID of the run to join.
	* @param lastEventId - The last event ID received, for resuming mid-stream.
	*   Defaults to `"-1"` (start from the beginning).
	* @param joinOptions - Additional options for stream mode and event filtering.
	*/
	async joinStream(runId, lastEventId, joinOptions) {
		lastEventId ??= "-1";
		const tid = this.#threadId;
		if (!tid) return;
		this.#threadIdStreaming = tid;
		const callbackMeta = {
			thread_id: tid,
			run_id: runId
		};
		const shouldRefetchJoin = this.historyLimit === true || typeof this.historyLimit === "number" || onFinishRequiresThreadState(this.#options.onFinish);
		const client = this.#accessors.getClient();
		await this.stream.start(async (signal) => {
			const rawStream = client.runs.joinStream(tid, runId, {
				signal,
				lastEventId,
				streamMode: joinOptions?.streamMode
			});
			return joinOptions?.filter != null ? filterStream(rawStream, joinOptions.filter) : rawStream;
		}, {
			getMessages: (value) => this.#getMessages(value),
			setMessages: (current, messages) => this.#setMessages(current, messages),
			initialValues: this.historyValues,
			callbacks: this.#options,
			onSuccess: async () => {
				this.#runMetadataStorage?.removeItem(`lg:stream:${tid}`);
				if (!shouldRefetchJoin) {
					if (this.#options.onFinish != null && !onFinishRequiresThreadState(this.#options.onFinish)) this.#options.onFinish(void 0, callbackMeta);
					return;
				}
				const lastHead = (await this.#mutate(tid))?.at(0);
				if (lastHead) this.#options.onFinish?.(lastHead, callbackMeta);
			},
			onError: (error) => {
				this.#options.onError?.(error, callbackMeta);
			},
			onFinish: () => {
				this.#threadIdStreaming = null;
			}
		});
	}
	/**
	* Submit input values directly to the LangGraph Platform, creating a new
	* thread if necessary. Starts a streaming run and processes events until
	* completion. Unlike {@link submit}, this does not handle queueing — if
	* a stream is already active, a concurrent run will be started.
	*
	* @param values - The state values to send as run input.
	* @param submitOptions - Optional configuration for the run (config,
	*   checkpoint, multitask strategy, optimistic values, etc.).
	*/
	submitDirect(values, submitOptions) {
		const currentBranchContext = this.branchContext;
		const checkpointId = submitOptions?.checkpoint?.checkpoint_id;
		this.#branch = checkpointId != null ? currentBranchContext.branchByCheckpoint[checkpointId]?.branch ?? "" : "";
		const includeImplicitBranch = this.historyLimit === true || typeof this.historyLimit === "number";
		const shouldRefetch = includeImplicitBranch || onFinishRequiresThreadState(this.#options.onFinish);
		let checkpoint = submitOptions?.checkpoint ?? (includeImplicitBranch ? currentBranchContext.threadHead?.checkpoint : void 0) ?? void 0;
		if (submitOptions?.checkpoint === null) checkpoint = void 0;
		if (checkpoint != null) delete checkpoint.thread_id;
		let callbackMeta;
		let rejoinKey;
		let usableThreadId;
		const client = this.#accessors.getClient();
		const assistantId = this.#accessors.getAssistantId();
		return this.stream.start(async (signal) => {
			usableThreadId = this.#threadId;
			if (usableThreadId) this.#threadIdStreaming = usableThreadId;
			if (!usableThreadId) {
				const threadPromise = client.threads.create({
					threadId: submitOptions?.threadId,
					metadata: submitOptions?.metadata
				});
				this.#threadIdPromise = threadPromise.then((t) => t.thread_id);
				usableThreadId = (await threadPromise).thread_id;
				this.#setThreadIdFromSubmit(usableThreadId);
			}
			const streamMode = unique([
				"values",
				"updates",
				...submitOptions?.streamMode ?? [],
				...this.#trackedStreamModes,
				...this.#callbackStreamModes
			]);
			this.stream.setStreamValues(() => {
				const prev = {
					...this.historyValues,
					...this.stream.values
				};
				if (submitOptions?.optimisticValues != null) return {
					...prev,
					...typeof submitOptions.optimisticValues === "function" ? submitOptions.optimisticValues(prev) : submitOptions.optimisticValues
				};
				return { ...prev };
			});
			const streamResumable = submitOptions?.streamResumable ?? !!this.#runMetadataStorage;
			return client.runs.stream(usableThreadId, assistantId, {
				input: values,
				config: submitOptions?.config,
				context: submitOptions?.context,
				command: submitOptions?.command,
				interruptBefore: submitOptions?.interruptBefore,
				interruptAfter: submitOptions?.interruptAfter,
				metadata: submitOptions?.metadata,
				multitaskStrategy: submitOptions?.multitaskStrategy,
				onCompletion: submitOptions?.onCompletion,
				onDisconnect: submitOptions?.onDisconnect ?? (streamResumable ? "continue" : "cancel"),
				signal,
				checkpoint,
				streamMode,
				streamSubgraphs: submitOptions?.streamSubgraphs,
				streamResumable,
				durability: submitOptions?.durability,
				onRunCreated: (params) => {
					callbackMeta = {
						run_id: params.run_id,
						thread_id: params.thread_id ?? usableThreadId
					};
					if (this.#runMetadataStorage) {
						rejoinKey = `lg:stream:${usableThreadId}`;
						this.#runMetadataStorage.setItem(rejoinKey, callbackMeta.run_id);
					}
					this.#options.onCreated?.(callbackMeta);
				}
			});
		}, {
			getMessages: (value) => this.#getMessages(value),
			setMessages: (current, messages) => this.#setMessages(current, messages),
			initialValues: this.historyValues,
			callbacks: this.#options,
			onSuccess: async () => {
				if (rejoinKey) this.#runMetadataStorage?.removeItem(rejoinKey);
				if (shouldRefetch && usableThreadId) {
					const lastHead = (await this.#mutate(usableThreadId))?.at(0);
					if (lastHead) {
						this.#options.onFinish?.(lastHead, callbackMeta);
						return null;
					}
				} else if (this.#options.onFinish != null && !onFinishRequiresThreadState(this.#options.onFinish)) this.#options.onFinish(void 0, callbackMeta);
			},
			onError: (error) => {
				this.#options.onError?.(error, callbackMeta);
				submitOptions?.onError?.(error, callbackMeta);
			},
			onFinish: () => {
				this.#threadIdStreaming = null;
			}
		});
	}
	#drainQueue() {
		if (!this.isLoading && !this.#submitting && this.pendingRuns.size > 0) {
			const next = this.pendingRuns.shift();
			if (next) {
				this.#submitting = true;
				this.joinStream(next.id).finally(() => {
					this.#submitting = false;
					this.#drainQueue();
				});
			}
		}
	}
	/**
	* Trigger queue draining. Framework adapters should call this
	* when isLoading or queue size changes.
	*/
	drainQueue() {
		this.#drainQueue();
	}
	/**
	* Submit input values with automatic queue management. If a stream is
	* already active, the run is enqueued (unless the multitask strategy
	* is `"interrupt"` or `"rollback"`, in which case the current run is
	* replaced). Queued runs are drained sequentially via {@link drainQueue}.
	*
	* @param values - The state values to send as run input.
	* @param submitOptions - Optional configuration for the run.
	* @returns The result of {@link submitDirect} if the run was started
	*   immediately, or `void` if the run was enqueued.
	*/
	async submit(values, submitOptions) {
		if (this.stream.isLoading || this.#submitting) {
			if (submitOptions?.multitaskStrategy === "interrupt" || submitOptions?.multitaskStrategy === "rollback") {
				this.#submitting = true;
				try {
					await this.submitDirect(values, submitOptions);
				} finally {
					this.#submitting = false;
				}
				return;
			}
			let usableThreadId = this.#threadId;
			if (!usableThreadId && this.#threadIdPromise) usableThreadId = await this.#threadIdPromise;
			if (usableThreadId) {
				const client = this.#accessors.getClient();
				const assistantId = this.#accessors.getAssistantId();
				try {
					const run = await client.runs.create(usableThreadId, assistantId, {
						input: values,
						config: submitOptions?.config,
						context: submitOptions?.context,
						command: submitOptions?.command,
						interruptBefore: submitOptions?.interruptBefore,
						interruptAfter: submitOptions?.interruptAfter,
						metadata: submitOptions?.metadata,
						multitaskStrategy: "enqueue",
						streamResumable: true,
						streamSubgraphs: submitOptions?.streamSubgraphs,
						durability: submitOptions?.durability
					});
					this.pendingRuns.add({
						id: run.run_id,
						values,
						options: submitOptions,
						createdAt: new Date(run.created_at)
					});
				} catch (error) {
					this.#options.onError?.(error, void 0);
					submitOptions?.onError?.(error, void 0);
				}
				return;
			}
		}
		this.#submitting = true;
		const result = this.submitDirect(values, submitOptions);
		Promise.resolve(result).finally(() => {
			this.#submitting = false;
			this.#drainQueue();
		});
		return result;
	}
	/**
	* Switch to a different thread (or clear the current thread).
	* Clears the active stream, cancels all queued runs on the previous
	* thread, fetches history for the new thread, and notifies the
	* {@link AnyStreamOptions.onThreadId | onThreadId} callback.
	*
	* @param newThreadId - The thread ID to switch to, or `null` to clear.
	*/
	switchThread(newThreadId) {
		if (newThreadId !== (this.#threadId ?? null)) {
			const prevThreadId = this.#threadId;
			this.#threadId = newThreadId ?? void 0;
			this.#handledHeadlessToolInterruptIds.clear();
			this.stream.clear();
			const removed = this.pendingRuns.removeAll();
			if (prevThreadId && removed.length > 0) {
				const client = this.#accessors.getClient();
				Promise.all(removed.map((e) => client.runs.cancel(prevThreadId, e.id)));
			}
			this.#fetchHistoryForThread(this.#threadId);
			if (newThreadId != null) this.#options.onThreadId?.(newThreadId);
			this.#notify();
		}
	}
	/**
	* Attempt to reconnect to a previously running stream.
	* Returns true if a reconnection was initiated.
	*/
	tryReconnect() {
		if (this.#runMetadataStorage && this.#threadId) {
			const runId = this.#runMetadataStorage.getItem(`lg:stream:${this.#threadId}`);
			if (runId) {
				this.joinStream(runId);
				return true;
			}
		}
		return false;
	}
	/**
	* Whether reconnect-on-mount behaviour is enabled (i.e. run metadata
	* storage is available).
	*/
	get shouldReconnect() {
		return !!this.#runMetadataStorage;
	}
	/**
	* Tear down the orchestrator: stop the active stream, remove all
	* internal subscriptions, and mark the instance as disposed.
	* After calling this method, the orchestrator should not be reused.
	*/
	dispose() {
		this.#disposed = true;
		this.#streamUnsub?.();
		this.#queueUnsub?.();
		this.#streamUnsub = null;
		this.#queueUnsub = null;
		this.stop();
	}
	#flushPendingHeadlessToolInterrupts(values) {
		flushPendingHeadlessToolInterrupts(values, this.#options.tools, this.#handledHeadlessToolInterruptIds, {
			onTool: this.#options.onTool,
			defer: (run) => {
				Promise.resolve().then(run);
			},
			resumeSubmit: (command) => void this.submit(null, {
				multitaskStrategy: "interrupt",
				command
			})
		});
	}
};
//#endregion
export { StreamOrchestrator };

//# sourceMappingURL=orchestrator.js.map